Created
January 13, 2014 23:03
-
-
Save jctanner/8409824 to your computer and use it in GitHub Desktop.
sysctl rewrite
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
# (c) 2012, David "DaviXX" CHANIAL <[email protected]> | |
# | |
# 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/>. | |
# | |
DOCUMENTATION = ''' | |
--- | |
module: sysctl | |
short_description: Manage entries in sysctl.conf. | |
description: | |
- This module manipulates sysctl entries and optionally performs a C(/sbin/sysctl -p) after changing them. | |
version_added: "1.0" | |
options: | |
name: | |
description: | |
- The dot-separated path (aka I(key)) specifying the sysctl variable. | |
required: true | |
default: null | |
aliases: [ 'key' ] | |
value: | |
description: | |
- Desired value of the sysctl key. | |
required: false | |
default: null | |
aliases: [ 'val' ] | |
state: | |
description: | |
- Whether the entry should be present or absent. | |
choices: [ "present", "absent" ] | |
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: | |
description: | |
- If C(yes), performs a I(/sbin/sysctl -p) if the C(sysctl_file) is | |
updated. If C(no), does not reload I(sysctl) even if the | |
C(sysctl_file) is updated. | |
choices: [ "yes", "no" ] | |
default: "yes" | |
sysctl_file: | |
description: | |
- Specifies the absolute path to C(sysctl.conf), if not C(/etc/sysctl.conf). | |
required: false | |
default: /etc/sysctl.conf | |
notes: [] | |
requirements: [] | |
author: David "DaviXX" CHANIAL <[email protected]> | |
''' | |
EXAMPLES = ''' | |
# Set vm.swappiness to 5 in /etc/sysctl.conf | |
- sysctl: name=vm.swappiness value=5 state=present | |
# Remove kernel.panic entry from /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 | |
# seems writable, but do not reload sysctl, and do not check kernel value | |
# after (not needed, because the real /etc/sysctl.conf was not updated) | |
- sysctl: name=kernel.panic value=3 sysctl_file=/tmp/test_sysctl.conf checks=before reload=no | |
''' | |
# ============================================================== | |
import os | |
import tempfile | |
import re | |
class SysctlModule(object): | |
def __init__(self, module): | |
self.module = module | |
self.args = self.module.params | |
self.sysctl_cmd = self.module.get_bin_path('sysctl', required=True) | |
self.sysctl_file = self.args['sysctl_file'] | |
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 | |
self.process() | |
# ============================================================== | |
# LOGIC | |
# ============================================================== | |
def process(self): | |
self.args['name'] = self.args['name'].strip() | |
self.args['value'] = self.args['value'].strip() | |
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 | |
else: | |
#import epdb; epdb.serve() | |
pass | |
# update file contents with desired token/value | |
self.fix_lines() | |
#open("/tmp/awx.log", "a").write("proc_value: %s\n" % self.proc_value) | |
#open("/tmp/awx.log", "a").write("file_value: %s\n" % self.file_value) | |
# what do we need to do now? | |
if self.file_values[thisname] is None and self.args['state'] == "present": | |
open("/tmp/awx.log", "a").write("change: file value not set\n") | |
self.changed = True | |
self.write_file = True | |
#import epdb; epdb.serve() | |
elif self.file_values[thisname] != self.args['value']: | |
open("/tmp/awx.log", "a").write("change: file value wrong -- %s != %s\n" % (self.file_value, self.args['value'])) | |
self.changed = True | |
self.write_file = True | |
if self.proc_value is None: | |
open("/tmp/awx.log", "a").write("change: proc value not set\n") | |
self.changed = True | |
elif self.proc_value != self.args['value']: | |
open("/tmp/awx.log", "a").write("change: proc value wrong\n") | |
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 and self.args['sysctl_set']: | |
self.set_token_value(self.args['name'], self.args['value']) | |
# ============================================================== | |
# SYSCTL COMMAND MANAGEMENT | |
# ============================================================== | |
# Use the sysctl command to find the current value | |
def get_token_curr_value(self, token): | |
#sysctl_cmd = module.get_bin_path('sysctl', required=True) | |
rc,out,err = self.module.run_command([self.sysctl_cmd, ' -e -n ', token]) | |
if rc != 0: | |
return None | |
else: | |
return shlex.split(out)[-1] | |
# Use the sysctl command to set the current value | |
def set_token_value(self, token, value): | |
#sysctl_cmd = module.get_bin_path('sysctl', required=True) | |
rc,out,err = self.module.run_command([self.sysctl_cmd, ' -w ', token, "=", value]) | |
if rc != 0: | |
module.fail_json(msg='setting %s failed: %s' (token, out + err)) | |
else: | |
return rc | |
# ============================================================== | |
# SYSCTL FILE MANAGEMENT | |
# ============================================================== | |
# Get the token value from the sysctl file | |
def read_sysctl_file(self): | |
#import epdb; epdb.serve() | |
lines = open(self.sysctl_file, "r").readlines() | |
for line in lines: | |
line = line.strip() | |
self.file_lines.append(line) | |
#open("/tmp/awx.log", "a").write("line: %s\n" % line) | |
#line = line.strip() | |
# don't split empty lines or comments | |
if not line or line.startswith("#"): | |
continue | |
open("/tmp/awx.log", "a").write("line: %s\n" % line) | |
k, v = line.split('=',1) | |
k = k.strip() | |
v = v.strip() | |
""" | |
try: | |
k, v = line.split('=',1) | |
except ValueError: | |
open("/tmp/awx.log", "a").write("line-error: %s\n" % line) | |
#import epdb; epdb.serve() | |
sys.exit(1) | |
""" | |
self.file_values[k] = v.strip() | |
# Fix the value in the sysctl file content | |
def fix_lines(self): | |
checked = [] | |
self.fixed_lines = [] | |
for line in self.file_lines: | |
if not line.strip() or line.strip().startswith("#"): | |
self.fixed_lines.append(line) | |
continue | |
tmpline = line.strip() | |
k, v = line.split('=',1) | |
k = k.strip() | |
v = v.strip() | |
if k not in checked: | |
checked.append(k) | |
if k == self.args['name']: | |
if self.args['state'] == "present": | |
new_line = "%s = %s\n" % (k, self.args['value']) | |
self.fixed_lines.append(new_line) | |
else: | |
new_line = "%s = %s\n" % (k, v) | |
self.fixed_lines.append(new_line) | |
if self.args['name'] not in checked and self.args['state'] == "present": | |
new_line = "%s = %s\n" % (self.args['name'], self.args['value']) | |
self.fixed_lines.append(new_line) | |
import pprint; pprint.pprint(self.fixed_lines, open("/tmp/awx.log", "a")) | |
#import epdb; epdb.st() | |
# Run sysctl -p | |
def reload_sysctl(self): | |
# do it | |
if get_platform().lower() == 'freebsd': | |
# freebsd doesn't support -p, so reload the sysctl service | |
rc,out,err = self.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 = self.module.run_command([self.sysctl_cmd, '-p', self.sysctl_file]) | |
return rc,out+err | |
# Completely rewrite the sysctl file | |
def write_sysctl(self): | |
# open a tmp file | |
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) | |
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 | |
self.module.atomic_move(tmp_path, self.sysctl_file) | |
# end | |
#return sysctl_args | |
# ============================================================== | |
# main | |
def main(): | |
# defining module | |
module = AnsibleModule( | |
argument_spec = dict( | |
name = dict(aliases=['key'], required=True), | |
value = dict(aliases=['val'], required=False), | |
state = dict(default='present', choices=['present', 'absent']), | |
checks = dict(default='both', choices=['none', 'before', 'after', 'both']), | |
reload = dict(default=True, type='bool'), | |
sysctl_set = dict(default=True, type='bool'), | |
sysctl_file = dict(default='/etc/sysctl.conf') | |
), | |
supports_check_mode=True | |
) | |
result = SysctlModule(module) | |
module.exit_json(changed=result.changed) | |
sys.exit(0) | |
# import module snippets | |
from ansible.module_utils.basic import * | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment