diff --git a/library/blockinfile b/library/blockinfile index af894ce..a8cfd92 100644 --- a/library/blockinfile +++ b/library/blockinfile @@ -4,7 +4,7 @@ # Ansible blockinfile module # # Licensed under GPL version 3 or later -# (c) 2014 YAEGASHI Takeshi +# (c) 2014, 2015 YAEGASHI Takeshi # (c) 2013 Evan Kaufman import re @@ -41,6 +41,24 @@ options: description: - The text to insert inside the marker lines. If it's empty string, marker lines will also be removed. + insertafter: + required: false + default: EOF + description: + - If specified, the block will be inserted after the last match of + specified regular expression. A special value is available; C(EOF) for + inserting the block at the end of the file. If specified regular + expresion has no matches, EOF will be used instead. + choices: [ 'EOF', '*regex*' ] + insertbefore: + required: false + description: + - If specified, the block will be inserted before the last match of + specified regular expression. A special value is available; C(BOF) for + inserting the block at the beginning of the file. If specified regular + expresion has no matches, the block will be inserted at the end of the + file. + choices: [ 'BOF', '*regex*' ] create: required: false default: "no" @@ -116,10 +134,13 @@ def main(): dest=dict(required=True, aliases=['name', 'destfile']), marker=dict(default='# {mark} ANSIBLE MANAGED BLOCK', type='str'), content=dict(default='', type='str'), + insertafter=dict(default=None), + insertbefore=dict(default=None), create=dict(default='no', choices=BOOLEANS, type='bool'), backup=dict(default='no', choices=BOOLEANS, type='bool'), validate=dict(default=None, type='str'), ), + mutually_exclusive=[['insertbefore', 'insertafter']], add_file_common_args=True, supports_check_mode=True ) @@ -135,35 +156,81 @@ def main(): if not os.path.exists(dest): if not module.boolean(params['create']): module.fail_json(rc=257, msg='Destination %s does not exist !' % dest) - contents = '' + original = None + lines = [] else: f = open(dest, 'rb') - contents = f.read() + original = f.read() f.close() + lines = original.splitlines() - mfunc = lambda x: re.sub(r'{mark}', x, params['marker'], 0) - markers = tuple(map(mfunc, ("BEGIN", "END"))) - markers_escaped = tuple(map(lambda x: re.escape(x), markers)) - if params['content'] == '': - repl = '' + insertbefore = params['insertbefore'] + insertafter = params['insertafter'] + content = params['content'] + marker = params['marker'] + + 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: - repl = '%s\n%s\n%s\n' % (markers[0], params['content'], markers[1]) - mre = re.compile('^%s\n(.*\n)*%s\n' % markers_escaped, re.MULTILINE) - result = re.subn(mre, repl, contents, 0) - if result[1] == 0 and repl != '': - mre = re.compile('(\n)?\Z', re.MULTILINE) - result = re.subn(mre, '\n%s' % repl, contents, 0) - if result[1] > 0 and contents != result[0]: - msg = '%s replacements made' % result[1] - changed = True + insertre = None + + marker0 = re.sub(r'{mark}', 'BEGIN', marker, 0) + marker1 = re.sub(r'{mark}', 'END', marker, 0) + if content in (None, ''): + contentlines = [] else: + content = re.sub('', content, '', 0) + contentlines = [marker0] + content.splitlines() + [marker1] + + 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] = contentlines + + result = '\n'.join(lines)+'\n' if lines else '' + if original == result: msg = '' changed = False + elif original is None: + msg = 'File created' + changed = True + elif not contentlines: + msg = 'Block removed' + changed = True + else: + msg = 'Block inserted' + changed = True if changed and not module.check_mode: if module.boolean(params['backup']) and os.path.exists(dest): module.backup_local(dest) - write_changes(module, result[0], dest) + write_changes(module, result, dest) msg, changed = check_file_attrs(module, changed, msg) module.exit_json(changed=changed, msg=msg)