Allow modules to be categorized, and also sort them when generating the documentation.

This commit is contained in:
Michael DeHaan 2013-04-28 15:03:45 -04:00
commit 391fb98ee2
87 changed files with 118 additions and 67 deletions

136
library/files/assemble Normal file
View file

@ -0,0 +1,136 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Stephen Fromm <sfromm@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import os
import os.path
import shutil
import tempfile
DOCUMENTATION = '''
---
module: assemble
short_description: Assembles a configuration file from fragments
description:
- Assembles a configuration file from fragments. Often a particular
program will take a single configuration file and does not support a
C(conf.d) style structure where it is easy to build up the configuration
from multiple sources. M(assemble) will take a directory of files that have
already been transferred to the system, and concatenate them together to
produce a destination file. Files are assembled in string sorting order.
Puppet calls this idea I(fragments).
version_added: "0.5"
options:
src:
description:
- An already existing directory full of source files.
required: true
default: null
aliases: []
dest:
description:
- A file to create using the concatenation of all of the source files.
required: true
default: null
backup:
description:
- Create a backup file (if C(yes)), including the timestamp information so
you can get the original file back if you somehow clobbered it
incorrectly.
required: false
choices: [ "yes", "no" ]
default: "no"
others:
description:
- all arguments accepted by the M(file) module also work here
required: false
examples:
- code: "assemble: src=/etc/someapp/fragments dest=/etc/someapp/someapp.conf"
description: "Example from Ansible Playbooks"
author: Stephen Fromm
'''
# ===========================================
# Support methods
def assemble_from_fragments(path):
''' assemble a file from a directory of fragments '''
assembled = []
for f in sorted(os.listdir(path)):
fragment = "%s/%s" % (path, f)
if os.path.isfile(fragment):
assembled.append(file(fragment).read())
return "".join(assembled)
def write_temp_file(data):
fd, path = tempfile.mkstemp()
os.write(fd, data)
os.close(fd)
return path
# ==============================================================
# main
def main():
module = AnsibleModule(
# not checking because of daisy chain to file module
argument_spec = dict(
src = dict(required=True),
dest = dict(required=True),
backup=dict(default=False, type='bool'),
),
add_file_common_args=True
)
changed=False
pathmd5 = None
destmd5 = None
src = os.path.expanduser(module.params['src'])
dest = os.path.expanduser(module.params['dest'])
backup = module.params['backup']
if not os.path.exists(src):
module.fail_json(msg="Source (%s) does not exist" % src)
if not os.path.isdir(src):
module.fail_json(msg="Source (%s) is not a directory" % src)
path = write_temp_file(assemble_from_fragments(src))
pathmd5 = module.md5(path)
if os.path.exists(dest):
destmd5 = module.md5(dest)
if pathmd5 != destmd5:
if backup and destmd5 is not None:
module.backup_local(dest)
shutil.copy(path, dest)
changed = True
file_args = module.load_file_common_arguments(module.params)
changed = module.set_file_attributes_if_different(file_args, changed)
# Mission complete
module.exit_json(src=src, dest=dest, md5sum=destmd5,
changed=changed, msg="OK")
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

175
library/files/copy Normal file
View file

