zuul/zuul/ansible/library/zuul_runner.py

132 lines
3.8 KiB
Python

#!/usr/bin/python
# Copyright (c) 2016 IBM Corp.
#
# This module 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.
#
# This software 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 this software. If not, see <http://www.gnu.org/licenses/>.
import datetime
import getpass
import os
import subprocess
import threading
class Console(object):
def __enter__(self):
self.logfile = open('/tmp/console.html', 'a', 0)
return self
def __exit__(self, etype, value, tb):
self.logfile.close()
def addLine(self, ln):
# Note this format with deliminator is "inspired" by the old
# Jenkins format but with microsecond resolution instead of
# millisecond. It is kept so log parsing/formatting remains
# consistent.
ts = datetime.datetime.now()
outln = '%s | %s' % (ts, ln)
self.logfile.write(outln)
def get_env():
env = {}
env['HOME'] = os.path.expanduser('~')
env['USER'] = getpass.getuser()
# Known locations for PAM mod_env sources
for fn in ['/etc/environment', '/etc/default/locale']:
if os.path.exists(fn):
with open(fn) as f:
for line in f:
if not line:
continue
if line[0] == '#':
continue
if '=' not in line:
continue
k, v = line.strip().split('=')
for q in ["'", '"']:
if v[0] == q:
v = v.strip(q)
env[k] = v
return env
def follow(fd):
newline_warning = False
with Console() as console:
while True:
line = fd.readline()
if not line:
break
if not line.endswith('\n'):
line += '\n'
newline_warning = True
console.addLine(line)
if newline_warning:
console.addLine('[Zuul] No trailing newline\n')
def run(cwd, cmd, args):
env = get_env()
env.update(args)
proc = subprocess.Popen(
['/bin/bash', '-l', '-c', cmd],
cwd=cwd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=env,
)
t = threading.Thread(target=follow, args=(proc.stdout,))
t.daemon = True
t.start()
ret = proc.wait()
# Give the thread that is writing the console log up to 10 seconds
# to catch up and exit. If it hasn't done so by then, it is very
# likely stuck in readline() because it spawed a child that is
# holding stdout or stderr open.
t.join(10)
with Console() as console:
if t.isAlive():
console.addLine("[Zuul] standard output/error still open "
"after child exited")
console.addLine("[Zuul] Task exit code: %s\n" % ret)
return ret
def main():
module = AnsibleModule(
argument_spec=dict(
command=dict(required=True, default=None),
cwd=dict(required=True, default=None),
parameters=dict(default={}, type='dict')
)
)
p = module.params
env = p['parameters'].copy()
ret = run(p['cwd'], p['command'], env)
if ret == 0:
module.exit_json(changed=True, rc=ret)
else:
module.fail_json(msg="Exit code %s" % ret, rc=ret)
from ansible.module_utils.basic import * # noqa
if __name__ == '__main__':
main()