ansible/library/files/file

287 lines
11 KiB
Python

#!/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
version_added: "historical"
short_description: Sets attributes of files
extends_documentation_fragment: 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).
notes:
- See also M(copy), M(template), M(assemble)
requirements: [ ]
author: Michael DeHaan
'''
EXAMPLES = '''
- file: path=/etc/foo.conf owner=foo group=foo mode=0644
- file: src=/file/to/link/to dest=/path/to/symlink owner=foo group=foo state=link
- file: src=/tmp/{{ item.path }} dest={{ item.dest }} state=link
with_items:
- { path: 'x', dest: 'y' }
- { path: 'z', dest: 'k' }
'''
def main():
module = AnsibleModule(
argument_spec = dict(
state = dict(choices=['file','directory','link','hard','touch','absent'], default=None),
path = dict(aliases=['dest', 'name'], required=True),
original_basename = dict(required=False), # Internal use only, for recursive ops
recurse = dict(default='no', type='bool'),
force = dict(required=False,default=False,type='bool'),
diff_peek = dict(default=None),
validate = dict(required=False, default=None),
src = dict(required=False, default=None),
),
add_file_common_args=True,
supports_check_mode=True
)
params = module.params
state = params['state']
force = params['force']
diff_peek = params['diff_peek']
src = params['src']
# modify source as we later reload and pass, specially relevant when used by other modules.
params['path'] = path = os.path.expanduser(params['path'])
# short-circuit for diff_peek
if diff_peek is not None:
appears_binary = False
try:
f = open(path)
b = f.read(8192)
f.close()
if "\x00" in b:
appears_binary = True
except:
pass
module.exit_json(path=path, changed=False, appears_binary=appears_binary)
# Find out current state
prev_state = 'absent'
if os.path.lexists(path):
if os.path.islink(path):
prev_state = 'link'
elif os.path.isdir(path):
prev_state = 'directory'
elif os.stat(path).st_nlink > 1:
prev_state = 'hard'
else:
# could be many other things, but defaulting to file
prev_state = 'file'
# state should default to file, but since that creates many conflicts,
# default to 'current' when it exists.
if state is None:
if prev_state != 'absent':
state = prev_state
else:
state = 'file'
# 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
if src is not None:
src = os.path.expanduser(src)
# original_basename is used by other modules that depend on file.
if os.path.isdir(path) and state not in ["link", "absent"]:
if params['original_basename']:
basename = params['original_basename']
else:
basename = os.path.basename(src)
params['path'] = path = os.path.join(path, basename)
else:
if state in ['link','hard']:
module.fail_json(msg='src and dest are required for creating links')
file_args = module.load_file_common_arguments(params)
changed = False
recurse = params['recurse']
if recurse and state != 'directory':
module.fail_json(path=path, msg="recurse option requires state to be 'directory'")
if state == 'absent':
if state != prev_state:
if not module.check_mode:
if prev_state == 'directory':
try:
shutil.rmtree(path, ignore_errors=False)
except Exception, e:
module.fail_json(msg="rmtree failed: %s" % str(e))
else:
try:
os.unlink(path)
except Exception, e:
module.fail_json(path=path, msg="unlinking failed: %s " % str(e))
module.exit_json(path=path, changed=True)
else:
module.exit_json(path=path, changed=False)
elif state == 'file':
if state != prev_state:
# file is not absent and any other state is a conflict
module.fail_json(path=path, msg='file (%s) is %s, cannot continue' % (path, prev_state))
changed = module.set_fs_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)
changed = True
curpath = ''
for dirname in path.split('/'):
curpath = '/'.join([curpath, dirname])
if not os.path.exists(curpath):
os.mkdir(curpath)
tmp_file_args = file_args.copy()
tmp_file_args['path']=curpath
changed = module.set_fs_attributes_if_different(tmp_file_args, changed)
changed = module.set_fs_attributes_if_different(file_args, changed)
if recurse:
for root,dirs,files in os.walk( file_args['path'] ):
for fsobj in dirs + files:
fsname=os.path.join(root, fsobj)
tmp_file_args = file_args.copy()
tmp_file_args['path']=fsname
changed = module.set_fs_attributes_if_different(tmp_file_args, changed)
module.exit_json(path=path, changed=changed)
elif state in ['link','hard']:
if os.path.isdir(path) and not os.path.islink(path):
relpath = path
else:
relpath = os.path.dirname(path)
absrc = os.path.normpath('%s/%s' % (relpath, os.path.basename(src)))
if not os.path.exists(src) and not os.path.exists(absrc) and not force:
module.fail_json(path=path, src=src, msg='src file does not exist, use "force=yes" if you really want to create the link: %s' % absrc)
if state == 'hard':
if not os.path.isabs(src):
module.fail_json(msg="absolute paths are required")
elif prev_state == 'directory':
if not force:
module.fail_json(path=path, msg='refusing to convert between %s and %s for %s' % (prev_state, state, path))
elif len(os.listdir(path)) > 0:
# refuse to replace a directory that has files in it
module.fail_json(path=path, msg='the directory %s is not empty, refusing to convert it' % path)
elif prev_state in ['file', 'hard'] and not force:
module.fail_json(path=path, msg='refusing to convert between %s and %s for %s' % (prev_state, state, path))
if prev_state == 'absent':
changed = True
elif prev_state == 'link':
old_src = os.readlink(path)
if old_src != src:
changed = True
elif prev_state == 'hard':
if not (state == 'hard' and os.stat(path).st_ino == os.stat(src).st_ino):
changed = True
if not force:
module.fail_json(dest=path, src=src, msg='Cannot link, different hard link exists at destination')
elif prev_state in ['file', 'directory']:
changed = True
if not force:
module.fail_json(dest=path, src=src, msg='Cannot link, %s exists at destination' % prev_state)
else:
module.fail_json(dest=path, src=src, msg='unexpected position reached')
if changed and not module.check_mode:
if prev_state != 'absent':
# try to replace atomically
tmppath = '/'.join([os.path.dirname(path), ".%s.%s.tmp" % (os.getpid(),time.time())])
try:
if prev_state == 'directory' and (state == 'hard' or state == 'link'):
os.rmdir(path)
if state == 'hard':
os.link(src,tmppath)
else:
os.symlink(src, tmppath)
os.rename(tmppath, path)
except OSError, e:
if os.path.exists(tmppath):
os.unlink(tmppath)
module.fail_json(path=path, msg='Error while replacing: %s' % str(e))
else:
try:
if state == 'hard':
os.link(src,path)
else:
os.symlink(src, path)
except OSError, e:
module.fail_json(path=path, msg='Error while linking: %s' % str(e))
if module.check_mode and not os.path.exists(path):
module.exit_json(dest=path, src=src, changed=changed)
changed = module.set_fs_attributes_if_different(file_args, changed)
module.exit_json(dest=path, src=src, changed=changed)
elif state == 'touch':
if not module.check_mode:
if prev_state == 'absent':
try:
open(path, 'w').close()
except OSError, e:
module.fail_json(path=path, msg='Error, could not touch target: %s' % str(e))
elif prev_state in ['file', 'directory']:
try:
os.utime(path, None)
except OSError, e:
module.fail_json(path=path, msg='Error while touching existing target: %s' % str(e))
else:
module.fail_json(msg='Cannot touch other than files and directories')
module.set_fs_attributes_if_different(file_args, True)
module.exit_json(dest=path, changed=True)
module.fail_json(path=path, msg='unexpected position reached')
# import module snippets
from ansible.module_utils.basic import *
main()