7933c3faed
The service module was printing stuff to stderr, returning two JSON dicts, not using consistent 'failed' values, had dead code and unused variables. Added detection for the case when service status returns 'xxx is dead and pid file exists' and made the code a bit easier to read.
270 lines
8.6 KiB
Python
Executable file
270 lines
8.6 KiB
Python
Executable file
#!/usr/bin/python
|
|
|
|
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
|
#
|
|
# 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/>.
|
|
|
|
try:
|
|
import json
|
|
except ImportError:
|
|
import simplejson as json
|
|
import sys
|
|
import shlex
|
|
import subprocess
|
|
import os.path
|
|
import syslog
|
|
|
|
# TODO: switch to fail_json and other helper functions
|
|
# like other modules are using
|
|
|
|
# ===========================================
|
|
|
|
SERVICE = None
|
|
CHKCONFIG = None
|
|
|
|
def fail_json(d):
|
|
print json.dumps(d)
|
|
sys.exit(1)
|
|
|
|
def _find_binaries():
|
|
# list of possible paths for service/chkconfig binaries
|
|
# with the most probable first
|
|
global CHKCONFIG
|
|
global SERVICE
|
|
global INITCTL
|
|
paths = ['/sbin', '/usr/sbin', '/bin', '/usr/bin']
|
|
binaries = [ 'service', 'chkconfig', 'update-rc.d', 'initctl']
|
|
location = dict()
|
|
|
|
for binary in binaries:
|
|
location[binary] = None
|
|
|
|
for binary in binaries:
|
|
for path in paths:
|
|
if os.path.exists(path + '/' + binary):
|
|
location[binary] = path + '/' + binary
|
|
break
|
|
|
|
if location.get('chkconfig', None):
|
|
CHKCONFIG = location['chkconfig']
|
|
elif location.get('update-rc.d', None):
|
|
CHKCONFIG = location['update-rc.d']
|
|
else:
|
|
fail_json(dict(failed=True, msg='unable to find chkconfig or update-rc.d binary'))
|
|
if location.get('service', None):
|
|
SERVICE = location['service']
|
|
else:
|
|
fail_json(dict(failed=True, msg='unable to find service binary'))
|
|
if location.get('initctl', None):
|
|
INITCTL = location['initctl']
|
|
else:
|
|
INITCTL = None
|
|
|
|
def _get_service_status(name):
|
|
rc, status_stdout, status_stderr = _run("%s %s status" % (SERVICE, name))
|
|
|
|
# set the running state to None because we don't know it yet
|
|
running = None
|
|
|
|
# Check if we got upstart on the system and then the job state
|
|
if INITCTL != None:
|
|
# check the job status by upstart response
|
|
initctl_rc, initctl_status_stdout, initctl_status_stderr = _run("%s status %s" % (INITCTL, name))
|
|
if initctl_status_stdout.find("stop/waiting") != -1:
|
|
running = False
|
|
elif initctl_status_stdout.find("start/running") != -1:
|
|
running = True
|
|
|
|
# if the job status is still not known check it by response code
|
|
if running == None:
|
|
if rc == 3:
|
|
running = False
|
|
elif rc == 0:
|
|
running = True
|
|
|
|
# if the job status is still not known check it by status output keywords
|
|
if running == None:
|
|
# first tranform the status output that could irritate keyword matching
|
|
cleanout = status_stdout.lower().replace(name.lower(), '')
|
|
if "stop" in cleanout:
|
|
running = False
|
|
elif "run" in cleanout and "not" in cleanout:
|
|
running = False
|
|
elif "run" in cleanout and "not" not in cleanout:
|
|
running = True
|
|
elif "start" in cleanout and "not" not in cleanout:
|
|
running = True
|
|
elif 'could not access pid file' in cleanout:
|
|
running = False
|
|
elif 'is dead and pid file exists' in cleanout:
|
|
running = False
|
|
|
|
# if the job status is still not known check it by special conditions
|
|
if running == None:
|
|
if name == 'iptables' and status_stdout.find("ACCEPT") != -1:
|
|
# iptables status command output is lame
|
|
# TODO: lookup if we can use a return code for this instead?
|
|
running = True
|
|
|
|
return running
|
|
|
|
def _run(cmd):
|
|
# returns (rc, stdout, stderr) from shell command
|
|
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
stdout, stderr = process.communicate()
|
|
return (process.returncode, stdout, stderr)
|
|
|
|
|
|
def _do_enable(name, enable):
|
|
# we change argument depending on real binary used
|
|
# update-rc.d wants enable/disable while
|
|
# chkconfig wants on/off
|
|
valid_argument = dict({'on' : 'on', 'off' : 'off'})
|
|
|
|
if CHKCONFIG.endswith("update-rc.d"):
|
|
valid_argument['on'] = "enable"
|
|
valid_argument['off'] = "disable"
|
|
|
|
if enable.lower() in ['on', 'true', 'yes', 'enable']:
|
|
rc, stdout, stderr = _run("%s %s %s" % (CHKCONFIG, name, valid_argument['on']))
|
|
elif enable.lower() in ['off', 'false', 'no', 'disable']:
|
|
rc, stdout, stderr = _run("%s %s %s" % (CHKCONFIG, name, valid_argument['off']))
|
|
|
|
return rc, stdout, stderr
|
|
|
|
argfile = sys.argv[1]
|
|
args = open(argfile, 'r').read()
|
|
items = shlex.split(args)
|
|
syslog.openlog('ansible-%s' % os.path.basename(__file__))
|
|
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args)
|
|
|
|
if not len(items):
|
|
fail_json(dict(failed=True, msg='this module requires arguments (-a)'))
|
|
|
|
params = {}
|
|
for arg in items:
|
|
if "=" not in arg:
|
|
fail_json(dict(failed=True, msg='expected key=value format arguments'))
|
|
|
|
(name, value) = arg.split("=")
|
|
params[name] = value
|
|
|
|
name = params.get('name', None)
|
|
|
|
if name is None:
|
|
fail_json(dict(failed=True, msg='missing name'))
|
|
|
|
state = params.get('state', None)
|
|
list_items = params.get('list', None)
|
|
enable = params.get('enabled', params.get('enable', None))
|
|
|
|
# running and started are the same
|
|
if state and state.lower() not in [ 'running', 'started', 'stopped', 'restarted','reloaded' ]:
|
|
fail_json(dict(failed=True, msg='invalid value for state'))
|
|
if list_items and list_items.lower() not in [ 'status' ]:
|
|
fail_json(dict(failed=True, msg='invalid value for list'))
|
|
if enable and enable.lower() not in [ 'on', 'off', 'true', 'false', 'yes', 'no', 'enable', 'disable' ]:
|
|
fail_json(dict(failed=True, msg='invalid value for enable'))
|
|
|
|
|
|
# ===========================================
|
|
# find binaries locations on minion
|
|
_find_binaries()
|
|
|
|
|
|
# ===========================================
|
|
# get service status
|
|
running = _get_service_status(name)
|
|
|
|
|
|
if state or enable:
|
|
rc = 0
|
|
out = ''
|
|
err = ''
|
|
changed = False
|
|
|
|
if enable:
|
|
rc_enable, out_enable, err_enable = _do_enable(name, enable)
|
|
rc += rc_enable
|
|
out += out_enable
|
|
err += err_enable
|
|
|
|
if state and running == None:
|
|
print json.dumps({
|
|
"failed" : True,
|
|
"msg" : "failed determining the current service state => state stays unchanged",
|
|
"changed": False
|
|
})
|
|
sys.exit(1)
|
|
elif state:
|
|
# a state change command has been requested
|
|
|
|
# ===========================================
|
|
# determine if we are going to change anything
|
|
|
|
if not running and state in ("started", "running"):
|
|
changed = True
|
|
elif running and state in ("stopped","reloaded"):
|
|
changed = True
|
|
elif state == "restarted":
|
|
changed = True
|
|
|
|
# ===========================================
|
|
# run change commands if we need to
|
|
|
|
if changed:
|
|
if state in ('started', 'running'):
|
|
rc_state, stdout, stderr = _run("%s %s start" % (SERVICE, name))
|
|
elif state == 'stopped':
|
|
rc_state, stdout, stderr = _run("%s %s stop" % (SERVICE, name))
|
|
elif state == 'reloaded':
|
|
rc_state, stdout, stderr = _run("%s %s reload" % (SERVICE, name))
|
|
elif state == 'restarted':
|
|
rc1, stdout1, stderr1 = _run("%s %s stop" % (SERVICE, name))
|
|
rc2, stdout2, stderr2 = _run("%s %s start" % (SERVICE, name))
|
|
rc_state = rc + rc1 + rc2
|
|
stdout = stdout1 + stdout2
|
|
stderr = stderr1 + stderr2
|
|
|
|
out += stdout
|
|
err += stderr
|
|
rc = rc + rc_state
|
|
|
|
if rc != 0:
|
|
|
|
print json.dumps({
|
|
"failed" : True,
|
|
"rc" : rc,
|
|
})
|
|
sys.exit(1)
|
|
|
|
|
|
# ===============================================
|
|
# success
|
|
|
|
result = {"changed": changed}
|
|
|
|
rc, stdout, stderr = _run("%s %s status" % (SERVICE, name))
|
|
if list_items and list_items in [ 'status' ]:
|
|
result['status'] = stdout
|
|
print json.dumps(result)
|
|
|
|
else:
|
|
print json.dumps(dict(failed=True, msg="expected state or list parameters"))
|
|
|
|
|
|
sys.exit(0)
|
|
|