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
13 changes: 12 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,16 @@ jobs:
username: _json_key
password: ${{ secrets.GCR_HELM_CHART_SA_JSON }}

- name: Push Self-signer
- name: Push Self-signer to GCR
run: make build-and-push/self-signer

- name: Login to DockerHub
if: ${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Push Self-signer to DockerHub
if: ${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }}
run: make build-and-push/self-signer-dockerhub
24 changes: 19 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,37 @@ build: build/chart build/self-signer ## build the helm chart and self-signer
generate: ## generate files from templates in build/templates
@go run build/build.go generate

build/chart: bin/helm ## build the helm chart to build/artifacts
build/chart: bin/helm ## build the legacy helm chart to build/artifacts
@build/make.sh

build/v2-charts: bin/helm ## build operator + cockroachdb charts to build/artifacts/v2
@build/make.sh v2

SELF_SIGNER_TAG = $(shell bin/yq '.tls.selfSigner.image.tag' ./cockroachdb/values.yaml)
DOCKERHUB_SELF_SIGNER_REPO ?= cockroachdb/cockroach-self-signer-cert

build/self-signer: bin/yq ## build the self-signer image
@docker build --platform=linux/amd64 -f build/docker-image/self-signer-cert-utility/Dockerfile \
--build-arg COCKROACH_VERSION=$(shell bin/yq '.appVersion' ./cockroachdb/Chart.yaml) \
-t ${REGISTRY}/${REPOSITORY}:$(shell bin/yq '.tls.selfSigner.image.tag' ./cockroachdb/values.yaml) .
-t ${REGISTRY}/${REPOSITORY}:$(SELF_SIGNER_TAG) .

##@ Release

release: ## publish the build artifacts to S3
release: ## publish the legacy chart build artifacts to GCS
@build/release.sh

build-and-push/self-signer: bin/yq ## push the self-signer image
release/v2: ## publish v2 charts to GCS and OCI registries
@build/release.sh v2

build-and-push/self-signer: bin/yq ## push the self-signer image to GCR
@docker buildx build --platform=linux/amd64,linux/arm64 -f build/docker-image/self-signer-cert-utility/Dockerfile \
--build-arg COCKROACH_VERSION=$(shell bin/yq '.appVersion' ./cockroachdb/Chart.yaml) --push \
-t ${REGISTRY}/${REPOSITORY}:$(SELF_SIGNER_TAG) .

build-and-push/self-signer-dockerhub: bin/yq ## push the self-signer image to DockerHub
@docker buildx build --platform=linux/amd64,linux/arm64 -f build/docker-image/self-signer-cert-utility/Dockerfile \
--build-arg COCKROACH_VERSION=$(shell bin/yq '.appVersion' ./cockroachdb/Chart.yaml) --push \
-t ${REGISTRY}/${REPOSITORY}:$(shell bin/yq '.tls.selfSigner.image.tag' ./cockroachdb/values.yaml) .
-t docker.io/${DOCKERHUB_SELF_SIGNER_REPO}:$(SELF_SIGNER_TAG) .

