Skip to content

Instantly share code, notes, and snippets.

@aikomastboom

aikomastboom/pm2.py

Last active Jul 21, 2019
Embed
What would you like to do?
#!/usr/bin/python
# -*- coding: utf-8 -*-
DOCUMENTATION = """
---
author: Aiko Mastboom
module: pm2.py
short_description: nodeJS pm2 wrapper
description:
- This module helps managing processes using pm2 on hosts
options:
command:
required: true
choices: [ start, stop, restart ]
description:
- C(started)/C(stopped) are idempotent actions that will not run
commands unless necessary. C(restarted) will always bounce the
service.
persist:
required: false
default: false
description:
- C(persist) will always dump the current pm2 state
json:
description:
- The processes to manage.
default: processes.json
required: false
use_nave:
description:
- Is nave used to select node version https://github.com/isaacs/nave
default: false
required: false
node_version:
description:
- version of node nave has installed
default: None
required: only when use_nave = true
executable:
description:
- path to pm2 executable (useful when pm2 is not on the path or something)
default: pm2
required: false
"""
EXAMPLES = '''
# Example playbook
- local_action: pm2 start
- action: pm2.py command=start use_nave=true node_version=0.10.21 json=bower-registry.json
'''
import os
import tempfile
import subprocess
def run_command(module, args, check_rc=False, close_fds=False, executable=None,
data=None, binary_data=False, path_prefix=None, cwd=None):
'''
Execute a command, returns rc, stdout, and stderr.
args is the command to run
If args is a list, the command will be run with shell=False.
Otherwise, the command will be run with shell=True when args is a string.
Other arguments:
- check_rc (boolean) Whether to call fail_json in case of
non zero RC. Default is False.
- close_fds (boolean) See documentation for subprocess.Popen().
Default is False.
- executable (string) See documentation for subprocess.Popen().
Default is None.
'''
if isinstance(args, list):
shell = False
elif isinstance(args, basestring):
shell = True
else:
msg = "Argument 'args' to run_command must be list or string"
module.fail_json(rc=257, cmd=args, msg=msg)
rc = 0
msg = None
st_in = None
# Set a temporary env path if a prefix is passed
env = os.environ
if path_prefix:
env['PATH'] = "%s:%s" % (path_prefix, env['PATH'])
if data:
st_in = subprocess.PIPE
try:
if path_prefix is not None:
cmd = subprocess.Popen(args,
executable=executable,
shell=shell,
close_fds=close_fds,
stdin=st_in,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=cwd,
env=env)
else:
cmd = subprocess.Popen(args,
executable=executable,
shell=shell,
close_fds=close_fds,
stdin=st_in,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=cwd)
if data:
if not binary_data:
data += '\\n'
out, err = cmd.communicate(input=data)
rc = cmd.returncode
except (OSError, IOError), e:
module.fail_json(rc=e.errno, msg=str(e), cmd=args)
except:
module.fail_json(rc=257, msg=traceback.format_exc(), cmd=args)
if rc != 0 and check_rc:
msg = err.rstrip()
module.fail_json(cmd=args, rc=rc, stdout=out, stderr=err, msg=msg)
return (rc, out, err)
def main():
default_pm2 = 'pm2'
module = AnsibleModule(
argument_spec=dict(
command=dict(required=True,
choices=['start', 'stop', 'restart', 'dump']),
executable=dict(required=False, default=default_pm2),
persist=dict(required=False, choices=BOOLEANS),
use_nave=dict(required=False, choices=BOOLEANS),
node_version=dict(required=False, default=None),
json=dict(required=False, default=None)
),
supports_check_mode=False
)
command = module.params.get('command')
executable = module.params.get('executable')
persist = module.params.get('persist')
use_nave = module.params.get('use_nave')
node_version = module.params.get('node_version')
json = module.params.get('json')
if use_nave:
if not node_version:
return module.fail_json(msg='node_version required when using nave')
if module.check_mode:
def check_if_system_state_would_be_changed():
return False
# Check if any changes would be made by don't actually make those changes
module.exit_json(changed=check_if_system_state_would_be_changed())
env = os.environ
path_prefix = ''
if executable != default_pm2:
# assuming node to be in same folder as pm2
path_prefix = os.path.dirname(executable)
if use_nave:
path_prefix = env['HOME'] + '/.nave/installed/' + node_version + '/bin'
# TODO AM: duplicated line, remove when https://github.com/Unitech/pm2/issues/281 is fixed and released.
run_command(module,
[executable, 'jlist', '-s'],
path_prefix=path_prefix)
rc, json_out, err = run_command(module,
[executable, 'jlist', '-s'],
path_prefix=path_prefix)
jlist_out = module.from_json(json_out)
filename = 'processes.json'
processes = []
if not os.path.exists(filename):
filename = ''
if json and os.path.exists(json):
filename = json
if filename:
infile = open(filename, 'rb')
block = infile.read()
infile.close()
processes = module.from_json(block)
status = {}
changed = False
commands = []
rcs = []
outs = []
errs = []
started = []
if isinstance(processes, dict):
processes = [processes]
for proc in processes:
if 'name' in proc:
name = proc['name']
status[name] = {}
status[name]['process'] = proc
for running in jlist_out:
if name == running['name']:
status[name]['status'] = running['pm2_env']['status']
status[name]['pm_id'] = running['pm_id']
if not 'status' in status[name]:
status[name]['status'] = 'missing'
if command == 'start':
if status[name]['status'] == 'missing':
tmp_fd, tmp_file_path = tempfile.mkstemp(suffix='.json')
file = os.fdopen(tmp_fd, 'w')
file.write(module.jsonify(status[name]['process']))
file.close()
args = [executable, 'start', tmp_file_path, '-m']
cwd = os.path.dirname(json) or env['PWD']
rc, out, err = run_command(module, args, path_prefix=path_prefix, cwd=cwd)
os.unlink(tmp_file_path)
if rc == 0:
changed = True
else:
return module.fail_json(msg=command + ' '.join(args) + ' failed ' + str(rc))
commands.append(args)
started.append(status[name]['process'])
rcs.append(rc)
outs.append(out)
errs.append(err)
elif 'stopped' == status[name]['status']:
args = [executable, 'restart', str(status[name]['pm_id']), '-m']
rc, out, err = run_command(module, args, path_prefix=path_prefix)
if rc == 0:
changed = True
else:
return module.fail_json(msg='starting ' + ' '.join(args) + ' failed ' + str(rc))
commands.append(args)
rcs.append(rc)
outs.append(out)
errs.append(err)
if command == 'restart':
if 'missing' == status[name]['status']:
tmp_fd, tmp_file_path = tempfile.mkstemp(suffix='.json')
file = os.fdopen(tmp_fd, 'w')
file.write(module.jsonify(status[name]['process']))
file.close()
args = [executable, 'start', tmp_file_path, '-m']
cwd = os.path.dirname(json) or env['PWD']
rc, out, err = run_command(module, args, path_prefix=path_prefix, cwd=cwd)
os.unlink(tmp_file_path)
if rc == 0:
changed = True
else:
return module.fail_json(msg=command + ' '.join(args) + ' failed ' + str(rc))
commands.append(args)
started.append(status[name]['process'])
rcs.append(rc)
outs.append(out)
errs.append(err)
elif 'stopped' == status[name]['status'] or \
'online' == status[name]['status']:
args = [executable, 'restart', str(status[name]['pm_id']), '-m']
rc, out, err = run_command(module, args, path_prefix=path_prefix)
if rc == 0:
changed = True
else:
return module.fail_json(msg=command + ' '.join(args) + ' failed ' + str(rc))
commands.append(args)
rcs.append(rc)
outs.append(out)
errs.append(err)
if command == 'stop':
if status[name]['status'] == 'online':
args = [executable, 'stop', str(status[name]['pm_id']), '-m']
rc, out, err = run_command(module, args, path_prefix=path_prefix)
if rc == 0:
changed = True
else:
return module.fail_json(msg=command + ' '.join(args) + ' failed ' + str(rc))
commands.append(args)
rcs.append(rc)
outs.append(out)
errs.append(err)
if persist:
args = [executable, 'dump']
rc, out, err = run_command(module, args, path_prefix=path_prefix)
commands.append(args)
rcs.append(rc)
outs.append(out)
errs.append(err)
module.exit_json(changed=changed, commands=commands, started=started,
rcs=rcs, outs=outs, errs=errs)
from ansible.module_utils.basic import *
main()
@gamename

This comment has been minimized.

Copy link

@gamename gamename commented Nov 12, 2014

I'd like to use this as a basis for a pm2 library in ansible. Any objection?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.