862 lines
28 KiB
Bash
862 lines
28 KiB
Bash
|
|
# Methods library for zot — sourced by install-zot.sh (and the orchestrator's
|
||
|
|
# generated run-*.sh). SCRIPT_DIR and ZOT_* env must be set before sourcing.
|
||
|
|
|
||
|
|
_resolve_kubeconfig() {
|
||
|
|
if [ -n "${KUBECONFIG:-}" ] && [ -f "$KUBECONFIG" ]; then
|
||
|
|
return
|
||
|
|
fi
|
||
|
|
for candidate in \
|
||
|
|
/var/lib/k0s/pki/admin.conf \
|
||
|
|
/etc/kubernetes/admin.conf \
|
||
|
|
/root/.kube/config; do
|
||
|
|
if [ -f "$candidate" ]; then
|
||
|
|
export KUBECONFIG="$candidate"
|
||
|
|
return
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
if command -v k0s >/dev/null 2>&1; then
|
||
|
|
local tmp
|
||
|
|
tmp="$(mktemp)"
|
||
|
|
k0s kubeconfig admin > "$tmp" 2>/dev/null && export KUBECONFIG="$tmp" && return
|
||
|
|
rm -f "$tmp"
|
||
|
|
fi
|
||
|
|
echo "ERROR: kubeconfig not found — set KUBECONFIG explicitly" >&2; exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
_kubectl() {
|
||
|
|
if command -v kubectl >/dev/null 2>&1; then
|
||
|
|
command kubectl "$@"
|
||
|
|
elif command -v k0s >/dev/null 2>&1; then
|
||
|
|
k0s kubectl "$@"
|
||
|
|
else
|
||
|
|
echo "ERROR: no kubectl or k0s binary found" >&2; exit 127
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
_resolve_kubeconfig
|
||
|
|
|
||
|
|
_require_env() {
|
||
|
|
local var="$1"
|
||
|
|
if [ -z "${!var:-}" ]; then
|
||
|
|
echo "ERROR: required variable $var is not set" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
_namespace_exists() {
|
||
|
|
_kubectl get namespace "$ZOT_NAMESPACE" >/dev/null 2>&1
|
||
|
|
}
|
||
|
|
|
||
|
|
_wait_for_statefulset() {
|
||
|
|
local name="$1"
|
||
|
|
local timeout="${2:-300}"
|
||
|
|
_kubectl rollout status statefulset/"$name" \
|
||
|
|
--namespace "$ZOT_NAMESPACE" \
|
||
|
|
--timeout="${timeout}s"
|
||
|
|
}
|
||
|
|
|
||
|
|
_apply_cert_manager_resources() {
|
||
|
|
local cert_template="$SCRIPT_DIR/manifests/certificate.yaml"
|
||
|
|
[ -s "$cert_template" ] || return 0
|
||
|
|
|
||
|
|
_require_env ZOT_CF_DNS_TOKEN
|
||
|
|
|
||
|
|
echo " [cert-manager] applying CF DNS-01 token secret '${ZOT_CF_DNS_SECRET_NAME}' in namespace 'cert-manager'..."
|
||
|
|
_kubectl create secret generic "${ZOT_CF_DNS_SECRET_NAME}" \
|
||
|
|
--namespace cert-manager \
|
||
|
|
--from-literal=api-token="${ZOT_CF_DNS_TOKEN}" \
|
||
|
|
--dry-run=client -o yaml | _kubectl apply -f -
|
||
|
|
|
||
|
|
echo " [cert-manager] applying ClusterIssuer..."
|
||
|
|
local issuer_name
|
||
|
|
issuer_name=$(grep -m1 'name:' "$SCRIPT_DIR/manifests/clusterissuer.yaml" | awk '{print $2}')
|
||
|
|
if ! _kubectl get clusterissuer "$issuer_name" >/dev/null 2>&1; then
|
||
|
|
_kubectl apply --server-side -f "$SCRIPT_DIR/manifests/clusterissuer.yaml"
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo " [cert-manager] applying Certificate (namespace=${ZOT_TLS_NAMESPACE})..."
|
||
|
|
_kubectl apply --server-side -f "$cert_template"
|
||
|
|
|
||
|
|
if [ -s "$SCRIPT_DIR/manifests/referencegrant.yaml" ]; then
|
||
|
|
echo " [cert-manager] applying ReferenceGrant (cross-namespace cert read)..."
|
||
|
|
_kubectl apply --server-side -f "$SCRIPT_DIR/manifests/referencegrant.yaml"
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo " [cert-manager] waiting for TLS secret '${ZOT_TLS_SECRET}' in namespace '${ZOT_TLS_NAMESPACE}' (up to 300s)..."
|
||
|
|
local i=0
|
||
|
|
until _kubectl get secret "${ZOT_TLS_SECRET}" --namespace "${ZOT_TLS_NAMESPACE}" >/dev/null 2>&1; do
|
||
|
|
i=$((i + 1))
|
||
|
|
if [ $i -ge 60 ]; then
|
||
|
|
echo "ERROR: tls secret '${ZOT_TLS_SECRET}' not created after 300s in '${ZOT_TLS_NAMESPACE}' — check ClusterIssuer and DNS challenge" >&2
|
||
|
|
_kubectl describe certificate "${ZOT_TLS_SECRET}" --namespace "${ZOT_TLS_NAMESPACE}" 2>&1 | tail -30 >&2 || true
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
sleep 5
|
||
|
|
done
|
||
|
|
echo " [cert-manager] TLS secret '${ZOT_TLS_SECRET}' ready in '${ZOT_TLS_NAMESPACE}'"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Redesigned 2026-07-03 (candidate remediation, R4 — see
|
||
|
|
# provisioning/.coder/2026-07-03_laws-catalogo-provisioning.plan.md §8 T4-followup).
|
||
|
|
# Was: kubectl get gateway -o json | jq (decide if listener exists) | kubectl patch.
|
||
|
|
# Now: declarative Server-Side Apply of a manifest rendered by the orchestrator
|
||
|
|
# (manifests/public-gateway-listener.yaml.j2 -> .yaml, same convention as
|
||
|
|
# clusterissuer/certificate/referencegrant above). This works safely on a Gateway
|
||
|
|
# object SHARED with other components/field-managers because Gateway API's
|
||
|
|
# spec.listeners is marked `x-kubernetes-list-type: map` (merge key: name) —
|
||
|
|
# Server-Side Apply merges by listener name instead of replacing the whole array,
|
||
|
|
# so this never touches listeners owned by other managers. No read, no decision;
|
||
|
|
# naturally idempotent (re-applying identical content is a no-op).
|
||
|
|
# --force-conflicts: the sharing-key Gateway-creation branch above may have
|
||
|
|
# already declared this same listener via plain (client-side) `kubectl apply`:
|
||
|
|
# take ownership under field-manager=zot-registry rather than erroring on the
|
||
|
|
# pre-existing client-side-apply field ownership.
|
||
|
|
_patch_public_gateway_listener() {
|
||
|
|
local gw="${ZOT_PUBLIC_GATEWAY_NAME}"
|
||
|
|
[ -z "$gw" ] && return 0
|
||
|
|
[ -z "${ZOT_GATEWAY_HOSTNAME_REG}" ] && { echo "ERROR: ZOT_GATEWAY_HOSTNAME_REG required when ZOT_PUBLIC_GATEWAY_NAME is set" >&2; exit 1; }
|
||
|
|
|
||
|
|
local manifest="$SCRIPT_DIR/manifests/public-gateway-listener.yaml"
|
||
|
|
[ -s "$manifest" ] || { echo "ERROR: $manifest not found or empty — was it rendered by the orchestrator?" >&2; exit 1; }
|
||
|
|
|
||
|
|
_kubectl apply --server-side --force-conflicts --field-manager=zot-registry -f "$manifest"
|
||
|
|
echo " [ok] gateway ${gw}: HTTPS listener https-reg applied (server-side, field-manager=zot-registry)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Redesigned 2026-07-03, same pass as the add path: declarative Server-Side Apply
|
||
|
|
# retraction. Applying an explicit empty `listeners: []` under the SAME field
|
||
|
|
# manager that added the listener (zot-registry) tells the API server that
|
||
|
|
# manager now owns zero entries in the merge-keyed (x-kubernetes-list-type=map)
|
||
|
|
# list — it prunes exactly what zot-registry previously contributed (https-reg),
|
||
|
|
# leaving listeners owned by any other field manager on this shared Gateway
|
||
|
|
# untouched. field-manager=zot-registry never declared gatewayClassName or other
|
||
|
|
# top-level Gateway fields (those are owned by whichever manager created the
|
||
|
|
# Gateway itself), so this partial apply cannot affect them. Accepted on the same
|
||
|
|
# documented Gateway API + SSA per-manager ownership basis as the add path — not
|
||
|
|
# separately exercised against a live cluster.
|
||
|
|
_remove_public_gateway_listener() {
|
||
|
|
local gw="${ZOT_PUBLIC_GATEWAY_NAME}"
|
||
|
|
local gw_ns="${ZOT_PUBLIC_GATEWAY_NS}"
|
||
|
|
[ -z "$gw" ] && return 0
|
||
|
|
|
||
|
|
_kubectl apply --server-side --force-conflicts --field-manager=zot-registry -f - <<GATEWAY
|
||
|
|
apiVersion: gateway.networking.k8s.io/v1
|
||
|
|
kind: Gateway
|
||
|
|
metadata:
|
||
|
|
name: ${gw}
|
||
|
|
namespace: ${gw_ns}
|
||
|
|
spec:
|
||
|
|
listeners: []
|
||
|
|
GATEWAY
|
||
|
|
echo " [ok] gateway ${gw}: HTTPS listener https-reg retracted (server-side, field-manager=zot-registry)"
|
||
|
|
}
|
||
|
|
|
||
|
|
_create_public_httproute() {
|
||
|
|
local gw="${ZOT_PUBLIC_GATEWAY_NAME}"
|
||
|
|
local gw_ns="${ZOT_PUBLIC_GATEWAY_NS}"
|
||
|
|
local hostname="${ZOT_GATEWAY_HOSTNAME_REG}"
|
||
|
|
|
||
|
|
[ -z "$gw" ] && return 0
|
||
|
|
|
||
|
|
_kubectl apply -f - <<ROUTE
|
||
|
|
---
|
||
|
|
apiVersion: gateway.networking.k8s.io/v1
|
||
|
|
kind: HTTPRoute
|
||
|
|
metadata:
|
||
|
|
name: zot-public
|
||
|
|
namespace: ${ZOT_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
parentRefs:
|
||
|
|
- group: gateway.networking.k8s.io
|
||
|
|
kind: Gateway
|
||
|
|
name: ${gw}
|
||
|
|
namespace: ${gw_ns}
|
||
|
|
sectionName: https-reg
|
||
|
|
hostnames:
|
||
|
|
- ${hostname}
|
||
|
|
rules:
|
||
|
|
- backendRefs:
|
||
|
|
- name: zot
|
||
|
|
port: ${ZOT_PORT}
|
||
|
|
ROUTE
|
||
|
|
echo " [ok] HTTPRoute zot-public → ${gw}/${gw_ns} for ${hostname}"
|
||
|
|
}
|
||
|
|
|
||
|
|
_generate_config_json() {
|
||
|
|
local admin_user="${ZOT_ADMIN_USER:-regadm}"
|
||
|
|
local data_dir="${ZOT_DATA_DIR:-/var/lib/data/zot}"
|
||
|
|
local port="${ZOT_PORT:-5000}"
|
||
|
|
|
||
|
|
local write_policy
|
||
|
|
write_policy=$(jq -cn --arg u "$admin_user" \
|
||
|
|
'[{"users":[$u],"actions":["read","create","update","delete","detectManifestCollision"]}]')
|
||
|
|
|
||
|
|
local repos_block
|
||
|
|
if [ -n "${ZOT_REPOSITORIES_FILE:-}" ] && [ -f "${ZOT_REPOSITORIES_FILE}" ]; then
|
||
|
|
local repos_src
|
||
|
|
repos_src=$(cat "${ZOT_REPOSITORIES_FILE}")
|
||
|
|
repos_block=$(jq -cn \
|
||
|
|
--argjson src "$repos_src" \
|
||
|
|
--argjson wp "$write_policy" \
|
||
|
|
'{"**": {anonymousPolicy: [], defaultPolicy: []}} +
|
||
|
|
($src | to_entries | map({
|
||
|
|
key: .key,
|
||
|
|
value: {
|
||
|
|
anonymousPolicy: (if .value.anonymous_read then ["read"] else [] end),
|
||
|
|
defaultPolicy: (if .value.anonymous_read then ["read"] else [] end),
|
||
|
|
policies: $wp
|
||
|
|
}
|
||
|
|
}) | from_entries)')
|
||
|
|
else
|
||
|
|
echo "WARNING: ZOT_REPOSITORIES_FILE not set — using catch-all deny only (no namespace ACL)" >&2
|
||
|
|
repos_block='{"**":{"anonymousPolicy":[],"defaultPolicy":[]}}'
|
||
|
|
fi
|
||
|
|
|
||
|
|
local cfg
|
||
|
|
cfg=$(jq -cn \
|
||
|
|
--arg data_dir "$data_dir" \
|
||
|
|
--arg port "$port" \
|
||
|
|
--argjson wp "$write_policy" \
|
||
|
|
--arg admin "$admin_user" \
|
||
|
|
--argjson repos "$repos_block" \
|
||
|
|
'{
|
||
|
|
distSpecVersion: "1.1.0",
|
||
|
|
storage: { rootDirectory: $data_dir, dedupe: false, gc: true },
|
||
|
|
http: {
|
||
|
|
address: "0.0.0.0", port: $port, realm: "zot",
|
||
|
|
readTimeout: "0s", writeTimeout: "0s",
|
||
|
|
accessControl: {
|
||
|
|
adminPolicy: { users: [$admin] },
|
||
|
|
repositories: $repos
|
||
|
|
}
|
||
|
|
},
|
||
|
|
log: { level: "info" },
|
||
|
|
extensions: { search: { enable: true }, ui: { enable: true }, trust: { enable: true, cosign: true } }
|
||
|
|
}')
|
||
|
|
|
||
|
|
if [ -n "${ZOT_S3_BUCKET:-}" ]; then
|
||
|
|
cfg=$(echo "$cfg" | jq \
|
||
|
|
--arg bucket "$ZOT_S3_BUCKET" \
|
||
|
|
--arg region "${ZOT_S3_REGION:-eu-central-1}" \
|
||
|
|
--arg endpoint "${ZOT_S3_ENDPOINT:-}" \
|
||
|
|
--argjson pstyle "${ZOT_S3_PATH_STYLE:-true}" \
|
||
|
|
'.storage.storageDriver = {name:"s3",rootdirectory:"/zot",region:$region,bucket:$bucket,secure:true,skipverify:false,forcepathstyle:$pstyle}
|
||
|
|
| if $endpoint != "" then .storage.storageDriver.regionendpoint = $endpoint else . end')
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ -n "${ZOT_HTPASSWD:-}" ]; then
|
||
|
|
cfg=$(echo "$cfg" | jq '.http.auth = {htpasswd:{path:"/etc/zot/htpasswd"},failDelay:0}')
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ "${ZOT_POD_TLS:-}" = "true" ] && [ -n "${ZOT_TLS_SECRET:-}" ]; then
|
||
|
|
cfg=$(echo "$cfg" | jq '.http.tls = {cert:"/etc/zot/ssl/tls.crt",key:"/etc/zot/ssl/tls.key"}')
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ -n "${ZOT_SYNC_UPSTREAM:-}" ]; then
|
||
|
|
local prefixes_json
|
||
|
|
prefixes_json=$(printf '%s' "${ZOT_SYNC_UPSTREAM_PREFIXES:-domains/**,modes/**}" \
|
||
|
|
| tr ',' '\n' | sed 's/^ *//;s/ *$//' | jq -Rn '[inputs] | map({prefix:.})')
|
||
|
|
cfg=$(echo "$cfg" | jq \
|
||
|
|
--arg u "$ZOT_SYNC_UPSTREAM" \
|
||
|
|
--argjson tls "${ZOT_SYNC_UPSTREAM_TLS:-true}" \
|
||
|
|
--argjson od "${ZOT_SYNC_ON_DEMAND:-true}" \
|
||
|
|
--argjson content "$prefixes_json" \
|
||
|
|
--arg dl "${ZOT_DATA_DIR:-/var/lib/data/zot}/sync-tmp" \
|
||
|
|
'.extensions.sync = {downloadDir:$dl,credentialsFile:"/etc/zot/sync-creds.json",
|
||
|
|
registries:[{urls:[$u],tlsVerify:$tls,onDemand:$od,content:$content}]}')
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "$cfg"
|
||
|
|
}
|
||
|
|
|
||
|
|
_apply_inline_manifests() {
|
||
|
|
local config_json
|
||
|
|
config_json=$(_generate_config_json)
|
||
|
|
|
||
|
|
_kubectl apply -f - <<MANIFEST
|
||
|
|
apiVersion: v1
|
||
|
|
kind: Namespace
|
||
|
|
metadata:
|
||
|
|
name: ${ZOT_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
MANIFEST
|
||
|
|
|
||
|
|
_kubectl apply -f - <<MANIFEST
|
||
|
|
apiVersion: v1
|
||
|
|
kind: ConfigMap
|
||
|
|
metadata:
|
||
|
|
name: zot-config
|
||
|
|
namespace: ${ZOT_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
data:
|
||
|
|
config.json: |
|
||
|
|
$(echo "$config_json" | sed 's/^/ /')
|
||
|
|
MANIFEST
|
||
|
|
|
||
|
|
if [ -z "${ZOT_S3_BUCKET:-}" ]; then
|
||
|
|
if ! _kubectl get pvc zot-data --namespace "$ZOT_NAMESPACE" >/dev/null 2>&1; then
|
||
|
|
_kubectl apply -f - <<MANIFEST
|
||
|
|
apiVersion: v1
|
||
|
|
kind: PersistentVolumeClaim
|
||
|
|
metadata:
|
||
|
|
name: zot-data
|
||
|
|
namespace: ${ZOT_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
annotations:
|
||
|
|
provisioning.io/lifecycle: durable
|
||
|
|
spec:
|
||
|
|
accessModes:
|
||
|
|
- ReadWriteOnce
|
||
|
|
storageClassName: ${ZOT_STORAGE_CLASS}
|
||
|
|
resources:
|
||
|
|
requests:
|
||
|
|
storage: ${ZOT_STORAGE_SIZE}
|
||
|
|
MANIFEST
|
||
|
|
else
|
||
|
|
echo " [skip] PVC zot-data already exists — lifecycle=durable, not resized"
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
echo " [skip] PVC — storage backend is S3 (${ZOT_S3_BUCKET}), using emptyDir for local cache"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# StatefulSet with conditional volume mounts/volumes
|
||
|
|
local auth_mount="" auth_vol="" tls_mount="" tls_vol="" sync_mount="" sync_vol="" data_vol=""
|
||
|
|
if [ -n "${ZOT_HTPASSWD:-}" ]; then
|
||
|
|
auth_mount=$(printf ' - name: auth\n mountPath: /etc/zot/htpasswd\n subPath: htpasswd\n readOnly: true')
|
||
|
|
auth_vol=$(printf ' - name: auth\n secret:\n secretName: zot-auth\n items:\n - key: htpasswd\n path: htpasswd')
|
||
|
|
fi
|
||
|
|
if [ "${ZOT_POD_TLS:-}" = "true" ] && [ -n "${ZOT_TLS_SECRET:-}" ]; then
|
||
|
|
tls_mount=$(printf ' - name: tls\n mountPath: /etc/zot/ssl\n readOnly: true')
|
||
|
|
tls_vol=$(printf ' - name: tls\n secret:\n secretName: %s\n items:\n - key: tls.crt\n path: tls.crt\n - key: tls.key\n path: tls.key' "$ZOT_TLS_SECRET")
|
||
|
|
fi
|
||
|
|
if [ -n "${ZOT_SYNC_UPSTREAM:-}" ]; then
|
||
|
|
sync_mount=$(printf ' - name: sync-creds\n mountPath: /etc/zot/sync-creds.json\n subPath: sync-creds.json\n readOnly: true')
|
||
|
|
sync_vol=$(printf ' - name: sync-creds\n secret:\n secretName: zot-sync-creds\n items:\n - key: sync-creds.json\n path: sync-creds.json')
|
||
|
|
fi
|
||
|
|
if [ -n "${ZOT_S3_BUCKET:-}" ]; then
|
||
|
|
data_vol=' - name: data
|
||
|
|
emptyDir: {}'
|
||
|
|
else
|
||
|
|
data_vol=' - name: data
|
||
|
|
persistentVolumeClaim:
|
||
|
|
claimName: zot-data'
|
||
|
|
fi
|
||
|
|
|
||
|
|
_kubectl apply -f - <<MANIFEST
|
||
|
|
apiVersion: apps/v1
|
||
|
|
kind: StatefulSet
|
||
|
|
metadata:
|
||
|
|
name: zot
|
||
|
|
namespace: ${ZOT_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
serviceName: zot
|
||
|
|
replicas: 1
|
||
|
|
selector:
|
||
|
|
matchLabels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
template:
|
||
|
|
metadata:
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
spec:
|
||
|
|
containers:
|
||
|
|
- name: zot
|
||
|
|
image: ${ZOT_IMAGE}
|
||
|
|
args: ["serve", "/etc/zot/config.json"]
|
||
|
|
ports:
|
||
|
|
- containerPort: ${ZOT_PORT}
|
||
|
|
name: registry
|
||
|
|
env:
|
||
|
|
- name: AWS_ACCESS_KEY_ID
|
||
|
|
valueFrom:
|
||
|
|
secretKeyRef:
|
||
|
|
name: zot-s3-credentials
|
||
|
|
key: access_key
|
||
|
|
- name: AWS_SECRET_ACCESS_KEY
|
||
|
|
valueFrom:
|
||
|
|
secretKeyRef:
|
||
|
|
name: zot-s3-credentials
|
||
|
|
key: secret_key
|
||
|
|
- name: AWS_REGION
|
||
|
|
value: "${ZOT_S3_REGION:-eu-central-1}"
|
||
|
|
volumeMounts:
|
||
|
|
- name: config
|
||
|
|
mountPath: /etc/zot/config.json
|
||
|
|
subPath: config.json
|
||
|
|
readOnly: true
|
||
|
|
- name: data
|
||
|
|
mountPath: ${ZOT_DATA_DIR}
|
||
|
|
${auth_mount:+${auth_mount}}
|
||
|
|
${tls_mount:+${tls_mount}}
|
||
|
|
${sync_mount:+${sync_mount}}
|
||
|
|
readinessProbe:
|
||
|
|
tcpSocket:
|
||
|
|
port: ${ZOT_PORT}
|
||
|
|
initialDelaySeconds: 15
|
||
|
|
periodSeconds: 10
|
||
|
|
livenessProbe:
|
||
|
|
tcpSocket:
|
||
|
|
port: ${ZOT_PORT}
|
||
|
|
initialDelaySeconds: 30
|
||
|
|
periodSeconds: 20
|
||
|
|
volumes:
|
||
|
|
- name: config
|
||
|
|
configMap:
|
||
|
|
name: zot-config
|
||
|
|
${data_vol}
|
||
|
|
${auth_vol:+${auth_vol}}
|
||
|
|
${tls_vol:+${tls_vol}}
|
||
|
|
${sync_vol:+${sync_vol}}
|
||
|
|
MANIFEST
|
||
|
|
|
||
|
|
_kubectl apply -f - <<MANIFEST
|
||
|
|
apiVersion: v1
|
||
|
|
kind: Service
|
||
|
|
metadata:
|
||
|
|
name: zot
|
||
|
|
namespace: ${ZOT_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
selector:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
type: ClusterIP
|
||
|
|
ports:
|
||
|
|
- name: registry
|
||
|
|
port: ${ZOT_PORT}
|
||
|
|
targetPort: ${ZOT_PORT}
|
||
|
|
MANIFEST
|
||
|
|
}
|
||
|
|
|
||
|
|
_do_install() {
|
||
|
|
_require_env ZOT_S3_ACCESS_KEY
|
||
|
|
_require_env ZOT_S3_SECRET_KEY
|
||
|
|
|
||
|
|
_apply_cert_manager_resources
|
||
|
|
|
||
|
|
_kubectl create secret generic zot-s3-credentials \
|
||
|
|
--namespace "$ZOT_NAMESPACE" \
|
||
|
|
--from-literal=access_key="${ZOT_S3_ACCESS_KEY}" \
|
||
|
|
--from-literal=secret_key="${ZOT_S3_SECRET_KEY}" \
|
||
|
|
--dry-run=client -o yaml | _kubectl apply -f -
|
||
|
|
|
||
|
|
if [ -n "${ZOT_HTPASSWD}" ]; then
|
||
|
|
_kubectl create secret generic zot-auth \
|
||
|
|
--namespace "$ZOT_NAMESPACE" \
|
||
|
|
--from-literal=htpasswd="${ZOT_HTPASSWD}" \
|
||
|
|
--dry-run=client -o yaml | _kubectl apply -f -
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ -n "${ZOT_SYNC_UPSTREAM}" ]; then
|
||
|
|
local sync_creds="{}"
|
||
|
|
if [ -n "${ZOT_SYNC_USERNAME}" ] && [ -n "${ZOT_SYNC_PASSWORD}" ]; then
|
||
|
|
# zot credential file keys are hostname-only (no scheme). Strip https:// or http://.
|
||
|
|
local sync_host="${ZOT_SYNC_UPSTREAM#https://}"
|
||
|
|
sync_host="${sync_host#http://}"
|
||
|
|
sync_creds=$(jq -cn \
|
||
|
|
--arg host "$sync_host" \
|
||
|
|
--arg user "${ZOT_SYNC_USERNAME}" \
|
||
|
|
--arg pass "${ZOT_SYNC_PASSWORD}" \
|
||
|
|
'{($host): {username: $user, password: $pass}}')
|
||
|
|
fi
|
||
|
|
_kubectl create secret generic zot-sync-creds \
|
||
|
|
--namespace "$ZOT_NAMESPACE" \
|
||
|
|
--from-literal=sync-creds.json="${sync_creds}" \
|
||
|
|
--dry-run=client -o yaml | _kubectl apply -f -
|
||
|
|
fi
|
||
|
|
|
||
|
|
_apply_inline_manifests
|
||
|
|
|
||
|
|
_kubectl rollout restart statefulset/zot --namespace "$ZOT_NAMESPACE"
|
||
|
|
echo "Waiting for zot StatefulSet to be ready..."
|
||
|
|
_wait_for_statefulset "zot" 300
|
||
|
|
echo "zot installed in namespace ${ZOT_NAMESPACE}"
|
||
|
|
echo " ClusterIP port : ${ZOT_PORT}"
|
||
|
|
echo " S3 bucket : ${ZOT_S3_BUCKET} @ ${ZOT_S3_ENDPOINT:-AWS}"
|
||
|
|
[ -n "${ZOT_HTPASSWD}" ] && echo " Auth : htpasswd enabled"
|
||
|
|
|
||
|
|
if [ "${ZOT_GATEWAY_ENABLED}" = "true" ]; then
|
||
|
|
[ -z "${ZOT_GATEWAY_IP}" ] && { echo "ERROR: ZOT_GATEWAY_ENABLED=true requires ZOT_GATEWAY_IP" >&2; exit 1; }
|
||
|
|
|
||
|
|
# Dedicated IP pool: skipped when sharing an existing LB IP (ZOT_GATEWAY_SHARING_KEY set).
|
||
|
|
# Sharing mode uses infrastructure.annotations (io.cilium/lb-ipam-ips +
|
||
|
|
# lbipam.cilium.io/sharing-key) so the existing pool for that IP is reused.
|
||
|
|
if [ -z "${ZOT_GATEWAY_SHARING_KEY}" ]; then
|
||
|
|
_kubectl apply -f - <<IPPOOL
|
||
|
|
---
|
||
|
|
apiVersion: cilium.io/v2
|
||
|
|
kind: CiliumLoadBalancerIPPool
|
||
|
|
metadata:
|
||
|
|
name: registry-gateway-pool
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: registry-gateway
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
blocks:
|
||
|
|
- cidr: "${ZOT_GATEWAY_IP}/32"
|
||
|
|
serviceSelector:
|
||
|
|
matchLabels:
|
||
|
|
io.cilium.gateway/owning-gateway: registry
|
||
|
|
IPPOOL
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Cross-namespace cert reference: when the TLS secret lives outside the gateway namespace,
|
||
|
|
# the listener must declare the namespace explicitly (Gateway API §SecretObjectReference).
|
||
|
|
# ReferenceGrant in the secret namespace authorizes this read (emitted by referencegrant.yaml).
|
||
|
|
local cert_ref_ns_line=""
|
||
|
|
if [ "${ZOT_TLS_NAMESPACE}" != "${ZOT_GATEWAY_NAMESPACE}" ]; then
|
||
|
|
cert_ref_ns_line=$'\n namespace: '"${ZOT_TLS_NAMESPACE}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
case "${ZOT_GATEWAY_MODE}" in
|
||
|
|
terminate)
|
||
|
|
[ -z "${ZOT_GATEWAY_CERT_SECRET}" ] && { echo "ERROR: mode=terminate requires ZOT_GATEWAY_CERT_SECRET" >&2; exit 1; }
|
||
|
|
if [ -n "${ZOT_GATEWAY_SHARING_KEY}" ]; then
|
||
|
|
_kubectl apply -f - <<GATEWAY
|
||
|
|
---
|
||
|
|
apiVersion: gateway.networking.k8s.io/v1
|
||
|
|
kind: Gateway
|
||
|
|
metadata:
|
||
|
|
name: registry
|
||
|
|
namespace: ${ZOT_GATEWAY_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: registry-gateway
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
gatewayClassName: cilium
|
||
|
|
infrastructure:
|
||
|
|
annotations:
|
||
|
|
io.cilium/lb-ipam-ips: "${ZOT_GATEWAY_IP}"
|
||
|
|
lbipam.cilium.io/sharing-key: "${ZOT_GATEWAY_SHARING_KEY}"
|
||
|
|
listeners:
|
||
|
|
- name: https-ui
|
||
|
|
hostname: "${ZOT_GATEWAY_HOSTNAME_UI}"
|
||
|
|
port: 443
|
||
|
|
protocol: HTTPS
|
||
|
|
tls:
|
||
|
|
mode: Terminate
|
||
|
|
certificateRefs:
|
||
|
|
- name: ${ZOT_GATEWAY_CERT_SECRET}${cert_ref_ns_line}
|
||
|
|
allowedRoutes:
|
||
|
|
namespaces:
|
||
|
|
from: All
|
||
|
|
- name: https-reg
|
||
|
|
hostname: "${ZOT_GATEWAY_HOSTNAME_REG}"
|
||
|
|
port: 443
|
||
|
|
protocol: HTTPS
|
||
|
|
tls:
|
||
|
|
mode: Terminate
|
||
|
|
certificateRefs:
|
||
|
|
- name: ${ZOT_GATEWAY_CERT_SECRET}${cert_ref_ns_line}
|
||
|
|
allowedRoutes:
|
||
|
|
namespaces:
|
||
|
|
from: All
|
||
|
|
---
|
||
|
|
apiVersion: gateway.networking.k8s.io/v1
|
||
|
|
kind: HTTPRoute
|
||
|
|
metadata:
|
||
|
|
name: zot
|
||
|
|
namespace: ${ZOT_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
parentRefs:
|
||
|
|
- name: registry
|
||
|
|
namespace: ${ZOT_GATEWAY_NAMESPACE}
|
||
|
|
hostnames:
|
||
|
|
- ${ZOT_GATEWAY_HOSTNAME_UI}
|
||
|
|
- ${ZOT_GATEWAY_HOSTNAME_REG}
|
||
|
|
rules:
|
||
|
|
- backendRefs:
|
||
|
|
- name: zot
|
||
|
|
port: ${ZOT_PORT}
|
||
|
|
GATEWAY
|
||
|
|
else
|
||
|
|
_kubectl apply -f - <<GATEWAY
|
||
|
|
---
|
||
|
|
apiVersion: gateway.networking.k8s.io/v1
|
||
|
|
kind: Gateway
|
||
|
|
metadata:
|
||
|
|
name: registry
|
||
|
|
namespace: ${ZOT_GATEWAY_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: registry-gateway
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
gatewayClassName: cilium
|
||
|
|
addresses:
|
||
|
|
- type: IPAddress
|
||
|
|
value: "${ZOT_GATEWAY_IP}"
|
||
|
|
listeners:
|
||
|
|
- name: https
|
||
|
|
port: 443
|
||
|
|
protocol: HTTPS
|
||
|
|
tls:
|
||
|
|
mode: Terminate
|
||
|
|
certificateRefs:
|
||
|
|
- name: ${ZOT_GATEWAY_CERT_SECRET}${cert_ref_ns_line}
|
||
|
|
allowedRoutes:
|
||
|
|
namespaces:
|
||
|
|
from: All
|
||
|
|
---
|
||
|
|
apiVersion: gateway.networking.k8s.io/v1
|
||
|
|
kind: HTTPRoute
|
||
|
|
metadata:
|
||
|
|
name: zot
|
||
|
|
namespace: ${ZOT_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
parentRefs:
|
||
|
|
- name: registry
|
||
|
|
namespace: ${ZOT_GATEWAY_NAMESPACE}
|
||
|
|
hostnames:
|
||
|
|
- ${ZOT_GATEWAY_HOSTNAME_UI}
|
||
|
|
- ${ZOT_GATEWAY_HOSTNAME_REG}
|
||
|
|
rules:
|
||
|
|
- backendRefs:
|
||
|
|
- name: zot
|
||
|
|
port: ${ZOT_PORT}
|
||
|
|
GATEWAY
|
||
|
|
fi
|
||
|
|
echo " Gateway : registry → ${ZOT_GATEWAY_IP} (TLS terminate :443 → HTTP :${ZOT_PORT})"
|
||
|
|
;;
|
||
|
|
passthrough)
|
||
|
|
if [ -n "${ZOT_GATEWAY_SHARING_KEY}" ]; then
|
||
|
|
_kubectl apply -f - <<GATEWAY
|
||
|
|
---
|
||
|
|
apiVersion: gateway.networking.k8s.io/v1
|
||
|
|
kind: Gateway
|
||
|
|
metadata:
|
||
|
|
name: registry
|
||
|
|
namespace: ${ZOT_GATEWAY_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: registry-gateway
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
gatewayClassName: cilium
|
||
|
|
infrastructure:
|
||
|
|
annotations:
|
||
|
|
io.cilium/lb-ipam-ips: "${ZOT_GATEWAY_IP}"
|
||
|
|
lbipam.cilium.io/sharing-key: "${ZOT_GATEWAY_SHARING_KEY}"
|
||
|
|
listeners:
|
||
|
|
- name: https-passthrough-ui
|
||
|
|
hostname: "${ZOT_GATEWAY_HOSTNAME_UI}"
|
||
|
|
port: 443
|
||
|
|
protocol: TLS
|
||
|
|
tls:
|
||
|
|
mode: Passthrough
|
||
|
|
allowedRoutes:
|
||
|
|
namespaces:
|
||
|
|
from: All
|
||
|
|
kinds:
|
||
|
|
- group: gateway.networking.k8s.io
|
||
|
|
kind: TLSRoute
|
||
|
|
- name: https-passthrough-reg
|
||
|
|
hostname: "${ZOT_GATEWAY_HOSTNAME_REG}"
|
||
|
|
port: 443
|
||
|
|
protocol: TLS
|
||
|
|
tls:
|
||
|
|
mode: Passthrough
|
||
|
|
allowedRoutes:
|
||
|
|
namespaces:
|
||
|
|
from: All
|
||
|
|
kinds:
|
||
|
|
- group: gateway.networking.k8s.io
|
||
|
|
kind: TLSRoute
|
||
|
|
---
|
||
|
|
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||
|
|
kind: TLSRoute
|
||
|
|
metadata:
|
||
|
|
name: zot
|
||
|
|
namespace: ${ZOT_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
parentRefs:
|
||
|
|
- name: registry
|
||
|
|
namespace: ${ZOT_GATEWAY_NAMESPACE}
|
||
|
|
hostnames:
|
||
|
|
- ${ZOT_GATEWAY_HOSTNAME_UI}
|
||
|
|
- ${ZOT_GATEWAY_HOSTNAME_REG}
|
||
|
|
rules:
|
||
|
|
- backendRefs:
|
||
|
|
- name: zot
|
||
|
|
port: ${ZOT_PORT}
|
||
|
|
GATEWAY
|
||
|
|
else
|
||
|
|
_kubectl apply -f - <<GATEWAY
|
||
|
|
---
|
||
|
|
apiVersion: gateway.networking.k8s.io/v1
|
||
|
|
kind: Gateway
|
||
|
|
metadata:
|
||
|
|
name: registry
|
||
|
|
namespace: ${ZOT_GATEWAY_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: registry-gateway
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
gatewayClassName: cilium
|
||
|
|
addresses:
|
||
|
|
- type: IPAddress
|
||
|
|
value: "${ZOT_GATEWAY_IP}"
|
||
|
|
listeners:
|
||
|
|
- name: https-passthrough
|
||
|
|
port: 443
|
||
|
|
protocol: TLS
|
||
|
|
tls:
|
||
|
|
mode: Passthrough
|
||
|
|
allowedRoutes:
|
||
|
|
namespaces:
|
||
|
|
from: All
|
||
|
|
kinds:
|
||
|
|
- group: gateway.networking.k8s.io
|
||
|
|
kind: TLSRoute
|
||
|
|
---
|
||
|
|
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||
|
|
kind: TLSRoute
|
||
|
|
metadata:
|
||
|
|
name: zot
|
||
|
|
namespace: ${ZOT_NAMESPACE}
|
||
|
|
labels:
|
||
|
|
app.kubernetes.io/name: zot
|
||
|
|
app.kubernetes.io/managed-by: provisioning
|
||
|
|
spec:
|
||
|
|
parentRefs:
|
||
|
|
- name: registry
|
||
|
|
namespace: ${ZOT_GATEWAY_NAMESPACE}
|
||
|
|
hostnames:
|
||
|
|
- ${ZOT_GATEWAY_HOSTNAME_UI}
|
||
|
|
- ${ZOT_GATEWAY_HOSTNAME_REG}
|
||
|
|
rules:
|
||
|
|
- backendRefs:
|
||
|
|
- name: zot
|
||
|
|
port: ${ZOT_PORT}
|
||
|
|
GATEWAY
|
||
|
|
fi
|
||
|
|
echo " Gateway : registry → ${ZOT_GATEWAY_IP} (TLS passthrough :443)"
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
echo "ERROR: unknown ZOT_GATEWAY_MODE '${ZOT_GATEWAY_MODE}'. Valid: terminate | passthrough" >&2
|
||
|
|
exit 1
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
echo " UI : https://${ZOT_GATEWAY_HOSTNAME_UI}"
|
||
|
|
echo " Registry : https://${ZOT_GATEWAY_HOSTNAME_REG}"
|
||
|
|
|
||
|
|
_patch_public_gateway_listener
|
||
|
|
_create_public_httproute
|
||
|
|
fi
|
||
|
|
|
||
|
|
_upload_cosign_trust_key
|
||
|
|
}
|
||
|
|
|
||
|
|
# Upload cosign public key to zot's imagetrust trust store.
|
||
|
|
# API: POST /v2/_zot/ext/cosign (Content-Type: text/plain, PEM public key body).
|
||
|
|
# Skipped when ZOT_COSIGN_PUBKEY_PATH is unset or ZOT_ADMIN_PASS is empty.
|
||
|
|
# Uses ZOT_GATEWAY_HOSTNAME_REG via curl — never propagates failures.
|
||
|
|
_upload_cosign_trust_key() {
|
||
|
|
[ -z "${ZOT_COSIGN_PUBKEY_PATH:-}" ] && return 0
|
||
|
|
if [ ! -f "${ZOT_COSIGN_PUBKEY_PATH}" ]; then
|
||
|
|
echo "WARNING: ZOT_COSIGN_PUBKEY_PATH=${ZOT_COSIGN_PUBKEY_PATH} not found — skipping imagetrust upload" >&2
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
if [ -z "${ZOT_ADMIN_PASS:-}" ]; then
|
||
|
|
echo "WARNING: ZOT_ADMIN_PASS not set — skipping imagetrust cosign key upload" >&2
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
local upload_url="https://${ZOT_GATEWAY_HOSTNAME_REG}/v2/_zot/ext/cosign"
|
||
|
|
echo " [imagetrust] uploading cosign public key → ${upload_url}..."
|
||
|
|
|
||
|
|
local http_code curl_exit
|
||
|
|
set +e
|
||
|
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
||
|
|
-X POST "${upload_url}" \
|
||
|
|
-u "${ZOT_ADMIN_USER}:${ZOT_ADMIN_PASS}" \
|
||
|
|
-H "Content-Type: text/plain" \
|
||
|
|
--data-binary "@${ZOT_COSIGN_PUBKEY_PATH}" 2>/dev/null)
|
||
|
|
curl_exit=$?
|
||
|
|
set -e
|
||
|
|
|
||
|
|
if [ "${curl_exit}" -ne 0 ]; then
|
||
|
|
echo "WARNING: curl failed (exit ${curl_exit}) reaching ${ZOT_GATEWAY_HOSTNAME_REG} — imagetrust key upload skipped" >&2
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ "${http_code}" = "200" ] || [ "${http_code}" = "201" ] || [ "${http_code}" = "204" ]; then
|
||
|
|
echo " [imagetrust] cosign public key uploaded (HTTP ${http_code})"
|
||
|
|
else
|
||
|
|
echo "WARNING: imagetrust cosign key upload returned HTTP ${http_code} — run manually: POST ${upload_url}" >&2
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
_do_update() {
|
||
|
|
_kubectl set image statefulset/zot \
|
||
|
|
zot="${ZOT_IMAGE}" \
|
||
|
|
--namespace "$ZOT_NAMESPACE"
|
||
|
|
echo "Waiting for zot rollout..."
|
||
|
|
_wait_for_statefulset "zot" 300
|
||
|
|
echo "zot updated to image ${ZOT_IMAGE}"
|
||
|
|
}
|
||
|
|
|
||
|
|
_do_delete() {
|
||
|
|
if _namespace_exists; then
|
||
|
|
echo "WARNING: this deletes the zot StatefulSet but NOT the PVC (durable lifecycle)"
|
||
|
|
_kubectl delete statefulset zot --namespace "$ZOT_NAMESPACE" --timeout=60s || true
|
||
|
|
_kubectl delete service zot --namespace "$ZOT_NAMESPACE" --timeout=30s || true
|
||
|
|
_kubectl delete configmap zot-config --namespace "$ZOT_NAMESPACE" --timeout=30s || true
|
||
|
|
_kubectl delete httproute zot --namespace "$ZOT_NAMESPACE" --timeout=30s 2>/dev/null || true
|
||
|
|
_kubectl delete httproute zot-public --namespace "$ZOT_NAMESPACE" --timeout=30s 2>/dev/null || true
|
||
|
|
_kubectl delete tlsroute zot --namespace "$ZOT_NAMESPACE" --timeout=30s 2>/dev/null || true
|
||
|
|
echo "zot removed — PVC and S3 data preserved"
|
||
|
|
else
|
||
|
|
echo "Namespace ${ZOT_NAMESPACE} does not exist, nothing to delete"
|
||
|
|
fi
|
||
|
|
_kubectl delete gateway registry --namespace "${ZOT_GATEWAY_NAMESPACE}" --timeout=30s || true
|
||
|
|
_remove_public_gateway_listener
|
||
|
|
}
|
||
|
|
|
||
|
|
_do_health() {
|
||
|
|
local pod
|
||
|
|
pod=$(_kubectl get pod --namespace "$ZOT_NAMESPACE" \
|
||
|
|
--selector app.kubernetes.io/name=zot \
|
||
|
|
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||
|
|
if [ -z "$pod" ]; then
|
||
|
|
echo "UNHEALTHY: no zot pod found in namespace ${ZOT_NAMESPACE}" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
local scheme="http"
|
||
|
|
local wget_tls_flag=""
|
||
|
|
if [ -n "${ZOT_TLS_SECRET}" ]; then
|
||
|
|
scheme="https"
|
||
|
|
wget_tls_flag="--no-check-certificate"
|
||
|
|
fi
|
||
|
|
local status
|
||
|
|
status=$(_kubectl exec --namespace "$ZOT_NAMESPACE" "$pod" -- \
|
||
|
|
wget -qO- --server-response ${wget_tls_flag} "${scheme}://localhost:${ZOT_PORT}/v2/" 2>&1 \
|
||
|
|
| grep "HTTP/" | tail -1 | awk '{print $2}' || echo "")
|
||
|
|
if [ "$status" = "200" ] || [ "$status" = "401" ]; then
|
||
|
|
echo "HEALTHY: zot /v2/ returned HTTP ${status}"
|
||
|
|
else
|
||
|
|
echo "UNHEALTHY: zot /v2/ returned HTTP ${status} (expected 200 or 401)" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|