aoh

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

commit 82037b2b3977b203db1e73d6945490e31bcb21e6
parent f8a2fcbd4891fc430191cac1c614f5a630b3c853
Author: Asher Morgan <asher@ashermorgan.net>
Date:   Fri, 14 Aug 2026 20:00:08 -0700

Create skeleton HTTP connection plugin

Diffstat:
M.gitignore | 2++
Ademo/connection_plugins/http.py | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmain.py | 19+++++++++++++++++--
Mrequirements.txt | 1+
4 files changed, 108 insertions(+), 2 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,3 +1,5 @@ .venv +__pycache__ +*.pyc private !private/.gitkeep diff --git a/demo/connection_plugins/http.py b/demo/connection_plugins/http.py @@ -0,0 +1,88 @@ +#!/usr/bin/python + +# Copyright: (c) 2026, Asher Morgan <asher@ashermorgan.net> +# GNU General Public License v3.0+ + +DOCUMENTATION = r''' + name: http + short_description: Control hosts over a "reverse" HTTP connection + + options: + runner: + description: The UUID of the associated Ansible runner + required: true + vars: + - name: ansible_http_runner +''' + +# ruff: disable[E402] +import typing as t + +from ansible.plugins.connection import ConnectionBase +from ansible.utils.display import Display +# ruff: enable[E402] + +display = Display() + + +class Connection(ConnectionBase): + """Reverse-HTTP connection.""" + + transport = 'http' + has_pipelining = False + + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + super(Connection, self).__init__(*args, **kwargs) + + + def _connect(self) -> Connection: # noqa: F821 + """Connect to the host (nop).""" + + if not self._connected: + display.vvv('ESTABLISH HTTP CONNECTION FOR RUNNER ' + f'{self.get_option('runner')}', + host=self._play_context.remote_addr) + self._connected = True + + return self + + + def exec_command(self, cmd: str, in_data: bytes | None = None, + sudoable: bool = True) -> tuple[int, bytes, bytes]: + """Run a command on the host (TODO).""" + + super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) + + display.vvv(f'EXEC {cmd}', host=self._play_context.remote_addr) + + return (0, b'', b'') + + + def put_file(self, in_path: str, out_path: str) -> None: + """Transfer file to host (TODO).""" + + super(Connection, self).put_file(in_path, out_path) + + display.vvv(f'PUT {in_path} TO {out_path}', + host=self._play_context.remote_addr) + + + def fetch_file(self, in_path: str, out_path: str) -> None: + """Fetch file from host (TODO).""" + + super(Connection, self).fetch_file(in_path, out_path) + + display.vvv(f'FETCH {in_path} TO {out_path}', + host=self._play_context.remote_addr) + + with open(out_path, 'wb') as f: + f.write(b'') + + + def close(self) -> None: + """Close connection.""" + + self._connected = False + + super(Connection, self).close() diff --git a/main.py b/main.py @@ -2,6 +2,7 @@ import os import ansible_runner from flask import Flask, abort, make_response, request, send_file, session +from uuid import uuid4 DATA = {} @@ -19,10 +20,24 @@ def install(): @app.post('/runners/') def job_new(): + id = str(uuid4()) + + inv = { + 'all': { + 'hosts': { + request.json['host']: { + 'ansible_connection': 'http', + 'ansible_http_runner': id, + }, + }, + }, + } _, r = ansible_runner.run_async(private_data_dir='private', limit=request.json['host'], - playbook=PLAYBOOK) - id = r.config.ident + inventory=inv, + playbook=PLAYBOOK, + verbosity=4 + ) DATA[id] = r session['runner'] = id diff --git a/requirements.txt b/requirements.txt @@ -1,3 +1,4 @@ +ansible ansible_runner flask requests