Sync Harbor and GHCR Docker credentials from distinct source Secrets, avoid client-side secret annotations, and preserve Harbor-only default ServiceAccount binding.
107 lines
4.5 KiB
YAML
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
|