@ -0,0 +1,175 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import os
import time
DOCUMENTATION = '''
---
module: copy
short_description: Copies files to remote locations.
description:
- The M(copy) module copies a file on the local box to remote locations.
options:
src:
description:
- Local path to a file to copy to the remote server; can be absolute or relative.
required: false
default: null
aliases: []
content:
version_added: "1.1"
description:
- When used instead of 'src', sets the contents of a file directly to the specified value.
required: false
default: null
dest:
description:
- Remote absolute path where the file should be copied to.
required: true
default: null
backup:
description:
- Create a backup file including the timestamp information so you can get
the original file back if you somehow clobbered it incorrectly.
version_added: "0.7"
required: false
choices: [ "yes", "no" ]
default: "no"
force:
description:
- the default is C(yes), which will replace the remote file when contents
are different than the source. If C(no), the file will only be transferred
if the destination does not exist.
version_added: "1.1"
required: false
choices: [ "yes", "no" ]
default: "yes"
aliases: [ "thirsty" ]
validate:
description:
- validation to run before copying into place
required: false
default: ""
version_added: "1.2"
others:
description:
- all arguments accepted by the M(file) module also work here
required: false
examples:
- code: "copy: src=/srv/myfiles/foo.conf dest=/etc/foo.conf owner=foo group=foo mode=0644"
description: "Example from Ansible Playbooks"
- code: "copy: src=/mine/ntp.conf dest=/etc/ntp.conf owner=root group=root mode=644 backup=yes"
description: "Copy a new C(ntp.conf) file into place, backing up the original if it differs from the copied version"
- code: "copy: src=/mine/sudoers dest=/etc/sudoers validate='visudo -c %s'"
description: "Copy a new C(sudoers) file into place, after passing validation with visudo"
author: Michael DeHaan
notes:
- The "copy" module can't be used to recursively copy directory structures to the target machine. Please see the
"Delegation" section of the Advanced Playbooks documentation for a better approach to recursive copies.
'''
def main():
module = AnsibleModule(
# not checking because of daisy chain to file module
argument_spec = dict(
src = dict(required=False),
original_basename = dict(required=False), # used to handle 'dest is a directory' via template, a slight hack
content = dict(required=False),
dest = dict(required=True),
backup = dict(default=False, type='bool'),
force = dict(default=True, aliases=['thirsty'], type='bool'),
validate = dict(required=False, type='str'),
),
add_file_common_args=True,
)
src = os.path.expanduser(module.params['src'])
dest = os.path.expanduser(module.params['dest'])
backup = module.params['backup']
force = module.params['force']
original_basename = module.params.get('original_basename',None)
validate = module.params.get('validate',None)
if not os.path.exists(src):
module.fail_json(msg="Source %s failed to transfer" % (src))
if not os.access(src, os.R_OK):
module.fail_json(msg="Source %s not readable" % (src))
md5sum_src = module.md5(src)
md5sum_dest = None
if os.path.exists(dest):
if not force:
module.exit_json(msg="file already exists", src=src, dest=dest, changed=False)
if (os.path.isdir(dest)):
basename = os.path.basename(src)
if original_basename:
basename = original_basename
dest = os.path.join(dest, basename)
if os.access(dest, os.R_OK):
md5sum_dest = module.md5(dest)
else:
if not os.path.exists(os.path.dirname(dest)):
module.fail_json(msg="Destination directory %s does not exist" % (os.path.dirname(dest)))
if not os.access(os.path.dirname(dest), os.W_OK):
module.fail_json(msg="Destination %s not writable" % (os.path.dirname(dest)))
backup_file = None
if md5sum_src != md5sum_dest or os.path.islink(dest):
try:
if backup:
if os.path.exists(dest):
backup_file = module.backup_local(dest)
# allow for conversion from symlink.
if os.path.islink(dest):
os.unlink(dest)
open(dest, 'w').close()
if validate:
(rc,out,err) = module.run_command(validate % src)
if rc != 0:
module.fail_json(msg="failed to validate: rc:%s error:%s" % (rc,err))
if not module.atomic_move(src, dest):
try:
os.unlink(src) # cleanup tmp files on failure
except OSError, e:
sys.stderr.write("failed to clean up tmp file %s: %s\n" % (src, e))
except IOError:
module.fail_json(msg="failed to copy: %s to %s" % (src, dest))
changed = True
else:
changed = False
res_args = dict(
dest = dest, src = src, md5sum = md5sum_src, changed = changed
)
if backup_file:
res_args['backup_file'] = backup_file
module.params['dest'] = dest
file_args = module.load_file_common_arguments(module.params)
res_args['changed'] = module.set_file_attributes_if_different(file_args, res_args['changed'])
module.exit_json(**res_args)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

295
library/files/file Normal file
View file

