mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-10-03 23:14:02 -07:00
Allow modules to be categorized, and also sort them when generating the documentation.
This commit is contained in:
parent
f46bdb6343
commit
391fb98ee2
87 changed files with 118 additions and 67 deletions
136
library/files/assemble
Normal file
136
library/files/assemble
Normal 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
175
library/files/copy
Normal 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
295
library/files/file
Normal 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
186
library/files/ini_file
Normal 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
320
library/files/lineinfile
Normal 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
59
library/files/template
Normal 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
|
||||
'''
|
Loading…
Add table
Add a link
Reference in a new issue