Skip to content

Commit cf8c18e

Browse files
[CRDB-62682] helm: make hook images configurable for air-gapped support
This commit replaces hardcoded kubectl images in pre-upgrade validation hooks with configurable values via `hooks.image` for air-gapped environments. Image manifest which lists all the images used in both charts and a script for mirroring images are also added.
1 parent 4e7d26a commit cf8c18e

File tree

10 files changed

+291
-3
lines changed

10 files changed

+291
-3
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [Unreleased]
6+
### Added
7+
- Hook images (`bitnami/kubectl`, `dtzar/helm-kubectl`) are now configurable via
8+
`hooks.kubectlImage.{registry,repository,tag,pullPolicy}` in both charts. This
9+
unblocks air-gapped deployments where pulling from public registries is not possible.
10+
- Added `cockroachdb-parent/images.txt` manifest listing all container images
11+
required by both charts, including operator-managed runtime images.
12+
- Added `scripts/mirror-images.sh` to mirror images from the manifest into an
13+
internal registry using `crane` or `skopeo`.
14+
515
## [cockroachdb-parent-26.1.1-preview+2] 2026-03-26
616
### Changed
717
- **API Version Migration**: The operator now uses an image that removes `v1alpha1` entirely and keeps only `v1beta1`.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ test/e2e/single-region: bin/cockroach bin/kubectl bin/helm build/self-signer bin
130130
@PATH="$(PWD)/bin:${PATH}" go test -timeout 60m -v -test.run TestOperatorInSingleRegion ./tests/e2e/operator/singleRegion/... || (echo "Single region tests failed with exit code $$?" && exit 1)
131131

132132
test/e2e/migrate: bin/cockroach bin/kubectl bin/helm bin/migration-helper build/self-signer test/cluster/up/3
133-
@PATH="$(PWD)/bin:${PATH}" go test -timeout 30m -v ./tests/e2e/migrate/... || EXIT_CODE=$$?; \
133+
@PATH="$(PWD)/bin:${PATH}" go test -timeout 60m -v ./tests/e2e/migrate/... || EXIT_CODE=$$?; \
134134
$(MAKE) test/cluster/down; \
135135
exit $${EXIT_CODE:-0}
136136

build/templates/cockroachdb-parent/charts/cockroachdb/values.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,24 @@ cockroachdb:
544544
# # Possible Values: disabled, primary, standby
545545
# mode: "disabled"
546546

547+
# hooks captures the configuration for Helm hook jobs (pre-upgrade validation, etc.).
548+
# All hook jobs use the image defined here by default. Override these
549+
# values to use images from an internal registry in air-gapped environments.
550+
hooks:
551+
# kubectlImage is the container image used by hook jobs that run kubectl commands.
552+
# If a future hook requires a different image (e.g., cockroach sql), add a new
553+
# sibling key (e.g., hooks.cockroachImage) rather than overloading this one.
554+
kubectlImage:
555+
# registry defines the container registry for the hook image.
556+
registry: docker.io
557+
# repository defines the image repository for the hook container.
558+
repository: dtzar/helm-kubectl
559+
# tag defines the image tag for the hook container. Pinned for reproducibility
560+
# in air-gapped environments.
561+
tag: "3.19"
562+
# pullPolicy defines the image pull policy for the hook container.
563+
pullPolicy: IfNotPresent
564+
547565
k8s:
548566
# nameOverride overrides the name of the chart. If not set, the chart name will be used.
549567
# For example, if the chart name is "cockroachdb" and nameOverride is set to "crdb",

build/templates/cockroachdb-parent/charts/operator/values.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,20 @@ appLabel: "cockroach-operator"
3838
# startup; certs rotate on every pod restart. When false (default), Helm provisions a stable
3939
# cockroach-operator-certs Secret. Switching requires deleting that Secret first.
4040
selfSignedOperatorCerts: false
41+
# hooks captures the configuration for Helm hook jobs (pre-upgrade validation, etc.).
42+
# All hook jobs use the image defined here by default. Override these
43+
# values to use images from an internal registry in air-gapped environments.
44+
hooks:
45+
# kubectlImage is the container image used by hook jobs that run kubectl commands.
46+
# If a future hook requires a different image (e.g., cockroach sql), add a new
47+
# sibling key (e.g., hooks.cockroachImage) rather than overloading this one.
48+
kubectlImage:
49+
# registry defines the container registry for the hook image.
50+
registry: docker.io
51+
# repository defines the image repository for the hook container.
52+
repository: dtzar/helm-kubectl
53+
# tag defines the image tag for the hook container. Pinned for reproducibility
54+
# in air-gapped environments.
55+
tag: "3.19"
56+
# pullPolicy defines the image pull policy for the hook container.
57+
pullPolicy: IfNotPresent

