2012-03-16 02:53:14 +01:00
|
|
|
#!/usr/bin/python
|
2012-08-03 03:29:10 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
2012-03-16 02:53:14 +01:00
|
|
|
|
|
|
|
# (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
|
2012-04-12 19:33:10 +02:00
|
|
|
try:
|
|
|
|
import selinux
|
|
|
|
HAVE_SELINUX=True
|
|
|
|
except ImportError:
|
|
|
|
HAVE_SELINUX=False
|
2012-03-16 02:53:14 +01:00
|
|
|
|
2012-09-19 16:09:26 +02:00
|
|
|
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
|
2012-11-22 07:23:10 +01:00
|
|
|
the M(file) module - including M(copy), M(template), and M(assemble).
|
2012-09-19 16:09:26 +02:00
|
|
|
options:
|
2013-07-12 21:20:04 +02:00
|
|
|
dest:
|
2012-09-28 03:06:31 +02:00
|
|
|
description:
|
2012-11-21 18:49:30 +01:00
|
|
|
- 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)
|
2012-09-28 03:06:31 +02:00
|
|
|
required: true
|
|
|
|
default: []
|
2013-07-12 21:20:04 +02:00
|
|
|
aliases: ['name', 'path']
|
2012-09-28 03:06:31 +02:00
|
|
|
state:
|
|
|
|
description:
|
2012-10-01 09:18:54 +02:00
|
|
|
- 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.
|
2013-06-15 17:14:34 +02:00
|
|
|
If C(link), the symbolic link will be created or changed. Use C(hard)
|
|
|
|
for hardlinks. If C(absent), directories will be recursively deleted,
|
|
|
|
and files or symlinks will be unlinked.
|
2012-09-28 03:06:31 +02:00
|
|
|
required: false
|
|
|
|
default: file
|
2013-06-15 17:14:34 +02:00
|
|
|
choices: [ file, link, directory, hard, absent ]
|
2012-09-28 03:06:31 +02:00
|
|
|
mode:
|
2012-10-01 09:18:54 +02:00
|
|
|
required: false
|
|
|
|
default: null
|
|
|
|
choices: []
|
|
|
|
description:
|
2012-11-21 18:49:30 +01:00
|
|
|
- mode the file or directory should be, such as 0644 as would be fed to I(chmod)
|
2012-10-01 09:18:54 +02:00
|
|
|
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
|
2012-11-21 18:49:30 +01:00
|
|
|
policy if available
|
2012-10-01 09:18:54 +02:00
|
|
|
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" ]
|
2012-09-28 03:06:31 +02:00
|
|
|
description:
|
2012-10-01 09:18:54 +02:00
|
|
|
- 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.
|
2013-02-09 18:01:11 +01:00
|
|
|
recurse:
|
|
|
|
required: false
|
2013-03-12 13:18:12 +01:00
|
|
|
default: "no"
|
2013-02-09 18:01:11 +01:00
|
|
|
choices: [ "yes", "no" ]
|
2013-02-17 18:48:17 +01:00
|
|
|
version_added: "1.1"
|
2013-02-09 18:01:11 +01:00
|
|
|
description:
|
|
|
|
- recursively set the specified file attributes (applies only to state=directory)
|
2013-06-04 12:06:08 +02:00
|
|
|
force:
|
|
|
|
required: false
|
|
|
|
default: "no"
|
2013-06-12 07:36:43 +02:00
|
|
|
choices: [ "yes", "no" ]
|
2013-06-04 12:06:08 +02:00
|
|
|
description:
|
2013-07-01 12:18:07 +02:00
|
|
|
- 'force the creation of the symlinks in two cases: the source file does
|
2013-06-04 12:06:08 +02:00
|
|
|
not exist (but will appear later); the destination exists and a file (so, we need to unlink the
|
2013-07-12 21:20:04 +02:00
|
|
|
"dest" file and create symlink to the "src" file in place of it).'
|
2012-09-19 16:09:26 +02:00
|
|
|
notes:
|
|
|
|
- See also M(copy), M(template), M(assemble)
|
|
|
|
requirements: [ ]
|
2012-10-01 09:18:54 +02:00
|
|
|
author: Michael DeHaan
|
2012-09-19 16:09:26 +02:00
|
|
|
'''
|
|
|
|
|
2013-06-14 11:53:43 +02:00
|
|
|
EXAMPLES = '''
|
2013-07-12 21:20:04 +02:00
|
|
|
- file: dest=/etc/foo.conf owner=foo group=foo mode=0644
|
2013-06-14 11:53:43 +02:00
|
|
|
- file: src=/file/to/link/to dest=/path/to/symlink owner=foo group=foo state=link
|
|
|
|
'''
|
|
|
|
|
2013-07-12 21:20:04 +02:00
|
|
|
def dolink(src, dest, state, module):
|
2013-06-15 17:14:34 +02:00
|
|
|
try:
|
|
|
|
if state == 'hard':
|
2013-07-12 21:20:04 +02:00
|
|
|
os.link(src,dest)
|
2013-06-15 17:14:34 +02:00
|
|
|
else:
|
2013-07-12 21:20:04 +02:00
|
|
|
os.symlink(src, dest)
|
2013-06-15 17:14:34 +02:00
|
|
|
except OSError, e:
|
2013-07-12 21:20:04 +02:00
|
|
|
module.fail_json(dest=dest, msg='Error while linking: %s' % str(e))
|
2013-06-15 17:14:34 +02:00
|
|
|
|
2012-07-31 03:50:32 +02:00
|
|
|
def main():
|
|
|
|
|
2012-08-11 18:35:58 +02:00
|
|
|
# FIXME: pass this around, should not use global
|
2012-07-31 03:50:32 +02:00
|
|
|
global module
|
2012-08-11 18:35:58 +02:00
|
|
|
|
2012-08-02 01:42:31 +02:00
|
|
|
module = AnsibleModule(
|
2012-07-31 03:50:32 +02:00
|
|
|
argument_spec = dict(
|
2013-06-15 17:14:34 +02:00
|
|
|
state = dict(choices=['file','directory','link','hard','absent'], default='file'),
|
2013-07-12 21:20:04 +02:00
|
|
|
dest = dict(aliases=['name', 'path'], required=True),
|
2013-02-27 02:57:36 +01:00
|
|
|
recurse = dict(default='no', type='bool'),
|
2013-05-30 12:53:24 +02:00
|
|
|
force = dict(required=False,default=False,type='bool'),
|
2013-03-27 04:12:56 +01:00
|
|
|
diff_peek = dict(default=None),
|
|
|
|
validate = dict(required=False, default=None),
|
2012-10-21 04:51:36 +02:00
|
|
|
),
|
2013-02-04 01:46:25 +01:00
|
|
|
add_file_common_args=True,
|
|
|
|
supports_check_mode=True
|
2012-07-31 03:50:32 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
params = module.params
|
|
|
|
state = params['state']
|
2013-05-30 12:53:24 +02:00
|
|
|
force = params['force']
|
2013-07-12 21:20:04 +02:00
|
|
|
params['dest'] = dest = os.path.expanduser(params['dest'])
|
2012-10-13 02:07:05 +02:00
|
|
|
|
2013-02-25 23:32:52 +01:00
|
|
|
# short-circuit for diff_peek
|
|
|
|
if params.get('diff_peek', None) is not None:
|
|
|
|
appears_binary = False
|
|
|
|
try:
|
2013-07-12 21:20:04 +02:00
|
|
|
f = open(dest)
|
2013-02-25 23:32:52 +01:00
|
|
|
b = f.read(8192)
|
|
|
|
f.close()
|
|
|
|
if b.find("\x00") != -1:
|
|
|
|
appears_binary = True
|
|
|
|
except:
|
|
|
|
pass
|
2013-07-12 21:20:04 +02:00
|
|
|
module.exit_json(dest=dest, changed=False, appears_binary=appears_binary)
|
2013-02-25 23:32:52 +01:00
|
|
|
|
2012-10-13 02:07:05 +02:00
|
|
|
# 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
|
|
|
|
|
2012-10-21 04:51:36 +02:00
|
|
|
src = params.get('src', None)
|
2012-07-31 03:50:32 +02:00
|
|
|
if src:
|
|
|
|
src = os.path.expanduser(src)
|
|
|
|
|
2013-07-12 21:20:04 +02:00
|
|
|
if src is not None and os.path.isdir(dest) and state != "link":
|
|
|
|
params['dest'] = dest = os.path.join(dest, os.path.basename(src))
|
2012-10-13 02:07:05 +02:00
|
|
|
|
2012-10-21 04:51:36 +02:00
|
|
|
file_args = module.load_file_common_arguments(params)
|
2012-07-31 03:50:32 +02:00
|
|
|
|
2013-07-12 21:20:04 +02:00
|
|
|
if state in ['link','hard'] and (src is None or dest is None):
|
2013-06-15 17:14:34 +02:00
|
|
|
module.fail_json(msg='src and dest are required for creating links')
|
2013-07-12 21:20:04 +02:00
|
|
|
elif dest is None:
|
2012-10-21 04:51:36 +02:00
|
|
|
module.fail_json(msg='path is required')
|
2012-07-31 03:50:32 +02:00
|
|
|
|
|
|
|
changed = False
|
|
|
|
|
|
|
|
prev_state = 'absent'
|
2012-10-13 02:07:05 +02:00
|
|
|
|
2013-07-12 21:20:04 +02:00
|
|
|
if os.path.lexists(dest):
|
|
|
|
if os.path.islink(dest):
|
2012-07-31 03:50:32 +02:00
|
|
|
prev_state = 'link'
|
2013-07-12 21:20:04 +02:00
|
|
|
elif os.path.isdir(dest):
|
2012-07-31 03:50:32 +02:00
|
|
|
prev_state = 'directory'
|
2012-08-21 19:36:48 +02:00
|
|
|
else:
|
|
|
|
prev_state = 'file'
|
2012-03-16 02:53:14 +01:00
|
|
|
|
2012-07-31 03:50:32 +02:00
|
|
|
if prev_state != 'absent' and state == 'absent':
|
|
|
|
try:
|
|
|
|
if prev_state == 'directory':
|
2013-07-12 21:20:04 +02:00
|
|
|
if os.path.islink(dest):
|
2013-05-11 21:04:42 +02:00
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(changed=True)
|
2013-07-12 21:20:04 +02:00
|
|
|
os.unlink(dest)
|
2012-07-31 03:50:32 +02:00
|
|
|
else:
|
2012-10-21 04:51:36 +02:00
|
|
|
try:
|
2013-05-11 21:04:42 +02:00
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(changed=True)
|
2013-07-12 21:20:04 +02:00
|
|
|
shutil.rmtree(dest, ignore_errors=False)
|
2012-10-21 04:51:36 +02:00
|
|
|
except:
|
|
|
|
module.exit_json(msg="rmtree failed")
|
2012-07-31 03:50:32 +02:00
|
|
|
else:
|
2013-04-26 03:33:47 +02:00
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(changed=True)
|
2013-07-12 21:20:04 +02:00
|
|
|
os.unlink(dest)
|
2012-07-31 03:50:32 +02:00
|
|
|
except Exception, e:
|
2013-07-12 21:20:04 +02:00
|
|
|
module.fail_json(dest=dest, msg=str(e))
|
|
|
|
module.exit_json(dest=dest, changed=True)
|
2012-03-16 02:53:14 +01:00
|
|
|
|
2012-10-17 00:14:42 +02:00
|
|
|
if prev_state != 'absent' and prev_state != state:
|
2013-05-30 12:53:24 +02:00
|
|
|
if force and prev_state == 'file' and state == 'link':
|
|
|
|
pass
|
|
|
|
else:
|
2013-07-12 21:20:04 +02:00
|
|
|
module.fail_json(dest=dest, msg='refusing to convert between %s and %s for %s' % (prev_state, state, src))
|
2012-03-16 02:53:14 +01:00
|
|
|
|
2012-07-31 03:50:32 +02:00
|
|
|
if prev_state == 'absent' and state == 'absent':
|
2013-07-12 21:20:04 +02:00
|
|
|
module.exit_json(dest=dest, changed=False)
|
2012-03-16 02:53:14 +01:00
|
|
|
|
2012-07-31 03:50:32 +02:00
|
|
|
if state == 'file':
|
2012-08-07 02:07:02 +02:00
|
|
|
|
2012-09-19 03:02:16 +02:00
|
|
|
if prev_state != 'file':
|
2013-07-12 21:20:04 +02:00
|
|
|
module.fail_json(dest=dest, msg='file (%s) does not exist, use copy or template module to create' % dest)
|
2012-03-16 02:53:14 +01:00
|
|
|
|
2012-10-21 04:51:36 +02:00
|
|
|
changed = module.set_file_attributes_if_different(file_args, changed)
|
2013-07-12 21:20:04 +02:00
|
|
|
module.exit_json(dest=dest, changed=changed)
|
2012-03-16 02:53:14 +01:00
|
|
|
|
2012-07-31 03:50:32 +02:00
|
|
|
elif state == 'directory':
|
|
|
|
if prev_state == 'absent':
|
2013-04-26 03:33:47 +02:00
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(changed=True)
|
2013-07-12 21:20:04 +02:00
|
|
|
os.makedirs(dest)
|
2012-07-31 03:50:32 +02:00
|
|
|
changed = True
|
2012-08-07 02:07:02 +02:00
|
|
|
|
2012-10-21 04:51:36 +02:00
|
|
|
changed = module.set_directory_attributes_if_different(file_args, changed)
|
2013-02-23 19:59:52 +01:00
|
|
|
recurse = params['recurse']
|
2013-02-09 18:01:11 +01:00
|
|
|
if recurse:
|
2013-07-12 21:20:04 +02:00
|
|
|
for root,dirs,files in os.walk( file_args['dest'] ):
|
2013-02-09 18:01:11 +01:00
|
|
|
for dir in dirs:
|
|
|
|
dirname=os.path.join(root,dir)
|
|
|
|
tmp_file_args = file_args.copy()
|
2013-07-12 21:20:04 +02:00
|
|
|
tmp_file_args['dest']=dirname
|
2013-02-09 18:01:11 +01:00
|
|
|
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()
|
2013-07-12 21:20:04 +02:00
|
|
|
tmp_file_args['dest']=filename
|
2013-02-09 18:01:11 +01:00
|
|
|
changed = module.set_file_attributes_if_different(tmp_file_args, changed)
|
2013-07-12 21:20:04 +02:00
|
|
|
module.exit_json(dest=dest, changed=changed)
|
2012-03-16 02:53:14 +01:00
|
|
|
|
2013-06-15 17:14:34 +02:00
|
|
|
elif state in ['link','hard']:
|
2012-08-07 02:07:02 +02:00
|
|
|
|
2012-07-31 03:50:32 +02:00
|
|
|
if os.path.isabs(src):
|
|
|
|
abs_src = src
|
|
|
|
else:
|
2012-08-10 03:46:03 +02:00
|
|
|
module.fail_json(msg="absolute paths are required")
|
2013-05-30 13:05:10 +02:00
|
|
|
|
|
|
|
if not os.path.exists(abs_src) and not force:
|
2013-07-12 21:20:04 +02:00
|
|
|
module.fail_json(dest=dest, src=src, msg='src file does not exist')
|
2012-08-07 02:07:02 +02:00
|
|
|
|
2012-07-31 03:50:32 +02:00
|
|
|
if prev_state == 'absent':
|
2013-04-26 03:33:47 +02:00
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(changed=True)
|
2013-07-12 21:20:04 +02:00
|
|
|
dolink(src, dest, state, module)
|
2012-07-31 03:50:32 +02:00
|
|
|
changed = True
|
|
|
|
elif prev_state == 'link':
|
2013-07-12 21:20:04 +02:00
|
|
|
old_src = os.readlink(dest)
|
2012-07-31 03:50:32 +02:00
|
|
|
if not os.path.isabs(old_src):
|
2013-07-12 21:20:04 +02:00
|
|
|
old_src = os.path.join(os.path.dirname(dest), old_src)
|
2012-07-31 03:50:32 +02:00
|
|
|
if old_src != src:
|
2013-04-26 03:33:47 +02:00
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(changed=True)
|
2013-07-12 21:20:04 +02:00
|
|
|
os.unlink(dest)
|
|
|
|
dolink(src, dest, state, module)
|
2012-09-19 03:02:16 +02:00
|
|
|
changed = True
|
2013-06-15 17:14:34 +02:00
|
|
|
elif prev_state == 'file':
|
2013-05-30 12:53:24 +02:00
|
|
|
if not force:
|
2013-07-12 21:20:04 +02:00
|
|
|
module.fail_json(dest=dest, src=src, msg='Cannot link, file exists at destination')
|
2013-05-30 12:53:24 +02:00
|
|
|
else:
|
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(changed=True)
|
2013-07-12 21:20:04 +02:00
|
|
|
os.unlink(dest)
|
|
|
|
dolink(src, dest, state, module)
|
2013-05-30 12:53:24 +02:00
|
|
|
changed = True
|
2012-07-31 03:50:32 +02:00
|
|
|
else:
|
2013-07-12 21:20:04 +02:00
|
|
|
module.fail_json(dest=dest, src=src, msg='unexpected position reached')
|
2012-07-31 03:50:32 +02:00
|
|
|
|
|
|
|
# set modes owners and context as needed
|
2012-10-21 04:51:36 +02:00
|
|
|
|
|
|
|
file_args = module.load_file_common_arguments(module.params)
|
2013-07-12 21:20:04 +02:00
|
|
|
changed = module.set_context_if_different(dest, file_args['secontext'], changed)
|
|
|
|
changed = module.set_owner_if_different(dest, file_args['owner'], changed)
|
|
|
|
changed = module.set_group_if_different(dest, file_args['group'], changed)
|
|
|
|
changed = module.set_mode_if_different(dest, file_args['mode'], changed)
|
2012-04-02 01:10:23 +02:00
|
|
|
|
2013-07-12 21:20:04 +02:00
|
|
|
module.exit_json(dest=dest, src=src, changed=changed)
|
2012-04-02 01:10:23 +02:00
|
|
|
|
2013-07-12 21:20:04 +02:00
|
|
|
module.fail_json(dest=dest, msg='unexpected position reached')
|
2012-04-02 01:10:23 +02:00
|
|
|
|
2012-07-31 03:50:32 +02:00
|
|
|
# this is magic, see lib/ansible/module_common.py
|
|
|
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
2012-08-01 06:21:36 +02:00
|
|
|
main()
|
2012-03-16 02:53:14 +01:00
|
|
|
|