ansible/lineinfile
Dag Wieers d4bb57d42d Fix the documentation booleans so they use "yes" and "no"
Both modules seboolean and zfs have not been adapted since they defer from the default (either by having a 'null' or special state, or prefering "on"/"off" for state indication.
2013-03-12 13:25:59 +01:00

278 lines
9 KiB
Python

#!/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. Uses Python regular expressions; see
U(http://docs.python.org/2/library/re.html).
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. May contain backreferences.
insertafter:
required: false
default: EOF
description:
- Used with C(state=present). If specified, the line will be inserted
after the specified regular expression. A special value is
available; C(EOF) for inserting the line at the end of the file.
choices: [ 'EOF', '*regex*' ]
insertbefore:
required: false
version_added: 1.1
description:
- Used with C(state=present). If specified, the line will be inserted
before the specified regular expression. A value is available;
C(BOF) for inserting the line at the beginning of the
file.
choices: [ 'BOF', '*regex*' ]
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"
choices: [ "yes", "no" ]
description:
- Create a backup file including the timestamp information so you can
get the original file back if you somehow clobbered it incorrectly.
others:
description:
- All arguments accepted by the M(file) module also work here.
required: false
"""
EXAMPLES = """
lineinfile: dest=/etc/selinux/config regexp=^SELINUX= line=SELINUX=disabled
lineinfile: dest=/etc/sudoers state=absent regexp="^%wheel"
lineinfile: dest=/etc/host regexp='^127\.0\.0\.1' line='127.0.0.1 localhost' owner=root group=root mode=0644
lineinfile: dest=/etc/httpd/conf/httpd.conf regexp="^Listen " insertafter="^#Listen " line="Listen 8080"
lineinfile: dest=/etc/services regexp="^# port for http" insertbefore="^www.*80/tcp" line="# port for http by default"
lineinfile: \\\"dest=/etc/sudoers state=present regexp='^%wheel' line ='%wheel ALL=(ALL) NOPASSWD: ALL'\\\"
lineinfile dest=/tmp/grub.conf state=present regexp='^(splashimage=.*)$' line="#\\1"
"""
def check_file_attrs(module, changed, message):
file_args = module.load_file_common_arguments(module.params)
if module.set_file_attributes_if_different(file_args, False):
if changed:
message += " and "
changed = True
message += "ownership, perms or SE linux context changed"
return [ message, changed ]
def present(module, dest, regexp, line, insertafter, insertbefore, 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()
msg = ""
mre = re.compile(regexp)
if insertafter is not None and insertafter not in ('BOF', 'EOF'):
insre = re.compile(insertafter)
elif insertbefore is not None and insertbefore not in ('BOF'):
insre = re.compile(insertbefore)
else:
insre = None
# index[0] is the line num where regexp has been found
# index[1] is the line num where insertafter/inserbefore has been found
index = [-1, -1]
m = None
for lineno in range(0, len(lines)):
match_found = mre.search(lines[lineno])
if match_found:
index[0] = lineno
m = match_found
elif insre is not None and insre.search(lines[lineno]):
if insertafter:
# + 1 for the next line
index[1] = lineno + 1
if insertbefore:
# + 1 for the previous line
index[1] = lineno
# Regexp matched a line in the file
if index[0] != -1:
if lines[index[0]] == m.expand(line) + os.linesep:
msg = ''
changed = False
else:
lines[index[0]] = m.expand(line) + os.linesep
msg = 'line replaced'
changed = True
# Add it to the beginning of the file
elif insertbefore == 'BOF' or 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=/insertbefore didn't match anything
# (so default behaviour is to add at the end)
elif insertafter == 'EOF' or index[1] == -1:
lines.append(line + os.linesep)
msg = 'line added'
changed = True
# insertafter/insertbefore= matched
else:
lines.insert(index[1], line + os.linesep)
msg = 'line added'
changed = True
if changed and not module.check_mode:
if backup and os.path.exists(dest):
module.backup_local(dest)
f = open(dest, 'wb')
f.writelines(lines)
f.close()
[ msg, changed ] = check_file_attrs(module, changed, msg)
module.exit_json(changed=changed, msg=msg)
def absent(module, dest, regexp, backup):
if os.path.isdir(dest):
module.fail_json(rc=256, msg='Destination %s is a directory !' % dest)
elif not os.path.exists(dest):
module.exit_json(changed=False, msg="file not present")
msg = ""
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 and not module.check_mode:
if backup:
module.backup_local(dest)
f = open(dest, 'wb')
f.writelines(lines)
f.close()
if changed:
msg = "%s line(s) removed" % len(found)
[ msg, changed ] = check_file_attrs(module, changed, msg)
module.exit_json(changed=changed, found=len(found), msg=msg)
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=None),
insertbefore=dict(default=None),
create=dict(default=False, type='bool'),
backup=dict(default=False, type='bool'),
),
mutually_exclusive = [['insertbefore', 'insertafter']],
add_file_common_args = True,
supports_check_mode = True
)
params = module.params
create = module.params['create']
backup = module.params['backup']
dest = os.path.expanduser(params['dest'])
if params['state'] == 'present':
if 'line' not in params:
module.fail_json(msg='line= is required with state=present')
present(module, dest, params['regexp'], params['line'],
params['insertafter'], params['insertbefore'], create, backup)
else:
absent(module, dest, params['regexp'], backup)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()