atlas-iac/services/harbor/hermes-agent-base-mirror-job.yaml
jenkins a7fc9b20a0 fix(harbor): mirror to external Harbor endpoint (valid TLS)
harbor-core internally advertises an HTTPS token realm, so skopeo could not push
over HTTP. Push to registry.bstein.dev (valid cert, the path kaniko already
uses); the image lands in the same 'mirror' project and stays internally pullable.
2026-08-25 14:23:51 -03:00

150 lines
6.4 KiB
YAML

# services/harbor/hermes-agent-base-mirror-job.yaml
#
# One-shot mirror of the Hermes agent base image INDEX from docker.io into the
# in-cluster Harbor "mirror" project, so the reviewed image build
# (dockerfiles/Dockerfile.hermes-agent) pulls its FROM base internally with no
# docker.io fallback (which is IPv6-broken from build pods).
#
# Kept `suspend: true` like bootstrap-jobs/cassandra-registry-ensure-job.yaml:
# it needs egress to docker.io (registry-1.docker.io / *.pythonhosted is NOT
# involved here) and is only run deliberately, once per base-digest bump. To run
# it, an operator clears suspend (or `kubectl create job --from`) AFTER updating
# the digest in BOTH args below and in the Dockerfile FROM. Because the digest
# is content-addressed, `skopeo copy --all` reproduces the identical index and
# both arch leaves in Harbor -- the build stays digest-pinned and multi-arch.
apiVersion: batch/v1
kind: Job
metadata:
name: harbor-hermes-agent-base-mirror-1
namespace: harbor
spec:
suspend: true
backoffLimit: 2
activeDeadlineSeconds: 1800
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-pre-populate-only: "true"
# This Job has an app init container (ensure-project) that reads the
# injected secret, so Vault's init must run FIRST — otherwise it is
# appended after ensure-project and the secret file isn't written yet.
vault.hashicorp.com/agent-init-first: "true"
vault.hashicorp.com/agent-run-as-user: "65532"
vault.hashicorp.com/agent-run-as-group: "65532"
vault.hashicorp.com/role: harbor-policy-bootstrap
vault.hashicorp.com/agent-inject-secret-harbor-admin-password: kv/data/atlas/harbor/harbor-core
vault.hashicorp.com/agent-inject-template-harbor-admin-password: |
{{- with secret "kv/data/atlas/harbor/harbor-core" -}}
{{ .Data.data.harbor_admin_password }}
{{- end -}}
spec:
serviceAccountName: harbor-policy-bootstrap
enableServiceLinks: false
restartPolicy: Never
nodeSelector:
hardware: rpi5
kubernetes.io/arch: arm64
node-role.kubernetes.io/worker: "true"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values: [titan-04, titan-14, titan-18, titan-19, titan-24]
securityContext:
fsGroup: 65532
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
initContainers:
# Ensure the public "mirror" project exists before skopeo tries to push
# into it (Harbor only auto-creates repositories inside an existing
# project). Uses the same Vault-injected admin password as the sibling
# Harbor immutability jobs -- no new credential is introduced.
- name: ensure-project
image: docker.io/library/python@sha256:efcdfa6a6b2fd2afb9c7dfa9a5b288a6f68338b5cfdebe6b637d986067d85757
imagePullPolicy: IfNotPresent
command: [python3, /scripts/harbor_mirror_project_ensure.py]
env:
- name: HARBOR_API_ORIGIN
value: http://harbor-core.harbor.svc.cluster.local/api/v2.0
- name: HARBOR_ADMIN_PASSWORD_FILE
value: /vault/secrets/harbor-admin-password
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
runAsGroup: 65532
runAsNonRoot: true
runAsUser: 65532
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
- name: tmp
mountPath: /tmp
resources:
requests: {cpu: 25m, memory: 32Mi}
limits: {cpu: 250m, memory: 128Mi}
containers:
# skopeo copies the WHOLE multi-arch index (--all) straight from docker.io
# to the internal Harbor registry over HTTP (--dest-tls-verify=false, the
# same insecure in-cluster endpoint Kaniko already trusts). Source is the
# public upstream image, so no source credential is needed; the digest is
# asserted on both ends so a drifted upstream tag cannot be mirrored.
- name: mirror
image: quay.io/skopeo/stable@sha256:94f5c5e26997e2e78c234ec9abf19a391c234b39eb22e6d1210d0b527c97dcc8
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c"]
args:
- |
set -eu
src="docker://nousresearch/hermes-agent@sha256:9c841866021c54c4596849f6135717e8a4d52ba510b7f52c50aef1de1a283973"
# Push to the EXTERNAL Harbor endpoint (valid TLS) — the same path
# kaniko already publishes to — to avoid harbor-core's internal
# HTTP/token-realm quirks. The image lands in the same Harbor
# 'mirror' project, so kaniko still pulls it internally via harbor-core.
dst="docker://registry.bstein.dev/mirror/hermes-agent@sha256:9c841866021c54c4596849f6135717e8a4d52ba510b7f52c50aef1de1a283973"
pw="$(cat /vault/secrets/harbor-admin-password)"
exec skopeo copy --all \
--src-tls-verify=true \
--dest-tls-verify=true \
--dest-creds "admin:${pw}" \
"${src}" "${dst}"
env:
- name: HOME
value: /tmp
- name: TMPDIR
value: /tmp
- name: XDG_RUNTIME_DIR
value: /tmp
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
runAsGroup: 65532
runAsNonRoot: true
runAsUser: 65532
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp
mountPath: /tmp
resources:
requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: "1", memory: 1Gi}
volumes:
- name: scripts
configMap:
name: harbor-hermes-agent-base-mirror-script
defaultMode: 0555
- name: tmp
emptyDir: {}