@ -0,0 +1,295 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import shutil
import stat
import grp
import pwd
try:
import selinux
HAVE_SELINUX=True
except ImportError:
HAVE_SELINUX=False
DOCUMENTATION = '''
---
module: file
short_description: Sets attributes of files
description:
- Sets attributes of files, symlinks, and directories, or removes
files/symlinks/directories. Many other modules support the same options as
the M(file) module - including M(copy), M(template), and M(assemble).
options:
path:
description:
- defines the file being managed, unless when used with C(state=link), and then sets the destination to create a symbolic link to using I(src)
required: true
default: []
aliases: []
state:
description:
- If C(directory), all immediate subdirectories will be created if they
do not exist. If C(file), the file will NOT be created if it does not
exist, see the M(copy) or M(template) module if you want that behavior.
If C(link), the symbolic link will be created or changed. If C(absent),
directories will be recursively deleted, and files or symlinks will be
unlinked.
required: false
default: file
choices: [ file, link, directory, absent ]
mode:
required: false
default: null
choices: []
description:
- mode the file or directory should be, such as 0644 as would be fed to I(chmod)
owner:
required: false
default: null
choices: []
description:
- name of the user that should own the file/directory, as would be fed to I(chown)
group:
required: false
default: null
choices: []
description:
- name of the group that should own the file/directory, as would be fed to I(chown)
src:
required: false
default: null
choices: []
description:
- path of the file to link to (applies only to C(state=link)).
seuser:
required: false
default: null
choices: []
description:
- user part of SELinux file context. Will default to system policy, if
applicable. If set to C(_default), it will use the C(user) portion of the
policy if available
serole:
required: false
default: null
choices: []
description:
- role part of SELinux file context, C(_default) feature works as for I(seuser).
setype:
required: false
default: null
choices: []
description:
- type part of SELinux file context, C(_default) feature works as for I(seuser).
selevel:
required: false
default: "s0"
choices: []
description:
- level part of the SELinux file context. This is the MLS/MCS attribute,
sometimes known as the C(range). C(_default) feature works as for
I(seuser).
context:
required: false
default: null
choices: [ "default" ]
description:
- accepts only C(default) as value. This will restore a file's SELinux context
in the policy. Does nothing if no default value is available.
recurse:
required: false
default: "no"
choices: [ "yes", "no" ]
version_added: "1.1"
description:
- recursively set the specified file attributes (applies only to state=directory)
examples:
- code: "file: path=/etc/foo.conf owner=foo group=foo mode=0644"
description: Example from Ansible Playbooks
- code: "file: src=/file/to/link/to dest=/path/to/symlink owner=foo group=foo state=link"
notes:
- See also M(copy), M(template), M(assemble)
requirements: [ ]
author: Michael DeHaan
'''
def main():
# FIXME: pass this around, should not use global
global module
module = AnsibleModule(
argument_spec = dict(
state = dict(choices=['file','directory','link','absent'], default='file'),
path = dict(aliases=['dest', 'name'], required=True),
recurse = dict(default='no', type='bool'),
diff_peek = dict(default=None),
validate = dict(required=False, default=None),
),
add_file_common_args=True,
supports_check_mode=True
)
params = module.params
state = params['state']
params['path'] = path = os.path.expanduser(params['path'])
# short-circuit for diff_peek
if params.get('diff_peek', None) is not None:
appears_binary = False
try:
f = open(path)
b = f.read(8192)
f.close()
if b.find("\x00") != -1:
appears_binary = True
except:
pass
module.exit_json(path=path, changed=False, appears_binary=appears_binary)
# source is both the source of a symlink or an informational passing of the src for a template module
# or copy module, even if this module never uses it, it is needed to key off some things
src = params.get('src', None)
if src:
src = os.path.expanduser(src)
if src is not None and os.path.isdir(path) and state != "link":
params['path'] = path = os.path.join(path, os.path.basename(src))
file_args = module.load_file_common_arguments(params)
if state == 'link' and (src is None or path is None):
module.fail_json(msg='src and dest are required for "link" state')
elif path is None:
module.fail_json(msg='path is required')
changed = False
prev_state = 'absent'
if os.path.lexists(path):
if os.path.islink(path):
prev_state = 'link'
elif os.path.isdir(path):
prev_state = 'directory'
else:
prev_state = 'file'
if prev_state != 'absent' and state == 'absent':
try:
if prev_state == 'directory':
if module.check_mode:
module.exit_json(changed=True)
if os.path.islink(path):
os.unlink(path)
else:
try:
shutil.rmtree(path, ignore_errors=False)
except:
module.exit_json(msg="rmtree failed")
else:
if module.check_mode:
module.exit_json(changed=True)
os.unlink(path)
except Exception, e:
module.fail_json(path=path, msg=str(e))
module.exit_json(path=path, changed=True)
if prev_state != 'absent' and prev_state != state:
module.fail_json(path=path, msg='refusing to convert between %s and %s for %s' % (prev_state, state, src))
if prev_state == 'absent' and state == 'absent':
module.exit_json(path=path, changed=False)
if state == 'file':
if prev_state != 'file':
module.fail_json(path=path, msg='file (%s) does not exist, use copy or template module to create' % path)
changed = module.set_file_attributes_if_different(file_args, changed)
module.exit_json(path=path, changed=changed)
elif state == 'directory':
if prev_state == 'absent':
if module.check_mode:
module.exit_json(changed=True)
os.makedirs(path)
changed = True
changed = module.set_directory_attributes_if_different(file_args, changed)
recurse = params['recurse']
if recurse:
for root,dirs,files in os.walk( file_args['path'] ):
for dir in dirs:
dirname=os.path.join(root,dir)
tmp_file_args = file_args.copy()
tmp_file_args['path']=dirname
changed = module.set_directory_attributes_if_different(tmp_file_args, changed)
for file in files:
filename=os.path.join(root,file)
tmp_file_args = file_args.copy()
tmp_file_args['path']=filename
changed = module.set_file_attributes_if_different(tmp_file_args, changed)
module.exit_json(path=path, changed=changed)
elif state == 'link':
if os.path.isabs(src):
abs_src = src
else:
module.fail_json(msg="absolute paths are required")
if not os.path.exists(abs_src):
module.fail_json(path=path, src=src, msg='src file does not exist')
if prev_state == 'absent':
if module.check_mode:
module.exit_json(changed=True)
os.symlink(src, path)
changed = True
elif prev_state == 'link':
old_src = os.readlink(path)
if not os.path.isabs(old_src):
old_src = os.path.join(os.path.dirname(path), old_src)
if old_src != src:
if module.check_mode:
module.exit_json(changed=True)
os.unlink(path)
os.symlink(src, path)
changed = True
else:
module.fail_json(dest=path, src=src, msg='unexpected position reached')
# set modes owners and context as needed
file_args = module.load_file_common_arguments(module.params)
changed = module.set_context_if_different(path, file_args['secontext'], changed)
changed = module.set_owner_if_different(path, file_args['owner'], changed)
changed = module.set_group_if_different(path, file_args['group'], changed)
changed = module.set_mode_if_different(path, file_args['mode'], changed)
module.exit_json(dest=path, src=src, changed=changed)
module.fail_json(path=path, msg='unexpected position reached')
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