cockroachdb-parent/charts/cockroachdb/templates/pre-upgrade-validation.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ spec:
2121
restartPolicy: Never
2222
containers:
2323
- name: validation
24-
image: bitnami/kubectl:latest
24+
image: "{{ .Values.hooks.kubectlImage.registry }}/{{ .Values.hooks.kubectlImage.repository }}:{{ .Values.hooks.kubectlImage.tag }}"
25+
imagePullPolicy: {{ .Values.hooks.kubectlImage.pullPolicy }}
2526
command:
2627
- /bin/bash
2728
- -c

cockroachdb-parent/charts/cockroachdb/values.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,24 @@ cockroachdb:
545545
# # Possible Values: disabled, primary, standby
546546
# mode: "disabled"
547547

548+
# hooks captures the configuration for Helm hook jobs (pre-upgrade validation, etc.).
549+
# All hook jobs use the image defined here by default. Override these
550+
# values to use images from an internal registry in air-gapped environments.
551+
hooks:
552+
# kubectlImage is the container image used by hook jobs that run kubectl commands.
553+
# If a future hook requires a different image (e.g., cockroach sql), add a new
554+
# sibling key (e.g., hooks.cockroachImage) rather than overloading this one.
555+
kubectlImage:
556+
# registry defines the container registry for the hook image.
557+
registry: docker.io
558+
# repository defines the image repository for the hook container.
559+
repository: dtzar/helm-kubectl
560+
# tag defines the image tag for the hook container. Pinned for reproducibility
561+
# in air-gapped environments.
562+
tag: "3.19"
563+
# pullPolicy defines the image pull policy for the hook container.
564+
pullPolicy: IfNotPresent
565+
548566
k8s:
549567
# nameOverride overrides the name of the chart. If not set, the chart name will be used.
550568
# For example, if the chart name is "cockroachdb" and nameOverride is set to "crdb",

cockroachdb-parent/charts/operator/templates/pre-upgrade-validation.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ spec:
2222
restartPolicy: Never
2323
containers:
2424
- name: pre-upgrade-check
25-
image: dtzar/helm-kubectl:latest
25+
image: "{{ .Values.hooks.kubectlImage.registry }}/{{ .Values.hooks.kubectlImage.repository }}:{{ .Values.hooks.kubectlImage.tag }}"
26+
imagePullPolicy: {{ .Values.hooks.kubectlImage.pullPolicy }}
2627
command: ["/bin/bash", "-c"]
2728
args:
2829
- |

cockroachdb-parent/charts/operator/values.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,20 @@ appLabel: "cockroach-operator"
3939
# startup; certs rotate on every pod restart. When false (default), Helm provisions a stable
4040
# cockroach-operator-certs Secret. Switching requires deleting that Secret first.
4141
selfSignedOperatorCerts: false
42+
# hooks captures the configuration for Helm hook jobs (pre-upgrade validation, etc.).
43+
# All hook jobs use the image defined here by default. Override these
44+
# values to use images from an internal registry in air-gapped environments.
45+
hooks:
46+
# kubectlImage is the container image used by hook jobs that run kubectl commands.
47+
# If a future hook requires a different image (e.g., cockroach sql), add a new
48+
# sibling key (e.g., hooks.cockroachImage) rather than overloading this one.
49+
kubectlImage:
50+
# registry defines the container registry for the hook image.
51+
registry: docker.io
52+
# repository defines the image repository for the hook container.
53+
repository: dtzar/helm-kubectl
54+
# tag defines the image tag for the hook container. Pinned for reproducibility
55+
# in air-gapped environments.
56+
tag: "3.19"
57+
# pullPolicy defines the image pull policy for the hook container.
58+
pullPolicy: IfNotPresent