##@ Dev
dev/clean: ## remove built artifacts
Expand Down
10 changes: 5 additions & 5 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,10 @@ func processTemplate(

// bumpVersion increases the patch release version (the last digit) of a given version
func bumpVersion(chart versions, newCRDBVersion *semver.Version) (string, error) {
isOperatorBasedChart := isEnterpriseOperatorChart(chart)
isOperatorBasedChart := isCockroachDBOperatorChart(chart)
// Bump chart major version in case appVersion changes its major or minor version
// For example, 22.1.0 or 22.2.0 should trigger this behaviour. \
// This is applicable only for the old chart, not for the enterprise operator chart.
// This is applicable only for the old chart, not for the CockroachDB Operator chart.
if !isOperatorBasedChart &&
(chart.AppVersion.Major() != newCRDBVersion.Major() || chart.AppVersion.Minor() != newCRDBVersion.Minor()) {
nextMajor := chart.Version.IncMajor()
Expand All @@ -221,7 +221,7 @@ func bumpVersion(chart versions, newCRDBVersion *semver.Version) (string, error)
return nextVersion.Original(), nil
}

// For enterprise operator charts, if appVersion changes, set the chart version to the new CRDB version,
// For CockroachDB Operator charts, if appVersion changes, set the chart version to the new CRDB version,
// preserving any existing prerelease information from the current chart version.
if isOperatorBasedChart && (chart.AppVersion.String() != newCRDBVersion.String()) {
newVer := newCRDBVersion.String()
Expand Down Expand Up @@ -282,8 +282,8 @@ func getVersions(chartPath string) (versions, error) {
return chart, nil
}

// isEnterpriseOperatorChart checks if the chart is an enterprise operator chart.
func isEnterpriseOperatorChart(chart versions) bool {
// isCockroachDBOperatorChart checks if the chart is the CockroachDB Operator chart.
func isCockroachDBOperatorChart(chart versions) bool {
return chart.Version.Major() == chart.AppVersion.Major() && chart.Version.Minor() == chart.AppVersion.Minor() &&
chart.Version.Patch() == chart.AppVersion.Patch()
}
82 changes: 76 additions & 6 deletions build/make.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ case $charts_hostname in
esac

artifacts_dir="build/artifacts/"
v2_artifacts_dir="build/artifacts/v2/"
HELM_INSTALL_DIR=$PWD/bin

install_helm() {
Expand All @@ -29,6 +30,10 @@ install_helm() {
HELM_INSTALL_DIR="$HELM_INSTALL_DIR" "$HELM_INSTALL_DIR/get_helm.sh" --no-sudo --version v3.13.3
}

# chart_version_exists returns 1 (failure) when the version exists, which is
# inverted from standard convention. This legacy behavior is preserved to avoid
# breaking the existing skip logic below. See v2_chart_version_exists for the
# standard convention (returns 0 when exists).
chart_version_exists() {
helm repo add cockroachdb "https://${charts_hostname}" --force-update
helm repo update
Expand All @@ -52,11 +57,76 @@ build_chart() {
diff -u "${artifacts_dir}/old-index.yaml" "${artifacts_dir}/index.yaml" || true
}

install_helm
# v2_chart_version_exists checks if a specific chart version already exists
# in the v2 Helm repository.
v2_chart_version_exists() {
local chart_name="$1"
local chart_dir="$2"

if [ "$is_prod" = true ] && ! chart_version_exists; then
echo "Skipping build: chart version already present in production."
exit 0
fi
helm repo add cockroachdb-v2 "https://${charts_hostname}/v2" --force-update 2>/dev/null || true
helm repo update cockroachdb-v2 2>/dev/null || true

local existing_version
existing_version=$(grep 'version:' "${chart_dir}/Chart.yaml" | awk '{print $2}')
# Use --devel to also match prerelease versions (e.g., 26.1.2-preview).
if helm search repo "cockroachdb-v2/${chart_name}" --devel --version "$existing_version" 2>/dev/null | grep -q "$existing_version"; then
echo "Chart ${chart_name} version $existing_version already exists in v2 repository."
return 0
fi
return 1
}
Comment on lines +60 to +77
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chart_version_exists and the new v2_chart_version_exists use opposite success semantics (legacy returns non-zero when the version exists, v2 returns zero when the version exists). This inconsistency is easy to misuse and can lead to incorrect skip logic later. Consider aligning the return codes or renaming one of the functions to reflect its actual truthiness.

Copilot uses AI. Check for mistakes.

# build_v2_charts packages the operator and cockroachdb charts from
# cockroachdb-parent/charts/ into build/artifacts/v2/ and generates
# a merged v2 index.yaml for the Helm repository.
build_v2_charts() {
mkdir -p "$v2_artifacts_dir"

build_chart
# Fetch the current v2 index.yaml to merge into. If v2 has never been
# published, start with an empty index.
if curl -fsSL "https://storage.googleapis.com/$gcs_bucket/v2/index.yaml" > "${v2_artifacts_dir}/old-index.yaml" 2>/dev/null; then
echo "Fetched existing v2 index.yaml"
else
echo "No existing v2 index.yaml found, starting fresh"
echo -e "apiVersion: v1\nentries: {}" > "${v2_artifacts_dir}/old-index.yaml"
fi

local packaged=false

# Package operator chart (skip if version already published in prod).
if [ "$is_prod" = true ] && v2_chart_version_exists "operator" "cockroachdb-parent/charts/operator"; then
echo "Skipping operator chart: version already published."
else
$HELM_INSTALL_DIR/helm package cockroachdb-parent/charts/operator --destination "${v2_artifacts_dir}"
packaged=true
fi

# Package cockroachdb chart (skip if version already published in prod).
if [ "$is_prod" = true ] && v2_chart_version_exists "cockroachdb" "cockroachdb-parent/charts/cockroachdb"; then
echo "Skipping cockroachdb chart: version already published."
else
$HELM_INSTALL_DIR/helm package cockroachdb-parent/charts/cockroachdb --destination "${v2_artifacts_dir}"
packaged=true
fi

if [ "$packaged" = true ]; then
$HELM_INSTALL_DIR/helm repo index "${v2_artifacts_dir}" --url "https://${charts_hostname}/v2" --merge "${v2_artifacts_dir}/old-index.yaml"
diff -u "${v2_artifacts_dir}/old-index.yaml" "${v2_artifacts_dir}/index.yaml" || true
else
echo "No new v2 charts to package."
fi
}

# When invoked directly (not sourced), run legacy chart build.
# Use build_v2_charts for v2 chart packaging.
if [[ "${1:-}" == "v2" ]]; then
install_helm
build_v2_charts
else
install_helm
if [ "$is_prod" = true ] && ! chart_version_exists; then
echo "Skipping build: chart version already present in production."
exit 0
fi
build_chart
fi
122 changes: 110 additions & 12 deletions build/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

set -euo pipefail

if [ ! -d build/artifacts ]; then
echo "Directory build/artifacts does not exist. Skipping release."
exit 0
fi
HELM="${HELM:-${PWD}/bin/helm}"

charts_hostname="${CHARTS_HOSTNAME:-charts.cockroachdb.com}"
case $charts_hostname in
Expand All @@ -22,7 +19,7 @@ case $charts_hostname in
google_project=releases-prod
;;
*)
echo "uknown host $charts_hostname"
echo "unknown host $charts_hostname"
exit 1
;;
esac
Expand All @@ -33,12 +30,113 @@ remove_files_on_exit() {

trap remove_files_on_exit EXIT

echo "${google_credentials}" > .google-credentials.json
gcloud auth activate-service-account --key-file=.google-credentials.json
gcs_authenticate() {
echo "${google_credentials}" > .google-credentials.json
gcloud auth activate-service-account --key-file=.google-credentials.json
}

# release_legacy publishes the legacy statefulset chart (cockroachdb/) to the
# root of the GCS bucket.
release_legacy() {
if [ ! -d build/artifacts ]; then
echo "Directory build/artifacts does not exist. Skipping legacy release."
return 0
fi

gcs_authenticate

# Push the new chart file and updated index.yaml file to GCS.
gsutil rsync -x old-index.yaml "build/artifacts/" "gs://${gcs_bucket}/"

# Invalidate any cached version of index.yaml (so this version is immediately available).
gcloud --project "$google_project" compute url-maps invalidate-cdn-cache "$lb_name" --path "/index.yaml" --async
}

# release_v2 publishes the operator and cockroachdb charts to the /v2/ path
# in the GCS bucket, and pushes them as OCI artifacts to container registries.
release_v2() {
if [ ! -d build/artifacts/v2 ]; then
echo "Directory build/artifacts/v2 does not exist. Skipping v2 release."
return 0
fi

# Check if there are any .tgz chart packages to publish.
if ! ls build/artifacts/v2/*.tgz 1>/dev/null 2>&1; then
echo "No v2 chart packages found. Skipping v2 release."
return 0
fi

gcs_authenticate

# Push v2 chart packages and index.yaml to GCS under /v2/ prefix.
gsutil rsync -x old-index.yaml "build/artifacts/v2/" "gs://${gcs_bucket}/v2/"

# Invalidate cached v2 index.yaml.
gcloud --project "$google_project" compute url-maps invalidate-cdn-cache "$lb_name" --path "/v2/index.yaml" --async

# Push charts as OCI artifacts to Google Artifact Registry.
push_oci_gar

# Push the new chart file and updated index.yaml file to GCS.
# We rely on the gcloud CLI version installed system-wide.
gsutil rsync -x old-index.yaml "build/artifacts/" "gs://${gcs_bucket}/"
# Push charts as OCI artifacts to DockerHub.
push_oci_dockerhub
}

# push_oci_gar pushes chart packages as OCI artifacts to Google Artifact Registry.
# Uses the gcloud service account (already activated by gcs_authenticate) to obtain
# an access token for Helm OCI registry login.
push_oci_gar() {
local gar_registry="${OCI_GAR_REGISTRY:-us-docker.pkg.dev/releases-prod/self-hosted/charts}"
local gar_host="${gar_registry%%/*}"

echo "Authenticating with GAR for OCI push (${gar_host})..."
gcloud auth print-access-token | "$HELM" registry login "${gar_host}" --username oauth2accesstoken --password-stdin

echo "Pushing charts to OCI registry: ${gar_registry}"
for chart_pkg in build/artifacts/v2/*.tgz; do
echo " Pushing ${chart_pkg}..."
push_with_retry "${chart_pkg}" "${gar_registry}"
done
}
Comment on lines +94 to +99
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both OCI push loops swallow helm push failures (they only print a warning and continue). This means the release job can succeed even when charts are not actually published to the OCI registries, which contradicts the PR’s intent to publish to GAR/DockerHub. Consider failing the script on push errors (or making the behavior explicitly configurable via an env var).

Copilot uses AI. Check for mistakes.

# push_oci_dockerhub pushes chart packages as OCI artifacts to DockerHub.
# Requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN environment variables.
push_oci_dockerhub() {
local dockerhub_registry="${OCI_DOCKERHUB_REGISTRY:-registry-1.docker.io/cockroachdb}"

if [ -z "${DOCKERHUB_USERNAME:-}" ] || [ -z "${DOCKERHUB_TOKEN:-}" ]; then
echo "Skipping OCI push to DockerHub: DOCKERHUB_USERNAME and DOCKERHUB_TOKEN not set."
return 0
fi

# Invalidate any cached version of index.yaml (so this version is immediately available)
gcloud --project $google_project compute url-maps invalidate-cdn-cache $lb_name --path "/index.yaml" --async
echo "Authenticating with DockerHub for OCI push..."
echo "${DOCKERHUB_TOKEN}" | "$HELM" registry login registry-1.docker.io --username "${DOCKERHUB_USERNAME}" --password-stdin

echo "Pushing charts to OCI registry: ${dockerhub_registry}"
for chart_pkg in build/artifacts/v2/*.tgz; do
echo " Pushing ${chart_pkg}..."
push_with_retry "${chart_pkg}" "${dockerhub_registry}"
done
}

# push_with_retry pushes a chart package to an OCI registry with retries.
# Usage: push_with_retry <chart_pkg> <registry> [max_attempts]
push_with_retry() {
local chart_pkg="$1" registry="$2" max_attempts="${3:-3}"
for ((i=1; i<=max_attempts; i++)); do
if "$HELM" push "$chart_pkg" "oci://$registry"; then
return 0
fi
echo " Attempt $i/$max_attempts failed for $chart_pkg"
if ((i < max_attempts)); then
sleep $((i * 5))
fi
done
echo " All $max_attempts attempts exhausted for $chart_pkg"
return 1
}

if [[ "${1:-}" == "v2" ]]; then
release_v2
else
release_legacy
fi
5 changes: 5 additions & 0 deletions build/teamcity-make-and-publish-charts-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,10 @@ remove_artifacts() {
}
trap remove_artifacts EXIT

# Build and publish the legacy statefulset chart.
build/make.sh
build/release.sh

# Build and publish v2 charts (operator + cockroachdb from cockroachdb-parent).
build/make.sh v2
build/release.sh v2
Loading