summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Coca <bcoca@users.noreply.github.com>2016-11-07 15:48:04 -0500
committerGitHub <noreply@github.com>2016-11-07 15:48:04 -0500
commita0f27d552c4bee9374ab224cb969119e5d9c0cc5 (patch)
tree8be2ec79da36a65aef09e0b9722667b1bfa1ffae
parent7a33c14782ebc944e812c52d94b7d3d33395febe (diff)
downloadansible-a0f27d552c4bee9374ab224cb969119e5d9c0cc5.tar.gz
File attributes (#18213)
* added attributes to base file params * dont change attributes when none * fixed test to deal with new attributes
-rw-r--r--lib/ansible/module_utils/basic.py93
-rw-r--r--lib/ansible/utils/module_docs_fragments/files.py7
-rw-r--r--test/units/module_utils/test_basic.py1
3 files changed, 100 insertions, 1 deletions
diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py
index 177396d4d7..6eea60d825 100644
--- a/lib/ansible/module_utils/basic.py
+++ b/lib/ansible/module_utils/basic.py
@@ -34,6 +34,29 @@ BOOLEANS = BOOLEANS_TRUE + BOOLEANS_FALSE
SIZE_RANGES = { 'Y': 1<<80, 'Z': 1<<70, 'E': 1<<60, 'P': 1<<50, 'T': 1<<40, 'G': 1<<30, 'M': 1<<20, 'K': 1<<10, 'B': 1 }
+FILE_ATTRIBUTES = {
+ 'A': 'noatime',
+ 'a': 'append',
+ 'c': 'compressed',
+ 'C': 'nocow',
+ 'd': 'nodump',
+ 'D': 'dirsync',
+ 'e': 'extents',
+ 'E': 'encrypted',
+ 'h': 'blocksize',
+ 'i': 'immutable',
+ 'I': 'indexed',
+ 'j': 'journalled',
+ 'N': 'inline',
+ 's': 'zero',
+ 'S': 'synchronous',
+ 't': 'notail',
+ 'T': 'blockroot',
+ 'u': 'undelete',
+ 'X': 'compressedraw',
+ 'Z': 'compresseddirty',
+}
+
# ansible modules can be written in any language. To simplify
# development of Python modules, the functions available here can
# be used to do many common tasks
@@ -203,6 +226,7 @@ FILE_COMMON_ARGUMENTS=dict(
delimiter = dict(), # used by assemble
directory_mode = dict(), # used by copy
unsafe_writes = dict(type='bool'), # should be available to any module using atomic_move
+ attributes = dict(aliases=['attr']),
)
PASSWD_ARG_RE = re.compile(r'^[-]{0,2}pass[-]?(word|wd)?')
@@ -618,6 +642,19 @@ def _lenient_lowercase(lst):
lowered.append(value)
return lowered
+def format_attributes(attributes):
+ attribute_list = []
+ for attr in attributes:
+ if attr in FILE_ATTRIBUTES:
+ attribute_list.append(FILE_ATTRIBUTES[attr])
+ return attribute_list
+
+def get_flags_from_attributes(attributes):
+ flags = []
+ for key,attr in FILE_ATTRIBUTES.iteritems():
+ if attr in attributes:
+ flags.append(key)
+ return ''.join(flags)
class AnsibleFallbackNotFound(Exception):
pass
@@ -759,10 +796,11 @@ class AnsibleModule(object):
if i is not None and secontext[i] == '_default':
secontext[i] = default_secontext[i]
+ attributes = params.get('attributes', None)
return dict(
path=path, mode=mode, owner=owner, group=group,
seuser=seuser, serole=serole, setype=setype,
- selevel=selevel, secontext=secontext,
+ selevel=selevel, secontext=secontext, attributes=attributes,
)
@@ -1058,6 +1096,56 @@ class AnsibleModule(object):
changed = True
return changed
+ def set_attributes_if_different(self, path, attributes, changed, diff=None):
+
+ if attributes is None:
+ return changed
+
+ b_path = to_bytes(path, errors='surrogate_or_strict')
+ b_path = os.path.expanduser(os.path.expandvars(b_path))
+ existing = self.get_file_attributes(b_path)
+
+ if existing.get('attr_flags','') != attributes:
+ attrcmd = self.get_bin_path('chattr')
+ if attrcmd:
+ attrcmd = [attrcmd, '=%s' % attributes, b_path]
+ changed = True
+
+ if diff is not None:
+ if 'before' not in diff:
+ diff['before'] = {}
+ diff['before']['attributes'] = existing.get('attr_flags')
+ if 'after' not in diff:
+ diff['after'] = {}
+ diff['after']['attributes'] = attributes
+
+ if not self.check_mode:
+ try:
+ rc, out, err = self.run_command(attrcmd)
+ if rc != 0 or err:
+ raise Exception("Error while setting attributes: %s" % (out + err))
+ except:
+ e = get_exception()
+ self.fail_json(path=path, msg='chattr failed', details=str(e))
+ return changed
+
+ def get_file_attributes(self, path):
+ output = {}
+ attrcmd = self.get_bin_path('lsattr', False)
+ if attrcmd:
+ attrcmd = [attrcmd, '-vd', path]
+ try:
+ rc, out, err = self.run_command(attrcmd)
+ if rc == 0:
+ res = out.split(' ')[0:2]
+ output['attr_flags'] = res[1].replace('-','').strip()
+ output['version'] = res[0].strip()
+ output['attributes'] = format_attributes(output['attr_flags'])
+ except:
+ pass
+ return output
+
+
def _symbolic_mode_to_octal(self, path_stat, symbolic_mode):
new_mode = stat.S_IMODE(path_stat.st_mode)
@@ -1167,6 +1255,9 @@ class AnsibleModule(object):
changed = self.set_mode_if_different(
file_args['path'], file_args['mode'], changed, diff
)
+ changed = self.set_attributes_if_different(
+ file_args['path'], file_args['attributes'], changed, diff
+ )
return changed
def set_directory_attributes_if_different(self, file_args, changed, diff=None):
diff --git a/lib/ansible/utils/module_docs_fragments/files.py b/lib/ansible/utils/module_docs_fragments/files.py
index 9bc40a0929..fc1ca70892 100644
--- a/lib/ansible/utils/module_docs_fragments/files.py
+++ b/lib/ansible/utils/module_docs_fragments/files.py
@@ -70,4 +70,11 @@ options:
required: false
default: false
version_added: "2.2"
+ attributes:
+ description:
+ - Attributes of the file or directory should be. To get supported flags look at the man page for I(chattr) on the taget system.
+ required: false
+ default: None
+ aliases: ['attr']
+ version_added: "2.3"
"""
diff --git a/test/units/module_utils/test_basic.py b/test/units/module_utils/test_basic.py
index 6da86db2a1..a03e3401f1 100644
--- a/test/units/module_utils/test_basic.py
+++ b/test/units/module_utils/test_basic.py
@@ -422,6 +422,7 @@ class TestModuleUtilsBasic(ModuleTestCase):
final_params.update(dict(
path = '/path/to/real_file',
secontext=['unconfined_u', 'object_r', 'default_t', 's0'],
+ attributes=None,
))
# with the proper params specified, the returned dictionary should represent