cockroachdb-parent/images.txt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# CockroachDB Helm Charts - Container Image Manifest
2+
#
3+
# This file lists all container images required by the CockroachDB Helm charts.
4+
# Use it to mirror images into an internal registry for air-gapped environments.
5+
#
6+
# Format: <registry>/<repository>:<tag> or <registry>/<repository>@<digest>
7+
#
8+
# To mirror all images, run:
9+
# scripts/mirror-images.sh --source-file cockroachdb-parent/images.txt --target-registry <your-registry>
10+
11+
# === Operator chart images ===
12+
13+
# CockroachDB Operator
14+
us-docker.pkg.dev/releases-prod/self-hosted/cockroachdb-operator@sha256:62796d0ad9c87cb0f1f85ebcbc905235ca6c1fc3549bf2a62f5c534af0497332
15+
16+
# Operator pre-upgrade validation hook
17+
docker.io/dtzar/helm-kubectl:3.19
18+
19+
# === CockroachDB chart images ===
20+
21+
# CockroachDB database
22+
docker.io/cockroachdb/cockroach:v26.1.2
23+
24+
# Certificate self-signer
25+
gcr.io/cockroachlabs-helm-charts/cockroach-self-signer-cert:1.9
26+
27+
# CockroachDB pre-upgrade validation hook
28+
docker.io/dtzar/helm-kubectl:3.19
29+
30+
# === Operator-managed images (embedded in operator binary) ===
31+
# These images are pulled by the operator at runtime, not referenced in Helm values.
32+
# They must also be mirrored for air-gapped environments.
33+
34+
# Init container
35+
us-docker.pkg.dev/releases-prod/self-hosted/init-container@sha256:bcfc9312af84c7966f017c2325981b30314c0c293491f942e54da1667bedaf69
36+
37+
# Cert reloader (inotifywait)
38+
us-docker.pkg.dev/releases-prod/self-hosted/inotifywait@sha256:94db3d416e3df8d1ee2605f05b0526b3bd7217ae69b4eeb147929fe0b77aeafc

