commit db5a7846c6ae76987a470b16c98bdf17c04282cb
parent c8224bfb943d1f863dfb1386dc1edc21e168b507
Author: Asher Morgan <asher@ashermorgan.net>
Date: Sat, 15 Aug 2026 16:04:47 -0700
Extract server logic into class
Also improve session cleanup.
Diffstat:
| M | main.py | | | 122 | +++++++++++++++++++++++++++++++++++++++++++++---------------------------------- |
1 file changed, 69 insertions(+), 53 deletions(-)
diff --git a/main.py b/main.py
@@ -1,5 +1,7 @@
import json
import os
+import shutil
+import tempfile
import ansible_runner
from flask import Flask, abort, make_response, request, send_file, session
@@ -8,10 +10,60 @@ from uuid import uuid4
DATA = {}
PLAYBOOK = os.path.abspath('./demo/playbook.yml') # TODO
-PRIVATE_DIR = os.path.abspath('./private/') # TODO
app = Flask(__name__)
-app.secret_key = os.urandom(16) # TODO
+app.secret_key = os.urandom(16)
+
+
+class Runner:
+ def __init__(self, host, playbook):
+ self.id = str(uuid4())
+ self.dir = tempfile.mkdtemp()
+
+ os.mkfifo(f'{self.dir}/sendbuf')
+ os.mkfifo(f'{self.dir}/recvbuf')
+
+ self._start(host, playbook)
+
+ self.sendbuf = open(f'{self.dir}/sendbuf', 'r')
+ self.recvbuf = open(f'{self.dir}/recvbuf', 'w')
+
+ def _start(self, host, playbook):
+ inv = {
+ 'all': {
+ 'hosts': {
+ host: {
+ 'ansible_connection': 'http',
+ 'ansible_http_runner': self.dir,
+ },
+ },
+ },
+ }
+ env = {
+ 'ANSIBLE_CONNECTION_PLUGINS': ':'.join([
+ os.path.abspath('./connection_plugins/'), # TODO
+ # Default paths:
+ 'demo/plugins/connection', # TODO
+ '/usr/share/ansible/plugins/connection',
+ ])
+ }
+
+ self.thread, self.runner = ansible_runner.run_async(
+ private_data_dir=self.dir,
+ limit=host,
+ inventory=inv,
+ envvars=env,
+ playbook=playbook,
+ ident=self.id,
+ verbosity=3
+ )
+
+ def teardown(self):
+ self.thread.join()
+ self.id = None
+ self.sendbuf.close()
+ self.recvbuf.close()
+ shutil.rmtree(self.dir)
@app.get('/install')
@@ -22,66 +74,30 @@ def install():
@app.post('/runners/')
def job_new():
- id = str(uuid4())
-
- inv = {
- 'all': {
- 'hosts': {
- request.json['host']: {
- 'ansible_connection': 'http',
- 'ansible_http_runner': f'{PRIVATE_DIR}/artifacts/{id}',
- },
- },
- },
- }
- env = {
- 'ANSIBLE_CONNECTION_PLUGINS': ':'.join([
- os.path.abspath('./connection_plugins/'), # TODO
- # Default paths:
- 'demo/plugins/connection', # TODO
- '/usr/share/ansible/plugins/connection',
- ])
- }
- os.makedirs(f'{PRIVATE_DIR}/artifacts/{id}/')
- os.mkfifo(f'{PRIVATE_DIR}/artifacts/{id}/sendbuf')
- os.mkfifo(f'{PRIVATE_DIR}/artifacts/{id}/recvbuf')
- _, r = ansible_runner.run_async(private_data_dir=PRIVATE_DIR,
- limit=request.json['host'],
- inventory=inv,
- envvars=env,
- playbook=PLAYBOOK,
- ident=id,
- verbosity=3
- )
- sendbuf = open(f'{PRIVATE_DIR}/artifacts/{id}/sendbuf', 'r')
- recvbuf = open(f'{PRIVATE_DIR}/artifacts/{id}/recvbuf', 'w')
- DATA[id] = {
- 'runner': r,
- 'sendbuf': sendbuf,
- 'recvbuf': recvbuf,
- }
- session['runner'] = id
-
- return make_response('', 201, {'Location': f'/runners/{id}'})
+ runner = Runner(request.json['host'], PLAYBOOK)
+
+ DATA[runner.id] = runner
+ session['runner'] = runner.id
+
+ return make_response('', 201, {'Location': f'/runners/{runner.id}'})
@app.put('/runners/<id>')
def job_status(id):
if id != session.get('runner'):
abort(401)
+ runner = DATA[id]
- data = request.json
- if data:
- DATA[id]['recvbuf'].write(json.dumps(data) + '\n')
- DATA[id]['recvbuf'].flush()
+ if request.json:
+ runner.recvbuf.write(json.dumps(request.json) + '\n')
+ runner.recvbuf.flush()
- sendline = DATA[id]['sendbuf'].readline()
- if sendline:
- res = json.loads(sendline)
- else:
- res = {}
+ res = json.loads(runner.sendbuf.readline() or '{}')
+ res['status'] = runner.runner.status
- res['status'] = DATA[id]['runner'].status
+ if runner.runner.status not in ['started', 'running']:
+ runner.teardown()
+ del DATA[id]
return res