Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 48 additions & 15 deletions migrations/cassandra/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ARG GO_IMAGE=golang:1.27.0-alpine3.23@sha256:3747dcba41c8b0db3211fda4db61638b980e17ac5bb3c94460a975a9cfe19395
ARG KUBECTL_GO_IMAGE=golang:1.26.6-alpine3.23@sha256:e57c41c1d5864341031181b0db34b9a537bb5773eb6428e4e5bdaea0f9135406

FROM --platform=$BUILDPLATFORM ${GO_IMAGE} AS migrate-builder

Expand All @@ -21,26 +22,58 @@ RUN apk add --no-cache curl unzip && \
-ldflags="-s -w -buildid= -X main.Version=${MIGRATE_BUILD_VERSION}" \
-o /out/migrate ./cmd/migrate

FROM --platform=$BUILDPLATFORM alpine:3.23 AS kubectl-downloader
FROM --platform=$BUILDPLATFORM ${KUBECTL_GO_IMAGE} AS kubectl-builder

ARG TARGETARCH
ARG KUBECTL_VERSION=v1.36.4
ARG KUBECTL_LINUX_AMD64_SHA256=8b8f088da2dab964f853b38464033b1be15ede2839eca751482357c45abdd05a
ARG KUBECTL_LINUX_ARM64_SHA256=0ecf44450ee6063bf19dd166a103ee6df4a9034455c2abce626e6eea657d73fb
ARG KUBECTL_SOURCE_SHA256=3c28f11492472df48e658551bf268fd92938b127b0f9dcef7090ac800318c821
ARG KUBECTL_SOURCE_COMMIT=bb826b1d48562f110659e64e8ec444327433db95
ARG KUBECTL_BUILD_DATE=2026-08-20T03:09:25Z

# Verify both the official checksum and the reviewed per-architecture digest.
RUN apk add --no-cache curl && \
case "${TARGETARCH}" in \
amd64) KUBECTL_SHA256="${KUBECTL_LINUX_AMD64_SHA256}" ;; \
arm64) KUBECTL_SHA256="${KUBECTL_LINUX_ARM64_SHA256}" ;; \
# kubectl v1.36 is compatible with every Kubernetes minor supported by NVCF
# (v1.35-v1.37). The official v1.36.4 binary was built with Go 1.26.5, so
# rebuild the checksum-pinned release source with the patched Go 1.26.6 toolchain.
RUN apk add --no-cache curl tar && \
case "${TARGETARCH}" in amd64|arm64) ;; \
*) echo "Unsupported architecture for kubectl: ${TARGETARCH}" >&2; exit 1 ;; \
esac && \
KUBECTL_BASE_URL="https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${TARGETARCH}" && \
mkdir -p /out && \
curl -fsSLo /out/kubectl "${KUBECTL_BASE_URL}/kubectl" && \
curl -fsSLo /tmp/kubectl.sha256 "${KUBECTL_BASE_URL}/kubectl.sha256" && \
test "$(cat /tmp/kubectl.sha256)" = "${KUBECTL_SHA256}" && \
printf '%s %s\n' "${KUBECTL_SHA256}" /out/kubectl | sha256sum -c - && \
test "$(go env GOVERSION)" = "go1.26.6" && \
curl -fsSLo /tmp/kubernetes-src.tar.gz \
"https://dl.k8s.io/${KUBECTL_VERSION}/kubernetes-src.tar.gz" && \
printf '%s %s\n' "${KUBECTL_SOURCE_SHA256}" /tmp/kubernetes-src.tar.gz | sha256sum -c - && \
mkdir -p /src /out && \
tar -xzf /tmp/kubernetes-src.tar.gz -C /src && \
cd /src && \
KUBECTL_LDFLAGS="-s -w -buildid= \
-X k8s.io/client-go/pkg/version.gitVersion=${KUBECTL_VERSION} \
-X k8s.io/client-go/pkg/version.gitCommit=${KUBECTL_SOURCE_COMMIT} \
-X k8s.io/client-go/pkg/version.gitTreeState=clean \
-X k8s.io/client-go/pkg/version.buildDate=${KUBECTL_BUILD_DATE} \
-X k8s.io/client-go/pkg/version.gitMajor=1 \
-X k8s.io/client-go/pkg/version.gitMinor=36 \
-X k8s.io/component-base/version.gitVersion=${KUBECTL_VERSION} \
-X k8s.io/component-base/version.gitCommit=${KUBECTL_SOURCE_COMMIT} \
-X k8s.io/component-base/version.gitTreeState=clean \
-X k8s.io/component-base/version.buildDate=${KUBECTL_BUILD_DATE} \
-X k8s.io/component-base/version.gitMajor=1 \
-X k8s.io/component-base/version.gitMinor=36" && \
CGO_ENABLED=0 GOOS=linux GOARCH="${TARGETARCH}" GOTOOLCHAIN=local GOPROXY=off \
go build -mod=vendor -trimpath -tags=selinux,notest,grpcnotrace \
-ldflags="${KUBECTL_LDFLAGS}" -o /out/kubectl ./cmd/kubectl && \
CGO_ENABLED=0 GOOS=linux GOARCH="$(go env GOHOSTARCH)" GOTOOLCHAIN=local GOPROXY=off \
go build -mod=vendor -trimpath -tags=selinux,notest,grpcnotrace \
-ldflags="${KUBECTL_LDFLAGS}" -o /tmp/kubectl-host ./cmd/kubectl && \
KUBECTL_METADATA="$(/tmp/kubectl-host version --client -o json)" && \
KUBECTL_GIT_VERSION="$(printf '%s\n' "${KUBECTL_METADATA}" | awk -F '"' '/"gitVersion":/ { print $4; exit }')" && \
KUBECTL_GIT_COMMIT="$(printf '%s\n' "${KUBECTL_METADATA}" | awk -F '"' '/"gitCommit":/ { print $4; exit }')" && \
KUBECTL_BUILD_METADATA_DATE="$(printf '%s\n' "${KUBECTL_METADATA}" | awk -F '"' '/"buildDate":/ { print $4; exit }')" && \
KUBECTL_GO_VERSION="$(printf '%s\n' "${KUBECTL_METADATA}" | awk -F '"' '/"goVersion":/ { sub(/^go/, "", $4); print $4; exit }')" && \
test "${KUBECTL_GIT_VERSION}" = "${KUBECTL_VERSION}" && \
test "${KUBECTL_GIT_COMMIT}" = "${KUBECTL_SOURCE_COMMIT}" && \
test "${KUBECTL_BUILD_METADATA_DATE}" = "${KUBECTL_BUILD_DATE}" && \
test "${KUBECTL_GO_VERSION}" = "1.26.6" && \
go version -m /out/kubectl | grep -Eq '^[[:space:]]*build[[:space:]]+GOOS=linux$' && \
go version -m /out/kubectl | grep -Eq "^[[:space:]]*build[[:space:]]+GOARCH=${TARGETARCH}$" && \
chmod 0555 /out/kubectl

