aoh

A tool for running Ansible playbooks with `curl <url> | python` commands
git clone https://git.ashermorgan.net/aoh/
Log | Files | Refs | README

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:
Mmain.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