mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
update shared services
added new daemonization function documented existing functions
This commit is contained in:
parent
93a689958e
commit
18b062760c
1 changed files with 151 additions and 0 deletions
|
@ -28,13 +28,32 @@
|
|||
#
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import glob
|
||||
import select
|
||||
import pickle
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.six import PY2, b
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
|
||||
def sysv_is_enabled(name):
|
||||
'''
|
||||
This function will check if the service name supplied
|
||||
is enabled in any of the sysv runlevels
|
||||
|
||||
:arg name: name of the service to test for
|
||||
'''
|
||||
return bool(glob.glob('/etc/rc?.d/S??%s' % name))
|
||||
|
||||
def get_sysv_script(name):
|
||||
'''
|
||||
This function will return the expected path for an init script
|
||||
corresponding to the service name supplied.
|
||||
|
||||
:arg name: name or path of the service to test for
|
||||
'''
|
||||
if name.startswith('/'):
|
||||
result = name
|
||||
else:
|
||||
|
@ -43,11 +62,143 @@ def get_sysv_script(name):
|
|||
return result
|
||||
|
||||
def sysv_exists(name):
|
||||
'''
|
||||
This function will return True or False depending on
|
||||
the existance of an init script corresponding to the service name supplied.
|
||||
|
||||
:arg name: name of the service to test for
|
||||
'''
|
||||
return os.path.exists(get_sysv_script(name))
|
||||
|
||||
def fail_if_missing(module, found, service, msg=''):
|
||||
'''
|
||||
This function will return an error or exit gracefully depending on check mode status
|
||||
and if the service is missing or not.
|
||||
|
||||
:arg module: is an AnsbileModule object, used for it's utility methods
|
||||
:arg found: boolean indicating if services was found or not
|
||||
:arg service: name of service
|
||||
:kw msg: extra info to append to error/success msg when missing
|
||||
'''
|
||||
if not found:
|
||||
if module.check_mode:
|
||||
module.exit_json(msg="Service %s not found on %s, assuming it will exist on full run" % (service, msg), changed=True)
|
||||
else:
|
||||
module.fail_json(msg='Could not find the requested service %s: %s' % (service, msg))
|
||||
|
||||
def daemonize(module, cmd):
|
||||
'''
|
||||
Execute a command while detaching as a deamon, returns rc, stdout, and stderr.
|
||||
|
||||
:arg module: is an AnsbileModule object, used for it's utility methods
|
||||
:arg cmd: is a list or string representing the command and options to run
|
||||
|
||||
This is complex because daemonization is hard for people.
|
||||
What we do is daemonize a part of this module, the daemon runs the command,
|
||||
picks up the return code and output, and returns it to the main process.
|
||||
'''
|
||||
|
||||
# init some vars
|
||||
chunk = 4096 #FIXME: pass in as arg?
|
||||
errors = 'surrogate_or_strict'
|
||||
|
||||
#start it!
|
||||
try:
|
||||
pipe = os.pipe()
|
||||
pid = os.fork()
|
||||
except OSError:
|
||||
module.fail_json(msg="Error while attempting to fork: %s", exception=traceback.format_exc())
|
||||
|
||||
# we don't do any locking as this should be a unique module/process
|
||||
if pid == 0:
|
||||
|
||||
os.close(pipe[0])
|
||||
# Set stdin/stdout/stderr to /dev/null
|
||||
fd = os.open(os.devnull, os.O_RDWR)
|
||||
|
||||
# clone stdin/out/err
|
||||
for num in range(3):
|
||||
if fd != num:
|
||||
os.dup2(fd, num)
|
||||
|
||||
# close otherwise
|
||||
if fd not in range(3):
|
||||
os.close(fd)
|
||||
|
||||
# Make us a daemon
|
||||
pid = os.fork()
|
||||
|
||||
# end if not in child
|
||||
if pid > 0:
|
||||
os._exit(0)
|
||||
|
||||
# get new process session and detach
|
||||
sid = os.setsid()
|
||||
if sid == -1:
|
||||
module.fail_json(msg="Unable to detach session while daemonizing")
|
||||
|
||||
# avoid possible problems with cwd being removed
|
||||
os.chdir("/")
|
||||
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
os._exit(0)
|
||||
|
||||
# if command is string deal with py2 vs py3 conversions for shlex
|
||||
if not isinstance(cmd, list):
|
||||
if PY2:
|
||||
cmd = shlex.split(to_bytes(cmd, errors=errors))
|
||||
else:
|
||||
cmd = shlex.split(to_text(cmd, errors=errors))
|
||||
|
||||
# make sure we always use byte strings
|
||||
run_cmd = []
|
||||
for c in cmd:
|
||||
end_cmd.append(to_bytes(c, errors=errors))
|
||||
|
||||
# execute the command in forked process
|
||||
p = subprocess.Popen(run_cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=lambda: os.close(pipe[1]))
|
||||
fds = [p.stdout, p.stderr]
|
||||
|
||||
# loop reading output till its done
|
||||
output = { p.stdout: b(""), p.sterr: b("") }
|
||||
while fds:
|
||||
rfd, wfd, efd = select.select(fds, [], fds, 1)
|
||||
if (rfd + wfd + efd) or p.poll():
|
||||
for out in fds:
|
||||
if out in rfd:
|
||||
data = os.read(out.fileno(), chunk)
|
||||
if not data:
|
||||
fds.remove(out)
|
||||
output[out] += b(data)
|
||||
|
||||
# even after fds close, we might want to wait for pid to die
|
||||
p.wait()
|
||||
|
||||
# Return a pickled data o parent
|
||||
return_data = pickle.dumps([p.returncode, to_text(output[p.stdout]), to_text(output[p.stderr])])
|
||||
os.write(pipe[1], to_bytes(return_data, errors=errors))
|
||||
|
||||
# clean up
|
||||
os.close(pipe[1])
|
||||
os._exit(0)
|
||||
|
||||
elif pid == -1:
|
||||
module.fail_json(msg="Unable to fork, no exception thrown, probably due to lack of resources, check logs.")
|
||||
|
||||
else:
|
||||
# in parent
|
||||
os.close(pipe[1])
|
||||
os.waitpid(pid, 0)
|
||||
|
||||
# Grab response data after child finishes
|
||||
return_data = b("")
|
||||
while True:
|
||||
rfd, wfd, efd = select.select([pipe[0]], [], [pipe[0]])
|
||||
if pipe[0] in rfd:
|
||||
data = os.read(pipe[0], chunk)
|
||||
if not dat:
|
||||
break
|
||||
return_data += b(data)
|
||||
|
||||
return pickle.loads(to_text(return-data, errors=errors))
|
||||
|
|
Loading…
Reference in a new issue