FROM cassandra:5.0.9
Expand All @@ -56,7 +89,7 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/*

COPY --from=migrate-builder --chmod=0555 /out/migrate /usr/bin/migrate
COPY --from=kubectl-downloader --chmod=0555 /out/kubectl /usr/local/bin/kubectl
COPY --from=kubectl-builder --chmod=0555 /out/kubectl /usr/local/bin/kubectl

COPY keyspaces /app/keyspaces
COPY --chmod=775 execute_sqls.sh /app
Expand Down
6 changes: 6 additions & 0 deletions migrations/cassandra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ This repository ships:
- The migration entrypoint (`execute_sqls.sh`)
- Cassandra DDL migrations under `keyspaces/<service>/*.up.sql`, one keyspace per service

The Cassandra chart also uses this image for its cluster-initialization hook.
That hook calls `kubectl`, so the image includes the checksum-verified official
Kubernetes v1.36 client for both supported architectures. The client is rebuilt
from the pinned official release source with Go 1.26.6 so it supports Kubernetes
v1.35 through v1.37 without retaining the older Go runtime.

## Migration driver

The container builds [`golang-migrate`](https://github.com/golang-migrate/migrate) v4.19.1 from its checksum-verified release source. The build enables only the Cassandra database driver. This keeps unrelated database and cloud-provider clients out of the runtime binary.
Expand Down
27 changes: 27 additions & 0 deletions migrations/cassandra/tests/test-execute-sqls.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ set -u

test_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
subtree_dir=$(CDPATH='' cd -- "${test_dir}/.." && pwd)
repo_dir=$(CDPATH='' cd -- "${subtree_dir}/../.." && pwd)
script="${subtree_dir}/execute_sqls.sh"
keyspaces="${subtree_dir}/keyspaces"
dockerfile="${subtree_dir}/Dockerfile"
init_script="${repo_dir}/deploy/helm/cassandra/helm/scripts/initdb.sh"
status=0

fail()
Expand Down Expand Up @@ -90,6 +93,30 @@ if ! grep -q '^until cqlsh ' "${script}"; then
fail "execute_sqls.sh must retain the Cassandra authentication readiness check"
fi

if ! sed '/^[[:space:]]*#/d' "${init_script}" |
grep -Eq '(^|[[:space:]])kubectl[[:space:]]+exec([[:space:]]|$)'; then
fail "Cassandra cluster initialization no longer requires kubectl; reconsider bundling it"
fi

if ! grep -F -q \
'COPY --from=kubectl-builder --chmod=0555 /out/kubectl /usr/local/bin/kubectl' \
"${dockerfile}"; then
fail "the migrations image must include kubectl for Cassandra cluster initialization"
fi

kubectl_version=$(sed -n 's/^ARG KUBECTL_VERSION=//p' "${dockerfile}")
if ! printf '%s\n' "${kubectl_version}" | grep -Eq '^v1\.36\.[0-9]+$'; then
fail "the migrations image must use kubectl v1.36 for v1.35-v1.37 compatibility"
fi

if ! grep -Eq '^ARG KUBECTL_GO_IMAGE=golang:1\.26\.6-[^@]+@sha256:[0-9a-f]{64}$' \
"${dockerfile}" ||
! grep -F -q 'GOPROXY=off' "${dockerfile}" ||
! grep -F -q 'go build -mod=vendor' "${dockerfile}" ||
! grep -F -q '/tmp/kubectl-host version --client -o json' "${dockerfile}"; then
fail "the migrations image must reproducibly rebuild and validate kubectl with Go 1.26.6"
fi

nvct_schema="${keyspaces}/nvct_api/03_init_tables.up.sql"
if ! grep -F -q 'health TEXT' "${nvct_schema}"; then
fail "NVCT fresh schema is missing tasks_v2.health"
Expand Down
Loading