Cloud Run Deploy

cloud-run-deploy@1.0.0 Template

Roll an image out to Google Cloud Run, wire Secret Manager secrets, and health-check the new revision.

Start from it

flightstack template use cloud-run-deploy

What it does

Ship a container image to Cloud Run with gcloud run services update (keeps every setting you did not name) or gcloud run deploy (creates the service if needed), optionally set env vars and Secret Manager secrets, then probe the service before calling the deploy done. Emits url and revision.

Inputs

project string required
Google Cloud project id.
region string required
Cloud Run region, e.g. us-central1.
service string required
Cloud Run service name.
image string required
Image to deploy. Prefer a digest, e.g. the imageRef output of docker-build-push, so the deploy is the exact build that was tested.
mode update | deploy default update
update changes only the image and the settings named here and fails if the service does not exist - safest for a service whose runtime account, scaling and secrets are managed elsewhere. deploy creates the service when it is missing.
envVars object
Plain environment variables to set (--update-env-vars). Never secrets.
secrets object
Environment variables read from Secret Manager (--update-secrets), e.g. DATABASE_URL: db-url:latest.
noTraffic boolean default false
Create the revision without sending it traffic. Pair with tag to get a preview URL.
tag string
Revision tag, which gets its own URL (https://TAG---SERVICE-...run.app).
extraArgs string
Extra flags for the gcloud command, e.g. "--memory 1Gi".
healthPath string
Path to GET after the deploy, e.g. /health. The job fails unless it returns 2xx within the retry window. Empty skips the check.
healthTimeoutSeconds number default 120
How long to keep retrying the health check.

Definition

name: Cloud Run Deploy
description: >-
  Roll a container image out to a Google Cloud Run service, optionally wire
  Secret Manager secrets into it, and check the new revision answers before
  calling the deploy done.

triggers:
  - type: push
    branches: [main]

jobs:
  deploy:
    name: Deploy API
    type: plugin/cloud-run-deploy
    with:
      project: my-project
      region: us-central1
      service: api
      image: "${{ steps.image.outputs.imageRef }}"
      healthPath: /health

plugin:
  name: cloud-run-deploy
  displayName: Cloud Run Deploy
  description: >-
    Ship an image to Cloud Run with `gcloud run services update` (keeps every
    setting you did not name) or `gcloud run deploy` (creates the service if
    needed), then probe the service URL. Emits `url` and `revision`.
  category: deploy

  supportedPlatforms:
    - linux
    - macos

  parameters:
    type: object
    properties:
      project:
        type: string
        description: Google Cloud project id.
      region:
        type: string
        description: Cloud Run region, e.g. us-central1.
      service:
        type: string
        description: Cloud Run service name.
      image:
        type: string
        description: >-
          Image to deploy. Prefer a digest, e.g. the `imageRef` output of
          docker-build-push, so the deploy is the exact build that was tested.
      mode:
        type: string
        enum: [update, deploy]
        default: update
        description: >-
          `update` changes only the image and the settings named here and
          fails if the service does not exist - safest for a service whose
          runtime account, scaling and secrets are managed elsewhere. `deploy`
          creates the service when it is missing.
      envVars:
        type: object
        description: Plain environment variables to set (--update-env-vars). Never secrets.
      secrets:
        type: object
        description: >-
          Environment variables read from Secret Manager (--update-secrets),
          e.g. DATABASE_URL: db-url:latest.
      noTraffic:
        type: boolean
        default: false
        description: >-
          Create the revision without sending it traffic. Pair with `tag` to
          get a preview URL.
      tag:
        type: string
        default: ""
        description: Revision tag, which gets its own URL (https://TAG---SERVICE-...run.app).
      extraArgs:
        type: string
        default: ""
        description: Extra flags for the gcloud command, e.g. "--memory 1Gi".
      healthPath:
        type: string
        default: ""
        description: >-
          Path to GET after the deploy, e.g. /health. The job fails unless it
          returns 2xx within the retry window. Empty skips the check.
      healthTimeoutSeconds:
        type: number
        default: 120
        description: How long to keep retrying the health check.
    required: [project, region, service, image]

  script: |
    set -euo pipefail

    if ! command -v gcloud >/dev/null 2>&1; then
      echo "gcloud is not installed on this agent." >&2
      exit 1
    fi

    if [ -n "${GCP_SA_KEY:-}" ]; then
      keyfile="$(mktemp)"
      trap 'rm -f "$keyfile"' EXIT
      printf '%s' "$GCP_SA_KEY" > "$keyfile"
      gcloud auth activate-service-account --key-file "$keyfile" --quiet
    elif [ -n "${GOOGLE_APPLICATION_CREDENTIALS:-}" ]; then
      gcloud auth activate-service-account --key-file "$GOOGLE_APPLICATION_CREDENTIALS" --quiet
    else
      echo "note: no GCP_SA_KEY or GOOGLE_APPLICATION_CREDENTIALS; using the agent's active gcloud account."
    fi

    common=(--project "{{project}}" --region "{{region}}" --quiet)

    if [ "{{mode}}" = "update" ]; then
      args=(run services update "{{service}}" --image "{{image}}")
    else
      args=(run deploy "{{service}}" --image "{{image}}")
    fi

    envs=""
    {{#each envVars}}
    envs="${envs:+$envs,}{{@key}}={{this}}"
    {{/each}}
    if [ -n "$envs" ]; then args+=(--update-env-vars "$envs"); fi

    secrets=""
    {{#each secrets}}
    secrets="${secrets:+$secrets,}{{@key}}={{this}}"
    {{/each}}
    if [ -n "$secrets" ]; then args+=(--update-secrets "$secrets"); fi

    if [ "{{noTraffic}}" = "true" ]; then args+=(--no-traffic); fi
    tag="{{tag}}"
    if [ -n "$tag" ]; then args+=(--tag "$tag"); fi

    echo "--- gcloud ${args[*]}"
    gcloud "${args[@]}" "${common[@]}" {{extraArgs}}

    url="$(gcloud run services describe "{{service}}" "${common[@]}" --format 'value(status.url)')"
    revision="$(gcloud run services describe "{{service}}" "${common[@]}" --format 'value(status.latestReadyRevisionName)')"
    if [ -n "$tag" ]; then
      url="$(echo "$url" | sed "s#https://#https://$tag---#")"
    fi
    echo "revision $revision at $url"
    fs-output url "$url"
    fs-output revision "$revision"

    path="{{healthPath}}"
    if [ -z "$path" ]; then
      exit 0
    fi

    deadline=$(( $(date +%s) + {{healthTimeoutSeconds}} ))
    while :; do
      code="$(curl -s -o /dev/null -w '%{http_code}' "$url$path" || echo 000)"
      case "$code" in
        2*) echo "health check $url$path -> $code"; exit 0 ;;
      esac
      if [ "$(date +%s)" -ge "$deadline" ]; then
        echo "health check $url$path still returns $code after {{healthTimeoutSeconds}}s" >&2
        exit 1
      fi
      echo "health check $url$path -> $code, retrying"
      sleep 5
    done