#!/usr/bin/env python3 """ awx_create_job_templates.py Run this AFTER committing and pushing the KNELIAC code to the git remote. It will: 1. Trigger a project sync in AWX (pulls latest code from git) 2. Wait for the sync to complete 3. Create the two job templates (Hello World + Setup New System) 4. Create the inventory source (auto-syncs hosts from inventory/hosts.yml) Usage: export AWX_PASSWORD='...' python3 awx_create_job_templates.py Configuration (environment variables): AWX_URL AWX API base URL (default: https://tsys-awx.knel.net/api/v2) AWX_USER AWX admin username (default: admin) AWX_PASSWORD AWX admin password (REQUIRED - never committed to the repo) AWX_PROJECT_ID, AWX_INVENTORY_ID, AWX_SCM_CRED_ID, AWX_MACHINE_CRED_ID, AWX_ORG_ID resource IDs (sensible defaults below) AWX_VERIFY_TLS "false" to skip TLS verification (for self-signed certs). Default: verify against the system trust store. AWX_CA_BUNDLE Path to a CA bundle file (e.g. internal CA) to verify against. """ import os import ssl import urllib.request, urllib.parse, json, base64, time, sys API = os.environ.get("AWX_URL", "https://tsys-awx.knel.net/api/v2") _AWX_USER = os.environ.get("AWX_USER", "admin") _AWX_PASS = os.environ.get("AWX_PASSWORD") if not _AWX_PASS: sys.exit("ERROR: AWX_PASSWORD environment variable is not set. " "Refusing to run — no credentials are stored in this repo.") AUTH = base64.b64encode(f"{_AWX_USER}:{_AWX_PASS}".encode()).decode() PROJECT_ID = int(os.environ.get("AWX_PROJECT_ID", "10")) INVENTORY_ID = int(os.environ.get("AWX_INVENTORY_ID", "2")) SCM_CRED_ID = int(os.environ.get("AWX_SCM_CRED_ID", "3")) MACHINE_CRED_ID = int(os.environ.get("AWX_MACHINE_CRED_ID", "4")) ORG_ID = int(os.environ.get("AWX_ORG_ID", "1")) # TLS verification context. With a self-signed cert (until the internal CA # bundle is distributed), set AWX_VERIFY_TLS=false. With an internal CA, point # AWX_CA_BUNDLE at it and leave verification enabled. if os.environ.get("AWX_VERIFY_TLS", "true").lower() == "false": _SSL_CONTEXT = ssl._create_unverified_context() elif os.environ.get("AWX_CA_BUNDLE"): _SSL_CONTEXT = ssl.create_default_context(cafile=os.environ["AWX_CA_BUNDLE"]) else: _SSL_CONTEXT = None # use urllib's default (system trust store) def awx_req(method, endpoint, data=None): url = f"{API}/{endpoint}" body = json.dumps(data).encode() if data else None req = urllib.request.Request(url, data=body, method=method) req.add_header("Authorization", f"Basic {AUTH}") req.add_header("Content-Type", "application/json") try: with urllib.request.urlopen(req, timeout=30, context=_SSL_CONTEXT) as resp: raw = resp.read() return json.loads(raw) if raw else {} except urllib.error.HTTPError as e: err = e.read().decode() print(f" ERROR {e.code}: {err[:300]}") return None def find_or_create(resource, name, payload): encoded = urllib.parse.quote(name) existing = awx_req("GET", f"{resource}/?name={encoded}") if existing and existing.get("count", 0) > 0: item = existing["results"][0] print(f" EXISTS: [{item['id']}] {name}") return item result = awx_req("POST", f"{resource}/", payload) if result: print(f" CREATED: [{result.get('id')}] {name}") return result # Step 1: Sync the project print("=" * 60) print("STEP 1: Syncing project (pulling latest code from git)") print("=" * 60) sync = awx_req("POST", f"projects/{PROJECT_ID}/update/", {}) if not sync: print("FAILED to trigger sync") sys.exit(1) update_id = sync.get("id") print(f" Sync started: project_updates/{update_id}") print(" Waiting...", end="", flush=True) for i in range(30): time.sleep(3) print(".", end="", flush=True) status = awx_req("GET", f"project_updates/{update_id}/") s = status.get("status") if status else "?" if s in ("successful", "failed", "error", "canceled"): print(f" {s}") break else: print(" timeout") sys.exit(1) if s != "successful": print(f" Project sync failed: {s}") print(" Make sure the code is committed and pushed to the git remote.") sys.exit(1) # Step 2: Verify playbooks are available print("\n" + "=" * 60) print("STEP 2: Verifying playbooks") print("=" * 60) playbooks = awx_req("GET", f"projects/{PROJECT_ID}/playbooks/") if playbooks: print(f" Found {len(playbooks)} playbook(s):") for p in playbooks: print(f" - {p}") else: print(" No playbooks found! Push your code and re-run.") sys.exit(1) # Step 3: Create inventory source print("\n" + "=" * 60) print("STEP 3: Creating inventory source") print("=" * 60) find_or_create("inventory_sources", "KNELIAC Fleet Source", { "name": "KNELIAC Fleet Source", "description": "Auto-synced from inventory/hosts.yml", "inventory": INVENTORY_ID, "source": "scm", "source_project": PROJECT_ID, "source_path": "inventory/hosts.yml", "update_on_launch": True, "overwrite": True, "overwrite_vars": True, }) # Step 4: Create job templates print("\n" + "=" * 60) print("STEP 4: Creating job templates") print("=" * 60) find_or_create("job_templates", "KNELIAC - Hello World", { "name": "KNELIAC - Hello World", "description": "AWX smoke test - verify connectivity to managed hosts", "organization": ORG_ID, "inventory": INVENTORY_ID, "project": PROJECT_ID, "playbook": "playbooks/hello_world.yml", "credential": MACHINE_CRED_ID, "verbosity": 1, "forks": 5, "ask_limit_on_launch": True, }) find_or_create("job_templates", "KNELIAC - Setup New System", { "name": "KNELIAC - Setup New System", "description": "Full host provisioning (port of SetupNewSystem.sh)", "organization": ORG_ID, "inventory": INVENTORY_ID, "project": PROJECT_ID, "playbook": "playbooks/setup_new_system.yml", "credential": MACHINE_CRED_ID, "verbosity": 1, "forks": 5, "ask_limit_on_launch": True, "ask_variables_on_launch": True, }) print("\n" + "=" * 60) print("ALL DONE - AWX is ready!") print("=" * 60) print(""" AWX URL: https://tsys-awx.knel.net (or https://100.91.39.53) Login: $AWX_USER (password supplied via $AWX_PASSWORD env var) Note: Self-signed cert for now — set AWX_VERIFY_TLS=false until the internal CA bundle is distributed (then use AWX_CA_BUNDLE). Resources created: Credentials: [3] KNELIAC Git Credential (Source Control - for git access) [4] KNELIAC Host SSH Key (Machine - for fleet host access) Project: [10] KNELIAC (git repo, auto-syncs on launch) Inventory: [2] KNELIAC Fleet (auto-syncs hosts from inventory/hosts.yml) Job Templates: - KNELIAC - Hello World (smoke test, run first!) - KNELIAC - Setup New System (full provisioning) """)