From 54526506056407a3480a4fcbb4b8c9c2d35090f5 Mon Sep 17 00:00:00 2001 From: David Pacheco Date: Wed, 12 Mar 2025 13:19:44 +0100 Subject: [PATCH] update `update-chart-values` action --- .../update-chart-values/README.md | 45 ++++ .../update-chart-values/action.yaml | 23 +- .../update-chart-values/entrypoint.sh | 208 ++++++++++++------ 3 files changed, 195 insertions(+), 81 deletions(-) create mode 100644 .github/actions/argocd-deployment/update-chart-values/README.md diff --git a/.github/actions/argocd-deployment/update-chart-values/README.md b/.github/actions/argocd-deployment/update-chart-values/README.md new file mode 100644 index 00000000..fde35ff5 --- /dev/null +++ b/.github/actions/argocd-deployment/update-chart-values/README.md @@ -0,0 +1,45 @@ +# ArgoCD Deployment GitHub Action + +## Introduction +This GitHub Action automates the deployment of applications using ArgoCD by updating Helm chart values. It supports various deployment scenarios, including staging, production, and multi-environment deployments. + +## Inputs +- **app_name**: The name of the application to be deployed. *(required)* +- **branch_name**: The name of the branch currently being deployed. *(required)* +- **deploy_on_sandbox**: Set to "true" to enable deployment to the sandbox environment during the ALL_ENV stage. Default is `true`. +- **env_to_deploy**: Specifies the target environment for deployment. Use 'ALL_ENV' to update all environments, or 'NO_SYNC' to only update common staging values. *(required)* +- **image_tag**: The specific image tag to deploy. +- **manual**: Set to true if the deployment is manual, to reflect this in the commit message. +- **rollout**: Set to true if the deployment is a rollout, to reflect this in the commit message. +- **synced_envs_as_outputs**: Set to true to output the list of environments that were synchronized. Default is `false`. +- **update_deployed_at**: Set to "true" to update the DEPLOYED_AT environment variable with the current date and time. Default is `false`. +- **release_version**: The version of the PROD release to be deployed. + +## Outputs +- **synced_staging_envs**: Outputs the list of environments that were successfully synchronized. +- **old_image_tag**: Outputs the old image tag for a possible rollback. + +## Usage Example +```yaml +name: Deploy Application + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Deploy to ArgoCD + uses: ./.github/actions/argocd-deployment/update-chart-values + with: + app_name: 'my-app' + branch_name: 'main' + env_to_deploy: 'prod' + image_tag: 'v1.0.0' + manual: 'true' +``` \ No newline at end of file diff --git a/.github/actions/argocd-deployment/update-chart-values/action.yaml b/.github/actions/argocd-deployment/update-chart-values/action.yaml index c7e1f11b..9ce7aec8 100755 --- a/.github/actions/argocd-deployment/update-chart-values/action.yaml +++ b/.github/actions/argocd-deployment/update-chart-values/action.yaml @@ -4,40 +4,39 @@ description: 'Update current_tag value locally and commit changes.' inputs: app_name: - description: 'The application name that will be deployed.' + description: 'The name of the application to be deployed.' required: true branch_name: - description: 'Current branch name.' + description: 'The name of the branch currently being deployed.' required: true deploy_on_sandbox: - description: 'If "true" the deployment in the ALL_ENV stage will be done on the sandbox environment.' + description: 'Set to "true" to enable deployment to the sandbox environment during the ALL_ENV stage.' required: false default: 'true' env_to_deploy: description: > - Environment where the image will be deployed. - If the value is 'ALL_ENV', then all environments will be updated. - If the value is 'NO_SYNC', then the action will only update the common staging values. + Specifies the target environment for deployment. + Use 'ALL_ENV' to update all environments, or 'NO_SYNC' to only update common staging values. required: true image_tag: - description: 'The image tag that will be deployed.' + description: 'The specific image tag to deploy.' required: false manual: - description: 'If true, the commit message will specify that is a Manual deployment.' + description: 'Set to true if the deployment is manual, to reflect this in the commit message.' required: false rollout: - description: 'If true, the commit message will specify that is a Rollout.' + description: 'Set to true if the deployment is a rollout, to reflect this in the commit message.' required: false synced_envs_as_outputs: - description: 'If true, the action will output the environments that were sycned.' + description: 'Set to true to output the list of environments that were synchronized.' required: false default: 'false' update_deployed_at: - description: 'If "true" the env DEPLOYED_AT will be updated with the current deployment date.' + description: 'Set to "true" to update the DEPLOYED_AT environment variable with the current date and time.' required: false default: 'false' release_version: - description: 'The release version that will be deployed.' + description: 'The version of the PROD release to be deployed.' required: false outputs: diff --git a/.github/actions/argocd-deployment/update-chart-values/entrypoint.sh b/.github/actions/argocd-deployment/update-chart-values/entrypoint.sh index 219b8c36..cec55c28 100755 --- a/.github/actions/argocd-deployment/update-chart-values/entrypoint.sh +++ b/.github/actions/argocd-deployment/update-chart-values/entrypoint.sh @@ -1,10 +1,27 @@ #!/usr/bin/env bash +# ============================================================================ +# ArgoCD Deployment Script for Helm Chart Values Update +# +# This script updates Helm chart values for ArgoCD deployments based on +# environment, branch name, and other parameters. It handles different +# deployment scenarios including staging, production, and multi-environment +# deployments. +# ============================================================================ + +# Exit immediately if a command exits with a non-zero status +# Treat unset variables as an error +# Exit if any command in a pipeline fails set -euo pipefail +# ============================================================================ +# HELPER FUNCTIONS +# ============================================================================ +# Variable to track environments that have been synchronized synced_staging_envs="" +# Adds a new staging environment to the list of synced environments add_synced_staging_envs() { local new_staging_env=$1 if [ -z "$synced_staging_envs" ]; then @@ -14,135 +31,188 @@ add_synced_staging_envs() { fi } +# Updates the deployment timestamp if enabled +update_deployment_timestamp() { + if [[ "$UPDATE_DEPLOYED_AT" = true ]]; then + echo "Updating 'DEPLOYED_AT' env variable at runtime." + DEPLOYED_AT=$(date -u +"%FT%TZ") + fi +} + +# Updates image tag in the specified values file +update_image_tag() { + local values_file=$1 + if [ "$IMAGE_TAG" != "" ]; then + sed -i "{s/currentTag:.*/currentTag: $IMAGE_TAG/;}" "$values_file" + fi +} + +# Updates DEPLOYED_AT timestamp in the specified values file +update_deployed_at() { + local values_file=$1 + if [ ! -z ${DEPLOYED_AT+x} ]; then + sed -i "{s/DEPLOYED_AT:.*/DEPLOYED_AT: $DEPLOYED_AT/;}" "$values_file" + fi +} + +# Commits and pushes changes to git repository +commit_and_push_changes() { + if [ -z "$(git diff --exit-code)" ]; then + echo "No changes in the working directory." + return 0 + fi + + # Configure git user + git config user.name github-actions + git config user.email github-actions@github.com + git pull + git add . -# The branch name can only start with 'master' or 'main' if the branch is MASTER/MAIN ref. + # Create appropriate commit message based on deployment type + if [[ $ROLLOUT == true ]]; then + git commit -m "ROLLOUT UNDO in ${APP_NAME^^} - $IMAGE_TAG -> [${ENV_TO_DEPLOY^^}]" + elif [[ $MANUAL == true ]] && [[ "$IMAGE_TAG" != "" ]]; then + git commit -m "MANUAL DEPLOYMENT in ${APP_NAME^^} - $IMAGE_TAG -> [${ENV_TO_DEPLOY^^}${RELEASE_VERSION:+-$RELEASE_VERSION}]" + elif [[ $MANUAL == true ]]; then + git commit -m "MANUAL DEPLOYMENT in ${APP_NAME^^} -> [${ENV_TO_DEPLOY^^}]" + else + git commit -m "DEPLOYMENT in ${APP_NAME^^} - $IMAGE_TAG -> [${ENV_TO_DEPLOY^^}]" + fi + + git push +} + +# ============================================================================ +# VALIDATION CHECKS +# ============================================================================ + +# Validate production deployment requirements if [[ "$ENV_TO_DEPLOY" == "prod" ]] && [[ "$BRANCH_NAME" != "master" && "$BRANCH_NAME" != "main" ]]; then - if [[ -z "$RELEASE_VERSION" ]]; then - echo "The Environment to Deploy cannot be 'prod' if the branches are not 'master' or 'main', and the RELEASE_VERSION is not set." + echo "ERROR: Production deployment requires either master/main branch or a RELEASE_VERSION." exit 1 fi fi -# The Environment name to Deploy cannot be 'master' or 'main'. +# Prevent deploying to master/main environments if [[ "$ENV_TO_DEPLOY" == "master" || "$ENV_TO_DEPLOY" == "main" ]]; then - echo "The Environment to Deploy cannot be 'master' or 'main'." + echo "ERROR: Cannot deploy directly to 'master' or 'main' environments." exit 1 fi -# The Environment name to Deploy cannot be 'ALL_ENV' if the branch is not 'master' or 'main'. +# Validate ALL_ENV deployment requirements if [[ "$ENV_TO_DEPLOY" == "ALL_ENV" ]] && [[ "$BRANCH_NAME" != "master" && "$BRANCH_NAME" != "main" ]]; then - echo "It cannot be deployed in All Environments if the branch is not 'master' or 'main'." + echo "ERROR: ALL_ENV deployment requires master or main branch." exit 1 fi -# Must be updated in each deploy if UPDATE_DEPLOYED_AT is true. -if [[ "$UPDATE_DEPLOYED_AT" = true ]]; then - echo "Updating 'DEPLOYED_AT' env variable at runtime." - DEPLOYED_AT=$(date -u +"%FT%TZ") -fi +# Update deployment timestamp if enabled +update_deployment_timestamp + +# ============================================================================ +# DEPLOYMENT LOGIC +# ============================================================================ -# Iter over all values-*.yaml files in order to sync thier content with the local values. +# SCENARIO 1: Deploy to ALL staging environments from master/main branch if [[ "$ENV_TO_DEPLOY" == "ALL_ENV" ]] && [[ "$BRANCH_NAME" == "master" || "$BRANCH_NAME" == "main" ]]; then cd helm-chart-$APP_NAME-values-staging/ + + # Process each staging environment for env_path in $(ls -d -- ./staging/*/ 2>/dev/null); do - # Get the source of the 'currentTag' environment + # Extract environment name and source file path export CURRENT_ENV=$(basename "${env_path%/}") - export CURRENT_SOURCE_FILE=$(echo "./../kube/values/$APP_NAME/staging/$CURRENT_ENV/values-stg.yaml") + export CURRENT_SOURCE_FILE="./../kube/values/$APP_NAME/staging/$CURRENT_ENV/values-stg.yaml" if [[ -e $CURRENT_SOURCE_FILE ]]; then + # Get current image tag information export CURRENT_IMAGE_TAG=$(cat "./staging/$CURRENT_ENV/values-stg-tag.yaml" | grep currentTag: | cut -d ':' -f 2 | sed 's/ //g') export CURRENT_IMAGE_TAG_ENV=$(cut -d '-' -f 1 <<< $( echo $CURRENT_IMAGE_TAG )) - # Check if the currentTag is an old 'master' image -> Then, sync the values. - if [[ "$CURRENT_IMAGE_TAG_ENV" == "master" || "$CURRENT_IMAGE_TAG_ENV" == "main" || "$CURRENT_IMAGE_TAG_ENV" == "latest" || "$CURRENT_ENV" == "sandbox" ]]; then + # Only sync environments with master/main/latest tags or sandbox + if [[ "$CURRENT_IMAGE_TAG_ENV" == "master" || "$CURRENT_IMAGE_TAG_ENV" == "main" || + "$CURRENT_IMAGE_TAG_ENV" == "latest" || "$CURRENT_ENV" == "sandbox" ]]; then - # Skip the deployment on sandbox if the DEPLOY_ON_SANDBOX is false. + # Skip sandbox if disabled if [[ $DEPLOY_ON_SANDBOX == false && "$CURRENT_ENV" == "sandbox" ]]; then + echo "Skipping sandbox deployment as DEPLOY_ON_SANDBOX is false" continue fi - # Update the currentTag to the new image tag if it is not empty. - if [ "$IMAGE_TAG" != "" ]; then - sed -i "{s/currentTag:.*/currentTag: $IMAGE_TAG/;}" "./staging/$CURRENT_ENV/values-stg-tag.yaml" - fi - - # Update the DEPLOYED_AT env variable if it is not empty. - if [ ! -z ${DEPLOYED_AT+x} ]; then - sed -i "{s/DEPLOYED_AT:.*/DEPLOYED_AT: $DEPLOYED_AT/;}" $CURRENT_SOURCE_FILE - fi + # Update image tag and deployment timestamp + update_image_tag "./staging/$CURRENT_ENV/values-stg-tag.yaml" + update_deployed_at "$CURRENT_SOURCE_FILE" - # Add the current environment to the synced_staging_envs variable if SYNCED_ENVS_AS_OUTPUTS is true. + # Track synced environments if enabled if [[ $SYNCED_ENVS_AS_OUTPUTS == true ]]; then add_synced_staging_envs $CURRENT_ENV fi - # Sync the values of the current environment from the local code repository to the helm-chart-$APP_NAME-values/staging repository. + # Sync values from local repository to helm chart repository + echo "Syncing values for environment: $CURRENT_ENV" cp -f -r "./../kube/values/$APP_NAME/staging/$CURRENT_ENV/" "./staging/" fi else - echo "$CURRENT_ENV not found in local code repository, but existing in helm-chart-$APP_NAME-values/staging repository." + echo "WARNING: $CURRENT_ENV not found in local code repository, but exists in helm-chart-$APP_NAME-values/staging repository." fi done - # The values-stg.yaml will always be synced when a Pull Request is closed. + + # Always sync the common staging values file cp -f "./../kube/values/$APP_NAME/staging/values-stg.yaml" "./staging/values-stg.yaml" + + # Output synced environments for GitHub Actions if [ ! -z ${synced_staging_envs+x} ]; then echo "synced_staging_envs=$( echo $synced_staging_envs )" >> $GITHUB_OUTPUT fi +# SCENARIO 2: No sync operation, just update common staging values elif [[ "$ENV_TO_DEPLOY" == "NO_SYNC" ]] && [[ "$BRANCH_NAME" == "master" || "$BRANCH_NAME" == "main" ]]; then cd helm-chart-$APP_NAME-values-staging/ + echo "Updating only common staging values file" cp -f "./../kube/values/$APP_NAME/staging/values-stg.yaml" "./staging/values-stg.yaml" +# SCENARIO 3: Deploy to production from master/main branch elif [[ "$ENV_TO_DEPLOY" == "prod" ]] && [[ "$BRANCH_NAME" == "master" || "$BRANCH_NAME" == "main" ]]; then cd helm-chart-$APP_NAME-values-prod/ - # Store the currentTag value before the deployment for rollout undo (just in case). + + # Store current image tag for potential rollback echo "old_image_tag=$(cat "./prod/values-prod-tag.yaml" | grep currentTag: | cut -d ':' -f 2 | sed 's/ //g')" >> $GITHUB_OUTPUT - if [ "$IMAGE_TAG" != "" ]; then - sed -i "{s/currentTag:.*/currentTag: $IMAGE_TAG/;}" "./prod/values-prod-tag.yaml" - fi - if [ ! -z ${DEPLOYED_AT} ]; then - sed -i "{s/DEPLOYED_AT:.*/DEPLOYED_AT: $DEPLOYED_AT/;}" "./../kube/values/$APP_NAME/prod/values-prod.yaml" - fi + + # Update image tag and deployment timestamp + update_image_tag "./prod/values-prod-tag.yaml" + update_deployed_at "./../kube/values/$APP_NAME/prod/values-prod.yaml" + + # Sync production values + echo "Syncing production values" cp -f -r "./../kube/values/$APP_NAME/prod/" "./" + +# SCENARIO 4: Deploy to production with specific release version elif [[ "$ENV_TO_DEPLOY" == "prod" ]] && [[ ! -z "$RELEASE_VERSION" ]]; then cd helm-chart-$APP_NAME-values-prod/ - if [ "$IMAGE_TAG" != "" ]; then - sed -i "{s/currentTag:.*/currentTag: $IMAGE_TAG/;}" "./prod-$RELEASE_VERSION/values-prod-tag.yaml" - fi - if [ ! -z ${DEPLOYED_AT} ]; then - sed -i "{s/DEPLOYED_AT:.*/DEPLOYED_AT: $DEPLOYED_AT/;}" "./../kube/values/$APP_NAME/prod-$RELEASE_VERSION/values-prod.yaml" - fi + + # Update image tag and deployment timestamp for specific release + update_image_tag "./prod-$RELEASE_VERSION/values-prod-tag.yaml" + update_deployed_at "./../kube/values/$APP_NAME/prod-$RELEASE_VERSION/values-prod.yaml" + + # Sync production values for specific release + echo "Syncing production values for release: $RELEASE_VERSION" cp -f -r "./../kube/values/$APP_NAME/prod-$RELEASE_VERSION/" "./" + +# SCENARIO 5: Deploy to specific staging environment else cd helm-chart-$APP_NAME-values-staging/ - # Store the currentTag value before the deployment for rollout undo (just in case). + + # Store current image tag for potential rollback echo "old_image_tag=$(cat "./staging/$ENV_TO_DEPLOY/values-stg-tag.yaml" | grep currentTag: | cut -d ':' -f 2 | sed 's/ //g')" >> $GITHUB_OUTPUT - if [ "$IMAGE_TAG" != "" ]; then - sed -i "{s/currentTag:.*/currentTag: $IMAGE_TAG/;}" "./staging/$ENV_TO_DEPLOY/values-stg-tag.yaml" - fi - if [ ! -z ${DEPLOYED_AT} ]; then - sed -i "{s/DEPLOYED_AT:.*/DEPLOYED_AT: $DEPLOYED_AT/;}" "./../kube/values/$APP_NAME/staging/$ENV_TO_DEPLOY/values-stg.yaml" - fi + + # Update image tag and deployment timestamp + update_image_tag "./staging/$ENV_TO_DEPLOY/values-stg-tag.yaml" + update_deployed_at "./../kube/values/$APP_NAME/staging/$ENV_TO_DEPLOY/values-stg.yaml" + + # Sync values for specific staging environment + echo "Syncing values for specific staging environment: $ENV_TO_DEPLOY" cp -f -r "./../kube/values/$APP_NAME/staging/$ENV_TO_DEPLOY/" "./staging/" fi -if [ -z "$(git diff --exit-code)" ]; then - echo "No changes in the working directory." -else - git config user.name github-actions - git config user.email github-actions@github.com - git pull - git add . - if [[ $ROLLOUT == true ]]; then - git commit -m "ROLLOUT UNDO in ${APP_NAME^^} - $IMAGE_TAG -> [${ENV_TO_DEPLOY^^}]" - elif [[ $MANUAL == true ]] && [[ "$IMAGE_TAG" != "" ]]; then - git commit -m "MANUAL DEPLOYMENT in ${APP_NAME^^} - $IMAGE_TAG -> [${ENV_TO_DEPLOY^^}${RELEASE_VERSION:+-$RELEASE_VERSION}]" - elif [[ $MANUAL == true ]]; then - git commit -m "MANUAL DEPLOYMENT in ${APP_NAME^^} -> [${ENV_TO_DEPLOY^^}]" - else - git commit -m "DEPLOYMENT in ${APP_NAME^^} - $IMAGE_TAG -> [${ENV_TO_DEPLOY^^}]" - fi - git push -fi \ No newline at end of file +# Commit and push changes to git repository +commit_and_push_changes \ No newline at end of file