aoh

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

commit 3a0aaa32acb75c48b307188da4d9827b75c113c3
parent dbfc04950798ab69ba7214aa3f970fc104cd07a1
Author: Asher Morgan <asher@ashermorgan.net>
Date:   Sun, 23 Aug 2026 13:27:09 -0700

Remove client dependency on requests library

Diffstat:
Maoh/client.py | 59+++++++++++++++++++++++++++++++++++++++++++++++++----------
Maoh/server.py | 2+-
Mrequirements.txt | 1-
3 files changed, 50 insertions(+), 12 deletions(-)

diff --git a/aoh/client.py b/aoh/client.py @@ -2,13 +2,14 @@ import base64 import getpass +import json import os import platform import subprocess import sys import time - -import requests # TODO: eliminate dependency? +from urllib.error import HTTPError +from urllib.request import Request, urlopen API = '{{ API_URL }}' # Substitution performed by Flask @@ -63,12 +64,44 @@ def fetch(args): except Exception as e: # noqa: BLE001 return { 'err': str(e) } + +def send_json_request(url, req_data=None, method='GET', cookies=''): + """Send an HTTP request, expecting JSON to be used for all content.""" + + req = Request( + url, + method=method, + data=json.dumps(req_data).encode() if req_data is not None else None, + headers={ + 'Content-Type': 'application/json', + 'Cookie': cookies, + }, + ) + + try: + with urlopen(req) as res: + status_code = res.status + headers = res.headers + data = res.read() + except HTTPError as e: + status_code = e.code + headers = e.headers + data = e.fp.read() + + # We assume that all responses will include JSON bodies + assert headers.get('Content-Type') == 'application/json' + + return status_code, headers, json.loads(data) + + def runner_loop(runner_url, cookies): """Main execution loop.""" req = {} while True: - res = requests.put(runner_url, cookies=cookies, json=req).json() + status, _, res = send_json_request(runner_url, req, 'PUT', cookies) + + assert status == 200 if 'logs' in res: print(res['logs'], end='') @@ -92,25 +125,31 @@ def runner_loop(runner_url, cookies): def create_runner(playbook, args): """Main execution loop.""" + url = f'{API}/runners/' + req = { 'host': platform.node() or 'aoh_node', 'playbook': playbook, 'args': args, } - res = requests.post(f'{API}/runners/', json=req) - if res.status_code == 401 and 'passwords' in res.json(): + status, headers, res = send_json_request(url, req, 'POST') + + if status == 401 and 'passwords' in res: req['passwords'] = {} - for pw_type in res.json()['passwords']: + for pw_type in res['passwords']: req['passwords'][pw_type] = \ getpass.getpass(PASSWORD_PROMPTS[pw_type]) - res = requests.post(f'{API}/runners/', json=req) + status, headers, res = send_json_request(url, req, 'POST') - if res.status_code != 201: - print(res.json()['err']) + if status != 201: + print(res['err']) sys.exit(1) - runner_loop(f'{API}{res.headers['Location']}', res.cookies) + # We assume that only one cookie will be set + session_token = headers['Set-Cookie'].split(';')[0] + + runner_loop(f'{API}{headers['Location']}', session_token) def cli(args): diff --git a/aoh/server.py b/aoh/server.py @@ -58,7 +58,7 @@ def new_runner(): _RUNNERS[runner.id] = runner session['runner'] = runner.id - return make_response('', 201, {'Location': f'/runners/{runner.id}'}) + return make_response({}, 201, {'Location': f'/runners/{runner.id}'}) @app.put('/runners/<id>') diff --git a/requirements.txt b/requirements.txt @@ -2,4 +2,3 @@ ansible ansible_runner flask pyyaml -requests