commit e6d49badfd6b7a55c3e4d55d426862ed5817da85
parent c579b90a09442fece0a9040dbc2a04ec86f68d73
Author: Asher Morgan <asher@ashermorgan.net>
Date: Fri, 21 Aug 2026 19:00:55 -0700
Minor cleanup
Diffstat:
4 files changed, 34 insertions(+), 27 deletions(-)
diff --git a/aoh/client.py b/aoh/client.py
@@ -12,6 +12,8 @@ API = '{{ API_URL }}' # Substitution performed by Flask
def exec(args):
+ """Run a command on the local host."""
+
try:
p = subprocess.Popen(
args,
@@ -31,6 +33,8 @@ def exec(args):
def put(args):
+ """Transfer a file to the local host."""
+
try:
with open(args['dest'], 'wb') as f:
f.write(base64.b64decode(args['data']))
@@ -41,6 +45,8 @@ def put(args):
def fetch(args):
+ """Fetch a file from the local host."""
+
try:
with open(args['dest'], 'rb') as f:
return {
@@ -51,6 +57,8 @@ def fetch(args):
def main():
+ """Main execution loop."""
+
res = requests.post(f'{API}/runners/', json={
'host': platform.node() or 'aoh_node',
'playbook': sys.argv[1] if len(sys.argv) >= 2 else 'main',
@@ -61,11 +69,11 @@ def main():
sys.exit(1)
cookies = res.cookies
- job_url = f'{API}{res.headers['Location']}'
+ runner_url = f'{API}{res.headers['Location']}'
req = {}
while True:
- res = requests.put(job_url, cookies=cookies, json=req).json()
+ res = requests.put(runner_url, cookies=cookies, json=req).json()
if 'logs' in res:
print(res['logs'], end='')
@@ -74,7 +82,7 @@ def main():
if 'err' in res:
print(res['err'])
sys.exit(1)
- if res['finished']:
+ if res.get('finished'):
break
elif 'exec' in res:
req = exec(res['exec'])
diff --git a/aoh/connection_plugins/aoh.py b/aoh/connection_plugins/aoh.py
@@ -1,7 +1,7 @@
# Copyright: (c) 2026, Asher Morgan <asher@ashermorgan.net>
# GNU General Public License v3.0+
-DOCUMENTATION = r'''
+DOCUMENTATION = """
name: aoh
short_description: Run tasks through an Ansible-over-HTTP (AoH) connection
@@ -11,7 +11,7 @@ DOCUMENTATION = r'''
required: true
vars:
- name: ansible_aoh_dir
-'''
+"""
import base64
import json
@@ -38,6 +38,7 @@ class Connection(ConnectionBase):
self.sendbuf = None
self.recvbuf = None
+
def _connect(self) -> Connection: # noqa: F821
"""Connect to the host."""
@@ -100,7 +101,7 @@ class Connection(ConnectionBase):
def put_file(self, in_path: str, out_path: str) -> None:
- """Transfer file to host."""
+ """Transfer a file to the host."""
super().put_file(in_path, out_path)
@@ -129,7 +130,7 @@ class Connection(ConnectionBase):
def fetch_file(self, in_path: str, out_path: str) -> None:
- """Fetch file from host."""
+ """Fetch a file from the host."""
super().fetch_file(in_path, out_path)
@@ -152,7 +153,7 @@ class Connection(ConnectionBase):
def close(self) -> None:
- """Close connection."""
+ """Close connection with host."""
display.vvv('CLOSE CONNECTION FOR AOH RUNNER '
f'{self.get_option('runner')}',
diff --git a/aoh/runner.py b/aoh/runner.py
@@ -21,14 +21,14 @@ class AoHRunner:
def __init__(self, config, playbook, host, args):
"""Create a new AoH runner."""
- self.id = str(uuid4())
+ self.id = str(uuid4()) # Runner ID (set to None after teardown)
self._lock = Lock() # Used to protect all public methods
- self._dir = tempfile.mkdtemp(prefix='aoh-')
- self._LOGS_PATH = f'{self._dir}/artifacts/{self.id}/stdout'
- self._RECVBUF_PATH = f'{self._dir}/recvbuf'
- self._SENDBUF_PATH = f'{self._dir}/sendbuf'
+ self._DIR = tempfile.mkdtemp(prefix='aoh-')
+ self._LOGS_PATH = f'{self._DIR}/artifacts/{self.id}/stdout'
+ self._RECVBUF_PATH = f'{self._DIR}/recvbuf'
+ self._SENDBUF_PATH = f'{self._DIR}/sendbuf'
self._sendbuf = None
self._recvbuf = None
self._logs = None
@@ -40,7 +40,7 @@ class AoHRunner:
try:
# Create hostname file so connection plugin can verify hosts
- with open(f'{self._dir}/hostname', 'w') as f:
+ with open(f'{self._DIR}/hostname', 'w') as f:
f.write(f'{host}\n')
self._start_runner(config, playbook, host, args)
@@ -68,15 +68,15 @@ class AoHRunner:
"""Configure and start the underlying ansible-runner."""
# Set ansible_connection=aoh for the client host only. Note that we
- # don't use ansible-runners inventory directory because that will
+ # don't use ansible-runner's inventory directory because that will
# shadow user inventory files.
- with open(f'{self._dir}/inventory.ini', 'w') as f:
+ with open(f'{self._DIR}/inventory.ini', 'w') as f:
f.write(f'[aoh]\n{host} ansible_connection=aoh')
raw_config = ansible_runner.get_ansible_config(
'dump',
config,
- private_data_dir=self._dir,
+ private_data_dir=self._DIR,
quiet=True,
)[0]
@@ -95,15 +95,15 @@ class AoHRunner:
),
'ANSIBLE_CONFIG': config,
'ANSIBLE_INVENTORY': ','.join(
- [f'{self._dir}/inventory.ini'] + inventory
+ [f'{self._DIR}/inventory.ini'] + inventory
),
}
vars = {
- 'ansible_aoh_dir': self._dir,
+ 'ansible_aoh_dir': self._DIR,
}
self._thread, self._runner = ansible_runner.run_async(
- private_data_dir=self._dir,
+ private_data_dir=self._DIR,
ident=self.id,
envvars=env,
extravars=vars,
@@ -111,7 +111,6 @@ class AoHRunner:
playbook=playbook,
- # verbosity=3,
quiet=True,
suppress_env_files=True,
)
@@ -134,8 +133,6 @@ class AoHRunner:
def process_client_request(self, req):
"""Process an AoH client request."""
- res = {}
-
with self._lock:
if self.id is None:
# Runner has already been torn down
@@ -144,10 +141,12 @@ class AoHRunner:
'err': 'Client timeout',
}
+ res = {}
self._last_req = time.time()
# We check runner status first, in case new logs come in afterwards
res['finished'] = self._runner_finished()
+
assert self._logs is not None
res['logs'] = self._logs.read()
@@ -206,4 +205,4 @@ class AoHRunner:
if self._thread.is_alive():
raise AoHError('Ansible runner thread not terminated')
- shutil.rmtree(self._dir)
+ shutil.rmtree(self._DIR)
diff --git a/aoh/server.py b/aoh/server.py
@@ -7,7 +7,6 @@ from flask_apscheduler import APScheduler
from .runner import AoHRunner
_RUNNERS = {}
-_LAST_GC = 0
_GC_INTERVAL = 60 # 1 minute
app = Flask(__name__, template_folder=os.path.dirname(__file__))
@@ -76,7 +75,7 @@ def install():
@app.post('/runners/')
-def job_new():
+def new_runner():
if not _validate_args(request.json['args']):
return { 'err': 'Bad or banned arguments passed.' }, 400
@@ -97,7 +96,7 @@ def job_new():
@app.put('/runners/<id>')
-def job_status(id):
+def runner_update(id):
if id != session.get('runner'):
abort(401)