From 8b34383db3ddd846e9abee932d610d0797d4fd60 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Thu, 11 Dec 2025 10:59:17 +0000 Subject: [PATCH] Update GitLab CI push solution to use Upsun CLI approach Modernize the GitLab CI push solution example to use the Upsun CLI instead of manual SSH key management and raw API calls. Changes: - Add .upsun-cli template using pjcdawkins/platformsh-cli image with Upsun config overlay - Replace deprecated only:/except: with modern rules: syntax - Use CI_DEFAULT_BRANCH instead of hardcoded 'main' - Add GitLab environment: integration with url, auto_stop_in, and on_stop - Use dotenv artifacts to expose PRIMARY_URL - Separate jobs for push-production, push-review-env, cleanup-review-envs, and delete-review-env - Use UPSUN_ prefixed variables (UPSUN_PROJECT_ID, UPSUN_PARENT_ENV, UPSUN_TARGET_BRANCH, UPSUN_CLI_COMMAND, UPSUN_CLI_TOKEN) - Add helper scripts (push.sh, cleanup.sh, delete.sh) based on ai-api reference implementation - Use upsun push --activate --force instead of manual git push + API calls - Use upsun url --primary --pipe to get environment URL - Add UPSUN_NO_CLONE_PARENT option for review environments Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/gitlab-ci/push-solution.yaml | 156 +++++++++++--------------- examples/gitlab-ci/scripts/cleanup.sh | 19 ++++ examples/gitlab-ci/scripts/delete.sh | 20 ++++ examples/gitlab-ci/scripts/push.sh | 43 +++++++ 4 files changed, 149 insertions(+), 89 deletions(-) create mode 100755 examples/gitlab-ci/scripts/cleanup.sh create mode 100755 examples/gitlab-ci/scripts/delete.sh create mode 100755 examples/gitlab-ci/scripts/push.sh diff --git a/examples/gitlab-ci/push-solution.yaml b/examples/gitlab-ci/push-solution.yaml index 30d1831..ff6e88d 100644 --- a/examples/gitlab-ci/push-solution.yaml +++ b/examples/gitlab-ci/push-solution.yaml @@ -5,103 +5,81 @@ stages: variables: GIT_STRATEGY: clone GIT_DEPTH: 0 # Full clone for accurate branch history + # Required: Set your Upsun project ID + # UPSUN_PROJECT_ID: your-project-id + # Required: Set your Upsun CLI token as a protected variable + # UPSUN_CLI_TOKEN: your-api-token -.setup_ssh: &setup_ssh - - echo "Setup SSH" - - mkdir -p ~/.ssh - - echo "$UPSUN_SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa - - chmod 600 ~/.ssh/id_rsa - - ssh-keyscan -H git.$UPSUN_REGION.platform.sh >> ~/.ssh/known_hosts - -.access_token: &access_token - - | - export UPSUN_ACCESS_TOKEN=$(curl -u platform-api-user: \ - -d "grant_type=api_token&api_token=$UPSUN_API_TOKEN" \ - https://auth.upsun.com/oauth2/token | jq -r .access_token) +.upsun-cli: + image: pjcdawkins/platformsh-cli + before_script: + - curl -fsSL https://raw.githubusercontent.com/platformsh/cli/refs/heads/main/internal/config/upsun-cli.yaml > cli-config.yaml + - echo > upsun 'CLI_CONFIG_FILE=cli-config.yaml platform "$@"' + - chmod +x upsun + variables: + UPSUN_CLI_COMMAND: ./upsun -# Deploy on push to branches and new merge requests -deploy_to_upsun: +# Push to production on default branch +push-production: + extends: .upsun-cli stage: deploy - image: alpine:latest - before_script: - - apk add --no-cache curl jq git openssh - - *setup_ssh - - *access_token + variables: + UPSUN_PARENT_ENV: $CI_DEFAULT_BRANCH + UPSUN_TARGET_BRANCH: $CI_DEFAULT_BRANCH + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH script: - - | - echo "Configure Git" - git config --global user.email "gitlab-ci@example.com" - git config --global user.name "GitLab CI" - - echo "Add Upsun remote" - git remote add upsun $UPSUN_GIT_REMOTE - - echo "Checkout branch and push to Upsun" - git checkout -B $CI_COMMIT_REF_NAME - git push upsun $CI_COMMIT_REF_NAME + - bash examples/gitlab-ci/scripts/push.sh + environment: + name: production + url: $PRIMARY_URL + artifacts: + reports: + dotenv: environment.env - echo "Activate environment" - echo "UPSUN_ACCESS_TOKEN: $UPSUN_ACCESS_TOKEN" - curl -s -X POST \ - -H "Authorization: Bearer $UPSUN_ACCESS_TOKEN" \ - -H "Content-Type: application/json" \ - "https://api.upsun.com/projects/$UPSUN_PROJECT_ID/environments/main/activate" - - only: - - merge_requests - except: - - main # Adjust based on your default branch - -# Deploy to production -deploy_production: +# Push to review environment for merge requests +push-review-env: + extends: .upsun-cli stage: deploy - image: alpine:latest - before_script: - - apk add --no-cache curl jq git openssh - - *setup_ssh + when: manual + variables: + UPSUN_PARENT_ENV: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME + UPSUN_TARGET_BRANCH: $CI_COMMIT_REF_SLUG + UPSUN_NO_CLONE_PARENT: "true" + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" script: - - | - git config --global user.email "gitlab-ci@example.com" - git config --global user.name "GitLab CI" - git remote add upsun $UPSUN_GIT_REMOTE || git remote set-url upsun $UPSUN_GIT_REMOTE - git push upsun main --force - only: - - main # Adjust based on your default branch + - bash examples/gitlab-ci/scripts/push.sh + environment: + name: review/$CI_COMMIT_REF_SLUG + url: $PRIMARY_URL + auto_stop_in: 7 day + on_stop: delete-review-env + artifacts: + reports: + dotenv: environment.env -# Cleanup environments when branches are deleted or MRs are merged -cleanup_environment: +# Cleanup merged environments +cleanup-review-envs: + extends: .upsun-cli stage: cleanup - image: alpine:latest - before_script: - - apk add --no-cache curl jq git openssh - - *setup_ssh - - *access_token - script: - - | - # Get the source branch name from the merge request - BRANCH_NAME="${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}" - - if [ -z "$BRANCH_NAME" ]; then - echo "No branch name found, skipping cleanup" - exit 0 - fi - - echo "Cleaning up Upsun environment: $BRANCH_NAME" - - # Delete the branch from Upsun remote - git push upsun --delete "$BRANCH_NAME" || echo "Branch already deleted from remote" - - # Deactivate the environment via API - curl -s -X POST \ - -H "Authorization: Bearer $UPSUN_ACCESS_TOKEN" \ - -H "Content-Type: application/json" \ - "https://api.upsun.com/projects/$UPSUN_PROJECT_ID/environments/$BRANCH_NAME/deactivate" || echo "Environment deactivation failed" - - # Optionally delete the environment completely - curl -s -X DELETE \ - -H "Authorization: Bearer $UPSUN_ACCESS_TOKEN" \ - "https://api.upsun.com/projects/$UPSUN_PROJECT_ID/environments/$BRANCH_NAME" || echo "Environment deletion failed" rules: - - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - when: manual + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + script: + - bash examples/gitlab-ci/scripts/cleanup.sh allow_failure: true + +# Delete specific review environment when stopped +delete-review-env: + extends: .upsun-cli + stage: cleanup + when: manual + variables: + UPSUN_TARGET_BRANCH: $CI_COMMIT_REF_SLUG + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + script: + - bash examples/gitlab-ci/scripts/delete.sh + environment: + name: review/$CI_COMMIT_REF_SLUG + action: stop diff --git a/examples/gitlab-ci/scripts/cleanup.sh b/examples/gitlab-ci/scripts/cleanup.sh new file mode 100755 index 0000000..00df897 --- /dev/null +++ b/examples/gitlab-ci/scripts/cleanup.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# This script cleans up Upsun environments that have been merged with their parent. + +set -e -o pipefail + +# Config. +if [ -z "$UPSUN_PROJECT_ID" ]; then + echo >&2 '$UPSUN_PROJECT_ID is required' + exit 1 +fi +if [ -z "$UPSUN_PARENT_ENV" ]; then + UPSUN_PARENT_ENV=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-${CI_DEFAULT_BRANCH:-main}} +fi + +CLI_COMMAND=${UPSUN_CLI_COMMAND:-upsun} + +# Clean up environments if they are merged with their parent. +$CLI_COMMAND environment:delete --no-interaction --project="$UPSUN_PROJECT_ID" --merged --no-wait --exclude-type production,staging --exclude "$UPSUN_PARENT_ENV" diff --git a/examples/gitlab-ci/scripts/delete.sh b/examples/gitlab-ci/scripts/delete.sh new file mode 100755 index 0000000..c894214 --- /dev/null +++ b/examples/gitlab-ci/scripts/delete.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# This script deletes a specific Upsun environment. + +set -e -o pipefail + +# Config. +if [ -z "$UPSUN_PROJECT_ID" ]; then + echo >&2 '$UPSUN_PROJECT_ID is required' + exit 1 +fi +BRANCH=${UPSUN_TARGET_BRANCH:-$CI_COMMIT_REF_SLUG} +if [ -z "$BRANCH" ]; then + echo >&2 'Branch name ($UPSUN_TARGET_BRANCH or $CI_COMMIT_REF_SLUG) not defined' + exit 1 +fi + +CLI_COMMAND=${UPSUN_CLI_COMMAND:-upsun} + +$CLI_COMMAND environment:delete --no-interaction --no-wait --project="$UPSUN_PROJECT_ID" --environment="$BRANCH" diff --git a/examples/gitlab-ci/scripts/push.sh b/examples/gitlab-ci/scripts/push.sh new file mode 100755 index 0000000..7fcb9f7 --- /dev/null +++ b/examples/gitlab-ci/scripts/push.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# This script can be used from GitLab CI to push code to an Upsun environment. +# It always force-pushes and activates the environment. + +set -e -o pipefail + +# Config. +if [ -z "$UPSUN_PROJECT_ID" ]; then + echo >&2 '$UPSUN_PROJECT_ID is required' + exit 1 +fi +BRANCH=${UPSUN_TARGET_BRANCH:-$CI_COMMIT_REF_SLUG} +if [ -z "$BRANCH" ]; then + echo >&2 'Target branch ($UPSUN_TARGET_BRANCH or $CI_COMMIT_REF_SLUG) not defined' + exit 1 +fi +UPSUN_PARENT_ENV=${UPSUN_PARENT_ENV:-$CI_MERGE_REQUEST_TARGET_BRANCH_NAME} +if [ -z "$UPSUN_PARENT_ENV" ]; then + UPSUN_PARENT_ENV=${CI_DEFAULT_BRANCH:-main} +fi + +CLI_COMMAND=${UPSUN_CLI_COMMAND:-upsun} + +$CLI_COMMAND project:set-remote --no-interaction "$UPSUN_PROJECT_ID" + +# Build and run the push command. +push_command="$CLI_COMMAND push --no-interaction --force --target=${BRANCH}" +if [ "$UPSUN_PARENT_ENV" != "$BRANCH" ]; then + push_command="$push_command --activate --parent=${UPSUN_PARENT_ENV}" + if [ "$BRANCH" != main ]; then + push_command="$push_command --resources-init=default" + fi +fi +if [ -n "$UPSUN_NO_CLONE_PARENT" ]; then + push_command="$push_command --no-clone-parent" +fi + +$push_command + +# Write the environment's primary URL to a dotenv file. +# This can be used by a GitLab job via the "dotenv" artifact type. +echo "PRIMARY_URL=$($CLI_COMMAND url --primary --pipe --no-interaction --environment="$BRANCH")" > environment.env