#!/usr/bin/env python import json import os import subprocess import time import threading try: # Py3 from http.server import BaseHTTPRequestHandler, HTTPServer except ImportError: # Py2 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer try: from shlex import quote as shell_quote # Py3 except ImportError: from pipes import quote as shell_quote # Py2 (Unix) RCSTACK_PATH_NAME = "RCSTACK_PATH" RCSTACK_PATH = os.environ.get(RCSTACK_PATH_NAME) RCSTACK_LOG_NAME = os.environ.get("RCSTACK_LOG_NAME", "./rcstack_status.log") STATUS = {"state": "ready"} # "ready" or "processing" def _log_line(text, log_path): # compatible with python 2 out = open(log_path, "ab") try: if not isinstance(text, bytes): text = text.encode("utf-8") out.write(text + b"\n") out.flush() finally: out.close() def _watch_process(popen_obj, log_path): try: rc = popen_obj.wait() stamp = time.strftime("%Y-%m-%d %H:%M:%S") _log_line("[" + stamp + "] finished rc=" + str(rc), log_path=log_path) except Exception as e: stamp = time.strftime("%Y-%m-%d %H:%M:%S") _log_line("[" + stamp + "] watcher error: " + str(e), log_path=log_path) finally: STATUS["state"] = "ready" class RcstackUpdater(BaseHTTPRequestHandler): def __init__(self, request, client_address, server): BaseHTTPRequestHandler.__init__(self, request, client_address, server) def _send_json(self, obj, code=200): body = json.dumps(obj).encode("utf-8") self.send_response(code) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def do_GET(self): if self.path == "/state": return self._send_json({"state": STATUS["state"]}) elif self.path == "/rcstack/update": # If already processing, return ok and do not spawn a new process. if STATUS["state"] != "ready": return self._send_json({"status": "ok", "state": STATUS["state"]}) ok, err_msg = self.spawn_rcstack_status() # If spawn failed for real reasons (bad env/path/etc.), return 500 if not ok: return self._send_http_error(err_msg.encode("utf-8"), status_code=500) return self._send_json({"status": "ok", "state": STATUS["state"]}) return self._send_http_error(b"not found", status_code=404) def spawn_rcstack_status(self): if not RCSTACK_PATH: return False, "%s env variable not set" % (RCSTACK_PATH_NAME,) if not os.path.exists(RCSTACK_PATH) or not os.path.isfile(RCSTACK_PATH): return False, "%s does not exist" % (RCSTACK_PATH,) rcstack = os.path.expanduser(RCSTACK_PATH) rcstack_dir = os.path.dirname(rcstack) log_path = os.path.join(rcstack_dir, RCSTACK_LOG_NAME) STATUS["state"] = "processing" stamp = time.strftime("%Y-%m-%d %H:%M:%S") _log_line("[%s] spawn: %s status" % (stamp, rcstack), log_path=log_path) out = open(log_path, "ab") try: cmd = "%s self-update && %s stack-upgrade all" % (shell_quote(rcstack), shell_quote(rcstack)) p = subprocess.Popen( ["/bin/sh", "-c", cmd], cwd=rcstack_dir, stdout=out, stderr=out, close_fds=True, preexec_fn=os.setsid, # Unix only ) except Exception as e: STATUS["state"] = "ready" _log_line("[%s] spawn error: %s" % (stamp, str(e)), log_path=log_path) return False, "spawn failed: %s" % (str(e),) finally: try: out.close() except Exception: pass t = threading.Thread(target=_watch_process, args=(p, log_path)) try: t.daemon = True except Exception: t.setDaemon(True) # Py2 t.start() return True, None def _send_http_error(self, msg, status_code): self.send_response(status_code) self.send_header("Content-Type", "text/plain") self.end_headers() self.wfile.write(msg) def main(host="127.0.0.1", port=10025): httpd = HTTPServer((host, port), RcstackUpdater) stamp = time.strftime("%Y-%m-%d %H:%M:%S") print("[%s] listening on http://%s:%d" % (stamp, host, port)) httpd.serve_forever() if __name__ == "__main__": main()