def main(): ''' Modify kubeconfig located at `client_path`, setting the certificate authority data to specified `ca_data` or contents of `ca_path`. ''' module = AnsibleModule( # noqa: F405 argument_spec=dict( client_path=dict(required=True), ca_data=dict(required=False, default=None), ca_path=dict(required=False, default=None), backup=dict(required=False, default=True, type='bool'), ), supports_check_mode=True, mutually_exclusive=[['ca_data', 'ca_path']], required_one_of=[['ca_data', 'ca_path']]) client_path = module.params['client_path'] ca_data = module.params['ca_data'] ca_path = module.params['ca_path'] backup = module.params['backup'] try: with open(client_path) as client_config_file: client_config_data = yaml.safe_load(client_config_file.read()) if ca_data is None: with open(ca_path) as ca_file: ca_data = base64.standard_b64encode(ca_file.read()) changes = [] # Naively update the CA information for each cluster in the # kubeconfig. for cluster in client_config_data['clusters']: if cluster['cluster']['certificate-authority-data'] != ca_data: cluster['cluster']['certificate-authority-data'] = ca_data changes.append(cluster['name']) if not module.check_mode: if len(changes) > 0 and backup: module.backup_local(client_path) with open(client_path, 'w') as client_config_file: client_config_string = yaml.dump(client_config_data, default_flow_style=False) client_config_string = client_config_string.replace( '\'\'', '""') client_config_file.write(client_config_string) return module.exit_json(changed=(len(changes) > 0)) # ignore broad-except error to avoid stack trace to ansible user # pylint: disable=broad-except except Exception as error: return module.fail_json(msg=str(error))
def main(): ''' Modify kubeconfig located at `client_path`, setting the certificate authority data to specified `ca_data` or contents of `ca_path`. ''' module = AnsibleModule( # noqa: F405 argument_spec=dict( client_path=dict(required=True), ca_data=dict(required=False, default=None), ca_path=dict(required=False, default=None), backup=dict(required=False, default=True, type='bool'), ), supports_check_mode=True, mutually_exclusive=[['ca_data', 'ca_path']], required_one_of=[['ca_data', 'ca_path']] ) client_path = module.params['client_path'] ca_data = module.params['ca_data'] ca_path = module.params['ca_path'] backup = module.params['backup'] try: with open(client_path) as client_config_file: client_config_data = yaml.safe_load(client_config_file.read()) if ca_data is None: with open(ca_path) as ca_file: ca_data = base64.standard_b64encode(ca_file.read()) changes = [] # Naively update the CA information for each cluster in the # kubeconfig. for cluster in client_config_data['clusters']: if cluster['cluster']['certificate-authority-data'] != ca_data: cluster['cluster']['certificate-authority-data'] = ca_data changes.append(cluster['name']) if not module.check_mode: if len(changes) > 0 and backup: module.backup_local(client_path) with open(client_path, 'w') as client_config_file: client_config_string = yaml.dump(client_config_data, default_flow_style=False) client_config_string = client_config_string.replace('\'\'', '""') client_config_file.write(client_config_string) return module.exit_json(changed=(len(changes) > 0)) # ignore broad-except error to avoid stack trace to ansible user # pylint: disable=broad-except except Exception as error: return module.fail_json(msg=str(error))
def main(): module = AnsibleModule( argument_spec=dict( dest=dict(type='path', default='/etc/network/interfaces'), iface=dict(type='str'), address_family=dict(type='str'), option=dict(type='str'), value=dict(type='str'), backup=dict(type='bool', default=False), state=dict(type='str', default='present', choices=['absent', 'present']), ), add_file_common_args=True, supports_check_mode=True, required_by=dict(option=('iface'), ), ) dest = module.params['dest'] iface = module.params['iface'] address_family = module.params['address_family'] option = module.params['option'] value = module.params['value'] backup = module.params['backup'] state = module.params['state'] if option is not None and state == "present" and value is None: module.fail_json( msg="Value must be set if option is defined and state is 'present'" ) lines, ifaces = read_interfaces_file(module, dest) changed = False if option is not None: changed, lines = setInterfaceOption(module, lines, iface, option, value, state, address_family) if changed: _, ifaces = read_interfaces_lines( module, [d['line'] for d in lines if 'line' in d]) if changed and not module.check_mode: if backup: module.backup_local(dest) write_changes(module, [d['line'] for d in lines if 'line' in d], dest) module.exit_json(dest=dest, changed=changed, ifaces=ifaces)
def main(): module = AnsibleModule( argument_spec=dict( dest=dict(default='/etc/network/interfaces', required=False), iface=dict(required=False), 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, supports_check_mode=True ) dest = os.path.expanduser(module.params['dest']) iface = module.params['iface'] option = module.params['option'] value = module.params['value'] backup = module.params['backup'] state = module.params['state'] if option is not None and iface is None: module.fail_json(msg="Inteface must be set if option is defined") if option is not None and state == "present" and value is None: module.fail_json(msg="Value must be set if option is defined and state is 'present'") lines, ifaces = read_interfaces_file(module, dest) changed = False if option is not None: changed, lines = setInterfaceOption(module, lines, iface, option, value, state) if changed: _, ifaces = read_interfaces_lines(module, [d['line'] for d in lines if 'line' in d]) if changed and not module.check_mode: if backup: module.backup_local(dest) write_changes(module, [d['line'] for d in lines if 'line' in d], dest) module.exit_json(dest=dest, changed=changed, ifaces=ifaces)
def main(): module = AnsibleModule(argument_spec=dict( path=dict(required=True, aliases=['dest', 'destfile', 'name'], type='path'), regexp=dict(required=True), replace=dict(default='', type='str'), backup=dict(default=False, type='bool'), validate=dict(default=None, type='str'), ), add_file_common_args=True, supports_check_mode=True) params = module.params path = os.path.expanduser(params['path']) res_args = dict() if os.path.isdir(path): module.fail_json(rc=256, msg='Path %s is a directory !' % path) if not os.path.exists(path): module.fail_json(rc=257, msg='Path %s does not exist !' % path) else: f = open(path, 'rb') contents = to_text(f.read(), errors='surrogate_or_strict') f.close() mre = re.compile(params['regexp'], re.MULTILINE) result = re.subn(mre, params['replace'], contents, 0) if result[1] > 0 and contents != result[0]: msg = '%s replacements made' % result[1] changed = True if module._diff: res_args['diff'] = { 'before_header': path, 'before': contents, 'after_header': path, 'after': result[0], } else: msg = '' changed = False if changed and not module.check_mode: if params['backup'] and os.path.exists(path): res_args['backup_file'] = module.backup_local(path) if params['follow'] and os.path.islink(path): path = os.path.realpath(path) write_changes(module, result[0], path) res_args['msg'], res_args['changed'] = check_file_attrs( module, changed, msg) module.exit_json(**res_args)
def main(): pam_items = [ 'core', 'data', 'fsize', 'memlock', 'nofile', 'rss', 'stack', 'cpu', 'nproc', 'as', 'maxlogins', 'maxsyslogins', 'priority', 'locks', 'sigpending', 'msgqueue', 'nice', 'rtprio', 'chroot' ] pam_types = ['soft', 'hard', '-'] limits_conf = '/etc/security/limits.conf' module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=dict( domain=dict(required=True, type='str'), limit_type=dict(required=True, type='str', choices=pam_types), limit_item=dict(required=True, type='str', choices=pam_items), value=dict(required=True, type='str'), use_max=dict(default=False, type='bool'), use_min=dict(default=False, type='bool'), backup=dict(default=False, type='bool'), dest=dict(default=limits_conf, type='str'), comment=dict(required=False, default='', type='str'))) domain = module.params['domain'] limit_type = module.params['limit_type'] limit_item = module.params['limit_item'] value = module.params['value'] use_max = module.params['use_max'] use_min = module.params['use_min'] backup = module.params['backup'] limits_conf = module.params['dest'] new_comment = module.params['comment'] changed = False if os.path.isfile(limits_conf): if not os.access(limits_conf, os.W_OK): module.fail_json(msg="%s is not writable. Use sudo" % limits_conf) else: limits_conf_dir = os.path.dirname(limits_conf) if os.path.isdir(limits_conf_dir) and os.access( limits_conf_dir, os.W_OK): open(limits_conf, 'a').close() changed = True else: module.fail_json( msg= "directory %s is not writable (check presence, access rights, use sudo)" % limits_conf_dir) if use_max and use_min: module.fail_json( msg="Cannot use use_min and use_max at the same time.") if not (value in ['unlimited', 'infinity', '-1'] or value.isdigit()): module.fail_json( msg= "Argument 'value' can be one of 'unlimited', 'infinity', '-1' or positive number. Refer to manual pages for more details." ) # Backup if backup: backup_file = module.backup_local(limits_conf) space_pattern = re.compile(r'\s+') message = '' f = open(limits_conf, 'rb') # Tempfile nf = tempfile.NamedTemporaryFile(mode='w+') found = False new_value = value for line in f: line = to_native(line, errors='surrogate_or_strict') if line.startswith('#'): nf.write(line) continue newline = re.sub(space_pattern, ' ', line).strip() if not newline: nf.write(line) continue # Remove comment in line newline = newline.split('#', 1)[0] try: old_comment = line.split('#', 1)[1] except Exception: old_comment = '' newline = newline.rstrip() if not new_comment: new_comment = old_comment line_fields = newline.split(' ') if len(line_fields) != 4: nf.write(line) continue line_domain = line_fields[0] line_type = line_fields[1] line_item = line_fields[2] actual_value = line_fields[3] if not (actual_value in ['unlimited', 'infinity', '-1'] or actual_value.isdigit()): module.fail_json( msg= "Invalid configuration of '%s'. Current value of %s is unsupported." % (limits_conf, line_item)) # Found the line if line_domain == domain and line_type == limit_type and line_item == limit_item: found = True if value == actual_value: message = line nf.write(line) continue actual_value_unlimited = actual_value in [ 'unlimited', 'infinity', '-1' ] value_unlimited = value in ['unlimited', 'infinity', '-1'] if use_max: if value.isdigit() and actual_value.isdigit(): new_value = str(max(int(value), int(actual_value))) elif actual_value_unlimited: new_value = actual_value else: new_value = value if use_min: if value.isdigit() and actual_value.isdigit(): new_value = str(min(int(value), int(actual_value))) elif value_unlimited: new_value = actual_value else: new_value = value # Change line only if value has changed if new_value != actual_value: changed = True if new_comment: new_comment = "\t#" + new_comment new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + new_value + new_comment + "\n" message = new_limit nf.write(new_limit) else: message = line nf.write(line) else: nf.write(line) if not found: changed = True if new_comment: new_comment = "\t#" + new_comment new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + new_value + new_comment + "\n" message = new_limit nf.write(new_limit) f.close() nf.flush() # Copy tempfile to newfile module.atomic_move(nf.name, f.name) try: nf.close() except Exception: pass res_args = dict(changed=changed, msg=message) if backup: res_args['backup_file'] = backup_file module.exit_json(**res_args)
def main(): module = AnsibleModule( # not checking because of daisy chain to file module argument_spec = dict( src = dict(required=False, type='path'), original_basename = dict(required=False), # used to handle 'dest is a directory' via template, a slight hack content = dict(required=False, no_log=True), dest = dict(required=True, type='path'), backup = dict(default=False, type='bool'), force = dict(default=True, aliases=['thirsty'], type='bool'), validate = dict(required=False, type='str'), directory_mode = dict(required=False), remote_src = dict(required=False, type='bool'), ), add_file_common_args=True, supports_check_mode=True, ) src = module.params['src'] b_src = to_bytes(src, errors='surrogate_or_strict') dest = module.params['dest'] b_dest = to_bytes(dest, errors='surrogate_or_strict') backup = module.params['backup'] force = module.params['force'] original_basename = module.params.get('original_basename', None) validate = module.params.get('validate', None) follow = module.params['follow'] mode = module.params['mode'] remote_src = module.params['remote_src'] if not os.path.exists(b_src): module.fail_json(msg="Source %s not found" % (src)) if not os.access(b_src, os.R_OK): module.fail_json(msg="Source %s not readable" % (src)) if os.path.isdir(b_src): module.fail_json(msg="Remote copy does not support recursive copy of directory: %s" % (src)) checksum_src = module.sha1(src) checksum_dest = None # Backwards compat only. This will be None in FIPS mode try: md5sum_src = module.md5(src) except ValueError: md5sum_src = None changed = False # Special handling for recursive copy - create intermediate dirs if original_basename and dest.endswith(os.sep): dest = os.path.join(dest, original_basename) b_dest = to_bytes(dest, errors='surrogate_or_strict') dirname = os.path.dirname(dest) b_dirname = to_bytes(dirname, errors='surrogate_or_strict') if not os.path.exists(b_dirname) and os.path.isabs(b_dirname): (pre_existing_dir, new_directory_list) = split_pre_existing_dir(dirname) os.makedirs(b_dirname) directory_args = module.load_file_common_arguments(module.params) directory_mode = module.params["directory_mode"] if directory_mode is not None: directory_args['mode'] = directory_mode else: directory_args['mode'] = None adjust_recursive_directory_permissions(pre_existing_dir, new_directory_list, module, directory_args, changed) if os.path.isdir(b_dest): basename = os.path.basename(src) if original_basename: basename = original_basename dest = os.path.join(dest, basename) b_dest = to_bytes(dest, errors='surrogate_or_strict') if os.path.exists(b_dest): if os.path.islink(b_dest) and follow: b_dest = os.path.realpath(b_dest) dest = to_native(b_dest, errors='surrogate_or_strict') if not force: module.exit_json(msg="file already exists", src=src, dest=dest, changed=False) if os.access(b_dest, os.R_OK): checksum_dest = module.sha1(dest) else: if not os.path.exists(os.path.dirname(b_dest)): try: # os.path.exists() can return false in some # circumstances where the directory does not have # the execute bit for the current user set, in # which case the stat() call will raise an OSError os.stat(os.path.dirname(b_dest)) except OSError: e = get_exception() if "permission denied" in to_native(e).lower(): module.fail_json(msg="Destination directory %s is not accessible" % (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(b_dest), os.W_OK): module.fail_json(msg="Destination %s not writable" % (os.path.dirname(dest))) backup_file = None if checksum_src != checksum_dest or os.path.islink(b_dest): if not module.check_mode: try: if backup: if os.path.exists(b_dest): backup_file = module.backup_local(dest) # allow for conversion from symlink. if os.path.islink(b_dest): os.unlink(b_dest) open(b_dest, 'w').close() if validate: # if we have a mode, make sure we set it on the temporary # file source as some validations may require it # FIXME: should we do the same for owner/group here too? if mode is not None: module.set_mode_if_different(src, mode, False) if "%s" not in validate: module.fail_json(msg="validate must contain %%s: %s" % (validate)) (rc, out, err) = module.run_command(validate % src) if rc != 0: module.fail_json(msg="failed to validate", exit_status=rc, stdout=out, stderr=err) b_mysrc = b_src if remote_src: _, b_mysrc = tempfile.mkstemp(dir=os.path.dirname(b_dest)) shutil.copy2(b_src, b_mysrc) module.atomic_move(b_mysrc, dest, unsafe_writes=module.params['unsafe_writes']) except IOError: module.fail_json(msg="failed to copy: %s to %s" % (src, dest), traceback=traceback.format_exc()) changed = True else: changed = False res_args = dict( dest=dest, src=src, md5sum=md5sum_src, checksum=checksum_src, changed=changed ) if backup_file: res_args['backup_file'] = backup_file module.params['dest'] = dest if not module.check_mode: file_args = module.load_file_common_arguments(module.params) res_args['changed'] = module.set_fs_attributes_if_different(file_args, res_args['changed']) module.exit_json(**res_args)
def main(): module = AnsibleModule(argument_spec=dict( dest=dict(required=True, aliases=['name', 'destfile'], type='path'), state=dict(default='present', choices=['absent', 'present']), marker=dict(default='# {mark} ANSIBLE MANAGED BLOCK', type='str'), block=dict(default='', type='str', aliases=['content']), insertafter=dict(default=None), insertbefore=dict(default=None), create=dict(default=False, type='bool'), backup=dict(default=False, type='bool'), validate=dict(default=None, type='str'), ), mutually_exclusive=[['insertbefore', 'insertafter']], add_file_common_args=True, supports_check_mode=True) params = module.params dest = params['dest'] if module.boolean(params.get('follow', None)): dest = os.path.realpath(dest) if os.path.isdir(dest): module.fail_json(rc=256, msg='Destination %s is a directory !' % dest) path_exists = os.path.exists(dest) if not path_exists: if not module.boolean(params['create']): module.fail_json(rc=257, msg='Destination %s does not exist !' % dest) original = None lines = [] else: f = open(dest, 'rb') original = f.read() f.close() lines = original.splitlines() insertbefore = params['insertbefore'] insertafter = params['insertafter'] block = to_bytes(params['block']) marker = to_bytes(params['marker']) present = params['state'] == 'present' if not present and not path_exists: module.exit_json(changed=False, msg="File not present") if insertbefore is None and insertafter is None: insertafter = 'EOF' if insertafter not in (None, 'EOF'): insertre = re.compile(insertafter) elif insertbefore not in (None, 'BOF'): insertre = re.compile(insertbefore) else: insertre = None marker0 = re.sub(b(r'{mark}'), b('BEGIN'), marker) marker1 = re.sub(b(r'{mark}'), b('END'), marker) if present and block: # Escape seqeuences like '\n' need to be handled in Ansible 1.x if module.ansible_version.startswith('1.'): block = re.sub('', block, '') blocklines = [marker0] + block.splitlines() + [marker1] else: blocklines = [] n0 = n1 = None for i, line in enumerate(lines): if line.startswith(marker0): n0 = i if line.startswith(marker1): n1 = i if None in (n0, n1): n0 = None if insertre is not None: for i, line in enumerate(lines): if insertre.search(line): n0 = i if n0 is None: n0 = len(lines) elif insertafter is not None: n0 += 1 elif insertbefore is not None: n0 = 0 # insertbefore=BOF else: n0 = len(lines) # insertafter=EOF elif n0 < n1: lines[n0:n1 + 1] = [] else: lines[n1:n0 + 1] = [] n0 = n1 lines[n0:n0] = blocklines if lines: result = b('\n').join(lines) if original is None or original.endswith(b('\n')): result += b('\n') else: result = '' if original == result: msg = '' changed = False elif original is None: msg = 'File created' changed = True elif not blocklines: msg = 'Block removed' changed = True else: msg = 'Block inserted' changed = True if changed and not module.check_mode: if module.boolean(params['backup']) and path_exists: module.backup_local(dest) write_changes(module, result, dest) if module.check_mode and not path_exists: module.exit_json(changed=changed, msg=msg) msg, changed = check_file_attrs(module, changed, msg) module.exit_json(changed=changed, msg=msg)
def main(): module = AnsibleModule(argument_spec=dict( name=dict(required=True, type='str'), conf_path=dict(required=False, default='/etc/aide.conf', type='path'), options=dict(required=False, type='list'), options_string=dict(required=False, type='str'), state=dict(required=False, default="updated", choices=['absent', 'present', 'updated']), backup=dict(default=False, type='bool'), add_if_not_present=dict(default=False, required=False)), supports_check_mode=True, mutually_exclusive=[['options', 'options_string']], required_one_of=[['options', 'options_string']]) # For existing rule sets rulesets = dict() content = str() options = [] options_changed = [] existing_options = [] backupdest = "" fname = module.params['conf_path'] action = module.params['state'] add_if_not_present = module.params['add_if_not_present'] ruleset_name = module.params['name'].rstrip().lstrip() # Get the options from either the 'options' or 'options_string' module arguments if module.params['options']: options = module.params['options'] else: options = module.params['options_string'].split('+') # Open the file and read the content or fail try: with open(fname, 'r') as aide_file_obj: content = aide_file_obj.read() except IOError as e: # If unable to read the file, fail out module.fail_json(msg='Unable to open/read AIDE configuration \ file %s with error %s.' % (fname, str(e))) matches = RULESET_REGEX.findall(content) # Load existing rule set for match in matches: existing_options = match[1].split('+') rulesets[match[0]] = existing_options module.log(msg="EXISTING RULESET KEYS: " + str(rulesets.keys())) module.log(msg="EXISTING RULESET: " + str(rulesets)) # Take action if action == 'absent': changed, rulesets[ruleset_name], options_changed = remove_options( rulesets[ruleset_name], options) elif action == 'present': changed, rulesets[ruleset_name], options_changed = add_options( rulesets[ruleset_name], options) elif action == 'updated': if ruleset_name in rulesets.keys() or add_if_not_present: rulesets[ruleset_name] = options options_changed = options changed = True # Write file if not module.check_mode and changed: module.log(msg="WRITING") # Update the content pattern = r"^" + ruleset_name + r"\s?=\s?.*$" module.log(msg="PATTERN: " + pattern) replacement = ruleset_name + " = " + "+".join(rulesets[ruleset_name]) module.log(msg="REPLACEMENT: " + replacement) new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE) module.log(msg="OLD CONTENT equals NEW CONTENT: " + str(content == new_content)) # First, create a backup if desired. if module.params['backup']: backupdest = module.backup_local(fname) # Write the file try: temp_file = NamedTemporaryFile(mode='w') module.log(msg="TEMP FILE NAME: " + temp_file.name) with open(temp_file.name, 'w') as fd: fd.write(new_content) except IOError: module.fail_json(msg='Unable to create temporary \ file %s' % temp_file) module.atomic_move(temp_file.name, fname) facts = {} facts['aide_ruleset'] = { 'action': action, 'name': ruleset_name, 'existing_options': existing_options, 'options_changed': options_changed, 'backupdest': backupdest } module.exit_json(changed=changed, ansible_facts=facts)
def main(): module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=dict( src=dict(type='path'), _original_basename=dict( type='str' ), # used to handle 'dest is a directory' via template, a slight hack content=dict(type='str', no_log=True), dest=dict(type='path', required=True), backup=dict(type='bool', default=False), force=dict(type='bool', default=True, aliases=['thirsty']), validate=dict(type='str'), directory_mode=dict(type='raw'), remote_src=dict(type='bool'), local_follow=dict(type='bool'), checksum=dict(), ), add_file_common_args=True, supports_check_mode=True, ) src = module.params['src'] b_src = to_bytes(src, errors='surrogate_or_strict') dest = module.params['dest'] # Make sure we always have a directory component for later processing if os.path.sep not in dest: dest = '.{0}{1}'.format(os.path.sep, dest) b_dest = to_bytes(dest, errors='surrogate_or_strict') backup = module.params['backup'] force = module.params['force'] _original_basename = module.params.get('_original_basename', None) validate = module.params.get('validate', None) follow = module.params['follow'] local_follow = module.params['local_follow'] mode = module.params['mode'] owner = module.params['owner'] group = module.params['group'] remote_src = module.params['remote_src'] checksum = module.params['checksum'] if not os.path.exists(b_src): module.fail_json(msg="Source %s not found" % (src)) if not os.access(b_src, os.R_OK): module.fail_json(msg="Source %s not readable" % (src)) # Preserve is usually handled in the action plugin but mode + remote_src has to be done on the # remote host if module.params['mode'] == 'preserve': module.params['mode'] = '0%03o' % stat.S_IMODE(os.stat(b_src).st_mode) mode = module.params['mode'] checksum_dest = None if os.path.isfile(src): checksum_src = module.sha1(src) else: checksum_src = None # Backwards compat only. This will be None in FIPS mode try: if os.path.isfile(src): md5sum_src = module.md5(src) else: md5sum_src = None except ValueError: md5sum_src = None changed = False if checksum and checksum_src != checksum: module.fail_json( msg= 'Copied file does not match the expected checksum. Transfer failed.', checksum=checksum_src, expected_checksum=checksum) # Special handling for recursive copy - create intermediate dirs if _original_basename and dest.endswith(os.sep): dest = os.path.join(dest, _original_basename) b_dest = to_bytes(dest, errors='surrogate_or_strict') dirname = os.path.dirname(dest) b_dirname = to_bytes(dirname, errors='surrogate_or_strict') if not os.path.exists(b_dirname): try: (pre_existing_dir, new_directory_list) = split_pre_existing_dir(dirname) except AnsibleModuleError as e: e.result['msg'] += ' Could not copy to {0}'.format(dest) module.fail_json(**e.results) os.makedirs(b_dirname) directory_args = module.load_file_common_arguments(module.params) directory_mode = module.params["directory_mode"] if directory_mode is not None: directory_args['mode'] = directory_mode else: directory_args['mode'] = None adjust_recursive_directory_permissions(pre_existing_dir, new_directory_list, module, directory_args, changed) if os.path.isdir(b_dest): basename = os.path.basename(src) if _original_basename: basename = _original_basename dest = os.path.join(dest, basename) b_dest = to_bytes(dest, errors='surrogate_or_strict') if os.path.exists(b_dest): if os.path.islink(b_dest) and follow: b_dest = os.path.realpath(b_dest) dest = to_native(b_dest, errors='surrogate_or_strict') if not force: module.exit_json(msg="file already exists", src=src, dest=dest, changed=False) if os.access(b_dest, os.R_OK) and os.path.isfile(dest): checksum_dest = module.sha1(dest) else: if not os.path.exists(os.path.dirname(b_dest)): try: # os.path.exists() can return false in some # circumstances where the directory does not have # the execute bit for the current user set, in # which case the stat() call will raise an OSError os.stat(os.path.dirname(b_dest)) except OSError as e: if "permission denied" in to_native(e).lower(): module.fail_json( msg="Destination directory %s is not accessible" % (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(b_dest), os.W_OK) and not module.params['unsafe_writes']: module.fail_json(msg="Destination %s not writable" % (os.path.dirname(dest))) backup_file = None if checksum_src != checksum_dest or os.path.islink(b_dest): if not module.check_mode: try: if backup: if os.path.exists(b_dest): backup_file = module.backup_local(dest) # allow for conversion from symlink. if os.path.islink(b_dest): os.unlink(b_dest) open(b_dest, 'w').close() if validate: # if we have a mode, make sure we set it on the temporary # file source as some validations may require it if mode is not None: module.set_mode_if_different(src, mode, False) if owner is not None: module.set_owner_if_different(src, owner, False) if group is not None: module.set_group_if_different(src, group, False) if "%s" not in validate: module.fail_json(msg="validate must contain %%s: %s" % (validate)) (rc, out, err) = module.run_command(validate % src) if rc != 0: module.fail_json(msg="failed to validate", exit_status=rc, stdout=out, stderr=err) b_mysrc = b_src if remote_src and os.path.isfile(b_src): _, b_mysrc = tempfile.mkstemp(dir=os.path.dirname(b_dest)) shutil.copyfile(b_src, b_mysrc) try: shutil.copystat(b_src, b_mysrc) except OSError as err: if err.errno == errno.ENOSYS and mode == "preserve": module.warn("Unable to copy stats {0}".format( to_native(b_src))) else: raise module.atomic_move( b_mysrc, dest, unsafe_writes=module.params['unsafe_writes']) except (IOError, OSError): module.fail_json(msg="failed to copy: %s to %s" % (src, dest), traceback=traceback.format_exc()) changed = True else: changed = False if checksum_src is None and checksum_dest is None: if remote_src and os.path.isdir(module.params['src']): b_src = to_bytes(module.params['src'], errors='surrogate_or_strict') b_dest = to_bytes(module.params['dest'], errors='surrogate_or_strict') if src.endswith(os.path.sep) and os.path.isdir( module.params['dest']): diff_files_changed = copy_diff_files(b_src, b_dest, module) left_only_changed = copy_left_only(b_src, b_dest, module) common_dirs_changed = copy_common_dirs(b_src, b_dest, module) owner_group_changed = chown_recursive(b_dest, module) if diff_files_changed or left_only_changed or common_dirs_changed or owner_group_changed: changed = True if src.endswith( os.path.sep) and not os.path.exists(module.params['dest']): b_basename = to_bytes(os.path.basename(src), errors='surrogate_or_strict') b_dest = to_bytes(os.path.join(b_dest, b_basename), errors='surrogate_or_strict') b_src = to_bytes(os.path.join(module.params['src'], ""), errors='surrogate_or_strict') if not module.check_mode: shutil.copytree(b_src, b_dest, symlinks=not (local_follow)) chown_recursive(dest, module) changed = True if not src.endswith(os.path.sep) and os.path.isdir( module.params['dest']): b_basename = to_bytes(os.path.basename(src), errors='surrogate_or_strict') b_dest = to_bytes(os.path.join(b_dest, b_basename), errors='surrogate_or_strict') b_src = to_bytes(os.path.join(module.params['src'], ""), errors='surrogate_or_strict') if not module.check_mode and not os.path.exists(b_dest): shutil.copytree(b_src, b_dest, symlinks=not (local_follow)) changed = True chown_recursive(dest, module) if module.check_mode and not os.path.exists(b_dest): changed = True if os.path.exists(b_dest): diff_files_changed = copy_diff_files(b_src, b_dest, module) left_only_changed = copy_left_only(b_src, b_dest, module) common_dirs_changed = copy_common_dirs( b_src, b_dest, module) owner_group_changed = chown_recursive(b_dest, module) if diff_files_changed or left_only_changed or common_dirs_changed or owner_group_changed: changed = True if not src.endswith(os.path.sep) and not os.path.exists( module.params['dest']): b_basename = to_bytes(os.path.basename(module.params['src']), errors='surrogate_or_strict') b_dest = to_bytes(os.path.join(b_dest, b_basename), errors='surrogate_or_strict') if not module.check_mode and not os.path.exists(b_dest): os.makedirs(b_dest) b_src = to_bytes(os.path.join(module.params['src'], ""), errors='surrogate_or_strict') diff_files_changed = copy_diff_files(b_src, b_dest, module) left_only_changed = copy_left_only(b_src, b_dest, module) common_dirs_changed = copy_common_dirs( b_src, b_dest, module) owner_group_changed = chown_recursive(b_dest, module) if diff_files_changed or left_only_changed or common_dirs_changed or owner_group_changed: changed = True if module.check_mode and not os.path.exists(b_dest): changed = True res_args = dict(dest=dest, src=src, md5sum=md5sum_src, checksum=checksum_src, changed=changed) if backup_file: res_args['backup_file'] = backup_file module.params['dest'] = dest if not module.check_mode: file_args = module.load_file_common_arguments(module.params) res_args['changed'] = module.set_fs_attributes_if_different( file_args, res_args['changed']) module.exit_json(**res_args)
def main(): module = AnsibleModule(argument_spec=dict( path=dict(required=True, aliases=['dest', 'destfile', 'name'], type='path'), state=dict(default='present', choices=['absent', 'present']), marker=dict(default='# {mark} ANSIBLE MANAGED BLOCK', type='str'), block=dict(default='', type='str', aliases=['content']), insertafter=dict(default=None), insertbefore=dict(default=None), create=dict(default=False, type='bool'), backup=dict(default=False, type='bool'), validate=dict(default=None, type='str'), ), mutually_exclusive=[['insertbefore', 'insertafter']], add_file_common_args=True, supports_check_mode=True) params = module.params path = params['path'] if os.path.isdir(path): module.fail_json(rc=256, msg='Path %s is a directory !' % path) path_exists = os.path.exists(path) if not path_exists: if not module.boolean(params['create']): module.fail_json(rc=257, msg='Path %s does not exist !' % path) original = None lines = [] else: f = open(path, 'rb') original = f.read() f.close() lines = original.splitlines() diff = { 'before': '', 'after': '', 'before_header': '%s (content)' % path, 'after_header': '%s (content)' % path } if module._diff and original: diff['before'] = original insertbefore = params['insertbefore'] insertafter = params['insertafter'] block = to_bytes(params['block']) marker = to_bytes(params['marker']) present = params['state'] == 'present' if not present and not path_exists: module.exit_json(changed=False, msg="File %s not present" % path) if insertbefore is None and insertafter is None: insertafter = 'EOF' if insertafter not in (None, 'EOF'): insertre = re.compile( to_bytes(insertafter, errors='surrogate_or_strict')) elif insertbefore not in (None, 'BOF'): insertre = re.compile( to_bytes(insertbefore, errors='surrogate_or_strict')) else: insertre = None marker0 = re.sub(b(r'{mark}'), b('BEGIN'), marker) marker1 = re.sub(b(r'{mark}'), b('END'), marker) if present and block: # Escape seqeuences like '\n' need to be handled in Ansible 1.x if module.ansible_version.startswith('1.'): block = re.sub('', block, '') blocklines = [marker0] + block.splitlines() + [marker1] else: blocklines = [] n0 = n1 = None for i, line in enumerate(lines): if line == marker0: n0 = i if line == marker1: n1 = i if None in (n0, n1): n0 = None if insertre is not None: for i, line in enumerate(lines): if insertre.search(line): n0 = i if n0 is None: n0 = len(lines) elif insertafter is not None: n0 += 1 elif insertbefore is not None: n0 = 0 # insertbefore=BOF else: n0 = len(lines) # insertafter=EOF elif n0 < n1: lines[n0:n1 + 1] = [] else: lines[n1:n0 + 1] = [] n0 = n1 lines[n0:n0] = blocklines if lines: result = b('\n').join(lines) if original is None or original.endswith(b('\n')): result += b('\n') else: result = '' if module._diff: diff['after'] = result if original == result: msg = '' changed = False elif original is None: msg = 'File created' changed = True elif not blocklines: msg = 'Block removed' changed = True else: msg = 'Block inserted' changed = True if changed and not module.check_mode: if module.boolean(params['backup']) and path_exists: module.backup_local(path) # We should always follow symlinks so that we change the real file real_path = os.path.realpath(params['path']) write_changes(module, result, real_path) if module.check_mode and not path_exists: module.exit_json(changed=changed, msg=msg, diff=diff) attr_diff = {} msg, changed = check_file_attrs(module, changed, msg, attr_diff) attr_diff['before_header'] = '%s (file attributes)' % path attr_diff['after_header'] = '%s (file attributes)' % path difflist = [diff, attr_diff] module.exit_json(changed=changed, msg=msg, diff=difflist)
def main(): argument_spec = url_argument_spec() # setup aliases argument_spec['url_username']['aliases'] = ['username'] argument_spec['url_password']['aliases'] = ['password'] argument_spec.update( url=dict(type='str', required=True), dest=dict(type='path', required=True), backup=dict(type='bool'), sha256sum=dict(type='str', default=''), checksum=dict(type='str', default=''), timeout=dict(type='int', default=10), headers=dict(type='raw'), tmp_dest=dict(type='path'), ) module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=argument_spec, add_file_common_args=True, supports_check_mode=True, mutually_exclusive=[['checksum', 'sha256sum']], ) url = module.params['url'] dest = module.params['dest'] backup = module.params['backup'] force = module.params['force'] sha256sum = module.params['sha256sum'] checksum = module.params['checksum'] use_proxy = module.params['use_proxy'] timeout = module.params['timeout'] tmp_dest = module.params['tmp_dest'] result = dict( changed=False, checksum_dest=None, checksum_src=None, dest=dest, elapsed=0, url=url, ) # Parse headers to dict if isinstance(module.params['headers'], dict): headers = module.params['headers'] elif module.params['headers']: try: headers = dict( item.split(':', 1) for item in module.params['headers'].split(',')) module.deprecate( 'Supplying `headers` as a string is deprecated. Please use dict/hash format for `headers`', version='2.10') except Exception: module.fail_json( msg= "The string representation for the `headers` parameter requires a key:value,key:value syntax to be properly parsed.", **result) else: headers = None dest_is_dir = os.path.isdir(dest) last_mod_time = None # workaround for usage of deprecated sha256sum parameter if sha256sum: checksum = 'sha256:%s' % (sha256sum) # checksum specified, parse for algorithm and checksum if checksum: try: algorithm, checksum = checksum.split(':', 1) except ValueError: module.fail_json( msg= "The checksum parameter has to be in format <algorithm>:<checksum>", **result) if checksum.startswith('http://') or checksum.startswith( 'https://') or checksum.startswith('ftp://'): checksum_url = checksum # download checksum file to checksum_tmpsrc checksum_tmpsrc, checksum_info = url_get(module, checksum_url, dest, use_proxy, last_mod_time, force, timeout, headers, tmp_dest) with open(checksum_tmpsrc) as f: lines = [line.rstrip('\n') for line in f] os.remove(checksum_tmpsrc) checksum_map = {} for line in lines: parts = line.split(None, 1) if len(parts) == 2: checksum_map[parts[0]] = parts[1] filename = url_filename(url) # Look through each line in the checksum file for a hash corresponding to # the filename in the url, returning the first hash that is found. for cksum in (s for (s, f) in checksum_map.items() if f.strip('./') == filename): checksum = cksum break else: checksum = None if checksum is None: module.fail_json( msg="Unable to find a checksum for file '%s' in '%s'" % (filename, checksum_url)) # Remove any non-alphanumeric characters, including the infamous # Unicode zero-width space checksum = re.sub(r'\W+', '', checksum).lower() # Ensure the checksum portion is a hexdigest try: int(checksum, 16) except ValueError: module.fail_json(msg='The checksum format is invalid', **result) if not dest_is_dir and os.path.exists(dest): checksum_mismatch = False # If the download is not forced and there is a checksum, allow # checksum match to skip the download. if not force and checksum != '': destination_checksum = module.digest_from_file(dest, algorithm) if checksum != destination_checksum: checksum_mismatch = True # Not forcing redownload, unless checksum does not match if not force and checksum and not checksum_mismatch: # Not forcing redownload, unless checksum does not match # allow file attribute changes module.params['path'] = dest file_args = module.load_file_common_arguments(module.params) file_args['path'] = dest result['changed'] = module.set_fs_attributes_if_different( file_args, False) if result['changed']: module.exit_json( msg="file already exists but file attributes changed", **result) module.exit_json(msg="file already exists", **result) # If the file already exists, prepare the last modified time for the # request. mtime = os.path.getmtime(dest) last_mod_time = datetime.datetime.utcfromtimestamp(mtime) # If the checksum does not match we have to force the download # because last_mod_time may be newer than on remote if checksum_mismatch: force = True # download to tmpsrc start = datetime.datetime.utcnow() tmpsrc, info = url_get(module, url, dest, use_proxy, last_mod_time, force, timeout, headers, tmp_dest) result['elapsed'] = (datetime.datetime.utcnow() - start).seconds result['src'] = tmpsrc # Now the request has completed, we can finally generate the final # destination file name from the info dict. if dest_is_dir: filename = extract_filename_from_headers(info) if not filename: # Fall back to extracting the filename from the URL. # Pluck the URL from the info, since a redirect could have changed # it. filename = url_filename(info['url']) dest = os.path.join(dest, filename) result['dest'] = dest # raise an error if there is no tmpsrc file if not os.path.exists(tmpsrc): os.remove(tmpsrc) module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'], **result) if not os.access(tmpsrc, os.R_OK): os.remove(tmpsrc) module.fail_json(msg="Source %s is not readable" % (tmpsrc), **result) result['checksum_src'] = module.sha1(tmpsrc) # check if there is no dest file if os.path.exists(dest): # raise an error if copy has no permission on dest if not os.access(dest, os.W_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not writable" % (dest), **result) if not os.access(dest, os.R_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not readable" % (dest), **result) result['checksum_dest'] = module.sha1(dest) else: if not os.path.exists(os.path.dirname(dest)): os.remove(tmpsrc) module.fail_json(msg="Destination %s does not exist" % (os.path.dirname(dest)), **result) if not os.access(os.path.dirname(dest), os.W_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not writable" % (os.path.dirname(dest)), **result) if module.check_mode: if os.path.exists(tmpsrc): os.remove(tmpsrc) result['changed'] = ('checksum_dest' not in result or result['checksum_src'] != result['checksum_dest']) module.exit_json(msg=info.get('msg', ''), **result) backup_file = None if result['checksum_src'] != result['checksum_dest']: try: if backup: if os.path.exists(dest): backup_file = module.backup_local(dest) module.atomic_move(tmpsrc, dest) except Exception as e: if os.path.exists(tmpsrc): os.remove(tmpsrc) module.fail_json(msg="failed to copy %s to %s: %s" % (tmpsrc, dest, to_native(e)), exception=traceback.format_exc(), **result) result['changed'] = True else: result['changed'] = False if os.path.exists(tmpsrc): os.remove(tmpsrc) if checksum != '': destination_checksum = module.digest_from_file(dest, algorithm) if checksum != destination_checksum: os.remove(dest) module.fail_json( msg="The checksum for %s did not match %s; it was %s." % (dest, checksum, destination_checksum), **result) # allow file attribute changes module.params['path'] = dest file_args = module.load_file_common_arguments(module.params) file_args['path'] = dest result['changed'] = module.set_fs_attributes_if_different( file_args, result['changed']) # Backwards compat only. We'll return None on FIPS enabled systems try: result['md5sum'] = module.md5(dest) except ValueError: result['md5sum'] = None if backup_file: result['backup_file'] = backup_file # Mission complete module.exit_json(msg=info.get('msg', ''), status_code=info.get('status', ''), **result)
def main(): module = AnsibleModule( argument_spec=dict( name=dict(type='str', required=True), type=dict(type='str', required=True, choices=VALID_TYPES), control=dict(type='str', required=True), module_path=dict(type='str', required=True), new_type=dict(type='str', choices=VALID_TYPES), new_control=dict(type='str'), new_module_path=dict(type='str'), module_arguments=dict(type='list', elements='str'), state=dict(type='str', default='updated', choices=[ 'absent', 'after', 'args_absent', 'args_present', 'before', 'updated' ]), path=dict(type='path', default='/etc/pam.d'), backup=dict(type='bool', default=False), ), supports_check_mode=True, required_if=[ ("state", "args_present", ["module_arguments"]), ("state", "args_absent", ["module_arguments"]), ("state", "before", ["new_control", "new_type", "new_module_path"]), ("state", "after", ["new_control", "new_type", "new_module_path"]), ], ) content = str() fname = os.path.join(module.params["path"], module.params["name"]) # Open the file and read the content or fail try: with open(fname, 'r') as service_file_obj: content = service_file_obj.read() except IOError as e: # If unable to read the file, fail out module.fail_json( msg='Unable to open/read PAM module file %s with error %s.' % (fname, str(e))) # Assuming we didn't fail, create the service service = PamdService(content) # Set the action action = module.params['state'] changes = 0 # Take action if action == 'updated': changes = service.update_rule( module.params['type'], module.params['control'], module.params['module_path'], module.params['new_type'], module.params['new_control'], module.params['new_module_path'], module.params['module_arguments']) elif action == 'before': changes = service.insert_before( module.params['type'], module.params['control'], module.params['module_path'], module.params['new_type'], module.params['new_control'], module.params['new_module_path'], module.params['module_arguments']) elif action == 'after': changes = service.insert_after( module.params['type'], module.params['control'], module.params['module_path'], module.params['new_type'], module.params['new_control'], module.params['new_module_path'], module.params['module_arguments']) elif action == 'args_absent': changes = service.remove_module_arguments( module.params['type'], module.params['control'], module.params['module_path'], module.params['module_arguments']) elif action == 'args_present': if [ arg for arg in parse_module_arguments( module.params['module_arguments']) if arg.startswith("[") ]: module.fail_json( msg= "Unable to process bracketed '[' complex arguments with 'args_present'. Please use 'updated'." ) changes = service.add_module_arguments( module.params['type'], module.params['control'], module.params['module_path'], module.params['module_arguments']) elif action == 'absent': changes = service.remove(module.params['type'], module.params['control'], module.params['module_path']) valid, msg = service.validate() # If the module is not valid (meaning one of the rules is invalid), we will fail if not valid: module.fail_json(msg=msg) result = dict( changed=(changes > 0), change_count=changes, backupdest='', ) # If not check mode and something changed, backup the original if necessary then write out the file or fail if not module.check_mode and result['changed']: # First, create a backup if desired. if module.params['backup']: result['backupdest'] = module.backup_local(fname) try: temp_file = NamedTemporaryFile(mode='w', dir=module.tmpdir, delete=False) with open(temp_file.name, 'w') as fd: fd.write(str(service)) except IOError: module.fail_json(msg='Unable to create temporary \ file %s' % temp_file) module.atomic_move(temp_file.name, os.path.realpath(fname)) module.exit_json(**result)
def main(): global module module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=dict( src=dict(type='path'), _original_basename=dict( type='str' ), # used to handle 'dest is a directory' via template, a slight hack content=dict(type='str', no_log=True), dest=dict(type='path', required=True), backup=dict(type='bool', default=False), force=dict(type='bool', default=True, aliases=['thirsty']), validate=dict(type='str'), directory_mode=dict(type='raw'), remote_src=dict(type='bool'), local_follow=dict(type='bool'), checksum=dict(type='str'), follow=dict(type='bool', default=False), ), add_file_common_args=True, supports_check_mode=True, ) if module.params.get('thirsty'): module.deprecate( 'The alias "thirsty" has been deprecated and will be removed, use "force" instead', version='2.13', collection_name='ansible.builtin') src = module.params['src'] b_src = to_bytes(src, errors='surrogate_or_strict') dest = module.params['dest'] # Make sure we always have a directory component for later processing if os.path.sep not in dest: dest = '.{0}{1}'.format(os.path.sep, dest) b_dest = to_bytes(dest, errors='surrogate_or_strict') backup = module.params['backup'] force = module.params['force'] _original_basename = module.params.get('_original_basename', None) validate = module.params.get('validate', None) follow = module.params['follow'] local_follow = module.params['local_follow'] mode = module.params['mode'] owner = module.params['owner'] group = module.params['group'] remote_src = module.params['remote_src'] checksum = module.params['checksum'] if not os.path.exists(b_src): module.fail_json(msg="Source %s not found" % (src)) if not os.access(b_src, os.R_OK): module.fail_json(msg="Source %s not readable" % (src)) # Preserve is usually handled in the action plugin but mode + remote_src has to be done on the # remote host if module.params['mode'] == 'preserve': module.params['mode'] = '0%03o' % stat.S_IMODE(os.stat(b_src).st_mode) mode = module.params['mode'] checksum_dest = None if os.path.isfile(src): checksum_src = module.sha1(src) else: checksum_src = None # Backwards compat only. This will be None in FIPS mode try: if os.path.isfile(src): md5sum_src = module.md5(src) else: md5sum_src = None except ValueError: md5sum_src = None changed = False if checksum and checksum_src != checksum: module.fail_json( msg= 'Copied file does not match the expected checksum. Transfer failed.', checksum=checksum_src, expected_checksum=checksum) # Special handling for recursive copy - create intermediate dirs if dest.endswith(os.sep): if _original_basename: dest = os.path.join(dest, _original_basename) b_dest = to_bytes(dest, errors='surrogate_or_strict') dirname = os.path.dirname(dest) b_dirname = to_bytes(dirname, errors='surrogate_or_strict') if not os.path.exists(b_dirname): try: (pre_existing_dir, new_directory_list) = split_pre_existing_dir(dirname) except AnsibleModuleError as e: e.result['msg'] += ' Could not copy to {0}'.format(dest) module.fail_json(**e.results) os.makedirs(b_dirname) directory_args = module.load_file_common_arguments(module.params) directory_mode = module.params["directory_mode"] if directory_mode is not None: directory_args['mode'] = directory_mode else: directory_args['mode'] = None adjust_recursive_directory_permissions(pre_existing_dir, new_directory_list, module, directory_args, changed) if os.path.isdir(b_dest): basename = os.path.basename(src) if _original_basename: basename = _original_basename dest = os.path.join(dest, basename) b_dest = to_bytes(dest, errors='surrogate_or_strict') if os.path.exists(b_dest): if os.path.islink(b_dest) and follow: b_dest = os.path.realpath(b_dest) dest = to_native(b_dest, errors='surrogate_or_strict') if not force: module.exit_json(msg="file already exists", src=src, dest=dest, changed=False) if os.access(b_dest, os.R_OK) and os.path.isfile(b_dest): checksum_dest = module.sha1(dest) else: if not os.path.exists(os.path.dirname(b_dest)): try: # os.path.exists() can return false in some # circumstances where the directory does not have # the execute bit for the current user set, in # which case the stat() call will raise an OSError os.stat(os.path.dirname(b_dest)) except OSError as e: if "permission denied" in to_native(e).lower(): module.fail_json( msg="Destination directory %s is not accessible" % (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(b_dest), os.W_OK) and not module.params['unsafe_writes']: module.fail_json(msg="Destination %s not writable" % (os.path.dirname(dest))) backup_file = None if checksum_src != checksum_dest or os.path.islink(b_dest): if not module.check_mode: try: if backup: if os.path.exists(b_dest): backup_file = module.backup_local(dest) # allow for conversion from symlink. if os.path.islink(b_dest): os.unlink(b_dest) open(b_dest, 'w').close() if validate: # if we have a mode, make sure we set it on the temporary # file source as some validations may require it if mode is not None: module.set_mode_if_different(src, mode, False) if owner is not None: module.set_owner_if_different(src, owner, False) if group is not None: module.set_group_if_different(src, group, False) if "%s" not in validate: module.fail_json(msg="validate must contain %%s: %s" % (validate)) (rc, out, err) = module.run_command(validate % src) if rc != 0: module.fail_json(msg="failed to validate", exit_status=rc, stdout=out, stderr=err) b_mysrc = b_src if remote_src and os.path.isfile(b_src): _, b_mysrc = tempfile.mkstemp(dir=os.path.dirname(b_dest)) shutil.copyfile(b_src, b_mysrc) try: shutil.copystat(b_src, b_mysrc) except OSError as err: if err.errno == errno.ENOSYS and mode == "preserve": module.warn("Unable to copy stats {0}".format( to_native(b_src))) else: raise # might be needed below if PY3 and hasattr(os, 'listxattr'): try: src_has_acls = 'system.posix_acl_access' in os.listxattr( src) except Exception as e: # assume unwanted ACLs by default src_has_acls = True module.atomic_move( b_mysrc, dest, unsafe_writes=module.params['unsafe_writes']) if PY3 and hasattr(os, 'listxattr') and platform.system( ) == 'Linux' and not remote_src: # atomic_move used above to copy src into dest might, in some cases, # use shutil.copy2 which in turn uses shutil.copystat. # Since Python 3.3, shutil.copystat copies file extended attributes: # https://docs.python.org/3/library/shutil.html#shutil.copystat # os.listxattr (along with others) was added to handle the operation. # This means that on Python 3 we are copying the extended attributes which includes # the ACLs on some systems - further limited to Linux as the documentation above claims # that the extended attributes are copied only on Linux. Also, os.listxattr is only # available on Linux. # If not remote_src, then the file was copied from the controller. In that # case, any filesystem ACLs are artifacts of the copy rather than preservation # of existing attributes. Get rid of them: if src_has_acls: # FIXME If dest has any default ACLs, there are not applied to src now because # they were overridden by copystat. Should/can we do anything about this? # 'system.posix_acl_default' in os.listxattr(os.path.dirname(b_dest)) try: clear_facls(dest) except ValueError as e: if 'setfacl' in to_native(e): # No setfacl so we're okay. The controller couldn't have set a facl # without the setfacl command pass else: raise except RuntimeError as e: # setfacl failed. if 'Operation not supported' in to_native(e): # The file system does not support ACLs. pass else: raise except (IOError, OSError): module.fail_json(msg="failed to copy: %s to %s" % (src, dest), traceback=traceback.format_exc()) changed = True else: changed = False # If neither have checksums, both src and dest are directories. if checksum_src is None and checksum_dest is None: if remote_src and os.path.isdir(module.params['src']): b_src = to_bytes(module.params['src'], errors='surrogate_or_strict') b_dest = to_bytes(module.params['dest'], errors='surrogate_or_strict') if src.endswith(os.path.sep) and os.path.isdir( module.params['dest']): diff_files_changed = copy_diff_files(b_src, b_dest, module) left_only_changed = copy_left_only(b_src, b_dest, module) common_dirs_changed = copy_common_dirs(b_src, b_dest, module) owner_group_changed = chown_recursive(b_dest, module) if diff_files_changed or left_only_changed or common_dirs_changed or owner_group_changed: changed = True if src.endswith( os.path.sep) and not os.path.exists(module.params['dest']): b_basename = to_bytes(os.path.basename(src), errors='surrogate_or_strict') b_dest = to_bytes(os.path.join(b_dest, b_basename), errors='surrogate_or_strict') b_src = to_bytes(os.path.join(module.params['src'], ""), errors='surrogate_or_strict') if not module.check_mode: shutil.copytree(b_src, b_dest, symlinks=not (local_follow)) chown_recursive(dest, module) changed = True if not src.endswith(os.path.sep) and os.path.isdir( module.params['dest']): b_basename = to_bytes(os.path.basename(src), errors='surrogate_or_strict') b_dest = to_bytes(os.path.join(b_dest, b_basename), errors='surrogate_or_strict') b_src = to_bytes(os.path.join(module.params['src'], ""), errors='surrogate_or_strict') if not module.check_mode and not os.path.exists(b_dest): shutil.copytree(b_src, b_dest, symlinks=not (local_follow)) changed = True chown_recursive(dest, module) if module.check_mode and not os.path.exists(b_dest): changed = True if os.path.exists(b_dest): diff_files_changed = copy_diff_files(b_src, b_dest, module) left_only_changed = copy_left_only(b_src, b_dest, module) common_dirs_changed = copy_common_dirs( b_src, b_dest, module) owner_group_changed = chown_recursive(b_dest, module) if diff_files_changed or left_only_changed or common_dirs_changed or owner_group_changed: changed = True if not src.endswith(os.path.sep) and not os.path.exists( module.params['dest']): b_basename = to_bytes(os.path.basename(module.params['src']), errors='surrogate_or_strict') b_dest = to_bytes(os.path.join(b_dest, b_basename), errors='surrogate_or_strict') if not module.check_mode and not os.path.exists(b_dest): os.makedirs(b_dest) b_src = to_bytes(os.path.join(module.params['src'], ""), errors='surrogate_or_strict') diff_files_changed = copy_diff_files(b_src, b_dest, module) left_only_changed = copy_left_only(b_src, b_dest, module) common_dirs_changed = copy_common_dirs( b_src, b_dest, module) owner_group_changed = chown_recursive(b_dest, module) if diff_files_changed or left_only_changed or common_dirs_changed or owner_group_changed: changed = True if module.check_mode and not os.path.exists(b_dest): changed = True res_args = dict(dest=dest, src=src, md5sum=md5sum_src, checksum=checksum_src, changed=changed) if backup_file: res_args['backup_file'] = backup_file if not module.check_mode: file_args = module.load_file_common_arguments(module.params, path=dest) res_args['changed'] = module.set_fs_attributes_if_different( file_args, res_args['changed']) module.exit_json(**res_args)
def main(): module = AnsibleModule(argument_spec=dict( path=dict(required=True, aliases=['dest', 'destfile', 'name'], type='path'), regexp=dict(required=True), replace=dict(default='', type='str'), after=dict(required=False), before=dict(required=False), backup=dict(default=False, type='bool'), validate=dict(default=None, type='str'), encoding=dict(default='utf-8', type='str'), ), add_file_common_args=True, supports_check_mode=True) params = module.params path = params['path'] encoding = params['encoding'] res_args = dict() params['after'] = to_text(params['after'], errors='surrogate_or_strict', nonstring='passthru') params['before'] = to_text(params['before'], errors='surrogate_or_strict', nonstring='passthru') params['regexp'] = to_text(params['regexp'], errors='surrogate_or_strict', nonstring='passthru') params['replace'] = to_text(params['replace'], errors='surrogate_or_strict', nonstring='passthru') if os.path.isdir(path): module.fail_json(rc=256, msg='Path %s is a directory !' % path) if not os.path.exists(path): module.fail_json(rc=257, msg='Path %s does not exist !' % path) else: f = open(path, 'rb') contents = to_text(f.read(), errors='surrogate_or_strict', encoding=encoding) f.close() pattern = u'' if params['after'] and params['before']: pattern = u'%s(?P<subsection>.*?)%s' % (params['before'], params['after']) elif params['after']: pattern = u'%s(?P<subsection>.*)' % params['after'] elif params['before']: pattern = u'(?P<subsection>.*)%s' % params['before'] if pattern: section_re = re.compile(pattern, re.DOTALL) match = re.search(section_re, contents) if match: section = match.group('subsection') else: res_args[ 'msg'] = 'Pattern for before/after params did not match the given file: %s' % pattern res_args['changed'] = False module.exit_json(**res_args) else: section = contents mre = re.compile(params['regexp'], re.MULTILINE) result = re.subn(mre, params['replace'], section, 0) if result[1] > 0 and section != result[0]: if pattern: result = (contents.replace(section, result[0]), result[1]) msg = '%s replacements made' % result[1] changed = True if module._diff: res_args['diff'] = { 'before_header': path, 'before': contents, 'after_header': path, 'after': result[0], } else: msg = '' changed = False if changed and not module.check_mode: if params['backup'] and os.path.exists(path): res_args['backup_file'] = module.backup_local(path) if params['follow'] and os.path.islink(path): path = os.path.realpath(path) write_changes(module, to_bytes(result[0], encoding=encoding), path) res_args['msg'], res_args['changed'] = check_file_attrs( module, changed, msg) module.exit_json(**res_args)
def main(): from ansible.module_utils.basic import AnsibleModule module = AnsibleModule( argument_spec = { 'name': { 'required': True, }, 'state': { 'required': False, 'default': 'present', 'choices': ['absent', 'present'], }, 'value': { 'required': False, 'default': None, }, 'edit_only': { 'type': 'bool', 'required': False, 'default': False, }, 'path': { 'required': False, 'default': '', }, 'bootloader': { 'required': False, 'choices': list(BOOTLOADERS.keys()), }, 'backup': { 'type': 'bool', 'required': False, 'default': False, }, }, supports_check_mode=True, ) params = module.params path = params['path'].strip() bootloader = params['bootloader'] if path and not bootloader: try: bootloader = PATHS[path] except KeyError: module.fail_json( msg=("If the `path` argument is given," " then `bootloader` must also be given."), changed=False, ) if not path: try: path, bootloader = find_bootloader_config() except LookupError as err: module.fail_json(msg=str(err), changed=False) # seed the result dict in the object result = { 'changed': False, 'path': path, 'bootloader': bootloader, 'edited': False, 'installed': False, } try: handler = BOOTLOADERS[bootloader](module) except KeyError: module.fail_json( msg=("Unknown value for `bootloader` argument: {bootloader}" .format(bootloader=bootloader)), **result ) # if the user is working with this module in only check mode we do not # want to make any changes to the environment, just return the current # state with no modifications if module.check_mode: return result # read in config file contents try: with open(path, 'r') as input: current_config = input.read() except (OSError, IOError) as err: module.fail_json( msg=("Cannot read file `{path}`: {err}" .format(path=path, err=err)), **result ) # apply requested changes new_config = handler.edit( current_config, params['state'], params['name'], params['value']) # exit early if no changes if new_config == current_config: module.exit_json(**result) # make a backup if requested if params['backup']: result['backup'] = module.backup_local(path) # write out changed config try: with NamedTemporaryFile( dir=dirname(path), prefix=(basename(path) + '.'), suffix='.tmp', delete=False) as edited: edited.write(new_config) module.atomic_move(edited.name, path) result['changed'] = True except (OSError, IOError) as err: module.fail_json( msg=("Cannot write back file `{path}`: {err}" .format(path=path, err=err)), **result ) finally: module.cleanup(edited.name) result['edited'] = True # ensure new config is used by the bootloader next time result['installed'] = False if not params['edit_only']: try: install_result = handler.install(path) result.update(install_result) result['installed'] = True result['changed'] = True except Exception as err: module.fail_json( msg=("Cannot install new config file `{path}`: {err}" .format(path=path, err=err)), **result ) # all done module.exit_json(**result)
def main(): module = AnsibleModule( argument_spec=dict( path=dict(type='path', required=True, aliases=['dest', 'destfile', 'name']), state=dict(type='str', default='present', choices=['absent', 'present']), marker=dict(type='str', default='# {mark} ANSIBLE MANAGED BLOCK'), block=dict(type='str', default='', aliases=['content']), insertafter=dict(type='str'), insertbefore=dict(type='str'), create=dict(type='bool', default=False), backup=dict(type='bool', default=False), validate=dict(type='str'), marker_begin=dict(type='str', default='BEGIN'), marker_end=dict(type='str', default='END'), ), mutually_exclusive=[['insertbefore', 'insertafter']], add_file_common_args=True, supports_check_mode=True ) params = module.params path = params['path'] if os.path.isdir(path): module.fail_json(rc=256, msg='Path %s is a directory !' % path) path_exists = os.path.exists(path) if not path_exists: if not module.boolean(params['create']): module.fail_json(rc=257, msg='Path %s does not exist !' % path) destpath = os.path.dirname(path) if not os.path.exists(destpath) and not module.check_mode: try: os.makedirs(destpath) except Exception as e: module.fail_json(msg='Error creating %s Error code: %s Error description: %s' % (destpath, e[0], e[1])) original = None lines = [] else: f = open(path, 'rb') original = f.read() f.close() lines = original.splitlines() diff = {'before': '', 'after': '', 'before_header': '%s (content)' % path, 'after_header': '%s (content)' % path} if module._diff and original: diff['before'] = original insertbefore = params['insertbefore'] insertafter = params['insertafter'] block = to_bytes(params['block']) marker = to_bytes(params['marker']) present = params['state'] == 'present' if not present and not path_exists: module.exit_json(changed=False, msg="File %s not present" % path) if insertbefore is None and insertafter is None: insertafter = 'EOF' if insertafter not in (None, 'EOF'): insertre = re.compile(to_bytes(insertafter, errors='surrogate_or_strict')) elif insertbefore not in (None, 'BOF'): insertre = re.compile(to_bytes(insertbefore, errors='surrogate_or_strict')) else: insertre = None marker0 = re.sub(b(r'{mark}'), b(params['marker_begin']), marker) marker1 = re.sub(b(r'{mark}'), b(params['marker_end']), marker) if present and block: # Escape seqeuences like '\n' need to be handled in Ansible 1.x if module.ansible_version.startswith('1.'): block = re.sub('', block, '') blocklines = [marker0] + block.splitlines() + [marker1] else: blocklines = [] n0 = n1 = None for i, line in enumerate(lines): if line == marker0: n0 = i if line == marker1: n1 = i if None in (n0, n1): n0 = None if insertre is not None: for i, line in enumerate(lines): if insertre.search(line): n0 = i if n0 is None: n0 = len(lines) elif insertafter is not None: n0 += 1 elif insertbefore is not None: n0 = 0 # insertbefore=BOF else: n0 = len(lines) # insertafter=EOF elif n0 < n1: lines[n0:n1 + 1] = [] else: lines[n1:n0 + 1] = [] n0 = n1 lines[n0:n0] = blocklines if lines: result = b('\n').join(lines) if original is None or original.endswith(b('\n')): result += b('\n') else: result = '' if module._diff: diff['after'] = result if original == result: msg = '' changed = False elif original is None: msg = 'File created' changed = True elif not blocklines: msg = 'Block removed' changed = True else: msg = 'Block inserted' changed = True if changed and not module.check_mode: if module.boolean(params['backup']) and path_exists: module.backup_local(path) # We should always follow symlinks so that we change the real file real_path = os.path.realpath(params['path']) write_changes(module, result, real_path) if module.check_mode and not path_exists: module.exit_json(changed=changed, msg=msg, diff=diff) attr_diff = {} msg, changed = check_file_attrs(module, changed, msg, attr_diff) attr_diff['before_header'] = '%s (file attributes)' % path attr_diff['after_header'] = '%s (file attributes)' % path difflist = [diff, attr_diff] module.exit_json(changed=changed, msg=msg, diff=difflist)
def main(): module = AnsibleModule( argument_spec=dict( path=dict(required=True, aliases=['dest', 'destfile', 'name'], type='path'), regexp=dict(required=True), replace=dict(default='', type='str'), after=dict(required=False), before=dict(required=False), backup=dict(default=False, type='bool'), validate=dict(default=None, type='str'), encoding=dict(default='utf-8', type='str'), ), add_file_common_args=True, supports_check_mode=True ) params = module.params path = params['path'] encoding = params['encoding'] res_args = dict() params['after'] = to_text(params['after'], errors='surrogate_or_strict', nonstring='passthru') params['before'] = to_text(params['before'], errors='surrogate_or_strict', nonstring='passthru') params['regexp'] = to_text(params['regexp'], errors='surrogate_or_strict', nonstring='passthru') params['replace'] = to_text(params['replace'], errors='surrogate_or_strict', nonstring='passthru') if os.path.isdir(path): module.fail_json(rc=256, msg='Path %s is a directory !' % path) if not os.path.exists(path): module.fail_json(rc=257, msg='Path %s does not exist !' % path) else: f = open(path, 'rb') contents = to_text(f.read(), errors='surrogate_or_strict', encoding=encoding) f.close() pattern = u'' if params['after'] and params['before']: pattern = u'%s(?P<subsection>.*?)%s' % (params['before'], params['after']) elif params['after']: pattern = u'%s(?P<subsection>.*)' % params['after'] elif params['before']: pattern = u'(?P<subsection>.*)%s' % params['before'] if pattern: section_re = re.compile(pattern, re.DOTALL) match = re.search(section_re, contents) if match: section = match.group('subsection') else: res_args['msg'] = 'Pattern for before/after params did not match the given file: %s' % pattern res_args['changed'] = False module.exit_json(**res_args) else: section = contents mre = re.compile(params['regexp'], re.MULTILINE) result = re.subn(mre, params['replace'], section, 0) if result[1] > 0 and section != result[0]: if pattern: result = (contents.replace(section, result[0]), result[1]) msg = '%s replacements made' % result[1] changed = True if module._diff: res_args['diff'] = { 'before_header': path, 'before': contents, 'after_header': path, 'after': result[0], } else: msg = '' changed = False if changed and not module.check_mode: if params['backup'] and os.path.exists(path): res_args['backup_file'] = module.backup_local(path) # We should always follow symlinks so that we change the real file path = os.path.realpath(path) write_changes(module, to_bytes(result[0], encoding=encoding), path) res_args['msg'], res_args['changed'] = check_file_attrs(module, changed, msg) module.exit_json(**res_args)
def main(): module = AnsibleModule( argument_spec=dict( dest=dict(required=True, aliases=['name', 'destfile'], type='path'), state=dict(default='present', choices=['absent', 'present']), marker=dict(default='# {mark} ANSIBLE MANAGED BLOCK', type='str'), block=dict(default='', type='str', aliases=['content']), insertafter=dict(default=None), insertbefore=dict(default=None), create=dict(default=False, type='bool'), backup=dict(default=False, type='bool'), validate=dict(default=None, type='str'), ), mutually_exclusive=[['insertbefore', 'insertafter']], add_file_common_args=True, supports_check_mode=True ) params = module.params dest = params['dest'] if module.boolean(params.get('follow', None)): dest = os.path.realpath(dest) if os.path.isdir(dest): module.fail_json(rc=256, msg='Destination %s is a directory !' % dest) path_exists = os.path.exists(dest) if not path_exists: if not module.boolean(params['create']): module.fail_json(rc=257, msg='Destination %s does not exist !' % dest) original = None lines = [] else: f = open(dest, 'rb') original = f.read() f.close() lines = original.splitlines() insertbefore = params['insertbefore'] insertafter = params['insertafter'] block = to_bytes(params['block']) marker = to_bytes(params['marker']) present = params['state'] == 'present' if not present and not path_exists: module.exit_json(changed=False, msg="File not present") if insertbefore is None and insertafter is None: insertafter = 'EOF' if insertafter not in (None, 'EOF'): insertre = re.compile(insertafter) elif insertbefore not in (None, 'BOF'): insertre = re.compile(insertbefore) else: insertre = None marker0 = re.sub(b(r'{mark}'), b('BEGIN'), marker) marker1 = re.sub(b(r'{mark}'), b('END'), marker) if present and block: # Escape seqeuences like '\n' need to be handled in Ansible 1.x if module.ansible_version.startswith('1.'): block = re.sub('', block, '') blocklines = [marker0] + block.splitlines() + [marker1] else: blocklines = [] n0 = n1 = None for i, line in enumerate(lines): if line == marker0: n0 = i if line == marker1: n1 = i if None in (n0, n1): n0 = None if insertre is not None: for i, line in enumerate(lines): if insertre.search(line): n0 = i if n0 is None: n0 = len(lines) elif insertafter is not None: n0 += 1 elif insertbefore is not None: n0 = 0 # insertbefore=BOF else: n0 = len(lines) # insertafter=EOF elif n0 < n1: lines[n0:n1+1] = [] else: lines[n1:n0+1] = [] n0 = n1 lines[n0:n0] = blocklines if lines: result = b('\n').join(lines) if original is None or original.endswith(b('\n')): result += b('\n') else: result = '' if original == result: msg = '' changed = False elif original is None: msg = 'File created' changed = True elif not blocklines: msg = 'Block removed' changed = True else: msg = 'Block inserted' changed = True if changed and not module.check_mode: if module.boolean(params['backup']) and path_exists: module.backup_local(dest) write_changes(module, result, dest) if module.check_mode and not path_exists: module.exit_json(changed=changed, msg=msg) msg, changed = check_file_attrs(module, changed, msg) module.exit_json(changed=changed, msg=msg)
def main(): argument_spec = url_argument_spec() # setup aliases argument_spec['url_username']['aliases'] = ['username'] argument_spec['url_password']['aliases'] = ['password'] argument_spec.update( url=dict(type='str', required=True), dest=dict(type='path', required=True), backup=dict(type='bool', default=False), checksum=dict(type='str', default=''), timeout=dict(type='int', default=10), headers=dict(type='dict'), tmp_dest=dict(type='path'), unredirected_headers=dict(type='list', elements='str', default=[]), ) module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=argument_spec, add_file_common_args=True, supports_check_mode=True, ) url = module.params['url'] dest = module.params['dest'] backup = module.params['backup'] force = module.params['force'] checksum = module.params['checksum'] use_proxy = module.params['use_proxy'] timeout = module.params['timeout'] headers = module.params['headers'] tmp_dest = module.params['tmp_dest'] unredirected_headers = module.params['unredirected_headers'] result = dict( changed=False, checksum_dest=None, checksum_src=None, dest=dest, elapsed=0, url=url, ) dest_is_dir = os.path.isdir(dest) last_mod_time = None # checksum specified, parse for algorithm and checksum if checksum: try: algorithm, checksum = checksum.split(':', 1) except ValueError: module.fail_json( msg= "The checksum parameter has to be in format <algorithm>:<checksum>", **result) if is_url(checksum): checksum_url = checksum # download checksum file to checksum_tmpsrc checksum_tmpsrc, checksum_info = url_get( module, checksum_url, dest, use_proxy, last_mod_time, force, timeout, headers, tmp_dest, unredirected_headers=unredirected_headers) with open(checksum_tmpsrc) as f: lines = [line.rstrip('\n') for line in f] os.remove(checksum_tmpsrc) checksum_map = [] filename = url_filename(url) if len(lines) == 1 and len(lines[0].split()) == 1: # Only a single line with a single string # treat it as a checksum only file checksum_map.append((lines[0], filename)) else: # The assumption here is the file is in the format of # checksum filename for line in lines: # Split by one whitespace to keep the leading type char ' ' (whitespace) for text and '*' for binary parts = line.split(" ", 1) if len(parts) == 2: # Remove the leading type char, we expect if parts[1].startswith(( " ", "*", )): parts[1] = parts[1][1:] # Append checksum and path without potential leading './' checksum_map.append((parts[0], parts[1].lstrip("./"))) # Look through each line in the checksum file for a hash corresponding to # the filename in the url, returning the first hash that is found. for cksum in (s for (s, f) in checksum_map if f == filename): checksum = cksum break else: checksum = None if checksum is None: module.fail_json( msg="Unable to find a checksum for file '%s' in '%s'" % (filename, checksum_url)) # Remove any non-alphanumeric characters, including the infamous # Unicode zero-width space checksum = re.sub(r'\W+', '', checksum).lower() # Ensure the checksum portion is a hexdigest try: int(checksum, 16) except ValueError: module.fail_json(msg='The checksum format is invalid', **result) if not dest_is_dir and os.path.exists(dest): checksum_mismatch = False # If the download is not forced and there is a checksum, allow # checksum match to skip the download. if not force and checksum != '': destination_checksum = module.digest_from_file(dest, algorithm) if checksum != destination_checksum: checksum_mismatch = True # Not forcing redownload, unless checksum does not match if not force and checksum and not checksum_mismatch: # Not forcing redownload, unless checksum does not match # allow file attribute changes file_args = module.load_file_common_arguments(module.params, path=dest) result['changed'] = module.set_fs_attributes_if_different( file_args, False) if result['changed']: module.exit_json( msg="file already exists but file attributes changed", **result) module.exit_json(msg="file already exists", **result) # If the file already exists, prepare the last modified time for the # request. mtime = os.path.getmtime(dest) last_mod_time = datetime.datetime.utcfromtimestamp(mtime) # If the checksum does not match we have to force the download # because last_mod_time may be newer than on remote if checksum_mismatch: force = True # download to tmpsrc start = datetime.datetime.utcnow() method = 'HEAD' if module.check_mode else 'GET' tmpsrc, info = url_get(module, url, dest, use_proxy, last_mod_time, force, timeout, headers, tmp_dest, method, unredirected_headers=unredirected_headers) result['elapsed'] = (datetime.datetime.utcnow() - start).seconds result['src'] = tmpsrc # Now the request has completed, we can finally generate the final # destination file name from the info dict. if dest_is_dir: filename = extract_filename_from_headers(info) if not filename: # Fall back to extracting the filename from the URL. # Pluck the URL from the info, since a redirect could have changed # it. filename = url_filename(info['url']) dest = os.path.join(dest, filename) result['dest'] = dest # raise an error if there is no tmpsrc file if not os.path.exists(tmpsrc): os.remove(tmpsrc) module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'], **result) if not os.access(tmpsrc, os.R_OK): os.remove(tmpsrc) module.fail_json(msg="Source %s is not readable" % (tmpsrc), **result) result['checksum_src'] = module.sha1(tmpsrc) # check if there is no dest file if os.path.exists(dest): # raise an error if copy has no permission on dest if not os.access(dest, os.W_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not writable" % (dest), **result) if not os.access(dest, os.R_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not readable" % (dest), **result) result['checksum_dest'] = module.sha1(dest) else: if not os.path.exists(os.path.dirname(dest)): os.remove(tmpsrc) module.fail_json(msg="Destination %s does not exist" % (os.path.dirname(dest)), **result) if not os.access(os.path.dirname(dest), os.W_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not writable" % (os.path.dirname(dest)), **result) if module.check_mode: if os.path.exists(tmpsrc): os.remove(tmpsrc) result['changed'] = ('checksum_dest' not in result or result['checksum_src'] != result['checksum_dest']) module.exit_json(msg=info.get('msg', ''), **result) backup_file = None if result['checksum_src'] != result['checksum_dest']: try: if backup: if os.path.exists(dest): backup_file = module.backup_local(dest) module.atomic_move(tmpsrc, dest, unsafe_writes=module.params['unsafe_writes']) except Exception as e: if os.path.exists(tmpsrc): os.remove(tmpsrc) module.fail_json(msg="failed to copy %s to %s: %s" % (tmpsrc, dest, to_native(e)), exception=traceback.format_exc(), **result) result['changed'] = True else: result['changed'] = False if os.path.exists(tmpsrc): os.remove(tmpsrc) if checksum != '': destination_checksum = module.digest_from_file(dest, algorithm) if checksum != destination_checksum: os.remove(dest) module.fail_json( msg="The checksum for %s did not match %s; it was %s." % (dest, checksum, destination_checksum), **result) # allow file attribute changes file_args = module.load_file_common_arguments(module.params, path=dest) result['changed'] = module.set_fs_attributes_if_different( file_args, result['changed']) # Backwards compat only. We'll return None on FIPS enabled systems try: result['md5sum'] = module.md5(dest) except ValueError: result['md5sum'] = None if backup_file: result['backup_file'] = backup_file # Mission complete module.exit_json(msg=info.get('msg', ''), status_code=info.get('status', ''), **result)
def main(): argument_spec = url_argument_spec() argument_spec.update( url=dict(type='str', required=True), dest=dict(type='path', required=True), backup=dict(type='bool'), sha256sum=dict(type='str', default=''), checksum=dict(type='str', default=''), timeout=dict(type='int', default=10), headers=dict(type='raw'), tmp_dest=dict(type='path'), ) module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=argument_spec, add_file_common_args=True, supports_check_mode=True, mutually_exclusive=(['checksum', 'sha256sum']), ) url = module.params['url'] dest = module.params['dest'] backup = module.params['backup'] force = module.params['force'] sha256sum = module.params['sha256sum'] checksum = module.params['checksum'] use_proxy = module.params['use_proxy'] timeout = module.params['timeout'] tmp_dest = module.params['tmp_dest'] # Parse headers to dict if isinstance(module.params['headers'], dict): headers = module.params['headers'] elif module.params['headers']: try: headers = dict( item.split(':', 1) for item in module.params['headers'].split(',')) module.deprecate( 'Supplying `headers` as a string is deprecated. Please use dict/hash format for `headers`', version='2.10') except Exception: module.fail_json( msg= "The string representation for the `headers` parameter requires a key:value,key:value syntax to be properly parsed." ) else: headers = None dest_is_dir = os.path.isdir(dest) last_mod_time = None # workaround for usage of deprecated sha256sum parameter if sha256sum: checksum = 'sha256:%s' % (sha256sum) # checksum specified, parse for algorithm and checksum if checksum: try: algorithm, checksum = checksum.rsplit(':', 1) # Remove any non-alphanumeric characters, including the infamous # Unicode zero-width space checksum = re.sub(r'\W+', '', checksum).lower() # Ensure the checksum portion is a hexdigest int(checksum, 16) except ValueError: module.fail_json( msg= "The checksum parameter has to be in format <algorithm>:<checksum>" ) if not dest_is_dir and os.path.exists(dest): checksum_mismatch = False # If the download is not forced and there is a checksum, allow # checksum match to skip the download. if not force and checksum != '': destination_checksum = module.digest_from_file(dest, algorithm) if checksum == destination_checksum: module.exit_json(msg="file already exists", dest=dest, url=url, changed=False) checksum_mismatch = True # Not forcing redownload, unless checksum does not match if not force and not checksum_mismatch: # allow file attribute changes module.params['path'] = dest file_args = module.load_file_common_arguments(module.params) file_args['path'] = dest changed = module.set_fs_attributes_if_different(file_args, False) if changed: module.exit_json( msg="file already exists but file attributes changed", dest=dest, url=url, changed=changed) module.exit_json(msg="file already exists", dest=dest, url=url, changed=changed) # If the file already exists, prepare the last modified time for the # request. mtime = os.path.getmtime(dest) last_mod_time = datetime.datetime.utcfromtimestamp(mtime) # If the checksum does not match we have to force the download # because last_mod_time may be newer than on remote if checksum_mismatch: force = True # download to tmpsrc tmpsrc, info = url_get(module, url, dest, use_proxy, last_mod_time, force, timeout, headers, tmp_dest) # Now the request has completed, we can finally generate the final # destination file name from the info dict. if dest_is_dir: filename = extract_filename_from_headers(info) if not filename: # Fall back to extracting the filename from the URL. # Pluck the URL from the info, since a redirect could have changed # it. filename = url_filename(info['url']) dest = os.path.join(dest, filename) checksum_src = None checksum_dest = None # If the remote URL exists, we're done with check mode if module.check_mode: os.remove(tmpsrc) res_args = dict(url=url, dest=dest, src=tmpsrc, changed=True, msg=info.get('msg', '')) module.exit_json(**res_args) # raise an error if there is no tmpsrc file if not os.path.exists(tmpsrc): os.remove(tmpsrc) module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg']) if not os.access(tmpsrc, os.R_OK): os.remove(tmpsrc) module.fail_json(msg="Source %s is not readable" % (tmpsrc)) checksum_src = module.sha1(tmpsrc) # check if there is no dest file if os.path.exists(dest): # raise an error if copy has no permission on dest if not os.access(dest, os.W_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not writable" % (dest)) if not os.access(dest, os.R_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not readable" % (dest)) checksum_dest = module.sha1(dest) else: if not os.path.exists(os.path.dirname(dest)): os.remove(tmpsrc) module.fail_json(msg="Destination %s does not exist" % (os.path.dirname(dest))) if not os.access(os.path.dirname(dest), os.W_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not writable" % (os.path.dirname(dest))) backup_file = None if checksum_src != checksum_dest: try: if backup: if os.path.exists(dest): backup_file = module.backup_local(dest) module.atomic_move(tmpsrc, dest) except Exception as e: if os.path.exists(tmpsrc): os.remove(tmpsrc) module.fail_json(msg="failed to copy %s to %s: %s" % (tmpsrc, dest, to_native(e)), exception=traceback.format_exc()) changed = True else: changed = False if os.path.exists(tmpsrc): os.remove(tmpsrc) if checksum != '': destination_checksum = module.digest_from_file(dest, algorithm) if checksum != destination_checksum: os.remove(dest) module.fail_json( msg="The checksum for %s did not match %s; it was %s." % (dest, checksum, destination_checksum)) # allow file attribute changes module.params['path'] = dest file_args = module.load_file_common_arguments(module.params) file_args['path'] = dest changed = module.set_fs_attributes_if_different(file_args, changed) # Backwards compat only. We'll return None on FIPS enabled systems try: md5sum = module.md5(dest) except ValueError: md5sum = None res_args = dict(url=url, dest=dest, src=tmpsrc, md5sum=md5sum, checksum_src=checksum_src, checksum_dest=checksum_dest, changed=changed, msg=info.get('msg', ''), status_code=info.get('status', '')) if backup_file: res_args['backup_file'] = backup_file # Mission complete module.exit_json(**res_args)
def main(): module = AnsibleModule(argument_spec=dict( path=dict(type='path', required=True, aliases=['dest', 'destfile', 'name']), state=dict(type='str', default='present', choices=['absent', 'present']), marker=dict(type='str', default='# {mark} ANSIBLE MANAGED BLOCK'), block=dict(type='str', default='', aliases=['content']), insertafter=dict(type='str'), insertbefore=dict(type='str'), create=dict(type='bool', default=False), backup=dict(type='bool', default=False), validate=dict(type='str'), marker_begin=dict(type='str', default='BEGIN'), marker_end=dict(type='str', default='END'), ), mutually_exclusive=[['insertbefore', 'insertafter']], add_file_common_args=True, supports_check_mode=True) params = module.params path = params['path'] if os.path.isdir(path): module.fail_json(rc=256, msg='Path %s is a directory !' % path) path_exists = os.path.exists(path) if not path_exists: if not module.boolean(params['create']): module.fail_json(rc=257, msg='Path %s does not exist !' % path) destpath = os.path.dirname(path) if not os.path.exists(destpath) and not module.check_mode: try: os.makedirs(destpath) except Exception as e: module.fail_json( msg='Error creating %s Error code: %s Error description: %s' % (destpath, e[0], e[1])) original = None lines = [] else: with open(path, 'rb') as f: original = f.read() lines = original.splitlines(True) diff = { 'before': '', 'after': '', 'before_header': '%s (content)' % path, 'after_header': '%s (content)' % path } if module._diff and original: diff['before'] = original insertbefore = params['insertbefore'] insertafter = params['insertafter'] block = to_bytes(params['block']) marker = to_bytes(params['marker']) present = params['state'] == 'present' if not present and not path_exists: module.exit_json(changed=False, msg="File %s not present" % path) if insertbefore is None and insertafter is None: insertafter = 'EOF' if insertafter not in (None, 'EOF'): insertre = re.compile( to_bytes(insertafter, errors='surrogate_or_strict')) elif insertbefore not in (None, 'BOF'): insertre = re.compile( to_bytes(insertbefore, errors='surrogate_or_strict')) else: insertre = None marker0 = re.sub(b(r'{mark}'), b(params['marker_begin']), marker) + b( os.linesep) marker1 = re.sub(b(r'{mark}'), b(params['marker_end']), marker) + b( os.linesep) if present and block: if not block.endswith(b(os.linesep)): block += b(os.linesep) blocklines = [marker0] + block.splitlines(True) + [marker1] else: blocklines = [] n0 = n1 = None for i, line in enumerate(lines): if line == marker0: n0 = i if line == marker1: n1 = i if None in (n0, n1): n0 = None if insertre is not None: for i, line in enumerate(lines): if insertre.search(line): n0 = i if n0 is None: n0 = len(lines) elif insertafter is not None: n0 += 1 elif insertbefore is not None: n0 = 0 # insertbefore=BOF else: n0 = len(lines) # insertafter=EOF elif n0 < n1: lines[n0:n1 + 1] = [] else: lines[n1:n0 + 1] = [] n0 = n1 # Ensure there is a line separator before the block of lines to be inserted if n0 > 0: if not lines[n0 - 1].endswith(b(os.linesep)): lines[n0 - 1] += b(os.linesep) lines[n0:n0] = blocklines if lines: result = b''.join(lines) else: result = b'' if module._diff: diff['after'] = result if original == result: msg = '' changed = False elif original is None: msg = 'File created' changed = True elif not blocklines: msg = 'Block removed' changed = True else: msg = 'Block inserted' changed = True backup_file = None if changed and not module.check_mode: if module.boolean(params['backup']) and path_exists: backup_file = module.backup_local(path) # We should always follow symlinks so that we change the real file real_path = os.path.realpath(params['path']) write_changes(module, result, real_path) if module.check_mode and not path_exists: module.exit_json(changed=changed, msg=msg, diff=diff) attr_diff = {} msg, changed = check_file_attrs(module, changed, msg, attr_diff) attr_diff['before_header'] = '%s (file attributes)' % path attr_diff['after_header'] = '%s (file attributes)' % path difflist = [diff, attr_diff] if backup_file is None: module.exit_json(changed=changed, msg=msg, diff=difflist) else: module.exit_json(changed=changed, msg=msg, diff=difflist, backup_file=backup_file)
def main(): module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=dict( src=dict(type='path'), original_basename=dict(type='str'), # used to handle 'dest is a directory' via template, a slight hack content=dict(type='str', no_log=True), dest=dict(type='path', required=True), backup=dict(type='bool', default=False), force=dict(type='bool', default=True, aliases=['thirsty']), validate=dict(type='str'), directory_mode=dict(type='raw'), remote_src=dict(type='bool'), local_follow=dict(type='bool'), checksum=dict(), ), add_file_common_args=True, supports_check_mode=True, ) src = module.params['src'] b_src = to_bytes(src, errors='surrogate_or_strict') dest = module.params['dest'] # Make sure we always have a directory component for later processing if os.path.sep not in dest: dest = '.{0}{1}'.format(os.path.sep, dest) b_dest = to_bytes(dest, errors='surrogate_or_strict') backup = module.params['backup'] force = module.params['force'] original_basename = module.params.get('original_basename', None) validate = module.params.get('validate', None) follow = module.params['follow'] remote_src = module.params['remote_src'] checksum = module.params['checksum'] if not os.path.exists(b_src): module.fail_json(msg="Source %s not found" % (src)) if not os.access(b_src, os.R_OK): module.fail_json(msg="Source %s not readable" % (src)) if os.path.isdir(b_src): module.fail_json(msg="Remote copy does not support recursive copy of directory: %s" % (src)) # Preserve is usually handled in the action plugin but mode + remote_src has to be done on the # remote host if module.params['mode'] == 'preserve': module.params['mode'] = '0%03o' % stat.S_IMODE(os.stat(b_src).st_mode) mode = module.params['mode'] checksum_src = module.sha1(src) checksum_dest = None # Backwards compat only. This will be None in FIPS mode try: md5sum_src = module.md5(src) except ValueError: md5sum_src = None changed = False if checksum and checksum_src != checksum: module.fail_json( msg='Copied file does not match the expected checksum. Transfer failed.', checksum=checksum_src, expected_checksum=checksum ) # Special handling for recursive copy - create intermediate dirs if original_basename and dest.endswith(os.sep): dest = os.path.join(dest, original_basename) b_dest = to_bytes(dest, errors='surrogate_or_strict') dirname = os.path.dirname(dest) b_dirname = to_bytes(dirname, errors='surrogate_or_strict') if not os.path.exists(b_dirname) and os.path.isabs(b_dirname): (pre_existing_dir, new_directory_list) = split_pre_existing_dir(dirname) os.makedirs(b_dirname) directory_args = module.load_file_common_arguments(module.params) directory_mode = module.params["directory_mode"] if directory_mode is not None: directory_args['mode'] = directory_mode else: directory_args['mode'] = None adjust_recursive_directory_permissions(pre_existing_dir, new_directory_list, module, directory_args, changed) if os.path.isdir(b_dest): basename = os.path.basename(src) if original_basename: basename = original_basename dest = os.path.join(dest, basename) b_dest = to_bytes(dest, errors='surrogate_or_strict') if os.path.exists(b_dest): if os.path.islink(b_dest) and follow: b_dest = os.path.realpath(b_dest) dest = to_native(b_dest, errors='surrogate_or_strict') if not force: module.exit_json(msg="file already exists", src=src, dest=dest, changed=False) if os.access(b_dest, os.R_OK): checksum_dest = module.sha1(dest) else: if not os.path.exists(os.path.dirname(b_dest)): try: # os.path.exists() can return false in some # circumstances where the directory does not have # the execute bit for the current user set, in # which case the stat() call will raise an OSError os.stat(os.path.dirname(b_dest)) except OSError as e: if "permission denied" in to_native(e).lower(): module.fail_json(msg="Destination directory %s is not accessible" % (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(b_dest), os.W_OK) and not module.params['unsafe_writes']: module.fail_json(msg="Destination %s not writable" % (os.path.dirname(dest))) backup_file = None if checksum_src != checksum_dest or os.path.islink(b_dest): if not module.check_mode: try: if backup: if os.path.exists(b_dest): backup_file = module.backup_local(dest) # allow for conversion from symlink. if os.path.islink(b_dest): os.unlink(b_dest) open(b_dest, 'w').close() if validate: # if we have a mode, make sure we set it on the temporary # file source as some validations may require it # FIXME: should we do the same for owner/group here too? if mode is not None: module.set_mode_if_different(src, mode, False) if "%s" not in validate: module.fail_json(msg="validate must contain %%s: %s" % (validate)) (rc, out, err) = module.run_command(validate % src) if rc != 0: module.fail_json(msg="failed to validate", exit_status=rc, stdout=out, stderr=err) b_mysrc = b_src if remote_src: _, b_mysrc = tempfile.mkstemp(dir=os.path.dirname(b_dest)) shutil.copy2(b_src, b_mysrc) module.atomic_move(b_mysrc, dest, unsafe_writes=module.params['unsafe_writes']) except IOError: module.fail_json(msg="failed to copy: %s to %s" % (src, dest), traceback=traceback.format_exc()) changed = True else: changed = False res_args = dict( dest=dest, src=src, md5sum=md5sum_src, checksum=checksum_src, changed=changed ) if backup_file: res_args['backup_file'] = backup_file module.params['dest'] = dest if not module.check_mode: file_args = module.load_file_common_arguments(module.params) res_args['changed'] = module.set_fs_attributes_if_different(file_args, res_args['changed']) module.exit_json(**res_args)
def main(): from ansible.module_utils.basic import AnsibleModule module = AnsibleModule( argument_spec={ 'name': { 'required': True, }, 'state': { 'required': False, 'default': 'present', 'choices': ['absent', 'present'], }, 'value': { 'required': False, 'default': None, }, 'edit_only': { 'type': 'bool', 'required': False, 'default': False, }, 'path': { 'required': False, 'default': '', }, 'bootloader': { 'required': False, 'choices': list(BOOTLOADERS.keys()), }, 'backup': { 'type': 'bool', 'required': False, 'default': False, }, }, supports_check_mode=True, ) params = module.params path = params['path'].strip() bootloader = params['bootloader'] if path and not bootloader: try: bootloader = PATHS[path] except KeyError: module.fail_json( msg=("If the `path` argument is given," " then `bootloader` must also be given."), changed=False, ) if not path: try: path, bootloader = find_bootloader_config() except LookupError as err: module.fail_json(msg=str(err), changed=False) # seed the result dict in the object result = { 'changed': False, 'path': path, 'bootloader': bootloader, 'edited': False, 'installed': False, } try: handler = BOOTLOADERS[bootloader](module) except KeyError: module.fail_json(msg=( "Unknown value for `bootloader` argument: {bootloader}".format( bootloader=bootloader)), **result) # if the user is working with this module in only check mode we do not # want to make any changes to the environment, just return the current # state with no modifications if module.check_mode: return result # read in config file contents try: with open(path, 'r') as input: current_config = input.read() except (OSError, IOError) as err: module.fail_json(msg=("Cannot read file `{path}`: {err}".format( path=path, err=err)), **result) # apply requested changes new_config = handler.edit(current_config, params['state'], params['name'], params['value']) # exit early if no changes if new_config == current_config: module.exit_json(**result) # make a backup if requested if params['backup']: result['backup'] = module.backup_local(path) # write out changed config try: with NamedTemporaryFile(dir=dirname(path), prefix=(basename(path) + '.'), suffix='.tmp', delete=False) as edited: edited.write(new_config) module.atomic_move(edited.name, path) result['changed'] = True except (OSError, IOError) as err: module.fail_json(msg=("Cannot write back file `{path}`: {err}".format( path=path, err=err)), **result) finally: module.cleanup(edited.name) result['edited'] = True # ensure new config is used by the bootloader next time result['installed'] = False if not params['edit_only']: try: install_result = handler.install(path) result.update(install_result) result['installed'] = True result['changed'] = True except Exception as err: module.fail_json( msg=("Cannot install new config file `{path}`: {err}".format( path=path, err=err)), **result) # all done module.exit_json(**result)
def main(): module = AnsibleModule( argument_spec=dict( host=dict(required=True, aliases=['hostname']), url_username=dict(required=True, aliases=['username', 'login']), url_password=dict(required=True, no_log=True, aliases=['password']), src=dict(required=True, aliases=['name']), datacenter=dict(required=True), datastore=dict(required=True), dest=dict(required=True, aliases=['path'], type='path'), backup=dict(type='bool'), tmp_dest=dict(type='path'), timeout=dict(type='int', default=10), force=dict(default='no', aliases=['thirsty'], type='bool'), validate_certs=dict(required=False, default=True, type='bool'), ), supports_check_mode=True, add_file_common_args=True, ) host = module.params.get('host') login = module.params.get('url_username') password = module.params.get('url_password') src = module.params.get('src') datacenter = module.params.get('datacenter') datastore = module.params.get('datastore') dest = module.params.get('dest') validate_certs = module.params.get('validate_certs') tmp_dest = module.params.get('tmp_dest') backup = module.params.get('backup') force = module.params.get('force') timeout = module.params.get('timeout') dest_is_dir = os.path.isdir(dest) last_mod_time = None remote_path = vmware_path(datastore, datacenter, src) url = 'https://%s%s' % (host, remote_path) if not dest_is_dir and os.path.exists(dest): if not force: # allow file attribute changes module.params['path'] = dest file_args = module.load_file_common_arguments(module.params) file_args['path'] = dest changed = module.set_fs_attributes_if_different(file_args, False) if changed: module.exit_json( msg="file already exists but file attributes changed", dest=dest, url=url, changed=changed) module.exit_json(msg="file already exists", dest=dest, url=url, changed=changed) # If the file already exists, prepare the last modified time for the # request. mtime = os.path.getmtime(dest) last_mod_time = datetime.datetime.utcfromtimestamp(mtime) tmpsrc, info = vmware_get(module, url=url, dest=dest, last_mod_time=last_mod_time, force=force, timeout=timeout, tmp_dest=tmp_dest) if dest_is_dir: filename = extract_filename_from_headers(info) if not filename: # Fall back to extracting the filename from the URL. # Pluck the URL from the info, since a redirect could have changed # it. filename = url_filename(info['url']) dest = os.path.join(dest, filename) # If the remote URL exists, we're done with check mode if module.check_mode: os.remove(tmpsrc) res_args = dict(url=url, dest=dest, src=tmpsrc, changed=True, msg=info.get('msg', '')) module.exit_json(**res_args) # raise an error if there is no tmpsrc file if not os.path.exists(tmpsrc): os.remove(tmpsrc) module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg']) if not os.access(tmpsrc, os.R_OK): os.remove(tmpsrc) module.fail_json(msg="Source %s is not readable" % (tmpsrc)) checksum_src = module.sha1(tmpsrc) # check if there is no dest file if os.path.exists(dest): # raise an error if copy has no permission on dest if not os.access(dest, os.W_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not writable" % (dest)) if not os.access(dest, os.R_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not readable" % (dest)) else: if not os.path.exists(os.path.dirname(dest)): os.remove(tmpsrc) module.fail_json(msg="Destination %s does not exist" % (os.path.dirname(dest))) if not os.access(os.path.dirname(dest), os.W_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not writable" % (os.path.dirname(dest))) backup_file = None try: if backup: if os.path.exists(dest): backup_file = module.backup_local(dest) module.atomic_move(tmpsrc, dest) except Exception as e: if os.path.exists(tmpsrc): os.remove(tmpsrc) module.fail_json(msg="failed to copy %s to %s: %s" % (tmpsrc, dest, to_native(e)), exception=traceback.format_exc()) changed = True # allow file attribute changes module.params['path'] = dest file_args = module.load_file_common_arguments(module.params) file_args['path'] = dest changed = module.set_fs_attributes_if_different(file_args, changed) # Backwards compat only. We'll return None on FIPS enabled systems try: md5sum = module.md5(dest) except ValueError: md5sum = None res_args = dict(url=url, dest=dest, src=tmpsrc, md5sum=md5sum, datacenter=datacenter, datastore=datastore, changed=changed, msg=info.get('msg', ''), status_code=info.get('status', '')) if backup_file: res_args['backup_file'] = backup_file # Mission complete module.exit_json(**res_args)
def main(): module = AnsibleModule( argument_spec=dict( name=dict(required=True, type='str'), type=dict(required=True, choices=['account', 'auth', 'password', 'session']), control=dict(required=True, type='str'), module_path=dict(required=True, type='str'), new_type=dict(required=False, choices=['account', 'auth', 'password', 'session']), new_control=dict(required=False, type='str'), new_module_path=dict(required=False, type='str'), module_arguments=dict(required=False, type='list'), state=dict(required=False, default="updated", choices=['before', 'after', 'updated', 'args_absent', 'args_present', 'absent']), path=dict(required=False, default='/etc/pam.d', type='str'), backup=dict(default=False, type='bool') ), supports_check_mode=True, required_if=[ ("state", "args_present", ["module_arguments"]), ("state", "args_absent", ["module_arguments"]), ("state", "before", ["new_control"]), ("state", "before", ["new_type"]), ("state", "before", ["new_module_path"]), ("state", "after", ["new_control"]), ("state", "after", ["new_type"]), ("state", "after", ["new_module_path"]) ] ) content = str() fname = os.path.join(module.params["path"], module.params["name"]) backupdest = "" # Open the file and read the content or fail try: with open(fname, 'r') as service_file_obj: content = service_file_obj.read() except IOError as e: # If unable to read the file, fail out module.fail_json(msg='Unable to open/read PAM module \ file %s with error %s.' % (fname, str(e))) # Assuming we didnt fail, create the service service = PamdService(content) # Set the action action = module.params['state'] # Take action if action == 'updated': changes = service.update_rule(module.params['type'], module.params['control'], module.params['module_path'], module.params['new_type'], module.params['new_control'], module.params['new_module_path'], module.params['module_arguments']) elif action == 'before': changes = service.insert_before(module.params['type'], module.params['control'], module.params['module_path'], module.params['new_type'], module.params['new_control'], module.params['new_module_path'], module.params['module_arguments']) elif action == 'after': changes = service.insert_after(module.params['type'], module.params['control'], module.params['module_path'], module.params['new_type'], module.params['new_control'], module.params['new_module_path'], module.params['module_arguments']) elif action == 'args_absent': changes = service.remove_module_arguments(module.params['type'], module.params['control'], module.params['module_path'], module.params['module_arguments']) elif action == 'args_present': changes = service.add_module_arguments(module.params['type'], module.params['control'], module.params['module_path'], module.params['module_arguments']) elif action == 'absent': changes = service.remove(module.params['type'], module.params['control'], module.params['module_path']) valid, msg = service.validate() # If the module is not valid (meaning one of the rules is invalid), we will fail if not valid: module.fail_json(msg=msg) # If not check mode and something changed, backup the original if necessary then write out the file or fail if not module.check_mode and changes > 0: pamd_file = os.path.realpath(fname) # First, create a backup if desired. if module.params['backup']: backupdest = module.backup_local(fname) print("BACKUP DEST", backupdest) try: temp_file = NamedTemporaryFile(mode='w') with open(temp_file.name, 'w') as fd: fd.write(str(service)) except IOError: module.fail_json(msg='Unable to create temporary \ file %s' % temp_file) module.atomic_move(temp_file.name, pamd_file) facts = {} facts['pamd'] = {'changed': changes > 0, 'change_count': changes, 'action': action, 'backupdest': backupdest} module.exit_json(changed=changes > 0, ansible_facts=facts)
def main(): pam_items = ['core', 'data', 'fsize', 'memlock', 'nofile', 'rss', 'stack', 'cpu', 'nproc', 'as', 'maxlogins', 'maxsyslogins', 'priority', 'locks', 'sigpending', 'msgqueue', 'nice', 'rtprio', 'chroot'] pam_types = ['soft', 'hard', '-'] limits_conf = '/etc/security/limits.conf' module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=dict( domain=dict(required=True, type='str'), limit_type=dict(required=True, type='str', choices=pam_types), limit_item=dict(required=True, type='str', choices=pam_items), value=dict(required=True, type='str'), use_max=dict(default=False, type='bool'), use_min=dict(default=False, type='bool'), backup=dict(default=False, type='bool'), dest=dict(default=limits_conf, type='str'), comment=dict(required=False, default='', type='str') ) ) domain = module.params['domain'] limit_type = module.params['limit_type'] limit_item = module.params['limit_item'] value = module.params['value'] use_max = module.params['use_max'] use_min = module.params['use_min'] backup = module.params['backup'] limits_conf = module.params['dest'] new_comment = module.params['comment'] changed = False if os.path.isfile(limits_conf): if not os.access(limits_conf, os.W_OK): module.fail_json(msg="%s is not writable. Use sudo" % limits_conf) else: limits_conf_dir = os.path.dirname(limits_conf) if os.path.isdir(limits_conf_dir) and os.access(limits_conf_dir, os.W_OK): open(limits_conf, 'a').close() changed = True else: module.fail_json(msg="directory %s is not writable (check presence, access rights, use sudo)" % limits_conf_dir) if use_max and use_min: module.fail_json(msg="Cannot use use_min and use_max at the same time.") if not (value in ['unlimited', 'infinity', '-1'] or value.isdigit()): module.fail_json(msg="Argument 'value' can be one of 'unlimited', 'infinity', '-1' or positive number. Refer to manual pages for more details.") # Backup if backup: backup_file = module.backup_local(limits_conf) space_pattern = re.compile(r'\s+') message = '' f = open(limits_conf, 'rb') # Tempfile nf = tempfile.NamedTemporaryFile(mode='w+') found = False new_value = value for line in f: line = to_native(line, errors='surrogate_or_strict') if line.startswith('#'): nf.write(line) continue newline = re.sub(space_pattern, ' ', line).strip() if not newline: nf.write(line) continue # Remove comment in line newline = newline.split('#', 1)[0] try: old_comment = line.split('#', 1)[1] except: old_comment = '' newline = newline.rstrip() if not new_comment: new_comment = old_comment line_fields = newline.split(' ') if len(line_fields) != 4: nf.write(line) continue line_domain = line_fields[0] line_type = line_fields[1] line_item = line_fields[2] actual_value = line_fields[3] if not (actual_value in ['unlimited', 'infinity', '-1'] or actual_value.isdigit()): module.fail_json(msg="Invalid configuration of '%s'. Current value of %s is unsupported." % (limits_conf, line_item)) # Found the line if line_domain == domain and line_type == limit_type and line_item == limit_item: found = True if value == actual_value: message = line nf.write(line) continue actual_value_unlimited = actual_value in ['unlimited', 'infinity', '-1'] value_unlimited = value in ['unlimited', 'infinity', '-1'] if use_max: if value.isdigit() and actual_value.isdigit(): new_value = str(max(int(value), int(actual_value))) elif actual_value_unlimited: new_value = actual_value else: new_value = value if use_min: if value.isdigit() and actual_value.isdigit(): new_value = str(min(int(value), int(actual_value))) elif value_unlimited: new_value = actual_value else: new_value = value # Change line only if value has changed if new_value != actual_value: changed = True if new_comment: new_comment = "\t#" + new_comment new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + new_value + new_comment + "\n" message = new_limit nf.write(new_limit) else: message = line nf.write(line) else: nf.write(line) if not found: changed = True if new_comment: new_comment = "\t#" + new_comment new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + new_value + new_comment + "\n" message = new_limit nf.write(new_limit) f.close() nf.flush() # Copy tempfile to newfile module.atomic_move(nf.name, f.name) try: nf.close() except: pass res_args = dict( changed=changed, msg=message ) if backup: res_args['backup_file'] = backup_file module.exit_json(**res_args)
def main(): module = AnsibleModule(argument_spec=dict( src=dict(default=None, required=False, type='path'), dest=dict(default='/etc/network/interfaces', required=False, type='path'), iface=dict(required=False), method=dict(default=None, required=False), option=dict(required=False), value=dict(required=False), backup=dict(default='no', type='bool'), backupdir=dict(default=None, required=False, type='path'), state=dict(default='present', choices=['present', 'absent', 'move', 'bridge']), bridge_options=dict(default=None, required=False, type='list'), ), add_file_common_args=True, supports_check_mode=True) src = module.params['src'] dest = module.params['dest'] iface = module.params['iface'] method = module.params['method'] option = module.params['option'] value = module.params['value'] backup = module.params['backup'] backupdir = module.params['backupdir'] state = module.params['state'] bridge_options = module.params['bridge_options'] if src is None: src = dest if option is not None and iface is None: module.fail_json(msg="Inteface must be set if option is defined") if option is not None and state == "present" and value is None: module.fail_json( msg="Value must be set if option is defined and state is 'present'" ) if state == "move" and (iface is None or option is not None or value is not None): module.fail_json( msg="Iface must be set if state is 'move' but not option nor value" ) if state == "bridge" and (iface is None or option is not None or value is not None or bridge_options is None): module.fail_json( msg= "Iface and bridge_options must be set if state is 'bridge' but not option nor value" ) lines, ifaces = read_interfaces_file(module, src) changed = False if option is not None: changed, lines, _ = setInterfaceOption(module, lines, iface, ifaces, option, value, state) elif state == 'absent' or state == 'move' or state == 'bridge': changed, lines, removed_lines = setInterfaceOption( module, lines, iface, ifaces, None, None, state, bridge_options, method) if state == 'bridge' and src == dest: lines = lines + removed_lines removed_lines = [] elif state == 'present' and option is None and value is None and iface is not None and iface not in ifaces.keys( ): for x in [{ 'line': '\n', 'line_type': 'empty' }, { 'comment': '# Added secondary network interface\n', 'line': 'auto ' + iface + '\n', 'iface': iface, 'line_type': 'auto' }, { 'params': { 'address_family': 'inet', 'post-up': [], 'auto': True, 'up': [], 'method': 'dhcp', 'down': [], 'pre-up': [] }, 'line': 'iface ' + iface + ' inet dhcp\n', 'iface': iface, 'line_type': 'iface' }]: lines.append(x) changed = True if changed: _, ifaces = read_interfaces_lines( module, [d['line'] for d in lines if 'line' in d]) if changed and not module.check_mode: if backup: backup_dest = module.backup_local(src) if backupdir: backupdir_infos = os.stat(os.path.dirname(backup_dest)) if not os.path.exists(backupdir): os.makedirs(backupdir, backupdir_infos.st_mode) os.chown(backupdir, backupdir_infos.st_uid, backupdir_infos.st_gid) module.atomic_move( backup_dest, os.path.join(backupdir, os.path.basename(backup_dest))) if src == dest: write_changes(module, select_lines_and_comments(lines), dest) else: if backup and os.path.exists(dest) and os.path.isfile(dest): module.backup_local(dest) write_changes(module, select_lines_and_comments(lines), src) write_changes(module, select_lines_and_comments(removed_lines), dest) module.exit_json(dest=dest, changed=changed, ifaces=ifaces)
def main(): argument_spec = url_argument_spec() argument_spec.update( url=dict(type='str', required=True), dest=dict(type='path', required=True), backup=dict(type='bool'), sha256sum=dict(type='str', default=''), checksum=dict(type='str', default=''), timeout=dict(type='int', default=10), headers=dict(type='raw'), tmp_dest=dict(type='path'), ) module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=argument_spec, add_file_common_args=True, supports_check_mode=True, mutually_exclusive=(['checksum', 'sha256sum']), ) url = module.params['url'] dest = module.params['dest'] backup = module.params['backup'] force = module.params['force'] sha256sum = module.params['sha256sum'] checksum = module.params['checksum'] use_proxy = module.params['use_proxy'] timeout = module.params['timeout'] tmp_dest = module.params['tmp_dest'] # Parse headers to dict if isinstance(module.params['headers'], dict): headers = module.params['headers'] elif module.params['headers']: try: headers = dict(item.split(':', 1) for item in module.params['headers'].split(',')) module.deprecate('Supplying `headers` as a string is deprecated. Please use dict/hash format for `headers`', version='2.10') except Exception: module.fail_json(msg="The string representation for the `headers` parameter requires a key:value,key:value syntax to be properly parsed.") else: headers = None dest_is_dir = os.path.isdir(dest) last_mod_time = None # workaround for usage of deprecated sha256sum parameter if sha256sum: checksum = 'sha256:%s' % (sha256sum) # checksum specified, parse for algorithm and checksum if checksum: try: algorithm, checksum = checksum.rsplit(':', 1) # Remove any non-alphanumeric characters, including the infamous # Unicode zero-width space checksum = re.sub(r'\W+', '', checksum).lower() # Ensure the checksum portion is a hexdigest int(checksum, 16) except ValueError: module.fail_json(msg="The checksum parameter has to be in format <algorithm>:<checksum>") if not dest_is_dir and os.path.exists(dest): checksum_mismatch = False # If the download is not forced and there is a checksum, allow # checksum match to skip the download. if not force and checksum != '': destination_checksum = module.digest_from_file(dest, algorithm) if checksum == destination_checksum: module.exit_json(msg="file already exists", dest=dest, url=url, changed=False) checksum_mismatch = True # Not forcing redownload, unless checksum does not match if not force and not checksum_mismatch: # allow file attribute changes module.params['path'] = dest file_args = module.load_file_common_arguments(module.params) file_args['path'] = dest changed = module.set_fs_attributes_if_different(file_args, False) if changed: module.exit_json(msg="file already exists but file attributes changed", dest=dest, url=url, changed=changed) module.exit_json(msg="file already exists", dest=dest, url=url, changed=changed) # If the file already exists, prepare the last modified time for the # request. mtime = os.path.getmtime(dest) last_mod_time = datetime.datetime.utcfromtimestamp(mtime) # If the checksum does not match we have to force the download # because last_mod_time may be newer than on remote if checksum_mismatch: force = True # download to tmpsrc tmpsrc, info = url_get(module, url, dest, use_proxy, last_mod_time, force, timeout, headers, tmp_dest) # Now the request has completed, we can finally generate the final # destination file name from the info dict. if dest_is_dir: filename = extract_filename_from_headers(info) if not filename: # Fall back to extracting the filename from the URL. # Pluck the URL from the info, since a redirect could have changed # it. filename = url_filename(info['url']) dest = os.path.join(dest, filename) checksum_src = None checksum_dest = None # If the remote URL exists, we're done with check mode if module.check_mode: os.remove(tmpsrc) res_args = dict(url=url, dest=dest, src=tmpsrc, changed=True, msg=info.get('msg', '')) module.exit_json(**res_args) # raise an error if there is no tmpsrc file if not os.path.exists(tmpsrc): os.remove(tmpsrc) module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg']) if not os.access(tmpsrc, os.R_OK): os.remove(tmpsrc) module.fail_json(msg="Source %s is not readable" % (tmpsrc)) checksum_src = module.sha1(tmpsrc) # check if there is no dest file if os.path.exists(dest): # raise an error if copy has no permission on dest if not os.access(dest, os.W_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not writable" % (dest)) if not os.access(dest, os.R_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not readable" % (dest)) checksum_dest = module.sha1(dest) else: if not os.path.exists(os.path.dirname(dest)): os.remove(tmpsrc) module.fail_json(msg="Destination %s does not exist" % (os.path.dirname(dest))) if not os.access(os.path.dirname(dest), os.W_OK): os.remove(tmpsrc) module.fail_json(msg="Destination %s is not writable" % (os.path.dirname(dest))) backup_file = None if checksum_src != checksum_dest: try: if backup: if os.path.exists(dest): backup_file = module.backup_local(dest) module.atomic_move(tmpsrc, dest) except Exception as e: if os.path.exists(tmpsrc): os.remove(tmpsrc) module.fail_json(msg="failed to copy %s to %s: %s" % (tmpsrc, dest, to_native(e)), exception=traceback.format_exc()) changed = True else: changed = False if os.path.exists(tmpsrc): os.remove(tmpsrc) if checksum != '': destination_checksum = module.digest_from_file(dest, algorithm) if checksum != destination_checksum: os.remove(dest) module.fail_json(msg="The checksum for %s did not match %s; it was %s." % (dest, checksum, destination_checksum)) # allow file attribute changes module.params['path'] = dest file_args = module.load_file_common_arguments(module.params) file_args['path'] = dest changed = module.set_fs_attributes_if_different(file_args, changed) # Backwards compat only. We'll return None on FIPS enabled systems try: md5sum = module.md5(dest) except ValueError: md5sum = None res_args = dict( url=url, dest=dest, src=tmpsrc, md5sum=md5sum, checksum_src=checksum_src, checksum_dest=checksum_dest, changed=changed, msg=info.get('msg', ''), status_code=info.get('status', '') ) if backup_file: res_args['backup_file'] = backup_file # Mission complete module.exit_json(**res_args)
def main(): module = AnsibleModule( # not checking because of daisy chain to file module argument_spec = dict( src = dict(required=True, type='path'), delimiter = dict(required=False), dest = dict(required=True, type='path'), backup=dict(default=False, type='bool'), remote_src=dict(default=False, type='bool'), regexp = dict(required=False), ignore_hidden = dict(default=False, type='bool'), validate = dict(required=False, type='str'), ), add_file_common_args=True ) changed = False path_hash = None dest_hash = None src = module.params['src'] dest = module.params['dest'] backup = module.params['backup'] delimiter = module.params['delimiter'] regexp = module.params['regexp'] compiled_regexp = None ignore_hidden = module.params['ignore_hidden'] validate = module.params.get('validate', None) result = dict(src=src, dest=dest) 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) if regexp is not None: try: compiled_regexp = re.compile(regexp) except re.error as e: module.fail_json(msg="Invalid Regexp (%s) in \"%s\"" % (to_native(e), regexp)) if validate and "%s" not in validate: module.fail_json(msg="validate must contain %%s: %s" % validate) path = assemble_from_fragments(src, delimiter, compiled_regexp, ignore_hidden) path_hash = module.sha1(path) result['checksum'] = path_hash # Backwards compat. This won't return data if FIPS mode is active try: pathmd5 = module.md5(path) except ValueError: pathmd5 = None result['md5sum'] = pathmd5 if os.path.exists(dest): dest_hash = module.sha1(dest) if path_hash != dest_hash: if validate: (rc, out, err) = module.run_command(validate % path) result['validation'] = dict(rc=rc, stdout=out, stderr=err) if rc != 0: cleanup(path) module.fail_json(msg="failed to validate: rc:%s error:%s" % (rc, err)) if backup and dest_hash is not None: result['backup_file'] = module.backup_local(dest) module.atomic_move(path, dest, unsafe_writes=module.params['unsafe_writes']) changed = True cleanup(path, result) # handle file permissions file_args = module.load_file_common_arguments(module.params) result['changed'] = module.set_fs_attributes_if_different(file_args, changed) # Mission complete result['msg'] = "OK" module.exit_json(**result)
def main(): module = AnsibleModule( argument_spec=dict( path=dict(type='path', required=True, aliases=['dest', 'destfile', 'name']), regexp=dict(type='str', required=True), replace=dict(type='str', default=''), after=dict(type='str'), before=dict(type='str'), backup=dict(type='bool', default=False), validate=dict(type='str'), encoding=dict(type='str', default='utf-8'), ), add_file_common_args=True, supports_check_mode=True, ) params = module.params path = params['path'] encoding = params['encoding'] res_args = dict() params['after'] = to_text(params['after'], errors='surrogate_or_strict', nonstring='passthru') params['before'] = to_text(params['before'], errors='surrogate_or_strict', nonstring='passthru') params['regexp'] = to_text(params['regexp'], errors='surrogate_or_strict', nonstring='passthru') params['replace'] = to_text(params['replace'], errors='surrogate_or_strict', nonstring='passthru') if os.path.isdir(path): module.fail_json(rc=256, msg='Path %s is a directory !' % path) if not os.path.exists(path): module.fail_json(rc=257, msg='Path %s does not exist !' % path) else: try: with open(path, 'rb') as f: contents = to_text(f.read(), errors='surrogate_or_strict', encoding=encoding) except (OSError, IOError) as e: module.fail_json(msg='Unable to read the contents of %s: %s' % (path, to_text(e)), exception=format_exc()) pattern = u'' if params['after'] and params['before']: pattern = u'%s(?P<subsection>.*?)%s' % (params['after'], params['before']) elif params['after']: pattern = u'%s(?P<subsection>.*)' % params['after'] elif params['before']: pattern = u'(?P<subsection>.*)%s' % params['before'] if pattern: section_re = re.compile(pattern, re.DOTALL) match = re.search(section_re, contents) if match: section = match.group('subsection') indices = [match.start('subsection'), match.end('subsection')] else: res_args[ 'msg'] = 'Pattern for before/after params did not match the given file: %s' % pattern res_args['changed'] = False module.exit_json(**res_args) else: section = contents mre = re.compile(params['regexp'], re.MULTILINE) result = re.subn(mre, params['replace'], section, 0) if result[1] > 0 and section != result[0]: if pattern: result = (contents[:indices[0]] + result[0] + contents[indices[1]:], result[1]) msg = '%s replacements made' % result[1] changed = True if module._diff: res_args['diff'] = { 'before_header': path, 'before': contents, 'after_header': path, 'after': result[0], } else: msg = '' changed = False if changed and not module.check_mode: if params['backup'] and os.path.exists(path): res_args['backup_file'] = module.backup_local(path) # We should always follow symlinks so that we change the real file path = os.path.realpath(path) write_changes(module, to_bytes(result[0], encoding=encoding), path) res_args['msg'], res_args['changed'] = check_file_attrs( module, changed, msg) module.exit_json(**res_args)
def main(): module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=dict( src=dict(required=True, type='path'), delimiter=dict(required=False), dest=dict(required=True, type='path'), backup=dict(default=False, type='bool'), remote_src=dict(default=False, type='bool'), regexp=dict(required=False), ignore_hidden=dict(default=False, type='bool'), validate=dict(required=False, type='str'), ), add_file_common_args=True, ) changed = False path_hash = None dest_hash = None src = module.params['src'] dest = module.params['dest'] backup = module.params['backup'] delimiter = module.params['delimiter'] regexp = module.params['regexp'] compiled_regexp = None ignore_hidden = module.params['ignore_hidden'] validate = module.params.get('validate', None) result = dict(src=src, dest=dest) 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) if regexp is not None: try: compiled_regexp = re.compile(regexp) except re.error as e: module.fail_json(msg="Invalid Regexp (%s) in \"%s\"" % (to_native(e), regexp)) if validate and "%s" not in validate: module.fail_json(msg="validate must contain %%s: %s" % validate) path = assemble_from_fragments(src, delimiter, compiled_regexp, ignore_hidden, module.tmpdir) path_hash = module.sha1(path) result['checksum'] = path_hash # Backwards compat. This won't return data if FIPS mode is active try: pathmd5 = module.md5(path) except ValueError: pathmd5 = None result['md5sum'] = pathmd5 if os.path.exists(dest): dest_hash = module.sha1(dest) if path_hash != dest_hash: if validate: (rc, out, err) = module.run_command(validate % path) result['validation'] = dict(rc=rc, stdout=out, stderr=err) if rc != 0: cleanup(path) module.fail_json(msg="failed to validate: rc:%s error:%s" % (rc, err)) if backup and dest_hash is not None: result['backup_file'] = module.backup_local(dest) module.atomic_move(path, dest, unsafe_writes=module.params['unsafe_writes']) changed = True cleanup(path, result) # handle file permissions file_args = module.load_file_common_arguments(module.params) result['changed'] = module.set_fs_attributes_if_different( file_args, changed) # Mission complete result['msg'] = "OK" module.exit_json(**result)
def main(): module = AnsibleModule( argument_spec=dict( name=dict(type='str', required=True), rule=dict(type='str', no_log=True, aliases=['rules', 'snippet']), hook=dict( type='str', default='custom', choices=['custom', 'input', 'forward', 'internal', 'external']), prio=dict(type='int', default=DEFAULT_PRIO, aliases=['priority']), state=dict(type='str', default='present', choices=['present', 'absent']), backup=dict(type='bool', default=False), reload=dict(type='bool', default=True), ferm_dir=dict(type='str', default='/etc/ferm'), ), supports_check_mode=True, required_if=[('state', 'present', ['rule'])], ) name = module.params['name'] if re.search(r'^\d+-|[\s\\\/]|\.ferm$', name): module.fail_json(msg="Invalid rule name: '%s'" % name) prio = module.params['prio'] if prio < 0 or prio > 99: module.fail_json(msg='Invalid rule prio: %d' % prio) hook = module.params['hook'] hook_dir = os.path.join(module.params['ferm_dir'], '%s.d' % hook) if not os.path.isdir(hook_dir) or not os.access(hook_dir, os.W_OK): module.fail_json(msg='Directory is absent or not writable: ' + hook_dir) new_path = os.path.join(hook_dir, '%02d-%s.ferm' % (prio, name)) b_new_path = to_bytes(new_path, errors='surrogate_or_strict') path_glob = os.path.join(hook_dir, '*-%s.ferm' % name) regexp = os.path.join(re.escape(hook_dir), '[0-9]{2}-%s.ferm' % re.escape(name)) path_regex = re.compile('^%s$' % regexp) old_list = sorted(p for p in glob.glob(path_glob) if path_regex.match(p)) exists = os.path.exists(b_new_path) if exists and not os.path.isfile(b_new_path): module.fail_json(msg='Destination is not a regular file: ' + new_path) if exists and not os.access(b_new_path, os.W_OK): module.fail_json(msg='Destination is not writable: ' + new_path) if exists: old_list.remove(new_path) # must be present in the list old_path = new_path elif old_list: old_path = old_list.pop(0) # first in sort order exists = True changed = False msg = '' backup = module.params['backup'] backup_file = None state = module.params['state'] if state == 'absent' and exists: changed = True if not module.check_mode: if backup: backup_file = module.backup_local(old_path) os.remove(to_bytes(old_path, errors='surrogate_or_strict')) msg = 'Rule removed: %s' % name if old_path != new_path: msg += ' (as old priority)' if state == 'present': rule = module.params['rule'] if rule is None: module.fail_json(msg='Please provide the rule') b_rule = to_bytes(rule) if exists: with open(old_path, 'rb') as f: b_orig_rule = f.read() changed = b_rule != b_orig_rule or old_path != new_path else: changed = True if changed and not module.check_mode: tmpfd, tmpfile = tempfile.mkstemp() with os.fdopen(tmpfd, 'wb') as f: f.write(b_rule) if exists and backup: backup_file = module.backup_local(old_path) if exists and old_path != new_path: os.remove(to_bytes(old_path, errors='surrogate_or_strict')) module.atomic_move(tmpfile, new_path, unsafe_writes=False) msg = 'Rule saved: %s' % name if exists and old_path != new_path: msg += ' (as new priority)' module.set_mode_if_different(new_path, '0640', changed) module.set_owner_if_different(new_path, 'root', changed) module.set_group_if_different(new_path, 'root', changed) result = {'path': new_path} if backup_file: result['backup'] = backup_file if exists and old_path != new_path: result['old_path'] = old_path if old_list: if not module.check_mode: for path in old_list: os.remove(to_bytes(path, errors='surrogate_or_strict')) result['num_duplicates'] = len(old_list) if not msg: msg = 'Rule unchanged' msg += ', %d duplicate(s) removed' % len(old_list) changed = True if changed and module.params['reload'] and not module.check_mode: cmd = ['systemctl', 'reload-or-restart', 'ferm.service'] rc, stdout, stderr = module.run_command(cmd) if rc: module.fail_json(msg='Failed to reload ferm', rc=rc, stdout=stdout, stderr=stderr) module.exit_json(changed=changed, msg=msg, **result)