@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# (c) 2012, David "DaviXX" CHANIAL <david.chanial@gmail.com>
# (c) 2012, David "DaviXX" CHANIAL <david.chanial@gmail.com>
# (c) 2014, James Tanner <tanner.jc@gmail.com>
#
#
# This file is part of Ansible
# This file is part of Ansible
#
#
@ -41,19 +42,9 @@ options:
aliases: [ 'val' ]
aliases: [ 'val' ]
state:
state:
description:
description:
- Whether the entry should be present or absent.
- Whether the entry should be present or absent in the sysctl file .
choices: [ "present", "absent" ]
choices: [ "present", "absent" ]
default: present
default: present
checks:
description:
- If C(none), no smart/facultative checks will be made. If
C(before), some checks are performed before any update (i.e. is
the sysctl key writable?). If C(after), some checks are performed
after an update (i.e. does kernel return the set value?). If
C(both), all of the smart checks (C(before) and C(after)) are
performed.
choices: [ "none", "before", "after", "both" ]
default: both
reload:
reload:
description:
description:
- If C(yes), performs a I(/sbin/sysctl -p) if the C(sysctl_file) is
- If C(yes), performs a I(/sbin/sysctl -p) if the C(sysctl_file) is
@ -66,6 +57,13 @@ options:
- Specifies the absolute path to C(sysctl.conf), if not C(/etc/sysctl.conf).
- Specifies the absolute path to C(sysctl.conf), if not C(/etc/sysctl.conf).
required: false
required: false
default: /etc/sysctl.conf
default: /etc/sysctl.conf
sysctl_set:
description:
- Verify token value with the sysctl command and set with -w if necessary
choices: [ "yes", "no" ]
required: false
version_added: 1.5
default: False
notes: []
notes: []
requirements: []
requirements: []
author: David "DaviXX" CHANIAL <david.chanial@gmail.com>
author: David "DaviXX" CHANIAL <david.chanial@gmail.com>
@ -78,10 +76,14 @@ EXAMPLES = '''
# Remove kernel.panic entry from /etc/sysctl.conf
# Remove kernel.panic entry from /etc/sysctl.conf
- sysctl: name=kernel.panic state=absent sysctl_file=/etc/sysctl.conf
- sysctl: name=kernel.panic state=absent sysctl_file=/etc/sysctl.conf
# Set kernel.panic to 3 in /tmp/test_sysctl.conf, check if the sysctl key
# Set kernel.panic to 3 in /tmp/test_sysctl.conf
# seems writable, but do not reload sysctl, and do not check kernel value
- sysctl: name=kernel.panic value=3 sysctl_file=/tmp/test_sysctl.conf reload=no
# after (not needed, because the real /etc/sysctl.conf was not updated)
- sysctl: name=kernel.panic value=3 sysctl_file=/tmp/test_sysctl.conf check=before reload=no
# Set ip fowarding on in /proc and do not reload the sysctl file
- sysctl: name="net.ipv4.ip_forward" value=1 sysctl_set=yes
# Set ip forwarding on in /proc and in the sysctl file and reload if necessary
- sysctl: name="net.ipv4.ip_forward" value=1 sysctl_set=yes state=present reload=yes
'''
'''
# ==============================================================
# ==============================================================
@ -90,137 +92,174 @@ import os
import tempfile
import tempfile
import re
import re
# ==============================================================
class SysctlModule(object):
def reload_sysctl(module, **sysctl_args):
def __init__(self, module):
# update needed ?
self.module = module
if not sysctl_args['reload']:
self.args = self.module.params
return 0, ''
# do it
self.sysctl_cmd = self.module.get_bin_path('sysctl', required=True)
if get_platform().lower() == 'freebsd':
self.sysctl_file = self.args['sysctl_file']
# freebsd doesn't support -p, so reload the sysctl service
rc,out,err = module.run_command('/etc/rc.d/sysctl reload')
else:
# system supports reloading via the -p flag to sysctl, so we'll use that
sysctl_cmd = module.get_bin_path('sysctl', required=True)
rc,out,err = module.run_command([sysctl_cmd, '-p', sysctl_args['sysctl_file']])
return rc,out+err
self.proc_value = None # current token value in proc fs
self.file_value = None # current token value in file
self.file_lines = [] # all lines in the file
self.file_values = {} # dict of token values
# ==============================================================
self.changed = False # will change occur
self.set_proc = False # does sysctl need to set value
self.write_file = False # does the sysctl file need to be reloaded
def write_sysctl(module, lines, **sysctl_args):
self.process()
# open a tmp file
fd, tmp_path = tempfile.mkstemp('.conf', '.ansible_m_sysctl_', os.path.dirname(sysctl_args['sysctl_file']))
f = open(tmp_path,"w")
try:
for l in lines:
f.write(l)
except IOError, e:
module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, str(e)))
f.flush()
f.close()
# replace the real one
# ==============================================================
module.atomic_move(tmp_path, sysctl_args['sysctl_file'])
# LOGIC
# ==============================================================
# end
def process(self):
return sysctl_args
# Whitespace is bad
self.args['name'] = self.args['name'].strip()
if self.args['value'] is not None:
self.args['value'] = self.args['value'].strip()
else:
self.args['value'] = ""
thisname = self.args['name']
# get the current proc fs value
self.proc_value = self.get_token_curr_value(thisname)
# get the currect sysctl file value
self.read_sysctl_file()
if thisname not in self.file_values:
self.file_values[thisname] = None
# update file contents with desired token/value
self.fix_lines()
# what do we need to do now?
if self.file_values[thisname] is None and self.args['state'] == "present":
self.changed = True
self.write_file = True
elif self.file_values[thisname] != self.args['value']:
self.changed = True
self.write_file = True
if self.args['sysctl_set']:
if self.proc_value is None:
self.changed = True
elif self.proc_value != self.args['value']:
self.changed = True
self.set_proc = True
# Do the work
if not self.module.check_mode:
if self.write_file:
self.write_sysctl()
if self.write_file and self.args['reload']:
self.reload_sysctl()
if self.set_proc:
self.set_token_value(self.args['name'], self.args['value'])
# ==============================================================
# ==============================================================
# SYSCTL COMMAND MANAGEMENT
# ==============================================================
def sysctl_args_expand(**sysctl_args):
# Use the sysctl command to find the current value
def get_token_curr_value(self, token):
thiscmd = "%s -e -n %s" % (self.sysctl_cmd, token)
rc,out,err = self.module.run_command(thiscmd)
if rc != 0:
return None
else:
return out
# Use the sysctl command to set the current value
def set_token_value(self, token, value):
if len(value.split()) > 0:
value = '"' + value + '"'
thiscmd = "%s -w %s=%s" % (self.sysctl_cmd, token, value)
rc,out,err = self.module.run_command(thiscmd)
if rc != 0:
self.module.fail_json(msg='setting %s failed: %s' % (token, out + err))
else:
return rc
# Run sysctl -p
def reload_sysctl(self):
# do it
if get_platform().lower() == 'freebsd':
if get_platform().lower() == 'freebsd':
# FreeBSD does not use the /proc file system, and instead
# freebsd doesn't support -p, so reload the sysctl service
# just uses the sysctl command to set the values
rc,out,err = self.module.run_command('/etc/rc.d/sysctl reload')
sysctl_args['key_path'] = None
else:
else:
sysctl_args['key_path'] = sysctl_args['name'].replace('.' ,'/')
# system supports reloading via the -p flag to sysctl, so we'll use that
sysctl_args['key_path'] = '/proc/sys/' + sysctl_args['key_path']
rc,out,err = self.module.run_command([self.sysctl_cmd, '-p', self.sysctl_file])
return sysctl_args
# ==============================================================
if rc != 0:
self.module.fail_json(msg="Failed to reload sysctl: %s" % str(out) + str(err))
def sysctl_args_collapse(**sysctl_args):
# ==============================================================
# go ahead
# SYSCTL FILE MANAGEMENT
if sysctl_args.get('key_path') is not None:
# ==============================================================
del sysctl_args['key_path']
if sysctl_args['state'] == 'absent' and 'value' in sysctl_args:
del sysctl_args['value']
# end
# Get the token value from the sysctl file
return sysctl_args
def read_sysctl_file(self):
lines = open(self.sysctl_file, "r").readlines()
for line in lines:
line = line.strip()
self.file_lines.append(line)
# ==============================================================
# don't split empty lines or comments
if not line or line.startswith("#"):
continue
def sysctl_check(module, current_step, **sysctl_args):
k, v = line.split('=',1)
k = k.strip()
# no smart checks at this step ?
v = v.strip()
if sysctl_args['checks'] == 'none':
self.file_values[k] = v.strip()
return 0, ''
if current_step == 'before' and sysctl_args['checks'] not in ['before', 'both']:
# Fix the value in the sysctl file content
return 0, ''
def fix_lines(self):
if current_step == 'after' and sysctl_args['checks'] not in ['after', 'both']:
checked = []
return 0, ''
self.fixed_lines = []
for line in self.file_lines:
# checking coherence
if not line.strip() or line.strip().startswith("#"):
if sysctl_args['state'] == 'absent' and sysctl_args['value'] is not None:
self.fixed_lines.append(line)
return 1, 'value=x must not be supplied when state=absent'
continue
tmpline = line.strip()
if sysctl_args['state'] == 'present' and sysctl_args['value'] is None:
k, v = line.split('=',1)
return 1, 'value=x must be supplied when state=present'
k = k.strip()
v = v.strip()
if not sysctl_args['reload'] and sysctl_args['checks'] in ['after', 'both']:
if k not in checked:
return 1, 'checks cannot be set to after or both if reload=no'
checked.append(k)
if k == self.args['name']:
if sysctl_args['key_path'] is not None:
if self.args['state'] == "present":
# getting file stat
new_line = "%s = %s\n" % (k, self.args['value'])
if not os.access(sysctl_args['key_path'], os.F_OK):
self.fixed_lines.append(new_line)
return 1, 'key_path is not an existing file, key %s seems invalid' % sysctl_args['key_path']
if not os.access(sysctl_args['key_path'], os.R_OK):
return 1, 'key_path is not a readable file, key seems to be uncheckable'
# checks before
if current_step == 'before' and sysctl_args['checks'] in ['before', 'both']:
if sysctl_args['key_path'] is not None and not os.access(sysctl_args['key_path'], os.W_OK):
return 1, 'key_path is not a writable file, key seems to be read only'
return 0, ''
# checks after
if current_step == 'after' and sysctl_args['checks'] in ['after', 'both']:
if sysctl_args['value'] is not None:
if sysctl_args['key_path'] is not None:
# reading the virtual file
f = open(sysctl_args['key_path'],'r')
output = f.read()
f.close()
else:
else:
# we're on a system without /proc (ie. freebsd), so just
new_line = "%s = %s\n" % (k, v)
# use the sysctl command to get the currently set value
self.fixed_lines.append(new_line)
sysctl_cmd = module.get_bin_path('sysctl', required=True)
rc,output,stderr = module.run_command("%s -n %s" % (sysctl_cmd, sysctl_args['name']))
if rc != 0:
return 1, 'failed to lookup the value via the sysctl command'
output = output.strip(' \t\n\r')
if self.args['name'] not in checked and self.args['state'] == "present":
output = re.sub(r'\s+', ' ', output)
new_line = "%s = %s\n" % (self.args['name'], self.args['value'])
self.fixed_lines.append(new_line)
# normal case, found value must be equal to the submitted value, and
# Completely rewrite the sysctl file
# we compare the exploded values to handle any whitepsace differences
def write_sysctl(self):
if output.split() != sysctl_args['value'].split():
# open a tmp file
return 1, 'key seems not set to value even after update/sysctl, founded : <%s>, wanted : <%s>' % (output, sysctl_args['value'])
fd, tmp_path = tempfile.mkstemp('.conf', '.ansible_m_sysctl_', os.path.dirname(self.sysctl_file))
f = open(tmp_path,"w")
try:
for l in self.fixed_lines:
f.write(l.strip() + "\n")
except IOError, e:
self.module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, str(e)))
f.flush()
f.close()
return 0, ''
# replace the real one
else:
self.module.atomic_move(tmp_path, self.sysctl_file)
# no value was supplied, so we're checking to make sure
# the associated name is absent. We just fudge this since
# the sysctl isn't really gone, just removed from the conf
# file meaning it will be whatever the system default is
return 0, ''
# weird end
return 1, 'unexpected position reached'
# ==============================================================
# ==============================================================
# main
# main
@ -233,110 +272,16 @@ def main():
name = dict(aliases=['key'], required=True),
name = dict(aliases=['key'], required=True),
value = dict(aliases=['val'], required=False),
value = dict(aliases=['val'], required=False),
state = dict(default='present', choices=['present', 'absent']),
state = dict(default='present', choices=['present', 'absent']),
checks = dict(default='both', choices=['none', 'before', 'after', 'both']),
reload = dict(default=True, type='bool'),
reload = dict(default=True, type='bool'),
sysctl_set = dict(default=False, type='bool'),
sysctl_file = dict(default='/etc/sysctl.conf')
sysctl_file = dict(default='/etc/sysctl.conf')
)
),
supports_check_mode=True
)
)
# defaults
result = SysctlModule(module)
sysctl_args = {
'changed': False,
'name': module.params['name'],
'state': module.params['state'],
'checks': module.params['checks'],
'reload': module.params['reload'],
'value': module.params.get('value'),
'sysctl_file': module.params['sysctl_file']
}
# prepare vars
sysctl_args = sysctl_args_expand(**sysctl_args)
if get_platform().lower() == 'freebsd':
# freebsd does not like spaces around the equal sign
pattern = "%s=%s\n"
else:
pattern = "%s = %s\n"
new_line = pattern % (sysctl_args['name'], sysctl_args['value'])
to_write = []
founded = False
# make checks before act
res,msg = sysctl_check(module, 'before', **sysctl_args)
if res != 0:
module.fail_json(msg='checks_before failed with: ' + msg)
if not os.access(sysctl_args['sysctl_file'], os.W_OK):
try:
f = open(sysctl_args['sysctl_file'],'w')
f.close()
except IOError, e:
module.fail_json(msg='unable to create supplied sysctl file (destination directory probably missing)')
# reading the file
for line in open(sysctl_args['sysctl_file'], 'r').readlines():
if not line.strip():
to_write.append(line)
continue
if line.strip().startswith('#'):
to_write.append(line)
continue
# write line if not the one searched
ld = {}
ld['name'], ld['val'] = line.split('=',1)
ld['name'] = ld['name'].strip()
if ld['name'] != sysctl_args['name']:
to_write.append(line)
continue
# should be absent ?
if sysctl_args['state'] == 'absent':
# not writing the founded line
# mark as changed
sysctl_args['changed'] = True
# should be present
if sysctl_args['state'] == 'present':
# is the founded line equal to the wanted one ?
ld['val'] = ld['val'].strip()
if ld['val'] == sysctl_args['value']:
# line is equal, writing it without update (but cancel repeats)
if sysctl_args['changed'] == False and founded == False:
to_write.append(line)
founded = True
else:
# update the line (but cancel repeats)
if sysctl_args['changed'] == False and founded == False:
to_write.append(new_line)
sysctl_args['changed'] = True
continue
# if not changed, but should be present, so we have to add it
module.exit_json(changed=result.changed)
if sysctl_args['state'] == 'present' and sysctl_args['changed'] == False and founded == False:
to_write.append(new_line)
sysctl_args['changed'] = True
# has changed ?
res = 0
if sysctl_args['changed'] == True:
sysctl_args = write_sysctl(module, to_write, **sysctl_args)
res,msg = reload_sysctl(module, **sysctl_args)
# make checks after act
res,msg = sysctl_check(module, 'after', **sysctl_args)
if res != 0:
module.fail_json(msg='checks_after failed with: ' + msg)
# look at the next link to avoid this workaround
# https://groups.google.com/forum/?fromgroups=#!topic/ansible-project/LMY-dwF6SQk
changed = sysctl_args['changed']
del sysctl_args['changed']
# end
sysctl_args = sysctl_args_collapse(**sysctl_args)
module.exit_json(changed=changed, **sysctl_args)
sys.exit(0)
sys.exit(0)
# import module snippets
# import module snippets