# services/harbor/bootstrap-jobs/cassandra-registry-ensure-job.yaml # One-off repair/audit job for the Cassandra Harbor migration. # Keep suspended unless the Harbor project or robot scopes need to be recreated. apiVersion: batch/v1 kind: Job metadata: name: cassandra-registry-ensure-1 namespace: harbor spec: suspend: true backoffLimit: 1 template: metadata: annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/agent-pre-populate-only: "true" vault.hashicorp.com/role: "harbor" vault.hashicorp.com/agent-inject-secret-harbor-admin.sh: "kv/data/atlas/harbor/harbor-core" vault.hashicorp.com/agent-inject-template-harbor-admin.sh: | {{- with secret "kv/data/atlas/harbor/harbor-core" }} export HARBOR_ADMIN_PASSWORD="{{ .Data.data.harbor_admin_password }}" {{- end }} spec: restartPolicy: Never serviceAccountName: harbor-vault-sync containers: - name: ensure image: python:3.11-alpine command: ["python", "-c"] args: - | import base64 import json import os import urllib.error import urllib.request secret_path = "/vault/secrets/harbor-admin.sh" env = {} with open(secret_path, "r", encoding="utf-8") as handle: for line in handle: line = line.strip() if not line.startswith("export "): continue key, value = line[len("export ") :].split("=", 1) env[key] = value.strip('"') password = env["HARBOR_ADMIN_PASSWORD"] api = os.environ.get( "HARBOR_API", "http://harbor-core.harbor.svc.cluster.local/api/v2.0", ).rstrip("/") auth = base64.b64encode(f"admin:{password}".encode("utf-8")).decode("ascii") def request(method, path, payload=None, ok=(200,)): data = None headers = {"Authorization": f"Basic {auth}"} if payload is not None: data = json.dumps(payload).encode("utf-8") headers["Content-Type"] = "application/json" req = urllib.request.Request( f"{api}{path}", data=data, method=method, headers=headers, ) try: with urllib.request.urlopen(req, timeout=30) as response: body = response.read() if response.status not in ok: raise RuntimeError(f"{method} {path} returned {response.status}") if not body: return None return json.loads(body.decode("utf-8")) except urllib.error.HTTPError as exc: detail = exc.read().decode("utf-8", errors="replace") if exc.code in ok: return None raise RuntimeError(f"{method} {path} returned {exc.code}: {detail}") from exc projects = request("GET", "/projects?name=cassandra") if not projects: request( "POST", "/projects", { "project_name": "cassandra", "metadata": { "public": "false", "auto_scan": "false", "enable_content_trust": "false", "prevent_vul": "false", "reuse_sys_cve_allowlist": "true", }, }, ok=(201, 409), ) def ensure_permission(robot_name, access, description=None): robots = request("GET", "/robots?page_size=100") matches = [robot for robot in robots if robot.get("name") == robot_name] if not matches: raise RuntimeError(f"Harbor robot {robot_name} not found") robot = request("GET", f"/robots/{matches[0]['id']}") permissions = list(robot.get("permissions") or []) if not any( item.get("kind") == "project" and item.get("namespace") == "cassandra" for item in permissions ): permissions.append( {"kind": "project", "namespace": "cassandra", "access": access} ) payload = { "name": robot["name"], "level": robot["level"], "disable": bool(robot.get("disable", False)), "duration": int(robot.get("duration", -1)), "permissions": permissions, } if description: payload["description"] = description elif robot.get("description"): payload["description"] = robot["description"] request("PUT", f"/robots/{robot['id']}", payload, ok=(200,)) ensure_permission( "robot$pull-atlas", [{"resource": "repository", "action": "pull"}], ) ensure_permission( "robot$jenkins-pipelines", [ {"resource": "repository", "action": "pull"}, {"resource": "repository", "action": "push"}, ], "Jenkins image push robot for bstein, veles, and cassandra pipelines", ) print("Cassandra Harbor project and robot scopes are present")