From 7c6d9282f666be21838551fee7dddf83841e2268 Mon Sep 17 00:00:00 2001 From: jenkins Date: Sat, 25 Jul 2026 00:52:21 -0300 Subject: [PATCH] feat(cassandra): formalize registry and Jenkins cutover --- .../cassandra-registry-ensure-job.yaml | 138 ++++++++++++++++++ services/harbor/kustomization.yaml | 1 + services/jenkins/configmap-jcasc.yaml | 6 +- 3 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 services/harbor/bootstrap-jobs/cassandra-registry-ensure-job.yaml diff --git a/services/harbor/bootstrap-jobs/cassandra-registry-ensure-job.yaml b/services/harbor/bootstrap-jobs/cassandra-registry-ensure-job.yaml new file mode 100644 index 000000000..c5db9c537 --- /dev/null +++ b/services/harbor/bootstrap-jobs/cassandra-registry-ensure-job.yaml @@ -0,0 +1,138 @@ +# 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") diff --git a/services/harbor/kustomization.yaml b/services/harbor/kustomization.yaml index 3018b3cab..6a4ce0025 100644 --- a/services/harbor/kustomization.yaml +++ b/services/harbor/kustomization.yaml @@ -12,6 +12,7 @@ resources: - certificate.yaml - helmrelease.yaml - vault-sync-deployment.yaml + - bootstrap-jobs/cassandra-registry-ensure-job.yaml - image.yaml configMapGenerator: - name: harbor-vault-entrypoint diff --git a/services/jenkins/configmap-jcasc.yaml b/services/jenkins/configmap-jcasc.yaml index c137b7dbf..0bee610d7 100644 --- a/services/jenkins/configmap-jcasc.yaml +++ b/services/jenkins/configmap-jcasc.yaml @@ -429,15 +429,15 @@ data: } } } - pipelineJob('veles') { + pipelineJob('cassandra') { disabled(false) - description('Staged Veles alpha image pipeline. Backend/frontend should build linux/amd64 and linux/arm64; sim-worker may begin amd64-only if Forge dependencies require it.') + description('Staged Cassandra alpha image pipeline. Backend/frontend should build linux/amd64 and linux/arm64; sim-worker may begin amd64-only if Forge dependencies require it.') definition { cpsScm { scm { git { remote { - url('https://scm.bstein.dev/bstein/veles.git') + url('https://scm.bstein.dev/bstein/cassandra.git') credentials('gitea-pat') } branches('*/main')