186
library/files/ini_file Normal file
View file

@ -0,0 +1,186 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
DOCUMENTATION = '''
---
module: ini_file
short_description: Tweak settings in INI files
description:
- Manage (add, remove, change) individual settings in an INI-style file without having
to manage the file as a whole with, say, M(template) or M(assemble). Adds missing
sections if they don't exist.
version_added: "0.9"
options:
dest:
description:
- Path to the INI-style file; this file is created if required
required: true
default: null
section:
description:
- Section name in INI file. This is added if C(state=present) automatically when
a single value is being set.
required: true
default: null
option:
description:
- if set (required for changing a I(value)), this is the name of the option.
- May be omitted if adding/removing a whole I(section).
required: false
default: null
value:
description:
- the string value to be associated with an I(option). May be omitted when removing an I(option).
required: false
default: null
backup:
description:
- Create a backup file including the timestamp information so you can get
the original file back if you somehow clobbered it incorrectly.
required: false
default: "no"
choices: [ "yes", "no" ]
others:
description:
- all arguments accepted by the M(file) module also work here
required: false
examples:
- code: "ini_file: dest=/etc/conf section=drinks option=fav value=lemonade mode=0600 backup=yes"
description: Ensure C(fav=lemonade) is in section C([drinks]) in said file
- code: |
ini_file: dest=/etc/anotherconf
section=drinks
option=temperature
value=cold
backup=yes
notes:
- While it is possible to add an I(option) without specifying a I(value), this makes
no sense.
- A section named C(default) cannot be added by the module, but if it exists, individual
options within the section can be updated. (This is a limitation of Python's I(ConfigParser).)
Either use M(template) to create a base INI file with a C([default]) section, or use
M(lineinfile) to add the missing line.
requirements: [ ConfigParser ]
author: Jan-Piet Mens
'''
import ConfigParser
# ==============================================================
# do_ini
def do_ini(module, filename, section=None, option=None, value=None, state='present', backup=False):
changed = False
cp = ConfigParser.ConfigParser()
try:
f = open(filename)
cp.readfp(f)
except IOError:
pass
if state == 'absent':
if option is None and value is None:
if cp.has_section(section):
cp.remove_section(section)
changed = True
else:
if option is not None:
try:
if cp.get(section, option):
cp.remove_option(section, option)
changed = True
except:
pass
if state == 'present':
if cp.has_section(section) == False:
if section.upper() == 'DEFAULT':
module.fail_json(msg="[DEFAULT] is an illegal section name")
cp.add_section(section)
changed = True
if option is not None and value is not None:
try:
oldvalue = cp.get(section, option)
if str(value) != str(oldvalue):
cp.set(section, option, value)
changed = True
except ConfigParser.NoSectionError:
cp.set(section, option, value)
changed = True
except ConfigParser.NoOptionError:
cp.set(section, option, value)
changed = True
if changed:
if backup:
module.backup_local(filename)
try:
f = open(filename, 'w')
cp.write(f)
except:
module.fail_json(msg="Can't creat %s" % filename)
return changed
# ==============================================================
# main
def main():
module = AnsibleModule(
argument_spec = dict(
dest = dict(required=True),
section = dict(required=True),
option = dict(required=False),
value = dict(required=False),
backup = dict(default='no', type='bool'),
state = dict(default='present', choices=['present', 'absent'])
),
add_file_common_args = True
)
info = dict()
dest = os.path.expanduser(module.params['dest'])
section = module.params['section']
option = module.params['option']
value = module.params['value']
state = module.params['state']
backup = module.params['backup']
changed = do_ini(module, dest, section, option, value, state, backup)
file_args = module.load_file_common_arguments(module.params)
changed = module.set_file_attributes_if_different(file_args, changed)
# Mission complete
module.exit_json(dest=dest, changed=changed, msg="OK")
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

