File: //usr/lib/python2.7/site-packages/salt/modules/apache.py
# -*- coding: utf-8 -*-
'''
Support for Apache
.. note::
The functions in here are generic functions designed to work with
all implementations of Apache. Debian-specific functions have been moved into
deb_apache.py, but will still load under the ``apache`` namespace when a
Debian-based system is detected.
'''
# Import python libs
from __future__ import absolute_import, generators, print_function, with_statement, unicode_literals
import re
import logging
# Import 3rd-party libs
# pylint: disable=import-error,no-name-in-module
from salt.ext import six
from salt.ext.six.moves import cStringIO
from salt.ext.six.moves.urllib.error import URLError
from salt.ext.six.moves.urllib.request import (
HTTPBasicAuthHandler as _HTTPBasicAuthHandler,
HTTPDigestAuthHandler as _HTTPDigestAuthHandler,
urlopen as _urlopen,
build_opener as _build_opener,
install_opener as _install_opener
)
# pylint: enable=import-error,no-name-in-module
# Import salt libs
import salt.utils.data
import salt.utils.files
import salt.utils.path
import salt.utils.stringutils
from salt.exceptions import SaltException
log = logging.getLogger(__name__)
def __virtual__():
'''
Only load the module if apache is installed
'''
cmd = _detect_os()
if salt.utils.path.which(cmd):
return 'apache'
return (False, 'The apache execution module cannot be loaded: apache is not installed.')
def _detect_os():
'''
Apache commands and paths differ depending on packaging
'''
# TODO: Add pillar support for the apachectl location
os_family = __grains__['os_family']
if os_family == 'RedHat':
return 'apachectl'
elif os_family == 'Debian' or os_family == 'Suse':
return 'apache2ctl'
else:
return 'apachectl'
def version():
'''
Return server version (``apachectl -v``)
CLI Example:
.. code-block:: bash
salt '*' apache.version
'''
cmd = '{0} -v'.format(_detect_os())
out = __salt__['cmd.run'](cmd).splitlines()
ret = out[0].split(': ')
return ret[1]
def fullversion():
'''
Return server version (``apachectl -V``)
CLI Example:
.. code-block:: bash
salt '*' apache.fullversion
'''
cmd = '{0} -V'.format(_detect_os())
ret = {}
ret['compiled_with'] = []
out = __salt__['cmd.run'](cmd).splitlines()
# Example
# -D APR_HAS_MMAP
define_re = re.compile(r'^\s+-D\s+')
for line in out:
if ': ' in line:
comps = line.split(': ')
if not comps:
continue
ret[comps[0].strip().lower().replace(' ', '_')] = comps[1].strip()
elif ' -D' in line:
cwith = define_re.sub('', line)
ret['compiled_with'].append(cwith)
return ret
def modules():
'''
Return list of static and shared modules (``apachectl -M``)
CLI Example:
.. code-block:: bash
salt '*' apache.modules
'''
cmd = '{0} -M'.format(_detect_os())
ret = {}
ret['static'] = []
ret['shared'] = []
out = __salt__['cmd.run'](cmd).splitlines()
for line in out:
comps = line.split()
if not comps:
continue
if '(static)' in line:
ret['static'].append(comps[0])
if '(shared)' in line:
ret['shared'].append(comps[0])
return ret
def servermods():
'''
Return list of modules compiled into the server (``apachectl -l``)
CLI Example:
.. code-block:: bash
salt '*' apache.servermods
'''
cmd = '{0} -l'.format(_detect_os())
ret = []
out = __salt__['cmd.run'](cmd).splitlines()
for line in out:
if not line:
continue
if '.c' in line:
ret.append(line.strip())
return ret
def directives():
'''
Return list of directives together with expected arguments
and places where the directive is valid (``apachectl -L``)
CLI Example:
.. code-block:: bash
salt '*' apache.directives
'''
cmd = '{0} -L'.format(_detect_os())
ret = {}
out = __salt__['cmd.run'](cmd)
out = out.replace('\n\t', '\t')
for line in out.splitlines():
if not line:
continue
comps = line.split('\t')
desc = '\n'.join(comps[1:])
ret[comps[0]] = desc
return ret
def vhosts():
'''
Show the settings as parsed from the config file (currently
only shows the virtualhost settings) (``apachectl -S``).
Because each additional virtual host adds to the execution
time, this command may require a long timeout be specified
by using ``-t 10``.
CLI Example:
.. code-block:: bash
salt -t 10 '*' apache.vhosts
'''
cmd = '{0} -S'.format(_detect_os())
ret = {}
namevhost = ''
out = __salt__['cmd.run'](cmd)
for line in out.splitlines():
if not line:
continue
comps = line.split()
if 'is a NameVirtualHost' in line:
namevhost = comps[0]
ret[namevhost] = {}
else:
if comps[0] == 'default':
ret[namevhost]['default'] = {}
ret[namevhost]['default']['vhost'] = comps[2]
ret[namevhost]['default']['conf'] = re.sub(
r'\(|\)',
'',
comps[3]
)
if comps[0] == 'port':
ret[namevhost][comps[3]] = {}
ret[namevhost][comps[3]]['vhost'] = comps[3]
ret[namevhost][comps[3]]['conf'] = re.sub(
r'\(|\)',
'',
comps[4]
)
ret[namevhost][comps[3]]['port'] = comps[1]
return ret
def signal(signal=None):
'''
Signals httpd to start, restart, or stop.
CLI Example:
.. code-block:: bash
salt '*' apache.signal restart
'''
no_extra_args = ('configtest', 'status', 'fullstatus')
valid_signals = ('start', 'stop', 'restart', 'graceful', 'graceful-stop')
if signal not in valid_signals and signal not in no_extra_args:
return
# Make sure you use the right arguments
if signal in valid_signals:
arguments = ' -k {0}'.format(signal)
else:
arguments = ' {0}'.format(signal)
cmd = _detect_os() + arguments
out = __salt__['cmd.run_all'](cmd)
# A non-zero return code means fail
if out['retcode'] and out['stderr']:
ret = out['stderr'].strip()
# 'apachectl configtest' returns 'Syntax OK' to stderr
elif out['stderr']:
ret = out['stderr'].strip()
elif out['stdout']:
ret = out['stdout'].strip()
# No output for something like: apachectl graceful
else:
ret = 'Command: "{0}" completed successfully!'.format(cmd)
return ret
def useradd(pwfile, user, password, opts=''):
'''
Add HTTP user using the ``htpasswd`` command. If the ``htpasswd`` file does not
exist, it will be created. Valid options that can be passed are:
.. code-block:: text
n Don't update file; display results on stdout.
m Force MD5 hashing of the password (default).
d Force CRYPT(3) hashing of the password.
p Do not hash the password (plaintext).
s Force SHA1 hashing of the password.
CLI Examples:
.. code-block:: bash
salt '*' apache.useradd /etc/httpd/htpasswd larry badpassword
salt '*' apache.useradd /etc/httpd/htpasswd larry badpass opts=ns
'''
return __salt__['webutil.useradd'](pwfile, user, password, opts)
def userdel(pwfile, user):
'''
Delete HTTP user from the specified ``htpasswd`` file.
CLI Example:
.. code-block:: bash
salt '*' apache.userdel /etc/httpd/htpasswd larry
'''
return __salt__['webutil.userdel'](pwfile, user)
def server_status(profile='default'):
'''
Get Information from the Apache server-status handler
.. note::
The server-status handler is disabled by default.
In order for this function to work it needs to be enabled.
See http://httpd.apache.org/docs/2.2/mod/mod_status.html
The following configuration needs to exists in pillar/grains.
Each entry nested in ``apache.server-status`` is a profile of a vhost/server.
This would give support for multiple apache servers/vhosts.
.. code-block:: yaml
apache.server-status:
default:
url: http://localhost/server-status
user: someuser
pass: password
realm: 'authentication realm for digest passwords'
timeout: 5
CLI Examples:
.. code-block:: bash
salt '*' apache.server_status
salt '*' apache.server_status other-profile
'''
ret = {
'Scoreboard': {
'_': 0,
'S': 0,
'R': 0,
'W': 0,
'K': 0,
'D': 0,
'C': 0,
'L': 0,
'G': 0,
'I': 0,
'.': 0,
},
}
# Get configuration from pillar
url = __salt__['config.get'](
'apache.server-status:{0}:url'.format(profile),
'http://localhost/server-status'
)
user = __salt__['config.get'](
'apache.server-status:{0}:user'.format(profile),
''
)
passwd = __salt__['config.get'](
'apache.server-status:{0}:pass'.format(profile),
''
)
realm = __salt__['config.get'](
'apache.server-status:{0}:realm'.format(profile),
''
)
timeout = __salt__['config.get'](
'apache.server-status:{0}:timeout'.format(profile),
5
)
# create authentication handler if configuration exists
if user and passwd:
basic = _HTTPBasicAuthHandler()
basic.add_password(realm=realm, uri=url, user=user, passwd=passwd)
digest = _HTTPDigestAuthHandler()
digest.add_password(realm=realm, uri=url, user=user, passwd=passwd)
_install_opener(_build_opener(basic, digest))
# get http data
url += '?auto'
try:
response = _urlopen(url, timeout=timeout).read().splitlines()
except URLError:
return 'error'
# parse the data
for line in response:
splt = line.split(':', 1)
splt[0] = splt[0].strip()
splt[1] = splt[1].strip()
if splt[0] == 'Scoreboard':
for c in splt[1]:
ret['Scoreboard'][c] += 1
else:
if splt[1].isdigit():
ret[splt[0]] = int(splt[1])
else:
ret[splt[0]] = float(splt[1])
# return the good stuff
return ret
def _parse_config(conf, slot=None):
'''
Recursively goes through config structure and builds final Apache configuration
:param conf: defined config structure
:param slot: name of section container if needed
'''
ret = cStringIO()
if isinstance(conf, six.string_types):
if slot:
print('{0} {1}'.format(slot, conf), file=ret, end='')
else:
print('{0}'.format(conf), file=ret, end='')
elif isinstance(conf, list):
is_section = False
for item in conf:
if 'this' in item:
is_section = True
slot_this = six.text_type(item['this'])
if is_section:
print('<{0} {1}>'.format(slot, slot_this), file=ret)
for item in conf:
for key, val in item.items():
if key != 'this':
print(_parse_config(val, six.text_type(key)), file=ret)
print('</{0}>'.format(slot), file=ret)
else:
for value in conf:
print(_parse_config(value, six.text_type(slot)), file=ret)
elif isinstance(conf, dict):
try:
print('<{0} {1}>'.format(slot, conf['this']), file=ret)
except KeyError:
raise SaltException('Apache section container "<{0}>" expects attribute. '
'Specify it using key "this".'.format(slot))
for key, value in six.iteritems(conf):
if key != 'this':
if isinstance(value, six.string_types):
print('{0} {1}'.format(key, value), file=ret)
elif isinstance(value, list):
print(_parse_config(value, key), file=ret)
elif isinstance(value, dict):
print(_parse_config(value, key), file=ret)
print('</{0}>'.format(slot), file=ret)
ret.seek(0)
return ret.read()
def config(name, config, edit=True):
'''
Create VirtualHost configuration files
name
File for the virtual host
config
VirtualHost configurations
.. note::
This function is not meant to be used from the command line.
Config is meant to be an ordered dict of all of the apache configs.
CLI Example:
.. code-block:: bash
salt '*' apache.config /etc/httpd/conf.d/ports.conf config="[{'Listen': '22'}]"
'''
configs = []
for entry in config:
key = next(six.iterkeys(entry))
configs.append(_parse_config(entry[key], key))
# Python auto-correct line endings
configstext = '\n'.join(salt.utils.data.decode(configs))
if edit:
with salt.utils.files.fopen(name, 'w') as configfile:
configfile.write('# This file is managed by Salt.\n')
configfile.write(salt.utils.stringutils.to_str(configstext))
return configstext