835d7d854e
We use the lineinfile module to modify configuration files of a proprietary application. This application reads configuration options from files, but does not require those files to exist (if the default options are fine). However this application may modify the configuration file at will, so we cannot copy or template those files. And after a silent install the configuration may not exist (depending on the response file). Whatever the case, during deployment we need to make sure some configuration options are set after the installation. So the cleanest way to handle this situation is to allow the lineinfile module to create the file if it is missing (and this is the expected behavior). When I proposed this behavior, @sergevanginderachter needed the same functionality and was now working around it as well.
205 lines
6.4 KiB
Python
Executable file
205 lines
6.4 KiB
Python
Executable file
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com>
|
|
#
|
|
# This file is part of Ansible
|
|
#
|
|
# Ansible is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Ansible is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import re
|
|
import os
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: lineinfile
|
|
author: Daniel Hokka Zakrisson
|
|
short_description: Ensure a particular line is in a file
|
|
description:
|
|
- This module will search a file for a line, and ensure that it is present or absent.
|
|
- This is primarily useful when you want to change a single line in a
|
|
file only. For other cases, see the M(copy) or M(template) modules.
|
|
version_added: "0.7"
|
|
options:
|
|
dest:
|
|
required: true
|
|
aliases: [ name, destfile ]
|
|
description:
|
|
- The file to modify
|
|
regexp:
|
|
required: true
|
|
description:
|
|
- The regular expression to look for in the file. For C(state=present),
|
|
the pattern to replace. For C(state=absent), the pattern of the line
|
|
to remove.
|
|
state:
|
|
required: false
|
|
choices: [ present, absent ]
|
|
default: "present"
|
|
aliases: []
|
|
description:
|
|
- Whether the line should be there or not.
|
|
line:
|
|
required: false
|
|
description:
|
|
- Required for C(state=present). The line to insert/replace into the
|
|
file. Must match the value given to C(regexp).
|
|
insertafter:
|
|
required: false
|
|
default: EOF
|
|
description:
|
|
- Used with C(state=present). If specified, the line will be inserted
|
|
after the specified regular expression. Two special values are
|
|
available; C(BOF) for inserting the line at the beginning of the
|
|
file, and C(EOF) for inserting the line at the end of the file.
|
|
choices: [ BOF, EOF ]
|
|
default: EOF
|
|
create:
|
|
required: false
|
|
choices: [ yes, no ]
|
|
default: no
|
|
description:
|
|
- Used with C(state=present). If specified, the file will be created
|
|
if it does not already exist. By default it will fail if the file
|
|
is missing.
|
|
backup:
|
|
required: false
|
|
default: no
|
|
description:
|
|
- Create a backup file including the timestamp information so you can
|
|
get the original file back if you somehow clobbered it incorrectly.
|
|
examples:
|
|
- code: "lineinfile: dest=/etc/selinux/config regexp=^SELINUX= line=SELINUX=disabled"
|
|
- code: 'lineinfile: dest=/etc/sudoers state=absent regexp="^%wheel"'
|
|
'''
|
|
|
|
def present(module, dest, regexp, line, insertafter, create, backup):
|
|
|
|
if os.path.isdir(dest):
|
|
module.fail_json(rc=256, msg='Destination %s is a directory !' % dest)
|
|
elif not os.path.exists(dest):
|
|
if not create:
|
|
module.fail_json(rc=257, msg='Destination %s does not exist !' % dest)
|
|
destpath = os.path.dirname(dest)
|
|
if not os.path.exists(destpath):
|
|
os.makedirs(destpath)
|
|
lines = []
|
|
else:
|
|
f = open(dest, 'rb')
|
|
lines = f.readlines()
|
|
f.close()
|
|
|
|
mre = re.compile(regexp)
|
|
if not mre.search(line):
|
|
module.fail_json(msg="usage error: line= doesn't match regexp (%s)" % regexp)
|
|
|
|
if insertafter in ('BOF', 'EOF'):
|
|
iare = None
|
|
else:
|
|
iare = re.compile(insertafter)
|
|
|
|
index = [-1, -1]
|
|
for lineno in range(0, len(lines)):
|
|
if mre.search(lines[lineno]):
|
|
index[0] = lineno
|
|
elif iare is not None and iare.search(lines[lineno]):
|
|
# + 1 for the next line
|
|
index[1] = lineno + 1
|
|
|
|
# Regexp matched a line in the file
|
|
if index[0] != -1:
|
|
if lines[index[0]] == line + os.linesep:
|
|
msg = ''
|
|
changed = False
|
|
else:
|
|
lines[index[0]] = line + os.linesep
|
|
msg = 'line replaced'
|
|
changed = True
|
|
# Add it to the beginning of the file
|
|
elif insertafter == 'BOF':
|
|
lines.insert(0, line + os.linesep)
|
|
msg = 'line added'
|
|
changed = True
|
|
# Add it to the end of the file if requested or if insertafter= didn't match
|
|
elif insertafter == 'EOF' or index[1] == -1:
|
|
lines.append(line + os.linesep)
|
|
msg = 'line added'
|
|
changed = True
|
|
# insertafter= matched
|
|
else:
|
|
lines.insert(index[1], line + os.linesep)
|
|
msg = 'line added'
|
|
changed = True
|
|
|
|
if changed:
|
|
if backup:
|
|
module.backup_local(dest)
|
|
f = open(dest, 'wb')
|
|
f.writelines(lines)
|
|
f.close()
|
|
|
|
module.exit_json(changed=changed, msg=msg)
|
|
|
|
def absent(module, dest, regexp, backup):
|
|
f = open(dest, 'rb')
|
|
lines = f.readlines()
|
|
f.close()
|
|
cre = re.compile(regexp)
|
|
found = []
|
|
def matcher(line):
|
|
if cre.search(line):
|
|
found.append(line)
|
|
return False
|
|
else:
|
|
return True
|
|
lines = filter(matcher, lines)
|
|
changed = len(found) > 0
|
|
if changed:
|
|
if backup:
|
|
module.backup_local(dest)
|
|
f = open(dest, 'wb')
|
|
f.writelines(lines)
|
|
f.close()
|
|
module.exit_json(changed=changed, found=len(found))
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec = dict(
|
|
dest=dict(required=True, aliases=['name', 'destfile']),
|
|
state=dict(default='present', choices=['absent', 'present']),
|
|
regexp=dict(required=True),
|
|
line=dict(aliases=['value']),
|
|
insertafter=dict(default='EOF'),
|
|
create=dict(default=false, choices=BOOLEANS),
|
|
backup=dict(default=False, choices=BOOLEANS),
|
|
),
|
|
)
|
|
|
|
params = module.params
|
|
create = module.boolean(module.params.get('create', False))
|
|
backup = module.boolean(module.params.get('backup', False))
|
|
|
|
if params['state'] == 'present':
|
|
if 'line' not in params:
|
|
module.fail_json(msg='line= is required with state=present')
|
|
present(module, params['dest'], params['regexp'], params['line'],
|
|
params['insertafter'], create, backup)
|
|
else:
|
|
absent(module, params['dest'], params['regexp'], backup)
|
|
|
|
# this is magic, see lib/ansible/module_common.py
|
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
|
|
|
main()
|
|
|