320
library/files/lineinfile Normal file
View file

@ -0,0 +1,320 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import re
import os
DOCUMENTATION = """
---
module: lineinfile
author: Daniel Hokka Zakrisson
short_description: Ensure a particular line is in a file, or replace an
existing line using a back-referenced regular expression.
description:
- This module will search a file for a line, and ensure that it is present or absent.
- This is primarily useful when you want to change a single line in a
file only. For other cases, see the M(copy) or M(template) modules.
version_added: "0.7"
options:
dest:
required: true
aliases: [ name, destfile ]
description:
- The file to modify.
regexp:
required: true
description:
- The regular expression to look for in every line of the file. For
C(state=present), the pattern to replace if found; only the last line
found will be replaced. For C(state=absent), the pattern of the line
to remove. Uses Python regular expressions; see
U(http://docs.python.org/2/library/re.html).
state:
required: false
choices: [ present, absent ]
default: "present"
aliases: []
description:
- Whether the line should be there or not.
line:
required: false
description:
- Required for C(state=present). The line to insert/replace into the
file. If backrefs is set, may contain backreferences that will get
expanded with the regexp capture groups if the regexp matches. The
backreferences should be double escaped (see examples).
backrefs:
required: false
default: "no"
choices: [ "yes", "no" ]
version_added: "1.1"
description:
- Used with C(state=present). If set, line can contain backreferences
(both positional and named) that will get populated if the regexp
matches. This flag changes the operation of the module slightly;
insertbefore and insertafter will be ignored, and if the regexp
doesn't match anywhere in the file, the file will be left unchanged.
If the regexp does match, the last matching line will be replaced by
the expanded line parameter.
insertafter:
required: false
default: EOF
description:
- Used with C(state=present). If specified, the line will be inserted
after the specified regular expression. A special value is
available; C(EOF) for inserting the line at the end of the file.
May not be used with backrefs.
choices: [ 'EOF', '*regex*' ]
insertbefore:
required: false
version_added: "1.1"
description:
- Used with C(state=present). If specified, the line will be inserted
before the specified regular expression. A value is available;
C(BOF) for inserting the line at the beginning of the file.
May not be used with backrefs.
choices: [ 'BOF', '*regex*' ]
create:
required: false
choices: [ "yes", "no" ]
default: "no"
description:
- Used with C(state=present). If specified, the file will be created
if it does not already exist. By default it will fail if the file
is missing.
backup:
required: false
default: "no"
choices: [ "yes", "no" ]
description:
- Create a backup file including the timestamp information so you can
get the original file back if you somehow clobbered it incorrectly.
others:
description:
- All arguments accepted by the M(file) module also work here.
required: false
"""
EXAMPLES = r"""
lineinfile: dest=/etc/selinux/config regexp=^SELINUX= line=SELINUX=disabled
lineinfile: dest=/etc/sudoers state=absent regexp="^%wheel"
lineinfile: dest=/etc/host regexp='^127\.0\.0\.1' line='127.0.0.1 localhost' owner=root group=root mode=0644
lineinfile: dest=/etc/httpd/conf/httpd.conf regexp="^Listen " insertafter="^#Listen " line="Listen 8080"
lineinfile: dest=/etc/services regexp="^# port for http" insertbefore="^www.*80/tcp" line="# port for http by default"
lineinfile: dest=/etc/sudoers state=present regexp='^%wheel' line='%wheel ALL=(ALL) NOPASSWD: ALL'
lineinfile: dest=/opt/jboss-as/bin/standalone.conf regexp='^(.*)Xms(\d+)m(.*)$' line='\\1Xms${xms}m\\3' backrefs=yes
"""
def check_file_attrs(module, changed, message):
file_args = module.load_file_common_arguments(module.params)
if module.set_file_attributes_if_different(file_args, False):
if changed:
message += " and "
changed = True
message += "ownership, perms or SE linux context changed"
return message, changed
def present(module, dest, regexp, line, insertafter, insertbefore, create,
backup, backrefs):
if os.path.isdir(dest):
module.fail_json(rc=256, msg='Destination %s is a directory !' % dest)
elif not os.path.exists(dest):
if not create:
module.fail_json(rc=257, msg='Destination %s does not exist !' % dest)
destpath = os.path.dirname(dest)
if not os.path.exists(destpath):
os.makedirs(destpath)
lines = []
else:
f = open(dest, 'rb')
lines = f.readlines()
f.close()
msg = ""
mre = re.compile(regexp)
if insertafter not in (None, 'BOF', 'EOF'):
insre = re.compile(insertafter)
elif insertbefore not in (None, 'BOF'):
insre = re.compile(insertbefore)
else:
insre = None
# index[0] is the line num where regexp has been found
# index[1] is the line num where insertafter/inserbefore has been found
index = [-1, -1]
m = None
for lineno, cur_line in enumerate(lines):
match_found = mre.search(cur_line)
if match_found:
index[0] = lineno
m = match_found
elif insre is not None and insre.search(cur_line):
if insertafter:
# + 1 for the next line
index[1] = lineno + 1
if insertbefore:
# + 1 for the previous line
index[1] = lineno
msg = ''
changed = False
# Regexp matched a line in the file
if index[0] != -1:
if backrefs:
new_line = m.expand(line)
else:
# Don't do backref expansion if not asked.
new_line = line
if lines[index[0]] != new_line + os.linesep:
lines[index[0]] = new_line + os.linesep
msg = 'line replaced'
changed = True
elif backrefs:
# Do absolutely nothing, since it's not safe generating the line
# without the regexp matching to populate the backrefs.
pass
# Add it to the beginning of the file
elif insertbefore == 'BOF' or insertafter == 'BOF':
lines.insert(0, line + os.linesep)
msg = 'line added'
changed = True
# Add it to the end of the file if requested or
# if insertafter=/insertbefore didn't match anything
# (so default behaviour is to add at the end)
elif insertafter == 'EOF':
lines.append(line + os.linesep)
msg = 'line added'
changed = True
# Do nothing if insert* didn't match
elif index[1] == -1:
pass
# insert* matched, but not the regexp
else:
lines.insert(index[1], line + os.linesep)
msg = 'line added'
changed = True
if changed and not module.check_mode:
if backup and os.path.exists(dest):
module.backup_local(dest)
f = open(dest, 'wb')
f.writelines(lines)
f.close()
msg, changed = check_file_attrs(module, changed, msg)
module.exit_json(changed=changed, msg=msg)
def absent(module, dest, regexp, backup):
if os.path.isdir(dest):
module.fail_json(rc=256, msg='Destination %s is a directory !' % dest)
elif not os.path.exists(dest):
module.exit_json(changed=False, msg="file not present")
msg = ""
f = open(dest, 'rb')
lines = f.readlines()
f.close()
cre = re.compile(regexp)
found = []
def matcher(line):
if cre.search(line):
found.append(line)
return False
else:
return True
lines = filter(matcher, lines)
changed = len(found) > 0
if changed and not module.check_mode:
if backup:
module.backup_local(dest)
f = open(dest, 'wb')
f.writelines(lines)
f.close()
if changed:
msg = "%s line(s) removed" % len(found)
msg, changed = check_file_attrs(module, changed, msg)
module.exit_json(changed=changed, found=len(found), msg=msg)
def main():
module = AnsibleModule(
argument_spec=dict(
dest=dict(required=True, aliases=['name', 'destfile']),
state=dict(default='present', choices=['absent', 'present']),
regexp=dict(required=True),
line=dict(aliases=['value']),
insertafter=dict(default=None),
insertbefore=dict(default=None),
backrefs=dict(default=False, type='bool'),
create=dict(default=False, type='bool'),
backup=dict(default=False, type='bool'),
),
mutually_exclusive=[['insertbefore', 'insertafter']],
add_file_common_args=True,
supports_check_mode=True
)
params = module.params
create = module.params['create']
backup = module.params['backup']
backrefs = module.params['backrefs']
dest = os.path.expanduser(params['dest'])
if params['state'] == 'present':
if 'line' not in params:
module.fail_json(msg='line= is required with state=present')
# Deal with the insertafter default value manually, to avoid errors
# because of the mutually_exclusive mechanism.
ins_bef, ins_aft = params['insertbefore'], params['insertafter']
if ins_bef is None and ins_aft is None:
ins_aft = 'EOF'
present(module, dest, params['regexp'], params['line'],
ins_aft, ins_bef, create, backup, backrefs)
else:
absent(module, dest, params['regexp'], backup)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

