Python REST API Automation
When managing complex applications (like a Kubernetes media stack with Jellyseerr, Prowlarr, Radarr, and Sonarr), manual configuration via Web UIs is brittle, unrepeatable, and violates Infrastructure-as-Code (IaC) principles.
However, many applications do not support declarative configuration files (like YAML) out of the box. Their entire state is stored in internal SQLite databases.
The solution is Python REST API Automation.
Core Concepts
1. The Imperative vs Declarative Gap
Kubernetes is declarative: you submit a YAML file, and the cluster makes it happen. But the applications running inside the pods are often imperative: you must click buttons or send explicit commands to change their state. We bridge this gap using Python scripts that run after the pods are healthy.
2. Bypassing Static Secrets
API keys for these applications are generated dynamically upon first boot and stored in their databases. Hardcoding them into Kubernetes Secrets is impossible because we don't know them in advance.
Instead of manually copying keys, we can use kubectl exec within Python to dynamically extract them directly from the container's configuration files (like config.xml).
import subprocess
import xml.etree.ElementTree as ET
def get_api_key(namespace, app):
cmd = f"kubectl exec -n {namespace} deploy/{app} -- cat /config/config.xml"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
root = ET.fromstring(result.stdout)
return root.find('ApiKey').text
3. State-Aware REST POST Requests
When automating Web UIs, we are mimicking the browser's network requests. We use the requests library to send POST requests containing JSON payloads to the application's API endpoints.
Crucial Lesson: Automation must be idempotent. If you send an "initial setup" payload (like {"hostname":"jellyfin"}) to an endpoint that was already configured, the application might throw an HTTP 500 Error (e.g., "Hostname already configured"). Your scripts must check the existing state via a GET request before attempting a POST, or dynamically adjust the payload to skip read-only fields during subsequent runs.