File: //usr/lib/python2.7/site-packages/salt/modules/linux_sysctl.py
# -*- coding: utf-8 -*-
'''
Module for viewing and modifying sysctl parameters
'''
from __future__ import absolute_import, print_function, unicode_literals
# Import python libs
import logging
import os
import re
import string
# Import salt libs
from salt.ext import six
from salt.ext.six import string_types
from salt.exceptions import CommandExecutionError
import salt.utils.data
import salt.utils.files
import salt.utils.systemd
import salt.utils.stringutils
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = 'sysctl'
# TODO: Add unpersist() to remove either a sysctl or sysctl/value combo from
# the config
def __virtual__():
'''
Only run on Linux systems
'''
if __grains__['kernel'] != 'Linux':
return (False, 'The linux_sysctl execution module cannot be loaded: only available on Linux systems.')
return __virtualname__
def default_config():
'''
Linux hosts using systemd 207 or later ignore ``/etc/sysctl.conf`` and only
load from ``/etc/sysctl.d/*.conf``. This function will do the proper checks
and return a default config file which will be valid for the Minion. Hosts
running systemd >= 207 will use ``/etc/sysctl.d/99-salt.conf``.
CLI Example:
.. code-block:: bash
salt -G 'kernel:Linux' sysctl.default_config
'''
if salt.utils.systemd.booted(__context__) \
and salt.utils.systemd.version(__context__) >= 207:
return '/etc/sysctl.d/99-salt.conf'
return '/etc/sysctl.conf'
def show(config_file=False):
'''
Return a list of sysctl parameters for this minion
config: Pull the data from the system configuration file
instead of the live data.
CLI Example:
.. code-block:: bash
salt '*' sysctl.show
'''
ret = {}
if config_file:
# If the file doesn't exist, return an empty list
if not os.path.exists(config_file):
return []
try:
with salt.utils.files.fopen(config_file) as fp_:
for line in fp_:
line = salt.utils.stringutils.to_str(line)
if not line.startswith('#') and '=' in line:
# search if we have some '=' instead of ' = ' separators
SPLIT = ' = '
if SPLIT not in line:
SPLIT = SPLIT.strip()
key, value = line.split(SPLIT, 1)
key = key.strip()
value = value.lstrip()
ret[key] = value
except (OSError, IOError):
log.error('Could not open sysctl file')
return None
else:
cmd = 'sysctl -a'
out = __salt__['cmd.run_stdout'](cmd, output_loglevel='trace')
for line in out.splitlines():
if not line or ' = ' not in line:
continue
comps = line.split(' = ', 1)
ret[comps[0]] = comps[1]
return ret
def get(name):
'''
Return a single sysctl parameter for this minion
CLI Example:
.. code-block:: bash
salt '*' sysctl.get net.ipv4.ip_forward
'''
cmd = 'sysctl -n {0}'.format(name)
out = __salt__['cmd.run'](cmd, python_shell=False)
return out
def assign(name, value):
'''
Assign a single sysctl parameter for this minion
CLI Example:
.. code-block:: bash
salt '*' sysctl.assign net.ipv4.ip_forward 1
'''
value = six.text_type(value)
if six.PY3:
tran_tab = name.translate(''.maketrans('./', '/.'))
else:
if isinstance(name, unicode): # pylint: disable=incompatible-py3-code,undefined-variable
trans_args = {ord('/'): u'.', ord('.'): u'/'}
else:
trans_args = string.maketrans('./', '/.')
tran_tab = name.translate(trans_args)
sysctl_file = '/proc/sys/{0}'.format(tran_tab)
if not os.path.exists(sysctl_file):
raise CommandExecutionError('sysctl {0} does not exist'.format(name))
ret = {}
cmd = 'sysctl -w {0}="{1}"'.format(name, value)
data = __salt__['cmd.run_all'](cmd, python_shell=False)
out = data['stdout']
err = data['stderr']
# Example:
# # sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
# net.ipv4.tcp_rmem = 4096 87380 16777216
regex = re.compile(r'^{0}\s+=\s+{1}$'.format(re.escape(name), re.escape(value)))
if not regex.match(out) or 'Invalid argument' in six.text_type(err):
if data['retcode'] != 0 and err:
error = err
else:
error = out
raise CommandExecutionError('sysctl -w failed: {0}'.format(error))
new_name, new_value = out.split(' = ', 1)
ret[new_name] = new_value
return ret
def persist(name, value, config=None):
'''
Assign and persist a simple sysctl parameter for this minion. If ``config``
is not specified, a sensible default will be chosen using
:mod:`sysctl.default_config <salt.modules.linux_sysctl.default_config>`.
CLI Example:
.. code-block:: bash
salt '*' sysctl.persist net.ipv4.ip_forward 1
'''
if config is None:
config = default_config()
edited = False
# If the sysctl.conf is not present, add it
if not os.path.isfile(config):
sysctl_dir = os.path.dirname(config)
if not os.path.exists(sysctl_dir):
os.makedirs(sysctl_dir)
try:
with salt.utils.files.fopen(config, 'w+') as _fh:
_fh.write('#\n# Kernel sysctl configuration\n#\n')
except (IOError, OSError):
msg = 'Could not write to file: {0}'
raise CommandExecutionError(msg.format(config))
# Read the existing sysctl.conf
nlines = []
try:
with salt.utils.files.fopen(config, 'r') as _fh:
# Use readlines because this should be a small file
# and it seems unnecessary to indent the below for
# loop since it is a fairly large block of code.
config_data = salt.utils.data.decode(_fh.readlines())
except (IOError, OSError):
msg = 'Could not read from file: {0}'
raise CommandExecutionError(msg.format(config))
for line in config_data:
if line.startswith('#'):
nlines.append(line)
continue
if '=' not in line:
nlines.append(line)
continue
# Strip trailing whitespace and split the k,v
comps = [i.strip() for i in line.split('=', 1)]
# On Linux procfs, files such as /proc/sys/net/ipv4/tcp_rmem or any
# other sysctl with whitespace in it consistently uses 1 tab. Lets
# allow our users to put a space or tab between multi-value sysctls
# and have salt not try to set it every single time.
if isinstance(comps[1], string_types) and ' ' in comps[1]:
comps[1] = re.sub(r'\s+', '\t', comps[1])
# Do the same thing for the value 'just in case'
if isinstance(value, string_types) and ' ' in value:
value = re.sub(r'\s+', '\t', value)
if len(comps) < 2:
nlines.append(line)
continue
if name == comps[0]:
# This is the line to edit
if six.text_type(comps[1]) == six.text_type(value):
# It is correct in the config, check if it is correct in /proc
if six.text_type(get(name)) != six.text_type(value):
assign(name, value)
return 'Updated'
else:
return 'Already set'
nlines.append('{0} = {1}\n'.format(name, value))
edited = True
continue
else:
nlines.append(line)
if not edited:
nlines.append('{0} = {1}\n'.format(name, value))
try:
with salt.utils.files.fopen(config, 'wb') as _fh:
_fh.writelines(salt.utils.data.encode(nlines))
except (IOError, OSError):
msg = 'Could not write to file: {0}'
raise CommandExecutionError(msg.format(config))
assign(name, value)
return 'Updated'