Files
ljwx-deploy/cluster-prod/registry-pull-secret-sync-cronjob.yaml
T
BrunoGao fc9dbaa9d1 fix(cluster): preserve registry-specific pull credentials (#343)
Sync Harbor and GHCR Docker credentials from distinct source Secrets, avoid client-side secret annotations, and preserve Harbor-only default ServiceAccount binding.
2026-07-31 18:33:34 +08:00

107 lines
4.5 KiB
YAML

apiVersion: batch/v1
kind: CronJob
metadata:
name: registry-pull-secret-sync
namespace: dev
labels:
app.kubernetes.io/name: registry-pull-secret-sync
app.kubernetes.io/part-of: bid-mvp-factory
spec:
schedule: "*/2 * * * *"
timeZone: Asia/Tokyo
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
serviceAccountName: registry-pull-secret-syncer
restartPolicy: OnFailure
containers:
- name: sync
image: harbor.omniverseai.net/library/docker.io/curlimages/curl:8.11.1
imagePullPolicy: IfNotPresent
env:
- name: SOURCE_NAMESPACE
value: default
- name: SECRET_MAPPINGS
value: harbor-registry=harbor-registry,ghcr-pull=ghcr-pull,harbor-registry=regcred
- name: TARGET_NAMESPACE_SELECTOR
value: registry-sync.ljwx.io/enabled=true
- name: DEFAULT_SERVICE_ACCOUNT_SECRET
value: harbor-registry
command:
- /bin/sh
- -ec
- |
set -eu
kubectl_bin="/tmp/kubectl"
case "$(uname -m)" in
x86_64|amd64) kubectl_arch="amd64" ;;
aarch64|arm64) kubectl_arch="arm64" ;;
*)
echo "unsupported arch: $(uname -m)"
exit 1
;;
esac
curl -fsSL -o "${kubectl_bin}" "https://dl.k8s.io/release/v1.33.5/bin/linux/${kubectl_arch}/kubectl"
chmod +x "${kubectl_bin}"
namespaces="$("${kubectl_bin}" get ns -l "${TARGET_NAMESPACE_SELECTOR}" -o jsonpath='{.items[*].metadata.name}')"
if [ -z "${namespaces}" ]; then
echo "no target namespaces matched: ${TARGET_NAMESPACE_SELECTOR}"
exit 0
fi
for mapping in $(printf '%s' "${SECRET_MAPPINGS}" | tr ',' ' '); do
source_secret="${mapping%%=*}"
target_secret="${mapping#*=}"
if [ -z "${source_secret}" ] || [ -z "${target_secret}" ] \
|| [ "${source_secret}" = "${mapping}" ]; then
echo "invalid registry secret mapping: ${mapping}"
exit 1
fi
dockerconfigjson="$("${kubectl_bin}" -n "${SOURCE_NAMESPACE}" get secret "${source_secret}" -o jsonpath='{.data.\.dockerconfigjson}')"
if [ -z "${dockerconfigjson}" ]; then
echo "source secret is empty: ${SOURCE_NAMESPACE}/${source_secret}"
exit 1
fi
for ns in ${namespaces}; do
cat <<EOF | "${kubectl_bin}" apply \
--server-side \
--force-conflicts \
--field-manager=registry-pull-secret-syncer \
-f -
apiVersion: v1
kind: Secret
metadata:
name: ${target_secret}
namespace: ${ns}
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: ${dockerconfigjson}
EOF
"${kubectl_bin}" -n "${ns}" annotate secret \
"${target_secret}" \
kubectl.kubernetes.io/last-applied-configuration- \
>/dev/null 2>&1 || true
done
done
for ns in ${namespaces}; do
current="$("${kubectl_bin}" -n "${ns}" get sa default -o jsonpath='{.imagePullSecrets[*].name}' 2>/dev/null || true)"
if printf '%s\n' "${current}" | tr ' ' '\n' | grep -qx "${DEFAULT_SERVICE_ACCOUNT_SECRET}"; then
continue
fi
if [ -z "${current}" ]; then
"${kubectl_bin}" -n "${ns}" patch sa default --type=merge -p "{\"imagePullSecrets\":[{\"name\":\"${DEFAULT_SERVICE_ACCOUNT_SECRET}\"}]}"
else
"${kubectl_bin}" -n "${ns}" patch sa default --type=json -p "[{\"op\":\"add\",\"path\":\"/imagePullSecrets/-\",\"value\":{\"name\":\"${DEFAULT_SERVICE_ACCOUNT_SECRET}\"}}]"
fi
done