In previous CloudBees CD/RO releases, a unified CloudBees Software Delivery Automation Helm chart was delivered to install CloudBees CI, CloudBees CD/RO, and CloudBees Analytics in Kubernetes environments. However, starting with CloudBees CD/RO release v2025.03.0, the CloudBees Software Delivery Automation Helm chart was deprecated and is not supported for future releases.
Prior to upgrading CloudBees CD/RO or CloudBees Analytics to v2025.03.0 or later, you must migrate your current CloudBees CI and CloudBees CD/RO Kubernetes deployments from using the CloudBees Software Delivery Automation Helm chart to using the standalone Helm charts for each product.
CloudBees has developed procedures and a migration script to help you. The following content guides you through the processes.
These processes only migrates your current CloudBees CI and CloudBees CD/RO Kubernetes deployments from the CloudBees Software Delivery Automation Helm chart to the standalone Helm charts for each product. No other changes or upgrades are intended to be part of this process. For more information, contact your CloudBees support representative. |
Before you start
Before you start migrating from the unified CloudBees Software Delivery Automation Helm charts to the CloudBees CD/RO and CloudBees CI product Helm charts:
-
The migration and rollback scripting provided on this page uses your existing CloudBees Software Delivery Automation deployment. It is critical that you do not delete the CloudBees Software Delivery Automation namespace before, during, or after this migration.
-
CloudBees recommends backing up your CloudBees CI data prior to performing any steps list on this page. For more information, refer to the CloudBees CI Backup and restore on Kubernetes.
Your CloudBees CI data should not be affected by this migration, and backing up your data is not explicitly required. However, in the event of unforeseen cluster errors, this data may be necessary to recover your previous deployment. -
CloudBees recommends backing up your CloudBees CD/RO and CloudBees Analytics data. For more information, refer to:
Your CloudBees CD/RO data should not be affected by this migration, and backing up your data is not explicitly required. However, in the event of unforeseen cluster errors, this data may be necessary to recover your previous deployment.
Prerequisites for migration
The following prerequisites must be met prior to performing the migrations steps on this page:
-
You must have adequate permissions within the Kubernetes cluster to access, create, and delete resources.
-
You must have the following third-party tools installed on your machine and accessible by the migration and rollback scripting:
-
Helm 3 must be installed. For installation instructions, refer to the Helm installation documentation.
-
yq
must be installed. For installation instructions, refer to the yq installation documentation.
If these tools are not available, the migration script will fail.
-
Migration overview
There are multiple procedures you must perform as part of the CloudBees Software Delivery Automation migration process:
If you encounter issues after running the migration script, refer to Rollback Helm chart migration. |
Assign environment variables
As part of the migration process, you must set the following environment variables for your Kubernetes deployment:
Check each variable below, and update them for your environment. Failing to have these variables assigned will result in the migration script failing. |
# Existing values # Your SDA Namespace & SDA Helm release name export RELEASE_NAME_SDA=cloudbees-sda export NAMESPACE_SDA=cloudbees # Export the last successful helm revision. This will be used to rollback changes if necessary, e.g.: # helm history $RELEASE_NAME_SDA -n $NAMESPACE_SDA # REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION # 1 Fri Sep 19 15:30:29 2025 superseded cloudbees-sda-1.629+145c7d6eb825 2024.06.0.175153+2.462.2.2 Install complete # 2 Fri Sep 19 15:40:17 2025 deployed cloudbees-sda-1.629+145c7d6eb825 2024.06.0.175153+2.462.2.2 Upgrade complete export LAST_SUCCESSFUL_HELM_REVISION=2
Custom environment variables
This is an optional step. The script allows you to customize some variables by exporting the environment variable. For example, some organizations have their own helm charts in a different repository.
# Helm variables - change if needed HELM_REPO_CLOUDBEES_NAME="${HELM_REPO_CLOUDBEES_NAME:-"cloudbees"}" HELM_REPO_CLOUDBEES_URL="${HELM_REPO_CLOUDBEES_URL:-"https://public-charts.artifacts.cloudbees.com/repository/public/"}" HELM_REPO_SDA="${HELM_REPO_SDA:-"${HELM_REPO_CLOUDBEES_NAME}/cloudbees-sda"}" HELM_REPO_CD="${HELM_REPO_CD:-"${HELM_REPO_CLOUDBEES_NAME}/cloudbees-flow"}" HELM_REPO_CI="${HELM_REPO_CI:-"${HELM_REPO_CLOUDBEES_NAME}/cloudbees-core"}" # The test installation needs a separate namespace to avoid conflicts. Change if needed. HELM_DUMMY_INSTALL_NS="${HELM_DUMMY_INSTALL_NS:-non-existent-namespace-for-dummy-install}" # Set to false if you are not using the chart internally managed MariaDB INCLUDE_MARIADB="${INCLUDE_MARIADB:-true}"
As mentioned earlier, these are optional environment variables and can be exported if the default values are changing. For example:
-
If using CloudBees public Helm repository: https://public-charts.artifacts.cloudbees.com/repository/public/
-
Then change the default value of
HELM_REPO_CLOUDBEES_URL
to:export HELM_REPO_CLOUDBEES_URL="https://public-charts.artifacts.cloudbees.com/repository/public/"
Now that you have set the required environment variables, proceed to Migrate Helm charts.
Migrate Helm charts
CloudBees has provided an automation script to migrate your CloudBees Software Delivery Automation Helm chart values to the standalone CloudBees CI and CloudBees CD/RO Helm charts.
You must have completed the steps in the following sections before attempting this procedure: Failing to do so will result in the automation script failing. |
This script does the following:
-
Creates backups of your secrets, PVCs, and passkey & keystore files.
-
Encodes the passkey & keystore backups.
-
Checks for any labels and patches them as needed.
-
Creates copies of the CloudBees CD/RO and CloudBees CI values from the CloudBees Software Delivery Automation chart.
-
Uninstalls the CloudBees Software Delivery Automation Helm chart.
-
Creates patches for secrets and PVCs.
-
Installs the Helm charts for CloudBees CD/RO, CloudBees CI, and Nginx controller (as applicable), and then patches your previous values.
The migration and rollback scripting provided on this page uses your existing CloudBees Software Delivery Automation deployment. It is critical that you do not delete the CloudBees Software Delivery Automation namespace before, during, or after this migration.
To migrate from the CloudBees Software Delivery Automation Helm chart to the standalone product Helm charts:
-
Create a copy of the following
sda-helm-chart-migration.sh
:Expand
sda-helm-chart-migration.sh
:#!/usr/bin/env bash set -euo pipefail # Default tools SED_BIN="sed" XARGS_BIN="xargs" AWK_BIN="awk" # Check if running on macOS if [[ "$(uname)" == "Darwin" ]]; then # macOS requires GNU tools for compatibility missing_tools=() if command -v gsed >/dev/null 2>&1; then SED_BIN="gsed" else missing_tools+=("gsed") fi if command -v gxargs >/dev/null 2>&1; then XARGS_BIN="gxargs" else missing_tools+=("gxargs") fi if command -v gawk >/dev/null 2>&1; then AWK_BIN="gawk" else missing_tools+=("gawk") fi # If any GNU tools are missing, exit with instructions if [[ ${#missing_tools[@]} -gt 0 ]]; then echo "Error: The following GNU tools are required but not installed:" for tool in "${missing_tools[@]}"; do echo " - $tool" done echo "" echo "You can install them using Homebrew:" echo " brew install gnu-sed gawk findutils coreutils" echo "" exit 1 fi fi # === CONFIG === ENV_FILE=".sda-helm-chart-migration-env" BACKUP_DIR="backup" EXTERNAL_SECRETS="external-secrets" BACKUP_VALUES_DIR="$BACKUP_DIR/values" POST_MIGRATION_DIR="post-migration" # Helm variables - change if needed HELM_REPO_CLOUDBEES_NAME="${HELM_REPO_CLOUDBEES_NAME:-cloudbees}" HELM_REPO_CLOUDBEES_URL="${HELM_REPO_CLOUDBEES_URL:-https://public-charts.artifacts.cloudbees.com/repository/public/}" HELM_REPO_SDA="${HELM_REPO_SDA:-"${HELM_REPO_CLOUDBEES_NAME}/cloudbees-sda"}" HELM_REPO_CD="${HELM_REPO_CD:-"${HELM_REPO_CLOUDBEES_NAME}/cloudbees-flow"}" HELM_REPO_CI="${HELM_REPO_CI:-"${HELM_REPO_CLOUDBEES_NAME}/cloudbees-core"}" HELM_REPO_NGINX_NAME="${HELM_REPO_NGINX_NAME:-ingress-nginx}" HELM_REPO_NGINX_URL="${HELM_REPO_NGINX_URL:-https://kubernetes.github.io/ingress-nginx/}" HELM_REPO_NGINX="${HELM_REPO_NGINX:-"${HELM_REPO_NGINX_NAME}/ingress-nginx"}" NGINX_INGRESS_CHART_VERSION_CHART="4.0.13" # The test installation needs a separate namespace to avoid conflicts. Change if needed. HELM_DUMMY_INSTALL_NS="${HELM_DUMMY_INSTALL_NS:-non-existent-namespace-for-dummy-install}" # Set to false if you are not using the chart internally managed MariaDB INCLUDE_MARIADB="${INCLUDE_MARIADB:-true}" # Need SDA values file to check dois.enabled VALUES_FILE_SDA="${VALUES_FILE_SDA:-"${RELEASE_NAME_SDA}-values.yaml"}" existing_keys=("existing_passkey" "existing_keystore" "existing_database" "existing_analytics" "existing_flowCredentials" "existing_boundAgentFlowCredentials") existing_secret_env_vars=("EXISTING_FLOW_DB_SECRET" "EXISTING_ANALYTICS_SECRET" "EXISTING_FLOW_CRED_SECRET") if [[ "$INCLUDE_MARIADB" == "true" ]]; then existing_keys+=("existing_mariadb") existing_secret_env_vars+=("EXISTING_MARIADB_SECRET") fi dois_enabled=$(yq '.cd.dois.enabled' "$VALUES_FILE_SDA" 2>/dev/null || echo "true") if [[ ! "$dois_enabled" == "false" ]]; then existing_keys+=("existing_dois") existing_secret_env_vars+=("EXISTING_DOIS_SECRET") fi die() { echo "Error: $1" >&2; exit 1; } log() { echo >&2 "[INFO] $*"; } # sanity check for required tools for cmd in helm kubectl yq jq $AWK_BIN $SED_BIN $XARGS_BIN diff; do if ! command -v "$cmd" &> /dev/null; then die "Command '$cmd' not found. Please install it and try again." fi done # is helm diff installed? helm_diff_installed() { if ! helm plugin list | grep -q diff; then return 1 fi return 0 } confirm() { local prompt="$1" local default="${2:-y}" local default_capitalized="$(echo "$default" | $AWK_BIN '{print toupper($0)}')" read -r -p "$prompt [${default_capitalized}]: " response response="${response:-$default}" if [[ ! "$response" =~ ^[Yy]$ ]]; then echo "Operation cancelled by user." return 1 fi } press_to_continue() { local prompt="$1" echo "$prompt" read -r -p "Press [Enter] to continue..." } announce() { echo echo "#########################################" echo "# $*" echo "#########################################" echo } get_chart_version() { local chart_name="$1" local app_version="$2" local chart_version chart_version=$(helm search repo "$chart_name" --versions --output yaml | yq '.[]|select(.app_version == "'"$app_version"'").version' | head -n 1) if [ -z "$chart_version" ]; then die "Chart version for $chart_name not found." fi echo "$chart_version" } determine_new_release_values() { local DEFAULT_SDA_VALUES="$1" local TMP_USER_SUPPLIED="$2" local HELM_REPO="$3" local CHART_VERSION_CHART="$4" local POST_MIGRATION_VALUES_FILE="$5" local POST_MIGRATION_VALUES_FILE_EXPECTED="$6" local NEW_RELEASE_NAME="$7" local REPLACE_NGINX_RELEASE_NS_WITH_CDRO_NS="${8:-false}" echo "INFO: Now we need to trick helm into giving us the new 'USER-SUPPLIED VALUES' for the CD and CI charts." MY_NS="$NAMESPACE_SDA" MY_RELEASE="${RELEASE_NAME_SDA}" helm install -n "$MY_NS" \ --values "$DEFAULT_SDA_VALUES" \ --values "$TMP_USER_SUPPLIED" \ "$MY_RELEASE" \ "$HELM_REPO" \ --version "$CHART_VERSION_CHART" \ --dry-run \ --debug &> "${TMP_USER_SUPPLIED}.install.log" || die "Failed to get user-supplied values for chart. Check ${TMP_USER_SUPPLIED}.install.log for details." # Now we need to extract the user-supplied values from the log file $AWK_BIN '/USER-SUPPLIED VALUES:/ {p=1; next} /COMPUTED VALUES:/ {p=0} p' "${TMP_USER_SUPPLIED}.install.log" > "$POST_MIGRATION_VALUES_FILE" if [ "${REPLACE_NGINX_RELEASE_NS_WITH_CDRO_NS}" == "true" ]; then echo "INFO: Replacing nginx-ingress release namespace reference with the CDRO ones in the values file..." $SED_BIN -i "s|{{ .Release.Namespace }}/flow|${NAMESPACE_SDA}/flow|g" "$POST_MIGRATION_VALUES_FILE" fi # Create the expected manifest file for the CDRO chart helm template \ --values "$POST_MIGRATION_VALUES_FILE" \ "${NEW_RELEASE_NAME}" \ "$HELM_REPO" \ --version "$CHART_VERSION_CHART" \ --namespace "$NAMESPACE_SDA" \ --api-versions="networking.k8s.io/v1/Ingress" \ > "${POST_MIGRATION_VALUES_FILE_EXPECTED}" || die "Failed to create expected manifests for chart." } empty_or_null() { [ -z "$1" ] || [ "$1" == "null" ] } check_existing_secrets() { local values_file="$1" local perform_check="${2:-false}" if [ ! -f "$values_file" ]; then die "Values file $values_file does not exist." fi # Check if the values are present in the values file existing_passkey=$(yq '.cd.server.customConfig."passkey.b64"' "$values_file" || echo "") existing_keystore=$(yq '.cd.server.customConfig."keystore.b64"' "$values_file" || echo "") existing_database=$(yq '.cd.database.existingSecret' "$values_file" || echo "") existing_analytics=$(yq '.cd.analytics.credentials.existingSecret' "$values_file" || echo "") existing_dois=$(yq '.cd.dois.credentials.existingSecret' "$values_file" || echo "") existing_flowCredentials=$(yq '.cd.flowCredentials.existingSecret' "$values_file" || echo "") existing_boundAgentFlowCredentials=$(yq '.cd.boundAgent.flowCredentials.existingSecret' "$values_file" || echo "") echo "Checking for existing secrets in the values file: $values_file" echo "existing_passkey: $(empty_or_null "$existing_passkey" && echo "not set" || echo "set")" echo "existing_keystore: $(empty_or_null "$existing_keystore" && echo "not set" || echo "set")" echo "existing_database: $(empty_or_null "$existing_database" && echo "not set" || echo "set")" echo "existing_analytics: $(empty_or_null "$existing_analytics" && echo "not set" || echo "set")" echo "existing_dois: $(empty_or_null "$existing_dois" && echo "not set" || echo "set")" echo "existing_flowCredentials: $(empty_or_null "$existing_flowCredentials" && echo "not set" || echo "set")" echo "existing_boundAgentFlowCredentials: $(empty_or_null "$existing_boundAgentFlowCredentials" && echo "not set" || echo "set")" if [[ "$INCLUDE_MARIADB" == "true" ]]; then existing_mariadb=$(yq '.cd.mariadb.existingSecret' "$values_file" || echo "") echo "existing_mariadb: $(empty_or_null "$existing_mariadb" && echo "not set" || echo "set")" fi if [ "$perform_check" = "true" ]; then local builtinsecrets_warnings=false for value in "${existing_keys[@]}"; do if [ -z "${!value}" ] || [ "${!value}" == "null" ]; then echo "WARNING: $value is not present in the values file" builtinsecrets_warnings=true fi done if [ "$builtinsecrets_warnings" = true ]; then announce "Some built-in secrets are not set in the values file (see above)." die "Use $0 externalize_secrets to set them." fi fi } ## Sanity checks and preparing the environment variables prepare_vars() { announce "Setting up environment variables for the migration script" RELEASE_NAME_SDA="${RELEASE_NAME_SDA:-"${1:-}"}" NAMESPACE_SDA="${NAMESPACE_SDA:-"${2:-}"}" LAST_SUCCESSFUL_HELM_REVISION="${LAST_SUCCESSFUL_HELM_REVISION:-}" NEW_RELEASE_NAME_CI="${NEW_RELEASE_NAME_CI:-cloudbees-core}" NEW_RELEASE_NAME_CDRO="${NEW_RELEASE_NAME_CDRO:-cloudbees-flow}" VALUES_FILE_SDA="${VALUES_FILE_SDA:-"${RELEASE_NAME_SDA}-values.yaml"}" if [ -z "${RELEASE_NAME_SDA:-}" ] || [ -z "${NAMESPACE_SDA:-}" ]; then die "Usage: prepare_vars <release_name> <release_namespace> -OR- set RELEASE_NAME_SDA and NAMESPACE_SDA environment variables." fi if [ -z "${LAST_SUCCESSFUL_HELM_REVISION:-}" ]; then helm history "$RELEASE_NAME_SDA" -n "$NAMESPACE_SDA" die "Please provide the LAST_SUCCESSFUL_HELM_REVISION environment variable from the list above. This will be used as a rollback point if we rollback before uninstalling." else echo "Using LAST_SUCCESSFUL_HELM_REVISION: $LAST_SUCCESSFUL_HELM_REVISION" helm history -n "$NAMESPACE_SDA" "$RELEASE_NAME_SDA" | grep -E "^$LAST_SUCCESSFUL_HELM_REVISION" || die "Release $RELEASE_NAME_SDA with revision $LAST_SUCCESSFUL_HELM_REVISION not found in namespace $NAMESPACE_SDA." fi if [ ! -f "${VALUES_FILE_SDA:-}" ]; then die "Values file $VALUES_FILE_SDA does not exist. Please provide the correct values file or set the VALUES_FILE_SDA environment variable." fi echo > "$ENV_FILE" announce "Sanity checks" echo "Checking if the Helm repo is set up correctly" check_helm_repo "$HELM_REPO_CLOUDBEES_NAME" "$HELM_REPO_CLOUDBEES_URL" echo "check if the namespace exists" kubectl get namespace "$NAMESPACE_SDA" > /dev/null 2>&1 || die "Namespace $NAMESPACE_SDA does not exist." echo "check if the release exists" SDA_APP_VERSION=$(helm ls -n "$NAMESPACE_SDA" -o yaml | yq '.[]|select(.name == "'"$RELEASE_NAME_SDA"'").app_version') echo "check if the release app version is set" [ -n "$SDA_APP_VERSION" ] || die "Release $RELEASE_NAME_SDA does not exist in namespace $NAMESPACE_SDA." SDA_CHART_VERSION=$(get_chart_version "$HELM_REPO_SDA" "$SDA_APP_VERSION") echo "check if the release chart version is set" [ -n "$SDA_CHART_VERSION" ] || die "Chart version for release $RELEASE_NAME_SDA with app version $SDA_APP_VERSION not found." CHART_DIR="charts/$SDA_CHART_VERSION" announce "Detect the versions of the CDRO and CI charts from the SDA app version" if [ -z "${CI_CORE_CHART_VERSION:-}" ] || [ -z "${CDRO_FLOW_CHART_VERSION:-}" ]; then echo "CI_CORE_CHART_VERSION or CDRO_FLOW_CHART_VERSION is not set. Attempting to detect versions from SDA_APP_VERSION..." if [[ "$SDA_APP_VERSION" != *"+"* ]]; then die "SDA_APP_VERSION $SDA_APP_VERSION does not contain a '+' character. Expected format: <cdro_version>+<ci_version>" fi DETECTED_CDRO_VERSION="${SDA_APP_VERSION%%+*}" DETECTED_CI_VERSION="${SDA_APP_VERSION#*+}" if [ -z "$DETECTED_CI_VERSION" ]; then die "SDA_APP_VERSION $SDA_APP_VERSION does not contain a CI version after the '+' character." fi echo "- Detected CDRO version: $DETECTED_CDRO_VERSION" echo "- Detected CI version: $DETECTED_CI_VERSION" export CDRO_FLOW_CHART_VERSION="$DETECTED_CDRO_VERSION" export CI_CORE_CHART_VERSION="$DETECTED_CI_VERSION" fi [ -n "${CDRO_FLOW_CHART_VERSION:-}" ] || die "CDRO_FLOW_CHART_VERSION is not set." [ -n "${CI_CORE_CHART_VERSION:-}" ] || die "CI_CORE_CHART_VERSION is not set." export CI_CORE_CHART_VERSION_CHART CDRO_FLOW_CHART_VERSION_CHART if [ -z "${CI_CORE_CHART_VERSION_CHART:-}" ]; then CI_CORE_CHART_VERSION_CHART=$(get_chart_version cloudbees/cloudbees-core "$CI_CORE_CHART_VERSION") echo "- Detected CI_CORE_CHART_VERSION_CHART: $CI_CORE_CHART_VERSION_CHART" else echo "Using existing CI_CORE_CHART_VERSION_CHART: $CI_CORE_CHART_VERSION_CHART" fi if [ -z "${CDRO_FLOW_CHART_VERSION_CHART:-}" ]; then CDRO_FLOW_CHART_VERSION_CHART=$(get_chart_version cloudbees/cloudbees-flow "$CDRO_FLOW_CHART_VERSION") echo "- Detected CDRO_FLOW_CHART_VERSION_CHART: $CDRO_FLOW_CHART_VERSION_CHART" else echo "Using existing CDRO_FLOW_CHART_VERSION_CHART: $CDRO_FLOW_CHART_VERSION_CHART" fi [ -n "${CI_CORE_CHART_VERSION_CHART:-}" ] || die "CI_CORE_CHART_VERSION_CHART is not set." [ -n "${CDRO_FLOW_CHART_VERSION_CHART:-}" ] || die "CDRO_FLOW_CHART_VERSION_CHART is not set." announce "Handle the NGINX ingress stuff if installed" export NEW_RELEASE_NAME_NGINX= export NEW_NGINX_NAMESPACE= local is_ingress_nginx_enabled is_ingress_nginx_enabled=$(yq '.ingress-nginx.enabled' "$VALUES_FILE_SDA" || echo "false") if [ "$is_ingress_nginx_enabled" == "true" ]; then echo "Ingress NGINX is enabled in the values file. Setting up environment variables for NGINX." NEW_RELEASE_NAME_NGINX="${NEW_RELEASE_NAME_NGINX:-ingress-nginx}" NEW_NGINX_NAMESPACE="${NEW_NGINX_NAMESPACE:-cloudbees-ingress-nginx}" else echo "Ingress NGINX is not enabled in the values file. Skipping NGINX setup." fi # add the other predetermined variables for reference later export BACKUP_VALUES_FILE="${BACKUP_VALUES_DIR}/${RELEASE_NAME_SDA}-helm-get-values.yaml" export BACKUP_MANIFESTS_FILE="${BACKUP_VALUES_DIR}/${RELEASE_NAME_SDA}-helm-get-manifest.yaml" export BACKUP_VALUES_FILE_ALL="${BACKUP_VALUES_DIR}/${RELEASE_NAME_SDA}-helm-get-all.txt" export BACKUP_VALUES_FILE_NGINX="${BACKUP_VALUES_DIR}/${RELEASE_NAME_SDA}-nginx.yaml" export POST_MIGRATION_VALUES_FILE_CI="post-migration-ci.yaml" export POST_MIGRATION_VALUES_FILE_CD="post-migration-cd.yaml" export POST_MIGRATION_VALUES_FILE_NGINX="post-migration-nginx.yaml" announce "Setting up environment variables for the migration script" { echo "export RELEASE_NAME_SDA=${RELEASE_NAME_SDA}" echo "export NAMESPACE_SDA=${NAMESPACE_SDA}" echo "export VALUES_FILE_SDA=${VALUES_FILE_SDA}" echo "export CDRO_FLOW_CHART_VERSION=${CDRO_FLOW_CHART_VERSION}" echo "export CDRO_FLOW_CHART_VERSION_CHART=${CDRO_FLOW_CHART_VERSION_CHART}" echo "export CI_CORE_CHART_VERSION=${CI_CORE_CHART_VERSION}" echo "export CI_CORE_CHART_VERSION_CHART=${CI_CORE_CHART_VERSION_CHART}" echo "export SDA_APP_VERSION=${SDA_APP_VERSION}" echo "export SDA_CHART_VERSION=${SDA_CHART_VERSION}" echo "export NEW_RELEASE_NAME_CI=${NEW_RELEASE_NAME_CI}" echo "export NEW_RELEASE_NAME_CDRO=${NEW_RELEASE_NAME_CDRO}" echo "export NEW_RELEASE_NAME_NGINX=${NEW_RELEASE_NAME_NGINX}" echo "export NEW_NGINX_NAMESPACE=${NEW_NGINX_NAMESPACE}" echo "export CHART_DIR=${CHART_DIR}" echo "export BACKUP_DIR=${BACKUP_DIR}" echo "export LAST_SUCCESSFUL_HELM_REVISION=${LAST_SUCCESSFUL_HELM_REVISION}" echo "export BACKUP_VALUES_FILE=${BACKUP_VALUES_FILE}" echo "export BACKUP_MANIFESTS_FILE=${BACKUP_MANIFESTS_FILE}" echo "export BACKUP_VALUES_FILE_ALL=${BACKUP_VALUES_FILE_ALL}" echo "export BACKUP_VALUES_FILE_NGINX=${BACKUP_VALUES_FILE_NGINX}" echo "export POST_MIGRATION_VALUES_FILE_CI=${POST_MIGRATION_VALUES_FILE_CI}" echo "export POST_MIGRATION_VALUES_FILE_CD=${POST_MIGRATION_VALUES_FILE_CD}" echo "export POST_MIGRATION_VALUES_FILE_NGINX=${POST_MIGRATION_VALUES_FILE_NGINX}" } > "$ENV_FILE" echo "Environment variables set and saved to $ENV_FILE" cat "$ENV_FILE" # sanity check for built-in secrets announce "Checking for built-in secrets in the release" check_existing_secrets "${VALUES_FILE_SDA}" true # if we got this far, we can process the values files process_values_files announce "Environment variables and values files processed successfully." } ## Backup the current state of the cluster - Usage: backup_current backup_current() { local clean_backup="${1:-false}" source_env echo "Backing up current state of the cluster..." # Creating required directories # if the directory already exists, die with an error cd "$BACKUP_DIR" || die "Failed to change directory to backup" for dir in secret/helm pvc keys; do [ "$clean_backup" == "true" ] && rm -rf "$dir" if [ -d "$dir" ]; then die "Backup directory $dir already exists. Please rename or remove it before running the backup." fi mkdir -p "$dir" done announce "Taking secret manifest backups" for i in $(kubectl get secret -n "${NAMESPACE_SDA}" --no-headers | $AWK_BIN '{print $1}'); do echo "Backing up secret: $i" if [[ "$i" == *"sh.helm.releas"* ]]; then kubectl get secret "$i" -n "${NAMESPACE_SDA}" -o yaml > secret/helm/"${i}.yaml" echo "${i} secret backups completed." else kubectl get secret "$i" -n "${NAMESPACE_SDA}" -o yaml > secret/"${i}.yaml" echo "${i} secret backups completed." fi done echo "**** All secret manifest backups completed ****" announce "Taking PVC manifest backups" for i in $(kubectl get pvc -n "${NAMESPACE_SDA}" --no-headers | $AWK_BIN '{print $1}'); do kubectl get pvc "$i" -n "${NAMESPACE_SDA}" -o yaml > pvc/"${i}.yaml" echo "${i} PVC backups completed." done echo "**** All PVC backup manifests are complete ****" announce "Taking keystore & passkey backups" cdServerPod=$(kubectl get pod -l app=flow-server -o jsonpath='{.items[*].metadata.name}' -n "${NAMESPACE_SDA}"); echo "Server pod name: $cdServerPod" for f in keystore passkey; do if kubectl exec -c flow-server "${cdServerPod}" -n "${NAMESPACE_SDA}" -- bash -c "test -f '/opt/cbflow/conf/${f}'"; then echo "Found ${f} in pod ${cdServerPod}. Proceeding with backup." else echo "Error: ${f} not found in pod ${cdServerPod}. Exiting script..." exit 1 fi done KEYSTORE_FILE="keys/keystore" PASSKEY_FILE="keys/passkey" kubectl cp -n "${NAMESPACE_SDA}" -c flow-server "${cdServerPod}:/opt/cbflow/conf/keystore" "$KEYSTORE_FILE" kubectl cp -n "${NAMESPACE_SDA}" -c flow-server "${cdServerPod}:/opt/cbflow/conf/passkey" "$PASSKEY_FILE" if [ ! -f "$KEYSTORE_FILE" ] || [ ! -f "$PASSKEY_FILE" ]; then die "Failed to copy keystore or passkey from pod ${cdServerPod}." fi announce "Encoding the keystore and passkey" export KEYSTORE_FILE_B64="keys/keystore.b64" export PASSKEY_FILE_B64="keys/passkey.b64" base64 -i "$KEYSTORE_FILE" > "$KEYSTORE_FILE_B64" base64 -i "$PASSKEY_FILE" > "$PASSKEY_FILE_B64" # If the number of files is not 4, exit with an error file_count=$(find keys -mindepth 1 -maxdepth 1 -type f | wc -l) if [[ "$file_count" -ne 4 ]]; then echo "Error: Keystore or passkey was not copied from pod or were not properly encoded. Exiting script..." exit 1 fi ls -l keys echo "**** Encoding completed ****" cd .. } ## Externalize secrets - Usage: externalize_secrets [clean_backup] externalize_secrets() { local clean_backup="${1:-false}" source_env announce "Externalizing secrets for release $RELEASE_NAME_SDA in namespace $NAMESPACE_SDA" check_existing_secrets "${VALUES_FILE_SDA}" # Creating required directories mkdir -p "$EXTERNAL_SECRETS" cd "$EXTERNAL_SECRETS" || die "Failed to change directory to $EXTERNAL_SECRETS" for dir in secret keys; do [ "$clean_backup" == "true" ] && rm -rf "$dir" if [ -d "$dir" ]; then die "Directory $dir already exists. Please rename or remove it before running the backup." fi mkdir -p "$dir" done CURRENT_EXISTING_FLOW_DB_SECRET="" CURRENT_EXISTING_ANALYTICS_SECRET="" CURRENT_EXISTING_DOIS_SECRET="" CURRENT_EXISTING_FLOW_CRED_SECRET="" CURRENT_EXISTING_MARIADB_SECRET="" for i in $(kubectl get secret -n "${NAMESPACE_SDA}" --no-headers | $AWK_BIN '{print $1}'); do if [[ "${i}" =~ .*(mariadb-initdb-secret|-db|-analytics|-dois|-credentials)$ ]]; then echo "Found a secret ${i}" kubectl get secret "$i" -n "${NAMESPACE_SDA}" -o yaml > secret/"${i}.yaml" if [[ "${i}" != external-* ]]; then echo "- needs to be renamed to external-${i}" external_secret_name="external-${i}" mv "secret/${i}.yaml" "secret/${external_secret_name}.yaml" yq eval -i ".metadata.name = \"${external_secret_name}\"" "secret/${external_secret_name}.yaml" echo "- Removing annotations and labels from the external secret manifest." yq eval -i 'del(.metadata.annotations)' "secret/${external_secret_name}.yaml" yq eval -i 'del(.metadata.labels)' "secret/${external_secret_name}.yaml" else external_secret_name="${i}" fi # Set existing secret variables if needed case $i in *-db) echo "- Setting existing flow database secret" CURRENT_EXISTING_FLOW_DB_SECRET=$(basename "secret/${external_secret_name}.yaml" | cut -d '.' -f1) ;; *-analytics) echo "- Setting existing analytics credentials secret" CURRENT_EXISTING_ANALYTICS_SECRET=$(basename "secret/${external_secret_name}.yaml" | cut -d '.' -f1) ;; *-dois) echo "- Setting existing DOIs credentials secret" CURRENT_EXISTING_DOIS_SECRET=$(basename "secret/${external_secret_name}.yaml" | cut -d '.' -f1) ;; *-credentials) echo "- Setting existing flow credentials secret" CURRENT_EXISTING_FLOW_CRED_SECRET=$(basename "secret/${external_secret_name}.yaml" | cut -d '.' -f1) ;; *mariadb-initdb-secret) echo "- Setting existing MariaDB initdb secret" CURRENT_EXISTING_MARIADB_SECRET=$(basename "secret/${external_secret_name}.yaml" | cut -d '.' -f1) # add additional key to the secret if mariadb-replication-password is not set if ! yq eval -e '.data."mariadb-replication-password"' "secret/${external_secret_name}.yaml" &>/dev/null; then echo "- Special Case: Adding the same base64 password for mariadb-replication-password to the secret" local mariadb_root_password mariadb_root_password=$(yq eval -r '.data."mariadb-root-password"' "secret/${external_secret_name}.yaml") if [ -z "$mariadb_root_password" ]; then die "mariadb-root-password is not set in the secret ${external_secret_name}. Cannot set mariadb-replication-password." fi mariadb_root_password="$mariadb_root_password" yq eval -i '.data."mariadb-replication-password" = strenv(mariadb_root_password)' "secret/${external_secret_name}.yaml" else echo "- Special Case: mariadb-replication-password already exists in the secret, skipping" fi ;; *) echo "Invalid secret" exit 1; ;; esac echo "- Backed up external secret: $external_secret_name" fi done announce "Taking keystore & passkey backups" cdServerPod=$(kubectl get pod -l app=flow-server -o jsonpath='{.items[*].metadata.name}' -n "${NAMESPACE_SDA}"); echo "Server pod name: $cdServerPod" for f in keystore passkey; do if kubectl exec -c flow-server "${cdServerPod}" -n "${NAMESPACE_SDA}" -- bash -c "test -f '/opt/cbflow/conf/${f}'"; then echo "Found ${f} in pod ${cdServerPod}. Proceeding with backup." else echo "Error: ${f} not found in pod ${cdServerPod}. Exiting script..." exit 1 fi done KEYSTORE_FILE="keys/keystore" PASSKEY_FILE="keys/passkey" kubectl cp -n "${NAMESPACE_SDA}" -c flow-server "${cdServerPod}:/opt/cbflow/conf/keystore" "$KEYSTORE_FILE" kubectl cp -n "${NAMESPACE_SDA}" -c flow-server "${cdServerPod}:/opt/cbflow/conf/passkey" "$PASSKEY_FILE" if [ ! -f "$KEYSTORE_FILE" ] || [ ! -f "$PASSKEY_FILE" ]; then die "Failed to copy keystore or passkey from pod ${cdServerPod}." fi announce "Encoding the keystore and passkey" export KEYSTORE_FILE_B64="keys/keystore.b64" export PASSKEY_FILE_B64="keys/passkey.b64" base64 -i "$KEYSTORE_FILE" > "$KEYSTORE_FILE_B64" base64 -i "$PASSKEY_FILE" > "$PASSKEY_FILE_B64" # If the number of files is not 4, exit with an error file_count=$(find keys -mindepth 1 -maxdepth 1 -type f | wc -l) if [[ "$file_count" -ne 4 ]]; then echo "Error: Keystore or passkey was not copied from pod or were not properly encoded. Exiting script..." exit 1 fi ls -l keys echo "**** Encoding completed ****" announce "Checking against current values..." local values_opts=() for key in "${existing_keys[@]}"; do local value="${!key}" if empty_or_null "$value"; then echo "INFO: $key is not set in the values file. We will set it" case $key in existing_passkey) values_opts+=("--set-file" "cd.server.customConfig.passkey\\.b64=${EXTERNAL_SECRETS}/${PASSKEY_FILE_B64}") ;; existing_keystore) values_opts+=("--set-file" "cd.server.customConfig.keystore\\.b64=${EXTERNAL_SECRETS}/${KEYSTORE_FILE_B64}") ;; existing_database) EXISTING_FLOW_DB_SECRET="$CURRENT_EXISTING_FLOW_DB_SECRET" values_opts+=("--set" "cd.database.existingSecret=${EXISTING_FLOW_DB_SECRET}") ;; existing_analytics) EXISTING_ANALYTICS_SECRET="$CURRENT_EXISTING_ANALYTICS_SECRET" values_opts+=("--set" "cd.analytics.credentials.existingSecret=${EXISTING_ANALYTICS_SECRET}") ;; existing_dois) EXISTING_DOIS_SECRET="$CURRENT_EXISTING_DOIS_SECRET" values_opts+=("--set" "cd.dois.credentials.existingSecret=${EXISTING_DOIS_SECRET}") ;; existing_flowCredentials) EXISTING_FLOW_CRED_SECRET="$CURRENT_EXISTING_FLOW_CRED_SECRET" values_opts+=("--set" "cd.flowCredentials.existingSecret=${EXISTING_FLOW_CRED_SECRET}") ;; existing_boundAgentFlowCredentials) EXISTING_FLOW_CRED_SECRET="$CURRENT_EXISTING_FLOW_CRED_SECRET" values_opts+=("--set" "cd.boundAgent.flowCredentials.existingSecret=${EXISTING_FLOW_CRED_SECRET}") ;; existing_mariadb) EXISTING_MARIADB_SECRET="$CURRENT_EXISTING_MARIADB_SECRET" values_opts+=("--set" "cd.mariadb.existingSecret=${EXISTING_MARIADB_SECRET}") ;; *) die "Invalid key: $key" ;; esac else echo "INFO: $key is set in the values file. We don't need to set it again." fi done cd .. # if values_opts is empty, we don't need to set anything if [ ${#values_opts[@]} -eq 0 ]; then echo "No values to set. Skipping the helm upgrade." else echo "INFO: Setting the following values: ${values_opts[*]}" announce "IMPORTANT: YOU need to manage these SECRETS and KEYS from now on!" announce "IMPORTANT: Again, YOU need to manage these SECRETS and KEYS from now on!" fi echo "INFO: Now we need to trick helm into giving us the new 'USER-SUPPLIED VALUES' for the SDA charts." local TMP_USER_SUPPLIED="${RELEASE_NAME_SDA}-with-external-secrets" helm upgrade --install -n "$NAMESPACE_SDA" \ --values "${VALUES_FILE_SDA}" \ "${values_opts[@]}" \ "${RELEASE_NAME_SDA}" \ "$HELM_REPO_SDA" \ --version "$SDA_CHART_VERSION" \ --dry-run \ --debug &> "${EXTERNAL_SECRETS}/${TMP_USER_SUPPLIED}.install.log" || die "Failed to get user-supplied values for chart. Check ${TMP_USER_SUPPLIED}.install.log for details." # Now we need to extract the user-supplied values from the log file local EXTERNALIZED_SECRETS_VALUES_FILE="${EXTERNAL_SECRETS}/${TMP_USER_SUPPLIED}-values.yaml" $AWK_BIN '/USER-SUPPLIED VALUES:/ {p=1; next} /COMPUTED VALUES:/ {p=0} p' "${EXTERNAL_SECRETS}/${TMP_USER_SUPPLIED}.install.log" > "$EXTERNALIZED_SECRETS_VALUES_FILE" announce "Adding the secrets to the env file: $ENV_FILE" for e in "${existing_secret_env_vars[@]}"; do echo "Processing environment variable: $e" local value if env | grep -q "^$e="; then echo "Environment variable $e already exists. Using its value." value="${!e}" else echo "Environment variable $e does not exist. Setting it to an empty value." value="" fi add_or_update_env_file "$e" "$value" done cat "$ENV_FILE" announce "IMPORTANT: Final reminder, IT IS YOU that needs to manage these secrets and keys from now on!" echo "Here's a diff of..." echo "- current values: '${VALUES_FILE_SDA}'" echo "- updated values: '${EXTERNALIZED_SECRETS_VALUES_FILE}'" echo # Handle the diff commands exit code local exit_code=0 diff \ <(yq -P '... comments="" | sort_keys(..)' "${VALUES_FILE_SDA}") \ <(yq -P '... comments="" | sort_keys(..)' "${EXTERNALIZED_SECRETS_VALUES_FILE}") || exit_code=$? if [ "$exit_code" -gt 1 ]; then die "Failed to compare the values files. Please check the files manually." elif [ "$exit_code" -eq 1 ]; then echo "INFO: The values files differ. Please review the changes carefully." else echo "INFO: The values files are the same." fi announce "A diff of the values files is above. Here are the secrets and keys you need to manage:" echo "Here are the files you need to manage:" find "$EXTERNAL_SECRETS" -type f -name "*.yaml" -o -name "*.b64" echo announce "Potential next steps..." echo "1. DIFF THE CHANGES: Use helm diff to see the differences between the current and new chart resources." echo "1a. Install helm diff if needed - see https://github.com/databus23/helm-diff?tab=readme-ov-file#install" echo "1b. For example, run 'helm diff upgrade ${RELEASE_NAME_SDA} ${HELM_REPO_SDA} --version ${SDA_CHART_VERSION} --values ${EXTERNALIZED_SECRETS_VALUES_FILE} -n ${NAMESPACE_SDA} | less'" echo if confirm "Do you want to diff the changes (needs helm diff)? (y/n)" "n"; then if helm_diff_installed; then echo "INFO: Running helm diff..." helm diff upgrade "${RELEASE_NAME_SDA}" "${HELM_REPO_SDA}" --version "${SDA_CHART_VERSION}" --values "${EXTERNALIZED_SECRETS_VALUES_FILE}" -n "${NAMESPACE_SDA}" else die "helm diff is not installed. Please install it first if you want to see the differences. See above..." fi fi echo echo "2. CREATE THE NEW SECRETS: If you are satisfied with the changes, you can proceed with the installation of the new secrets and keys." echo "2a. Review the secrets and keys in the '${EXTERNAL_SECRETS}' directory." echo "2b. If everything looks good, install the new secrets using 'kubectl apply -f ${EXTERNAL_SECRETS}/secret' command." echo "2c. NOTE: You will need to manage these in a more automated fashion in the future." if confirm "Do you want to create the new secrets now? (y/n)" "y"; then echo "INFO: Creating the new secrets..." kubectl delete -f "${EXTERNAL_SECRETS}/secret" 2> /dev/null || true kubectl apply -f "${EXTERNAL_SECRETS}/secret" echo "INFO: New secrets created successfully." else echo "INFO: Skipping the creation of new secrets. You can do this later." fi echo echo "3. BACK OLD AND USE THE NEW VALUES: Start using the new values yaml." echo "3a. Backup the current values file: cp ${VALUES_FILE_SDA} ${VALUES_FILE_SDA}.bak" echo "3b. Copy the new values file: cp ${EXTERNALIZED_SECRETS_VALUES_FILE} ${VALUES_FILE_SDA}" echo "3c. Now re-assess the environment variables file with '$0 prepare_vars'" if confirm "Do you want to backup the current values file and use the new one? (y/n)" "y"; then echo "INFO: Backing up the current values file and using the new one..." if [ -f "${VALUES_FILE_SDA}.bak" ]; then echo "INFO: Backup file already exists" else echo "Creatng backup... ${VALUES_FILE_SDA}.bak" cp "${VALUES_FILE_SDA}" "${VALUES_FILE_SDA}.bak" fi cp "${EXTERNALIZED_SECRETS_VALUES_FILE}" "${VALUES_FILE_SDA}" echo "INFO: New values file is now in use." echo "INFO: Environment variables file recreated successfully." else echo "INFO: Skipping the backup and use of new values file. You can do this later." fi echo echo "4. OPTION #1 APPLY TO CURRENT SDA RELEASE - THIS IS MANDATORY IF YOU WISH TO ROLLBACK LATER!!! Apply the new values yaml to the cluster." echo "4a. Run 'helm upgrade --install ${RELEASE_NAME_SDA} ${HELM_REPO_SDA} --version ${SDA_CHART_VERSION} --values ${VALUES_FILE_SDA} -n ${NAMESPACE_SDA}'" echo "4b. This will apply the new values and create the necessary secrets in the cluster." echo "4c. Check the diff again with 'helm diff upgrade ${RELEASE_NAME_SDA} ${HELM_REPO_SDA} --version ${SDA_CHART_VERSION} --values ${VALUES_FILE_SDA} -n ${NAMESPACE_SDA}'" echo echo "5. OPTION #2 APPLY TO MIGRATED CHARTS ONLY - Apply the new values yaml to the new split charts only." echo "5a. A check is performed in 'prepare_vars' to ensure the values file equals to that of the current sda release." echo "5b. If you are sure that the values file is correct, you can skip this check and use the new values directly." echo "5c. Run export VALUES_CHECK_USE_SDA_VALUES=true to use the current sda values files for the migrated charts." if confirm "Do you want to apply the new values to the current SDA release? (y/n)" "y"; then echo "INFO: Applying the new values to the current SDA release..." helm upgrade --install "${RELEASE_NAME_SDA}" "${HELM_REPO_SDA}" \ --version "${SDA_CHART_VERSION}" \ --values "${VALUES_FILE_SDA}" \ -n "${NAMESPACE_SDA}" echo "INFO: New values applied successfully." confirm "Please check that all applications are running as expected. Do you want to continue? (y/n)" "y" elif confirm "Do you want to apply the new values to the migrated charts only? (y/n)" "y"; then echo "INFO: Applying the new values to the migrated charts only..." add_or_update_env_file "VALUES_CHECK_USE_SDA_VALUES" "true" echo "INFO: New values will be applied to the migrated charts only. Please run the script again to apply the changes." fi announce "INFO: Please export the new LAST_SUCCESSFUL_HELM_REVISION and recreate the environment variables file with '$0 prepare_vars' to reflect the changes." } confirm_last_chance() { confirm "Please ensure you have - run the 'prepare_vars' function - migrated the built-in secrets and keys to external secrets if necessary - optionally: applied the changes to the helm release ensured the new release still functions as expected updated the LAST_SUCCESSFUL_HELM_REVISION in the $ENV_FILE - run the 'backup_current' function Confirm" } ## Start migration process migrate() { local dry_run_opts="--dry-run" if [[ "${1:-}" == "no-dry-run" ]]; then dry_run_opts="" else announce "Running migrate in dry-run mode. Use 'no-dry-run' to apply changes." fi source_env announce "Migrating the helm release ${RELEASE_NAME_SDA} in namespace ${NAMESPACE_SDA} into the child releases..." confirm_last_chance confirm "Are you sure you want to migrate the release ${RELEASE_NAME_SDA} in namespace ${NAMESPACE_SDA}?" # sanity checks before migrate if [ ! -d "${BACKUP_DIR}/secret/helm/" ]; then die "Backup directory ${BACKUP_DIR}/secret/helm/ does not exist. Run the backup script first." fi # steps uninstall_sda "${1:-}" patch_pvcs "migrate" "${1:-}" install_nginx "${1:-}" install_ci "${1:-}" install_cd "${1:-}" announce "Installation of CI and CD/RO releases completed. Check all resources are created as expected." echo "You can now check the status of the new releases using:" echo "kubectl get all -n ${NAMESPACE_SDA}" echo echo "You can also check the ingress IP using:" echo "kubectl get ingress -n ${NAMESPACE_SDA}" echo echo "If you have any issues, please check the logs of the pods in the ${NAMESPACE_SDA} namespace." echo "If you need to rollback, use the rollback_after_uninstall function." confirm "All CHART resources have been recreated." if [[ "${1:-}" == "no-dry-run" ]]; then validate echo "Your ingress IP may have changed during migration." echo "Run: kubectl get ingress -n ${NAMESPACE_SDA}" echo "Verify the IP, and update your DNS as needed." else echo "In dry run mode. Cannot validate new releases. Migration finished." fi exit 0 } ## Rollback function to restore the previous state of the helm release rollback_before_uninstall_sda() { local dry_run_opts="--dry-run" [[ "${1:-}" == "no-dry-run" ]] && dry_run_opts="" || echo "Running rollback in dry-run mode. Use 'no-dry-run' to apply changes." source_env announce "Rolling back the helm release ${RELEASE_NAME_SDA} in namespace ${NAMESPACE_SDA} to the last successful revision..." # check if the release exists if ! helm get all "${RELEASE_NAME_SDA}" -n "${NAMESPACE_SDA}" >/dev/null 2>&1; then if [ -z "$dry_run_opts" ]; then die "Helm release ${RELEASE_NAME_SDA} does not exist. Aborting rollback." else echo "Do not expect release ${RELEASE_NAME_SDA} to exist since this is a dry run." return 0 fi fi confirm "Rolling back to the LAST_SUCCESSFUL_HELM_REVISION ${LAST_SUCCESSFUL_HELM_REVISION} of the helm release ${RELEASE_NAME_SDA}..." if helm rollback "${RELEASE_NAME_SDA}" "${LAST_SUCCESSFUL_HELM_REVISION}" -n "${NAMESPACE_SDA}" $dry_run_opts; then echo "Helm rollback to ${LAST_SUCCESSFUL_HELM_REVISION} of ${RELEASE_NAME_SDA} completed successfully." helm history "${RELEASE_NAME_SDA}" -n "${NAMESPACE_SDA}" else die "Helm rollback failed. Check the logs for details." fi } ## Rollback function to restore the previous state of the cluster rollback_after_uninstall_sda() { local dry_run_opts="--dry-run" [[ "${1:-}" == "no-dry-run" ]] && dry_run_opts="" || echo "Running rollback in dry-run mode. Use 'no-dry-run' to apply changes." source_env announce "Rolling back the helm release ${RELEASE_NAME_SDA} in namespace ${NAMESPACE_SDA} to the last successful revision..." confirm "(in the uninstall_sda step) You have migrated the built-in secrets and keys to external secrets, and applied the changes to the helm release." confirm "(in the uninstall_sda step) You have ensured the new release still functions as expected and updated the LAST_SUCCESSFUL_HELM_REVISION." confirm "Are you sure you want to rollback the release ${RELEASE_NAME_SDA} in namespace ${NAMESPACE_SDA}?" # sanity checks before rollback if [ ! -d "${BACKUP_DIR}/secret/helm/" ]; then die "Backup directory ${BACKUP_DIR}/secret/helm/ does not exist. Cannot recreate secrets." fi # steps uninstall_ci "${1:-}" uninstall_cd "${1:-}" uninstall_nginx "${1:-}" announce "Uninstallation of CI and CD/RO releases completed. Check all resources are cleaned up before proceeding." confirm "All CHART resources have been cleaned up (NOTE: some non-chart, e.g. CI controllers, resources may still exist)." patch_pvcs "rollback" "${1:-}" restore_helm_secrets "${1:-}" rollback_before_uninstall_sda "${1:-}" announce "SDA chart rollback completed (${1:-dry-run})." echo "Your ingress IP may have changed during rollback." echo "Run: kubectl get ingress -n ${NAMESPACE_SDA}" echo "Verify the IP, and update your DNS as needed." exit 0 } nginx_needed() { [ -n "${NEW_RELEASE_NAME_NGINX:-}" ] } # Process the values files - Usage: process_values_files process_values_files() { announce "Getting helm release values" export DEFAULT_SDA_CI_VALUES="${BACKUP_VALUES_DIR}/tmp-default-values-cloudbees-sda-ci.yaml" export DEFAULT_SDA_CD_VALUES="${BACKUP_VALUES_DIR}/tmp-default-values-cloudbees-sda-cd.yaml" export DEFAULT_SDA_NGINX_VALUES="${BACKUP_VALUES_DIR}/tmp-default-values-cloudbees-sda-nginx.yaml" mkdir -p "$CHART_DIR" "$BACKUP_VALUES_DIR" "$POST_MIGRATION_DIR" if [ ! -d "$CHART_DIR/cloudbees-sda" ]; then echo "INFO: Downloading cloudbees-sda chart version $SDA_CHART_VERSION..." helm pull "$HELM_REPO_SDA" --version "$SDA_CHART_VERSION" --untar --untardir "$CHART_DIR" else echo "INFO: Using existing cloudbees-sda chart version $SDA_CHART_VERSION." fi yq '.cd' "$CHART_DIR/cloudbees-sda/values.yaml" > "$DEFAULT_SDA_CD_VALUES" yq '.ci' "$CHART_DIR/cloudbees-sda/values.yaml" > "$DEFAULT_SDA_CI_VALUES" yq '.ingress-nginx' "$CHART_DIR/cloudbees-sda/values.yaml" > "$DEFAULT_SDA_NGINX_VALUES" # We'll come back to this later, but for now we have the default values extracted. # get the values from the release echo "INFO: Getting values for release $RELEASE_NAME_SDA in namespace $NAMESPACE_SDA" helm get values "$RELEASE_NAME_SDA" -n "$NAMESPACE_SDA" -o yaml > "$BACKUP_VALUES_FILE" helm get all "$RELEASE_NAME_SDA" -n "$NAMESPACE_SDA" > "$BACKUP_VALUES_FILE_ALL" helm get manifest "$RELEASE_NAME_SDA" -n "$NAMESPACE_SDA" > "$BACKUP_MANIFESTS_FILE" if [ ! -s "$BACKUP_VALUES_FILE" ]; then die "Failed to get values for release $RELEASE_NAME_SDA in namespace $NAMESPACE_SDA. The file $BACKUP_VALUES_FILE is empty." else local VALUES_FILE_TO_USE="$BACKUP_VALUES_FILE" local exit_code=0 confirm "You will now see a diff of the current values file ${VALUES_FILE_SDA} and the backup values file ${BACKUP_VALUES_FILE}. Please continue..." "y" diff \ <(yq -P '... comments="" | sort_keys(..)' "${VALUES_FILE_SDA}") \ <(yq -P '... comments="" | sort_keys(..)' "${BACKUP_VALUES_FILE}") || exit_code=$? if [ "$exit_code" -gt 1 ]; then die "Failed to compare the values files. Please check the files manually." elif [ "$exit_code" -eq 1 ]; then announce "WARNING: The values files ${VALUES_FILE_SDA} and ${BACKUP_VALUES_FILE} are different." echo "- My guess is that the ${VALUES_FILE_SDA} file contains changes that are not reflected in the ${BACKUP_VALUES_FILE}." echo "- Please check the differences, then either:" echo " 1. USE THE CURRENT FILE ON MIGRATED RESOURCES ONLY: if you are sure that the ${VALUES_FILE_SDA} is correct and just want to proceed with that file:" echo " - export the environment variable VALUES_CHECK_USE_SDA_VALUES=true and run the script again." echo " - this will take the current values file into account and avoid superfluous pod restarts." echo " - NOTE: this will not apply the changes in the ${VALUES_FILE_SDA} file to the current SDA release (in the case of a rollback)." echo " 2. APPLY THE NEW CHANGES TO THE CURRENT SDA RELEASE: with 'helm upgrade --install ${RELEASE_NAME_SDA} ${CHART_DIR}/cloudbees-sda -n ${NAMESPACE_SDA} --values ${VALUES_FILE_SDA}'." echo " - NOTE: this will cause a restart of the CDRO and CI pods." if [ "${VALUES_CHECK_USE_SDA_VALUES:-}" == "true" ]; then echo "INFO: Using the current sda values file as requested by the environment variable VALUES_CHECK_USE_SDA_VALUES." VALUES_FILE_TO_USE="${VALUES_FILE_SDA}" else if confirm "Option 1: USE THE CURRENT FILE ON MIGRATED RESOURCES ONLY?" "y"; then echo "INFO: Using the current sda values file as requested by the user." VALUES_FILE_TO_USE="${VALUES_FILE_SDA}" elif confirm "Option 2: APPLY THE NEW CHANGES TO THE CURRENT SDA RELEASE?" "n"; then echo "INFO: Command to run 'helm upgrade --install ${RELEASE_NAME_SDA} ${CHART_DIR}/cloudbees-sda -n ${NAMESPACE_SDA} --values ${VALUES_FILE_SDA}'." echo "INFO: Please check the above command and execute if it looks correct. Then run the script again." exit 0 else die "WARNING: The values files ${VALUES_FILE_SDA} and ${BACKUP_VALUES_FILE} are different. Please check the notes above." fi fi else echo "INFO: The values files are the same." fi fi # split the values file into separate files for CI, CDRO, and NGINX export TMP_USER_SUPPLIED_CI="${BACKUP_VALUES_DIR}/tmp-user-supplied-${RELEASE_NAME_SDA}-ci.yaml" export TMP_USER_SUPPLIED_CD="${BACKUP_VALUES_DIR}/tmp-user-supplied-${RELEASE_NAME_SDA}-cd.yaml" export TMP_USER_SUPPLIED_NGINX="${BACKUP_VALUES_DIR}/tmp-user-supplied-${RELEASE_NAME_SDA}-nginx.yaml" echo "INFO: Splitting values file into CI and CDRO parts" yq 'select(.ci != null) | .ci' "$VALUES_FILE_TO_USE" > "$TMP_USER_SUPPLIED_CI" yq 'select(.cd != null) | .cd' "$VALUES_FILE_TO_USE" > "$TMP_USER_SUPPLIED_CD" yq 'select(.ingress-nginx != null) | .ingress-nginx' "$VALUES_FILE_TO_USE" > "$TMP_USER_SUPPLIED_NGINX" # ensure the files are not empty [ -s "$TMP_USER_SUPPLIED_CI" ] || die "CI values file $TMP_USER_SUPPLIED_CI is empty." [ -s "$TMP_USER_SUPPLIED_CD" ] || die "CD values file $TMP_USER_SUPPLIED_CD is empty." announce "Getting new release values" POST_MIGRATION_VALUES_FILE_CD_EXPECTED="${POST_MIGRATION_DIR}/expected-manifests-cd.yaml" POST_MIGRATION_VALUES_FILE_CI_EXPECTED="${POST_MIGRATION_DIR}/expected-manifests-ci.yaml" POST_MIGRATION_VALUES_FILE_NGINX_EXPECTED="${POST_MIGRATION_DIR}/expected-manifests-nginx.yaml" determine_new_release_values "$DEFAULT_SDA_CD_VALUES" \ "$TMP_USER_SUPPLIED_CD" \ "$HELM_REPO_CD" \ "$CDRO_FLOW_CHART_VERSION_CHART" \ "$POST_MIGRATION_VALUES_FILE_CD" \ "$POST_MIGRATION_VALUES_FILE_CD_EXPECTED" \ "$NEW_RELEASE_NAME_CDRO" determine_new_release_values "$DEFAULT_SDA_CI_VALUES" \ "$TMP_USER_SUPPLIED_CI" \ "$HELM_REPO_CI" \ "$CI_CORE_CHART_VERSION_CHART" \ "$POST_MIGRATION_VALUES_FILE_CI" \ "$POST_MIGRATION_VALUES_FILE_CI_EXPECTED" \ "$NEW_RELEASE_NAME_CI" # handle nginx if needed if nginx_needed; then # check if the user supplied nginx values file is not empty [ -s "$TMP_USER_SUPPLIED_NGINX" ] || die "NGINX values file $TMP_USER_SUPPLIED_NGINX is empty." determine_new_release_values "$DEFAULT_SDA_NGINX_VALUES" \ "$TMP_USER_SUPPLIED_NGINX" \ "$HELM_REPO_NGINX" \ "$NGINX_INGRESS_CHART_VERSION_CHART" \ "$POST_MIGRATION_VALUES_FILE_NGINX" \ "$POST_MIGRATION_VALUES_FILE_NGINX_EXPECTED" \ "$NEW_RELEASE_NAME_NGINX" \ "true" fi announce "Post-migration values files created" echo "Post-migration values file for CDRO chart: $POST_MIGRATION_VALUES_FILE_CD" echo "Post-migration values file for CI chart: $POST_MIGRATION_VALUES_FILE_CI" nginx_needed && echo "Post-migration values file for NGINX chart: $POST_MIGRATION_VALUES_FILE_NGINX" || true echo "Expected manifests for CDRO chart: $POST_MIGRATION_VALUES_FILE_CD_EXPECTED" echo "Expected manifests for CI chart: $POST_MIGRATION_VALUES_FILE_CI_EXPECTED" nginx_needed && echo "Expected manifests for NGINX chart: $POST_MIGRATION_VALUES_FILE_NGINX_EXPECTED" announce "Splitting the post-migration manifest files" split_manifest_files "$BACKUP_MANIFESTS_FILE" split_manifest_files "$POST_MIGRATION_VALUES_FILE_CD_EXPECTED" split_manifest_files "$POST_MIGRATION_VALUES_FILE_CI_EXPECTED" true nginx_needed && split_manifest_files "$POST_MIGRATION_VALUES_FILE_NGINX_EXPECTED" true || true announce "Splitting completed. Look at the diff between the manifests and the expected manifests to see the changes." echo ">>> To see the diff, run: $0 diff_manifests" announce "Adding the values info to the env file: $ENV_FILE" for e in BACKUP_VALUES_FILE BACKUP_VALUES_FILE_ALL BACKUP_VALUES_FILE_NGINX POST_MIGRATION_VALUES_FILE_CI POST_MIGRATION_VALUES_FILE_CD POST_MIGRATION_VALUES_FILE_NGINX; do local value value="${!e}" add_or_update_env_file "$e" "$value" done } add_or_update_env_file() { local e=$1 local value=$2 echo "Processing environment variable: $e with value: $value" if grep -q "$e" "$ENV_FILE"; then if [ -z "$value" ]; then echo "Environment variable $e is set to an empty value in $ENV_FILE. Skipping update." $SED_BIN -i "/^export $e=/d" "$ENV_FILE" else echo "Environment variable $e already exists in $ENV_FILE. Updating its value." $SED_BIN -i "s#^export $e=.*#export $e=${value}#" "$ENV_FILE" fi else if [ -z "$value" ]; then echo "Environment variable $e is not set and has an empty value. Skipping addition." else echo "Adding environment variable $e to $ENV_FILE." echo "export $e=${value}" >> "$ENV_FILE" fi fi } ## Diff the manifests between the backup and post-migration directories diff_manifests() { if [ ! -d "$BACKUP_VALUES_DIR/manifests" ] || [ ! -d "$POST_MIGRATION_DIR/manifests" ]; then die "Backup or post-migration manifests directory does not exist. Please run the script to create them first." fi diff -u "${BACKUP_VALUES_DIR}/manifests" "${POST_MIGRATION_DIR}/manifests" } ## Explain why we cannot just patch the labels/annotations why_not_annotations() { source_env announce "Why can we not just patch the labels/annotations?" grep -rE "(name:.*|=\{\{ )\.Release\.Name \}\}" "$CHART_DIR" | grep -v NOTES.txt || true echo echo "Because the release/instance/chart names are used in:" echo "- the selector fields for deployments, and the replica sets, etc" echo "- the metadata.name field and we would need to change the actual deployed objects. See above for the grep results." } source_env() { # source the environment variables if [ ! -f "$ENV_FILE" ]; then die "Environment file $ENV_FILE does not exist. Please run prepare_vars first." fi # shellcheck disable=SC1090 source "$ENV_FILE" } ## Patch the PVCs to update the release name - Usage: patch_pvcs <migrate|rollback> [no-dry-run] patch_pvcs() { local action="${1:-}" local no_dry_run="${2:-}" [ -z "$action" ] && die "Action must be specified: migrate or rollback." if [ "$no_dry_run" != "no-dry-run" ]; then announce "This is a dry-run. No changes will be made to the PVCs. Add 'no-dry-run' as the second argument to apply changes." else announce "Dry-run mode is off. Changes will be applied to the PVCs." fi source_env announce "Patching PVCs" if [ "$action" != "migrate" ] && [ "$action" != "rollback" ]; then die "Invalid action: $action. Use 'migrate' or 'rollback'." fi if [ "$action" == "migrate" ]; then echo "Patching PVCs for migration to new release names." RELEASES_TO_FIND="${RELEASE_NAME_SDA}" NEW_RELEASE_CI="${NEW_RELEASE_NAME_CI}" NEW_RELEASE_CDRO="${NEW_RELEASE_NAME_CDRO}" else echo "Patching PVCs for rollback to previous release names." RELEASES_TO_FIND="${NEW_RELEASE_NAME_CI} ${NEW_RELEASE_NAME_CDRO}" NEW_RELEASE_CI="${RELEASE_NAME_SDA}" NEW_RELEASE_CDRO="${RELEASE_NAME_SDA}" fi json_str=$(kubectl get pvc -n "$NAMESPACE_SDA" -o json --ignore-not-found 2> /dev/null) for rel in $RELEASES_TO_FIND; do # Extract PVCs for the specific release resource_str=$(echo "$json_str" | jq -r --arg rel "$rel" '.items[] | select(.metadata.labels.release == $rel) | .metadata.name' | tr -d '\r' | $XARGS_BIN -d '\n' echo) if [ -z "$resource_str" ] || [ "$resource_str" == "null" ]; then echo "No PVCs found for release ${rel} in namespace ${NAMESPACE_SDA}. Skipping." continue fi for i in $resource_str; do if [[ "$i" == *"cjoc"* ]]; then if kubectl get pvc "$i" -n "$NAMESPACE_SDA" -o jsonpath='{.metadata.labels.release}' | grep -q .; then echo "Patching PVC ${i} for CI chart with release name ${NEW_RELEASE_CI}" [ "$no_dry_run" == "no-dry-run" ] || continue kubectl patch pvc "$i" -n "$NAMESPACE_SDA" -p "{\"metadata\": {\"labels\": {\"release\": \"$NEW_RELEASE_CI\"}}}" else echo "No release label found for ${i}. No patch was made." fi else if kubectl get pvc "$i" -n "$NAMESPACE_SDA" -o jsonpath='{.metadata.labels.release}' | grep -q .; then echo "Patching PVC ${i} for CD/RO chart with release name ${NEW_RELEASE_CDRO}" [ "$no_dry_run" == "no-dry-run" ] || continue kubectl patch pvc "$i" -n "$NAMESPACE_SDA" -p "{\"metadata\": {\"labels\": {\"release\": \"$NEW_RELEASE_CDRO\"}}}" else echo "No release label found for ${i}. No patch was made." fi fi done done echo "**** PVC patching completed ****" } ## Migrate function to handle the migration process uninstall_sda() { local dry_run_opts="--dry-run" source_env [[ "${1:-}" == "no-dry-run" ]] && dry_run_opts="" || echo "Running uninstall in dry-run mode. Use 'no-dry-run' to apply changes." announce "Uninstalling the existing release ${RELEASE_NAME_SDA} in namespace ${NAMESPACE_SDA}..." # Sanity checks before uninstallation confirm_last_chance if ! helm get all "${RELEASE_NAME_SDA}" -n "${NAMESPACE_SDA}" >/dev/null 2>&1; then echo "Helm release ${RELEASE_NAME_SDA} does not exist in namespace ${NAMESPACE_SDA}. Aborting uninstallation." else helm uninstall "${RELEASE_NAME_SDA}" -n "${NAMESPACE_SDA}" --timeout 4200s $dry_run_opts || die "Failed to uninstall release ${RELEASE_NAME_SDA} in namespace ${NAMESPACE_SDA}. Please check the logs for details." fi echo echo "Uninstallation of release ${RELEASE_NAME_SDA} completed ($dry_run_opts)." echo "Please wait for 2 minutes to ensure all resources are cleaned up before proceeding with the migration." echo "You can check the status of the resources using:" echo "kubectl get all -n ${NAMESPACE_SDA}" announce "Uninstallation of SDA releases completed. Check all resources are cleaned up before proceeding." confirm "All CHART resources have been cleaned up." } ## CI - Uninstall function to handle the uninstallation process (run with no-dry-run to apply changes) uninstall_ci() { local dry_run_opts="--dry-run" source_env [[ "${1:-}" == "no-dry-run" ]] && dry_run_opts="" || echo "Running uninstall in dry-run mode. Use 'no-dry-run' to apply changes." announce "(${dry_run_opts:-"no-dry-run"}) Uninstalling the existing CI release ${NEW_RELEASE_NAME_CI} in namespace ${NAMESPACE_SDA}..." confirm "Are you sure you want to uninstall the release ${NEW_RELEASE_NAME_CI} in namespace ${NAMESPACE_SDA}?" if ! helm get all "${NEW_RELEASE_NAME_CI}" -n "${NAMESPACE_SDA}" >/dev/null 2>&1; then echo "Helm release ${NEW_RELEASE_NAME_CI} does not exist in namespace ${NAMESPACE_SDA}. Aborting uninstallation." else helm uninstall "${NEW_RELEASE_NAME_CI}" -n "${NAMESPACE_SDA}" --timeout 4200s $dry_run_opts || die "Failed to uninstall release ${NEW_RELEASE_NAME_CI} in namespace ${NAMESPACE_SDA}. Please check the logs for details." fi } ## CD - Uninstall function to handle the uninstallation process (run with no-dry-run to apply changes) uninstall_cd() { local dry_run_opts="--dry-run" source_env [[ "${1:-}" == "no-dry-run" ]] && dry_run_opts="" || echo "Running uninstall in dry-run mode. Use 'no-dry-run' to apply changes." announce "(${dry_run_opts:-"no-dry-run"}) Uninstalling the existing CD/RO release ${NEW_RELEASE_NAME_CDRO} in namespace ${NAMESPACE_SDA}..." confirm "Are you sure you want to uninstall the release ${NEW_RELEASE_NAME_CDRO} in namespace ${NAMESPACE_SDA}?" if ! helm get all "${NEW_RELEASE_NAME_CDRO}" -n "${NAMESPACE_SDA}" >/dev/null 2>&1; then echo "Helm release ${NEW_RELEASE_NAME_CDRO} does not exist in namespace ${NAMESPACE_SDA}. Aborting uninstallation." else helm uninstall "${NEW_RELEASE_NAME_CDRO}" -n "${NAMESPACE_SDA}" --timeout 4200s $dry_run_opts || die "Failed to uninstall release ${NEW_RELEASE_NAME_CDRO} in namespace ${NAMESPACE_SDA}. Please check the logs for details." fi } ## CI - Install function to handle the installation process (run with no-dry-run to apply changes) install_ci() { local dry_run_opts="--dry-run" source_env local ns_to_use="${HELM_DUMMY_INSTALL_NS}" local rel_to_use="${RELEASE_NAME_SDA}" if [[ "${1:-}" == "no-dry-run" ]]; then dry_run_opts="" ns_to_use="${NAMESPACE_SDA}" rel_to_use="${NEW_RELEASE_NAME_CI}" else echo "Running install in dry-run mode. Use 'no-dry-run' to apply changes. Using namespace ${ns_to_use} to avoid conflicts." fi announce "(${dry_run_opts:-"no-dry-run"}) Installing the new CI chart ${rel_to_use} in namespace ${ns_to_use}..." helm upgrade --install "${rel_to_use}" "${HELM_REPO_CI}" \ --version "${CI_CORE_CHART_VERSION_CHART}" \ --values "${POST_MIGRATION_VALUES_FILE_CI}" \ --timeout 4200s \ $dry_run_opts \ -n "${ns_to_use}" --wait || die "Failed to install CI chart ${rel_to_use} in namespace ${ns_to_use}. Please check the logs for details." echo "CI chart installation completed." } ## NGINX - Uninstall function to handle the uninstallation process (run with no-dry-run to apply changes) uninstall_nginx() { local dry_run_opts="--dry-run" source_env if ! nginx_needed; then echo "NGINX uninstallation skipped as NEW_RELEASE_NAME_NGINX is not set." return 0 fi [[ "${1:-}" == "no-dry-run" ]] && dry_run_opts="" || echo "Running uninstall in dry-run mode. Use 'no-dry-run' to apply changes." announce "(${dry_run_opts:-"no-dry-run"}) Uninstalling the existing NGINX release ${NEW_RELEASE_NAME_NGINX} in namespace ${NEW_NGINX_NAMESPACE}..." confirm "Are you sure you want to uninstall the release ${NEW_RELEASE_NAME_NGINX} in namespace ${NEW_NGINX_NAMESPACE}?" if ! helm get all "${NEW_RELEASE_NAME_NGINX}" -n "${NEW_NGINX_NAMESPACE}" >/dev/null 2>&1; then echo "Helm release ${NEW_RELEASE_NAME_NGINX} does not exist in namespace ${NEW_NGINX_NAMESPACE}. Aborting uninstallation." else helm uninstall "${NEW_RELEASE_NAME_NGINX}" -n "${NEW_NGINX_NAMESPACE}" --timeout 4200s $dry_run_opts || die "Failed to uninstall release ${NEW_RELEASE_NAME_NGINX} in namespace ${NEW_NGINX_NAMESPACE}. Please check the logs for details." fi } ## CD - Install function to handle the installation process (run with no-dry-run to apply changes) install_cd() { local dry_run_opts="--dry-run" source_env local ns_to_use="${HELM_DUMMY_INSTALL_NS}" local rel_to_use="${RELEASE_NAME_SDA}" if [[ "${1:-}" == "no-dry-run" ]]; then dry_run_opts="" ns_to_use="${NAMESPACE_SDA}" rel_to_use="${NEW_RELEASE_NAME_CDRO}" else echo "Running install in dry-run mode. Use 'no-dry-run' to apply changes. Using namespace ${ns_to_use} to avoid conflicts." fi announce "(${dry_run_opts:-"no-dry-run"}) Installing the new CD/RO chart ${rel_to_use} in namespace ${ns_to_use}..." helm upgrade --install "${rel_to_use}" "${HELM_REPO_CD}" \ --version "${CDRO_FLOW_CHART_VERSION_CHART}" \ --values "${POST_MIGRATION_VALUES_FILE_CD}" \ --timeout 4200s \ $dry_run_opts \ -n "${ns_to_use}" --wait || die "Failed to install CD/RO chart ${rel_to_use} in namespace ${ns_to_use}. Please check the logs for details." echo "CD/RO chart installation completed." } ## NGINX - Install function to handle the installation process (run with no-dry-run to apply changes) install_nginx() { local dry_run_opts="--dry-run" source_env if ! nginx_needed; then echo "NGINX installation skipped as NEW_RELEASE_NAME_NGINX is not set." return 0 fi local ns_to_use="${NAMESPACE_SDA}" local rel_to_use="${RELEASE_NAME_SDA}" if [[ "${1:-}" == "no-dry-run" ]]; then dry_run_opts="" ns_to_use="${NEW_NGINX_NAMESPACE}" rel_to_use="${NEW_RELEASE_NAME_NGINX}" else echo "Running install in dry-run mode. Use 'no-dry-run' to apply changes. Using namespace ${ns_to_use} to avoid conflicts." fi announce "(${dry_run_opts:-"no-dry-run"}) Installing the new NGINX chart ${rel_to_use} in namespace ${ns_to_use}..." helm upgrade --install "${rel_to_use}" "${HELM_REPO_NGINX}" \ --version "${NGINX_INGRESS_CHART_VERSION_CHART}" \ --values "${POST_MIGRATION_VALUES_FILE_NGINX}" \ --timeout 4200s \ $dry_run_opts \ -n "${ns_to_use}" --create-namespace --wait || die "Failed to install NGINX chart ${rel_to_use} in namespace ${ns_to_use}. Please check the logs for details." echo "NGINX chart installation completed." } ## Validate the new releases validate() { source_env announce "Validating the new releases ${NEW_RELEASE_NAME_CI} and ${NEW_RELEASE_NAME_CDRO} in namespace ${NAMESPACE_SDA}..." if ! helm get all "${NEW_RELEASE_NAME_CI}" -n "${NAMESPACE_SDA}" >/dev/null 2>&1; then die "Helm release ${NEW_RELEASE_NAME_CI} does not exist in namespace ${NAMESPACE_SDA}. Please check the installation." fi if ! helm get all "${NEW_RELEASE_NAME_CDRO}" -n "${NAMESPACE_SDA}" >/dev/null 2>&1; then die "Helm release ${NEW_RELEASE_NAME_CDRO} does not exist in namespace ${NAMESPACE_SDA}. Please check the installation." fi echo "To see the diff of the new releases, run:" echo "- helm diff upgrade ${HELM_REPO_CI} --version ${CI_CORE_CHART_VERSION_CHART} --values post-migration/ci.yaml" echo "- helm diff upgrade ${HELM_REPO_CD} --version ${CDRO_FLOW_CHART_VERSION_CHART} --values post-migration/cd.yaml" nginx_needed && echo "- helm diff upgrade ${HELM_REPO_NGINX} --version ${NGINX_INGRESS_CHART_VERSION_CHART} --values post-migration/nginx.yaml" || true echo echo "To see the status of the new releases, run:" echo "- helm status ${NEW_RELEASE_NAME_CI} -n ${NAMESPACE_SDA}" echo "- helm status ${NEW_RELEASE_NAME_CDRO} -n ${NAMESPACE_SDA}" nginx_needed && echo "- helm status ${NEW_RELEASE_NAME_NGINX} -n ${NEW_NGINX_NAMESPACE}" || true if helm_diff_installed; then echo "For transparency, here is the output of the above commands:" announce "Helm diff for CI:" helm diff upgrade "${NEW_RELEASE_NAME_CI}" "${HELM_REPO_CI}" \ --version "${CI_CORE_CHART_VERSION_CHART}" \ --values "${POST_MIGRATION_VALUES_FILE_CI}" \ -n "${NAMESPACE_SDA}" || die "Failed to diff CI chart. Please check the logs for details." announce "Helm diff for CD/RO: (ignore the mariadb secret diff - we are not using it)" helm diff upgrade "${NEW_RELEASE_NAME_CDRO}" "${HELM_REPO_CD}" \ --version "${CDRO_FLOW_CHART_VERSION_CHART}" \ --values "${POST_MIGRATION_VALUES_FILE_CD}" \ -n "${NAMESPACE_SDA}" || die "Failed to diff CD/RO chart. Please check the logs for details." if nginx_needed; then announce "Helm diff for NGINX:" helm diff upgrade "${NEW_RELEASE_NAME_NGINX}" "${HELM_REPO_NGINX}" \ --version "${NGINX_INGRESS_CHART_VERSION_CHART}" \ --values "${POST_MIGRATION_VALUES_FILE_NGINX}" \ -n "${NEW_NGINX_NAMESPACE}" || die "Failed to diff NGINX chart. Please check the logs for details." fi else echo "helm diff is not installed. Please install it first if you want to see the differences. See above..." fi announce "Helm status for CI:" helm status "${NEW_RELEASE_NAME_CI}" -n "${NAMESPACE_SDA}" announce "Helm status for CD/RO:" helm status "${NEW_RELEASE_NAME_CDRO}" -n "${NAMESPACE_SDA}" if nginx_needed; then announce "Helm status for NGINX:" helm status "${NEW_RELEASE_NAME_NGINX}" -n "${NEW_NGINX_NAMESPACE}" fi } restore_helm_secrets() { local dry_run_opts="--dry-run=client" [[ "${1:-}" == "no-dry-run" ]] && dry_run_opts="" || echo "Running restore helm secrets in dry-run mode. Use 'no-dry-run' to apply changes." source_env # recreate helm secrets announce "Recreating helm secrets from the backup directory ${BACKUP_DIR}/secret/helm/" if [ ! -d "${BACKUP_DIR}/secret/helm/" ]; then die "Backup directory ${BACKUP_DIR}/secret/helm/ does not exist. Cannot recreate secrets." fi kubectl apply -f "${BACKUP_DIR}/secret/helm/" $dry_run_opts || die "Failed to apply secret from ${BACKUP_DIR}/secret/helm. Check the logs for details." echo "Secrets recreated successfully from ${BACKUP_DIR}/secret/helm/" } # Split the manifest files into separate files - Usage: split_manifest_files <manifest_file> split_manifest_files() { local MANIFEST_FILE="${1:-}" local NO_CLEAN="${2:-}" echo "Splitting manifest file $MANIFEST_FILE into separate files..." local BASE_DIR OUT_DIR BASE_DIR=$(dirname "$MANIFEST_FILE") BASE_NAME=$(basename "$MANIFEST_FILE") OUT_DIR="${BASE_DIR}/manifests" mkdir -p "$OUT_DIR" if [ -n "$NO_CLEAN" ]; then echo "Skipping cleanup of existing files in $OUT_DIR" else echo "Cleaning up existing files in $OUT_DIR" rm -f "${OUT_DIR}"/* fi echo "Let's remove the comments because they cause issues with diffing..." cp "$MANIFEST_FILE" "$MANIFEST_FILE.tmp" yq -i '... comments=""' "${MANIFEST_FILE}.tmp" pushd "$BASE_DIR" # split the manifest file into separate files yq e -s '"manifests/file_" + .kind + "_" + .metadata.name' "${BASE_NAME}.tmp" rm "${BASE_NAME}.tmp" popd } ## check the helm repository - Usage: check_helm_repo <repo_name> <repo_url> check_helm_repo() { local repo_name="$1" local repo_url="$2" local current_url current_url=$(helm repo list -oyaml | yq '.[]|select(.name == "'"$1"'")|.url') if [ -z "$current_url" ]; then echo "Adding Helm repository: $repo_name at $repo_url" helm repo add "$repo_name" "$repo_url" else # ignore trailing slashes for comparison if [ "${current_url%/}" != "${repo_url%/}" ]; then die "Helm repository $repo_name is already set up at $current_url, but expected $repo_url" else echo "Helm repository $repo_name is already set up at $current_url" fi fi } ## Print help; use "help NAME" to show details help() { (( $# > 0 )) && { type "$@" return $? } local DESC='' while read -r; do case $REPLY in *\(\)) [ "$DESC" ] || continue echo "${REPLY%(*} - $DESC" DESC= ;; \#\#\ *) [ "$DESC" ] && continue DESC=${REPLY##*#} ;; esac done < "$0" } if [ "${BASH_SOURCE[0]}" == "$0" ]; then "${@:-help}" fi
-
Make the script executable:
chmod +x sda-helm-chart-migration.sh
-
Create new working directory:
mkdir "$RELEASE_NAME_SDA" && cd "$RELEASE_NAME_SDA"
-
Store your SDA helm values in the current working directory with the name
${RELEASE_NAME_SDA}-values.yaml
.-
If you did not preserve the sda values file earlier, you can run:
helm get values "$RELEASE_NAME_SDA" -n "$NAMESPACE_SDA" -o yaml > "${RELEASE_NAME_SDA}-values.yaml"
-
-
Execute the script.
Running the script without arguments gives you a list of possible functions. There are five main methods to look at:
❯ ../sda-helm-chart-migration.sh prepare_vars - Sanity checks and preparing the environment variables backup_current - Backup the current state of the cluster - Usage: backup_current migrate - Start migration process rollback_before_uninstall_sda - Rollback function to restore the previous state of the helm release rollback_after_uninstall_sda - Rollback function to restore the previous state of the cluster
Prepare variables:
../sda-helm-chart-migration.sh prepare_vars ######################################### # Setting up environment variables for the migration script ######################################### Using LAST_SUCCESSFUL_HELM_REVISION: 1 1 Fri Sep 19 13:57:56 2025 deployed cloudbees-sda-1.659+0a71e6d8c986 2024.12.1.178274+2.492.3.5 Install complete Error: Values file sda-values.yaml does not exist. Please provide the correct values file or set the VALUES_FILE_SDA environment variable.
Some secrets may need to be externalized before running the command again. Follow the prompts in the script output from Built-in secrets.
Built-in secrets
The CloudBees CD/RO chart generates some random built-in secrets during install, if not explicitly set in the values yaml.
If this is the case, the script will inform you:
... ... WARNING: existing_passkey is not present in the values file WARNING: existing_keystore is not present in the values file WARNING: existing_database is not present in the values file WARNING: existing_analytics is not present in the values file WARNING: existing_dois is not present in the values file WARNING: existing_flowCredentials is not present in the values file WARNING: existing_boundAgentFlowCredentials is not present in the values file WARNING: existing_mariadb is not present in the values file ######################################### # Some built-in secrets are not set in the values file (see above). ######################################### Error: Use ../sda-helm-chart-migration.sh externalize_secrets to set them.
Externalize secrets
Running the externalize secrets method will guide you through the process of creating explicit secrets for your release.
When asked during execution if you want to rollback after migration for any reason, it is mandatory to answer Yes. Otherwise, the rollback will break. Example: "Do you want to create the new secrets now? (y/n) [Y/n]: y" "Do you want to apply the new values to the current SDA release? (y/n) [Y/n]: y" |
If you are accepting this for future rollback, after it applies the changes to the current SDA installation, expect to find the last_successful_helm_revision
and set the LAST_SUCCESSFUL_HELM_REVISION env variable again.
../sda-helm-chart-migration.sh externalize_secrets ## expected to update LAST_SUCCESSFUL_HELM_REVISION env variable # e.g.: # helm history $RELEASE_NAME_SDA -n $NAMESPACE_SDA # REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION # 1 Fri Sep 19 15:30:29 2025 superseded cloudbees-sda-1.629+145c7d6eb825 2024.06.0.175153+2.462.2.2 Install complete # 2 Fri Sep 19 15:40:17 2025 superseded cloudbees-sda-1.629+145c7d6eb825 2024.06.0.175153+2.462.2.2 Upgrade complete # 3 Fri Sep 19 16:33:08 2025 deployed cloudbees-sda-1.629+145c7d6eb825 2024.06.0.175153+2.462.2.2 Upgrade complete export LAST_SUCCESSFUL_HELM_REVISION=3 # To update internal env variable with with updated LAST_SUCCESSFUL_HELM_REVISION ../sda-helm-chart-migration.sh prepare_vars
After this step, make sure your current pods are up and running. |
Now you have two or more new yaml files in the directory for CloudBees CI CloudBees CD/RO, and Nginx (if applicable) respectively:
❯ ls -1 *.yaml cloudbees-sda-values.yaml post-migration-cd.yaml post-migration-ci.yaml
The files are:
-
post-migration-cd.yaml
- contains the user-supplied values for the CloudBees CD/RO chart. -
post-migration-ci.yaml
- contains the user-supplied values for the CloudBees CI chart.
Backup current state
Now you are ready to back up your current values. This will back up the helm secrets, PVC definitions, keys, and Kubernetes secrets to the backup directory.
../sda-helm-chart-migration.sh backup_current
Migrate
Now you are ready to run the migration script. The migrate function will basically run through the following functions including confirmations and checks:
-
uninstall_sda
-
patch_pvcs
-
install_ci
-
install_cd
-
Validate
By default, this will run all the steps in dry-run mode so it does not affect the cluster. |
To run the migration in dry-run mode:
../sda-helm-chart-migration.sh migrate
If satisfied, run the migration with the 'no dry-run' argument:
../sda-helm-chart-migration.sh migrate no-dry-run
The script will guide you through:
-
Uninstalling the CloudBees Software Delivery Automation chart
-
Patching the PVCs
-
Installing the two dependent charts
-
Validating the helm releases and values
You can follow the status of the pods in another terminal:
-
The output towards the end of the script with look something like this:
❯ ../sda-helm-chart-migration.sh migrate no-dry-run ... ... ######################################### # Validating the new releases cloudbees-core and cloudbees-flow in namespace cloudbees... ######################################### To see the diff of the new releases, run: - helm diff upgrade cloudbees/cloudbees-core --version 3.22283.0+4b0b554033da --values post-migration/ci.yaml - helm diff upgrade cloudbees/cloudbees-flow --version 2.32.1 --values post-migration/cd.yaml To see the status of the new releases, run: - helm status cloudbees-core -n cloudbees - helm status cloudbees-flow -n cloudbees For transparency, here is the output of the above commands: ######################################### # Helm diff for CI: ######################################### ######################################### # Helm diff for CD/RO: (ignore the mariadb secret diff - we are not using it) ######################################### cloudbees, mariadb-initdb-secret, Secret (v1) has changed: # Source: cloudbees-flow/templates/server-secrets.yaml apiVersion: v1 kind: Secret metadata: labels: app: mariadb role: mariadb name: mariadb-initdb-secret data: - mariadb-password: '-------- # (20 bytes)' - mariadb-root-password: '-------- # (20 bytes)' + mariadb-password: '++++++++ # (20 bytes)' + mariadb-root-password: '++++++++ # (20 bytes)' type: Opaque ######################################### # Helm status for CI: ######################################### NAME: cloudbees-core LAST DEPLOYED: Fri Aug 8 14:14:27 2025 NAMESPACE: cloudbees STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: 1. Once Operations Center is up and running, get your initial admin user password by running: kubectl rollout status sts cjoc --namespace cloudbees kubectl exec cjoc-0 --namespace cloudbees -- cat /var/jenkins_home/secrets/initialAdminPassword 2. Visit http://ci.10.0.0.2.beesdns.com/cjoc/ 3. Login with the password from step 1. For more information on running CloudBees Core on Kubernetes, visit: https://go.cloudbees.com/docs/cloudbees-core/cloud-admin-guide/ ######################################### # Helm status for CD/RO: ######################################### NAME: cloudbees-flow LAST DEPLOYED: Fri Aug 8 14:15:01 2025 NAMESPACE: cloudbees STATUS: deployed REVISION: 1 NOTES: - CloudBees CD/RO Server was successfully deployed - CloudBees CD/RO Web was successfully deployed and is available at https://ci.10.0.0.2.beesdns.com Username: admin - CloudBees CD/RO Devops Insight was successfully deployed - CloudBees CD/RO Analytics Server was successfully deployed Your ingress IP may have changed during migration. Run: kubectl get ingress -n cloudbees Verify the IP, and update your DNS as needed.
Once you receive the CloudBees Software Delivery Automation chart deprecated successfully message from the script, proceed to the next section.
Check deployment operations
After running the migration script, it critical to double-check your environment to ensure the Kubernetes deployments are operating as expected. This section provides basic checks to help.
To check the status of your new deployment:
-
Check the status of the pods:
kubectl get pods -n $NAMESPACE_SDA # If you have used inbuilt nginx chart earlier for sda installation kubectl get pods -n cloudbees-ingress-nginx
You can check the updated Helm chart installations by running:
helm list -n $NAMESPACE_SDA helm list -n cloudbees-ingress-nginx
-
Check the ingress endpoints
ADDRESS
:kubectl get ingress -n $NAMESPACE_SDA
-
Your ingress IP addresses might have changed during migration. If so, update your DNS with the new ingress IP addresses and clear any cached records.
Failing to update your DNS and clearing caches may result in your components being unable to communicate. If you ran the externalize_secrets
command, secrets such as your passkey will be visible in your helm values, exposed to any user who can retrieve helm values in your cluster. If the migration is successful and you are confident you will not need to roll back, you should consider backing up current values in a secure place, and remove theserver.customConfig.passkey.b64
value from the CloudBees CD/RO helm values.
-
-
Log into both the CloudBees CI and CloudBees CD/RO instances:
-
Ensure your expected data is present.
-
Run jobs as needed to ensure all components can send and receive data within your deployment.
If you encounter any issues after running the migration script, refer to Rollback Helm chart migration. -
If your expected data is present, and your components are operating and communicating as expected, you have successfully migrated from the CloudBees Software Delivery Automation Helm chart to the standalone product Helm charts.
Rollback Helm chart migration
If you encountered issues during migration, the steps in this section will help rollback your cluster to the original CloudBees Software Delivery Automation deployment.
To roll back the migration to the previous CloudBees Software Delivery Automation deployment:
+ NOTE: By default, this will run all the steps in dry-run mode so it does not affect the cluster.
../sda-helm-chart-migration.sh rollback_after_uninstall_sda
If satisfied, run the rollback with the 'no dry-run' argument:
../sda-helm-chart-migration.sh rollback_after_uninstall_sda no-dry-run
After successful execution, your original CloudBees Software Delivery Automation deployment should be fully restored.
Your ingress IP addresses might have changed during migration. To check the ingress IP address, run:
Verify the ingress IP against your DNS, update your DNS with the new ingress IP addresses, and clear any cached records. Failing to perform this step may result in your components being unable to communicate. |