Erste lauffaehige Version: laedt Credentials, meldet sich per Bearer-Token bei anode/api/v1/agent/checkin, wendet zugewiesene Blueprints per ansible-pull an und schlaeft bis zum naechsten Intervall.
102 lines
2.5 KiB
Python
102 lines
2.5 KiB
Python
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
|
|
ANODE_URL = os.environ.get("TUXFLOTTE_ANODE_URL", "https://anode.tuxflotte.de")
|
|
CREDENTIALS_PATH = os.environ.get(
|
|
"TUXFLOTTE_AGENT_CREDENTIALS", "/etc/tuxflotte/agent.credentials"
|
|
)
|
|
FALLBACK_INTERVAL_SECONDS = 60
|
|
|
|
|
|
def log(message):
|
|
print(f"[tuxflotte-agent] {message}", flush=True)
|
|
|
|
|
|
def load_credentials():
|
|
with open(CREDENTIALS_PATH, "r", encoding="utf-8") as f:
|
|
credentials = json.load(f)
|
|
|
|
return credentials["device_id"], credentials["agent_secret"]
|
|
|
|
|
|
def checkin(device_id, agent_secret):
|
|
body = json.dumps({"device_id": device_id}).encode("utf-8")
|
|
|
|
req = urllib.request.Request(
|
|
ANODE_URL + "/api/v1/agent/checkin",
|
|
data=body,
|
|
headers={
|
|
"Content-Type": "application/json",
|
|
"Authorization": f"Bearer {agent_secret}",
|
|
},
|
|
method="POST",
|
|
)
|
|
|
|
with urllib.request.urlopen(req, timeout=15) as response:
|
|
return json.loads(response.read().decode("utf-8"))
|
|
|
|
|
|
def apply_blueprints(ansible_repo, blueprints):
|
|
roles = ",".join(b["ansible_role"] for b in blueprints)
|
|
|
|
log(f"Applying roles via ansible-pull: {roles}")
|
|
|
|
result = subprocess.run(
|
|
[
|
|
"ansible-pull",
|
|
"-U", ansible_repo,
|
|
"--tags", roles,
|
|
"-i", "localhost,",
|
|
"site.yml",
|
|
],
|
|
check=False,
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
log(f"ansible-pull exited with status {result.returncode}")
|
|
|
|
|
|
def run_once(device_id, agent_secret):
|
|
result = checkin(device_id, agent_secret)
|
|
|
|
if not result.get("success"):
|
|
log(f"Check-in failed: {result.get('message', result.get('error'))}")
|
|
return FALLBACK_INTERVAL_SECONDS
|
|
|
|
blueprints = result.get("blueprints", [])
|
|
|
|
if blueprints:
|
|
apply_blueprints(result["ansible_repo"], blueprints)
|
|
else:
|
|
log("Check-in ok, nothing to do.")
|
|
|
|
return result.get("poll_interval_seconds", FALLBACK_INTERVAL_SECONDS)
|
|
|
|
|
|
def main():
|
|
if shutil.which("ansible-pull") is None:
|
|
log("ansible-pull nicht gefunden. Bitte ansible-core installieren.")
|
|
sys.exit(1)
|
|
|
|
device_id, agent_secret = load_credentials()
|
|
|
|
while True:
|
|
try:
|
|
interval = run_once(device_id, agent_secret)
|
|
except (urllib.error.URLError, OSError) as exc:
|
|
log(f"Check-in-Fehler: {exc}")
|
|
interval = FALLBACK_INTERVAL_SECONDS
|
|
|
|
time.sleep(interval)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|