scripts/mirror-images.sh

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Mirror all container images required by CockroachDB Helm charts to an
4+
# internal registry. Designed for air-gapped and restricted environments.
5+
#
6+
# Prerequisites: crane (https://github.com/google/go-containerregistry/tree/main/cmd/crane)
7+
# or skopeo (https://github.com/containers/skopeo)
8+
#
9+
# Usage:
10+
# ./scripts/mirror-images.sh --target-registry my-registry.internal.io
11+
# ./scripts/mirror-images.sh --target-registry my-registry.internal.io --source-file cockroachdb-parent/images.txt
12+
# ./scripts/mirror-images.sh --target-registry my-registry.internal.io --tool skopeo
13+
# ./scripts/mirror-images.sh --target-registry my-registry.internal.io --dry-run
14+
15+
set -euo pipefail
16+
17+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18+
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
19+
20+
TARGET_REGISTRY=""
21+
SOURCE_FILE="${REPO_ROOT}/cockroachdb-parent/images.txt"
22+
TOOL="crane"
23+
DRY_RUN=false
24+
25+
usage() {
26+
cat <<EOF
27+
Usage: $(basename "$0") [OPTIONS]
28+
29+
Mirror CockroachDB Helm chart images to an internal registry.
30+
31+
Options:
32+
--target-registry REGISTRY Target registry to push images to (required)
33+
--source-file FILE Path to images.txt manifest (default: cockroachdb-parent/images.txt)
34+
--tool TOOL Tool to use for mirroring: crane or skopeo (default: crane)
35+
--dry-run Print commands without executing
36+
-h, --help Show this help message
37+
EOF
38+
}
39+
40+
while [[ $# -gt 0 ]]; do
41+
case "$1" in
42+
--target-registry) TARGET_REGISTRY="$2"; shift 2 ;;
43+
--source-file) SOURCE_FILE="$2"; shift 2 ;;
44+
--tool) TOOL="$2"; shift 2 ;;
45+
--dry-run) DRY_RUN=true; shift ;;
46+
-h|--help) usage; exit 0 ;;
47+
*) echo "Unknown option: $1"; usage; exit 1 ;;
48+
esac
49+
done
50+
51+
if [[ -z "${TARGET_REGISTRY}" ]]; then
52+
echo "Error: --target-registry is required"
53+
usage
54+
exit 1
55+
fi
56+
57+
if [[ ! -f "${SOURCE_FILE}" ]]; then
58+
echo "Error: source file not found: ${SOURCE_FILE}"
59+
exit 1
60+
fi
61+
62+
if [[ "${TOOL}" != "crane" && "${TOOL}" != "skopeo" ]]; then
63+
echo "Error: --tool must be 'crane' or 'skopeo'"
64+
exit 1
65+
fi
66+
67+
if ! command -v "${TOOL}" &>/dev/null; then
68+
echo "Error: ${TOOL} not found in PATH"
69+
echo "Install instructions:"
70+
if [[ "${TOOL}" == "crane" ]]; then
71+
echo " https://github.com/google/go-containerregistry/tree/main/cmd/crane#installation"
72+
else
73+
echo " https://github.com/containers/skopeo/blob/main/install.md"
74+
fi
75+
exit 1
76+
fi
77+
78+
# Compute the target image reference from a source reference.
79+
# Strips the source registry and prepends the target registry.
80+
# For digest references (@sha256:...), preserves the digest.
81+
compute_target() {
82+
local src="$1"
83+
local repo_and_ref
84+
85+
# Strip the registry prefix (everything before the first slash that isn't part of a path)
86+
# Examples:
87+
# us-docker.pkg.dev/releases-prod/self-hosted/cockroachdb-operator@sha256:abc -> cockroachdb-operator@sha256:abc
88+
# docker.io/cockroachdb/cockroach:v26.1.2 -> cockroachdb/cockroach:v26.1.2
89+
# gcr.io/cockroachlabs-helm-charts/cockroach-self-signer-cert:1.9 -> cockroach-self-signer-cert:1.9
90+
case "${src}" in
91+
us-docker.pkg.dev/*/*/*)
92+
# Google Artifact Registry: us-docker.pkg.dev/project/repo/image
93+
# Strip us-docker.pkg.dev/project/repo/ to keep only image:tag or image@digest
94+
local without_host="${src#us-docker.pkg.dev/}"
95+
local without_project="${without_host#*/}"
96+
repo_and_ref="${without_project#*/}"
97+
;;
98+
gcr.io/*)
99+
# GCR: gcr.io/project/image
100+
repo_and_ref="${src#gcr.io/*/}"
101+
;;
102+
docker.io/*)
103+
repo_and_ref="${src#docker.io/}"
104+
;;
105+
*)
106+
# Fallback: strip first path segment as registry
107+
repo_and_ref="${src#*/}"
108+
;;
109+
esac
110+
111+
echo "${TARGET_REGISTRY}/${repo_and_ref}"
112+
}
113+
114+
echo "Mirroring images from: ${SOURCE_FILE}"
115+
echo "Target registry: ${TARGET_REGISTRY}"
116+
echo "Tool: ${TOOL}"
117+
echo ""
118+
119+
SUCCESS=0
120+
FAILED=0
121+
122+
while IFS= read -r line; do
123+
# Skip comments and blank lines
124+
[[ "${line}" =~ ^[[:space:]]*# ]] && continue
125+
[[ -z "${line// /}" ]] && continue
126+
127+
src="${line}"
128+
dst="$(compute_target "${src}")"
129+
130+
echo "Mirroring: ${src}"
131+
echo " --> ${dst}"
132+
133+
if [[ "${DRY_RUN}" == true ]]; then
134+
if [[ "${TOOL}" == "crane" ]]; then
135+
echo " [dry-run] crane copy ${src} ${dst}"
136+
else
137+
echo " [dry-run] skopeo copy docker://${src} docker://${dst}"
138+
fi
139+
echo ""
140+
continue
141+
fi
142+
143+
if [[ "${TOOL}" == "crane" ]]; then
144+
if crane copy "${src}" "${dst}"; then
145+
echo " OK"
146+
((++SUCCESS))
147+
else
148+
echo " FAILED"
149+
((++FAILED))
150+
fi
151+
else
152+
if skopeo copy "docker://${src}" "docker://${dst}"; then
153+
echo " OK"
154+
((++SUCCESS))
155+
else
156+
echo " FAILED"
157+
((++FAILED))
158+
fi
159+
fi
160+
echo ""
161+
done < "${SOURCE_FILE}"
162+
163+
echo "================================"
164+
echo "Mirroring complete: ${SUCCESS} succeeded, ${FAILED} failed"
165+
166+
if [[ "${FAILED}" -gt 0 ]]; then
167+
exit 1
168+
fi

0 commit comments

Comments
 (0)