#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2012, David "DaviXX" CHANIAL # (c) 2014, James Tanner # # 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 . # 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 in the sysctl file. choices: [ "present", "absent" ] default: present 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 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: [] requirements: [] author: David "DaviXX" CHANIAL ''' 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 - sysctl: name=kernel.panic value=3 sysctl_file=/tmp/test_sysctl.conf 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 ''' # ============================================================== 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): # 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 # ============================================================== # 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': # 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 rc,out,err = self.module.run_command([self.sysctl_cmd, '-p', self.sysctl_file]) if rc != 0: self.module.fail_json(msg="Failed to reload sysctl: %s" % str(out) + str(err)) # ============================================================== # SYSCTL FILE MANAGEMENT # ============================================================== # Get the token value from the sysctl file 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 k, v = line.split('=',1) k = k.strip() v = v.strip() 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) # 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.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() # replace the real one self.module.atomic_move(tmp_path, self.sysctl_file) # ============================================================== # 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']), reload = dict(default=True, type='bool'), sysctl_set = dict(default=False, 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()