59
library/files/template Normal file
View file

@ -0,0 +1,59 @@
# this is a virtual module that is entirely implemented server side
DOCUMENTATION = '''
---
module: template
short_description: Templates a file out to a remote server.
description:
- Templates are processed by the Jinja2 templating language
(U(http://jinja.pocoo.org/docs/)) - documentation on the template
formatting can be found in the Template Designer Documentation
(U(http://jinja.pocoo.org/docs/templates/)).
- "Six additional variables can be used in templates: C(ansible_managed)
(configurable via the C(defaults) section of C(ansible.cfg)) contains a string
which can be used to describe the template name, host, modification time of the
template file and the owner uid, C(template_host) contains the node name of
the template's machine, C(template_uid) the owner, C(template_path) the
relative path of the template, C(template_fullpath) is the absolute path of the
template, and C(template_run_date) is the date that the template was rendered."
options:
src:
description:
- Path of a Jinja2 formatted template on the local server. This can be a relative or absolute path.
required: true
default: null
aliases: []
dest:
description:
- Location to render the template to on the remote machine.
required: true
default: null
backup:
description:
- Create a backup file including the timestamp information so you can get
the original file back if you somehow clobbered it incorrectly.
required: false
choices: [ "yes", "no" ]
default: "no"
validate:
description:
- validation to run before copying into place
required: false
default: ""
version_added: "1.2"
others:
description:
- all arguments accepted by the M(file) module also work here
required: false
examples:
- code: "template: src=/mytemplates/foo.j2 dest=/etc/file.conf owner=bin group=wheel mode=0644"
description: "Example from Ansible Playbooks"
- code: "action: temlpate src=/mine/sudoers dest=/etc/sudoers validate='visudo -c %s'"
description: "Copy a new C(sudoers) file into place, after passing validation with visudo"
notes:
- Since Ansible version 0.9, templates are loaded with C(trim_blocks=True).
- 'You can override jinja2 settings by adding a special header to template file.
i.e. c(#jinja2: trim_blocks: False)'
requirements: null
author: Michael DeHaan
'''