From 8e62329d9c2328e26128e97d3e1b264ec71a53f4 Mon Sep 17 00:00:00 2001 From: phette23 Date: Fri, 14 Feb 2025 13:49:58 -0800 Subject: [PATCH] ci: deploy to staging in github actions ref #165 --- .github/workflows/build.yml | 90 --------------------- .github/workflows/stage.yml | 152 ++++++++++++++++++++++++++++++++++++ docs/release | 1 + kubernetes/staging.yaml | 40 +++------- 4 files changed, 166 insertions(+), 117 deletions(-) delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/stage.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index a23c9a21..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,90 +0,0 @@ -# Build a Docker image and push it to Google Artifact Registry - -name: 'Build Wagtail Image' - -on: - push: - tags: - - 'stg-build-*' - -env: - PROJECT_ID: 'cca-web-staging' - GAR_LOCATION: 'us-west1' - REPOSITORY: 'us-west1-docker.pkg.dev/cca-web-staging/cca-docker-web' - IMAGE: 'libraries' - WORKLOAD_IDENTITY_PROVIDER: projects/316944295291/locations/global/workloadIdentityPools/github/providers/libraries-wagtail - -jobs: - lint: - name: 'Lint' - runs-on: 'ubuntu-latest' - environment: 'staging' - steps: - - run: npm run eslint - - run: npx run sass-lint - - setupbuildandpush: - name: 'Setup, Build, and Push Docker image to Artifact Registry' - runs-on: 'ubuntu-latest' - environment: 'staging' - - permissions: - contents: 'read' - id-token: 'write' - - steps: - - name: 'Checkout' - uses: 'actions/checkout@v4.2.2' - - # Configure Workload Identity Federation and generate an access token. - # See https://github.com/google-github-actions/auth for more options, - # including authenticating via a JSON credentials file. - - id: 'auth' - name: 'Authenticate to Google Cloud' - uses: 'google-github-actions/auth@v2.1.8' - with: - create_credentials_file: true # Important for Docker auth - project_id: '${{ env.PROJECT_ID }}' - service_account: 'libraries-wagtail-gh-actions@cca-web-staging.iam.gserviceaccount.com' - token_format: 'access_token' # Explicitly request OAuth token - workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' - - # Configure Docker to use the gcloud credentials - - name: 'Set up Cloud SDK' - uses: 'google-github-actions/setup-gcloud@v2.1.4' - - # Apparently this step is necessary too, google-github-actions/auth is not enough - - name: 'Docker Auth' - run: gcloud auth configure-docker ${{ env.GAR_LOCATION }}-docker.pkg.dev - - # Authenticate Docker to Google Cloud Artifact Registry - - name: 'Docker Auth' - uses: 'docker/login-action@v3.3.0' - with: - username: 'oauth2accesstoken' - password: '${{ steps.auth.outputs.auth_token }}' - registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' - - - name: 'Pull Latest Image' - # we need a consistent tag like "latest" or this docker pull fails & we lose our build cache - run: docker pull "${{ env.REPOSITORY }}/${{ env.IMAGE}}:latest" - - # unique Docker tag like ep-full-20-abcd123 - - name: Generate tags - id: tag - run: | - SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7) - if [ "${{ github.ref_type }}" = "tag" ]; then - VERSION="${{ github.ref_name }}-${SHORT_SHA}" - else - VERSION="${SHORT_SHA}" - fi - echo "tag=${{ env.REPOSITORY }}/${{ env.IMAGE}}:${VERSION}" >> $GITHUB_OUTPUT - - # TODO using existing Docker actions might be better? https://docs.docker.com/build/ci/github-actions/cache/ - - name: "Build and Tag Image" - # must build with --build-arg BUILDKIT_INLINE_CACHE=1 or image doesn't work with --cache-form - run: docker build --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from "${{ env.REPOSITORY }}/${{ env.IMAGE}}:latest" --tag "${{ steps.tag.outputs.tag }}" --tag "${{ env.REPOSITORY }}/${{ env.IMAGE}}:latest" . - - - name: 'Push Image' - run: docker push --all-tags "${{ env.REPOSITORY }}/${{ env.IMAGE}}" diff --git a/.github/workflows/stage.yml b/.github/workflows/stage.yml new file mode 100644 index 00000000..9d1733c9 --- /dev/null +++ b/.github/workflows/stage.yml @@ -0,0 +1,152 @@ +# Staging CD: +# git tag stg-build-X -> build docker image +# git tag stg-deploy-X -> build image & deploy to GKE + +name: 'Staging CD' +on: + push: + tags: + - 'stg-build-*' + - 'stg-deploy-*' + +env: + GAR_LOCATION: 'us-west1' + PROJECT_ID: 'cca-web-staging' + REPOSITORY: 'us-west1-docker.pkg.dev/cca-web-staging/cca-docker-web' + IMAGE: 'libraries' + SERVICE_ACCOUNT: 'libraries-wagtail-gh-actions@cca-web-staging.iam.gserviceaccount.com' + WORKLOAD_IDENTITY_PROVIDER: projects/316944295291/locations/global/workloadIdentityPools/github/providers/libraries-wagtail + CLUSTER_LOCATION: 'us-west1-b' + CLUSTER_NAME: 'ccaedu-stg' + K8S_NAMESPACE: 'lib-ep' + +jobs: + build: + name: 'Setup, Build, and Push Docker image to Artifact Registry' + runs-on: 'ubuntu-latest' + environment: 'staging' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@v4.2.2' + + # Configure Workload Identity Federation and generate an access token. + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@v2.1.8' + with: + create_credentials_file: true # Important for Docker auth + project_id: ${{ env.PROJECT_ID }} + service_account: ${{ env.SERVICE_ACCOUNT }} + token_format: 'access_token' # Explicitly request OAuth token + workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }} + + # Configure Docker to use the gcloud credentials + - name: 'Set up Cloud SDK' + uses: 'google-github-actions/setup-gcloud@v2.1.4' + + # This step is necessary too, google-github-actions/auth is not enough + - name: 'Configure Docker to use GCloud auth' + run: gcloud auth configure-docker ${{ env.GAR_LOCATION }}-docker.pkg.dev + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@v3.3.0' + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: ${{ env.GAR_LOCATION }}-docker.pkg.dev + + # Unique Docker tag like stg-deploy-20-abcd123 + - name: Generate tags + id: tag + run: | + SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7) + if [ "${{ github.ref_type }}" = "tag" ]; then + VERSION="${{ github.ref_name }}-${SHORT_SHA}" + else + VERSION="${SHORT_SHA}" + fi + echo "tag=${VERSION}" >> $GITHUB_OUTPUT + shell: bash + + # From https://docs.docker.com/build/ci/github-actions/cache/ + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.9.0 + + - name: Build and push + uses: docker/build-push-action@v6.14.0 + with: + cache-to: type=inline + cache-from: type=registry,ref=${{ env.REPOSITORY }}/${{ env.IMAGE}}:latest + tags: | + ${{ env.REPOSITORY }}/${{ env.IMAGE}}:latest + ${{ env.REPOSITORY }}/${{ env.IMAGE}}:${{ steps.tag.outputs.tag }} + push: true + + deploy: + needs: build + if: startsWith(github.ref_name, 'stg-deploy-') + runs-on: ubuntu-latest + environment: staging + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - uses: actions/checkout@v4.2.2 + + - name: Set up Google Cloud Auth + uses: google-github-actions/auth@v2.1.8 + with: + project_id: ${{ env.PROJECT_ID }} + service_account: ${{ env.SERVICE_ACCOUNT }} + workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }} + + - name: Set up GKE credentials + uses: google-github-actions/get-gke-credentials@v2.3.1 + with: + cluster_name: ${{ env.CLUSTER_NAME }} + location: ${{ env.CLUSTER_LOCATION }} + + - name: Determine Docker tag + id: tag + run: | + SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7) + if [ "${{ github.ref_type }}" = "tag" ]; then + VERSION="${{ github.ref_name }}-${SHORT_SHA}" + else + VERSION="${SHORT_SHA}" + fi + echo "tag=${VERSION}" >> $GITHUB_OUTPUT + shell: bash + + - name: Deploy to GKE + env: + # see staging.yaml for needed vars, most are derived from namespace + IMAGE: ${{ env.REPOSITORY }}/${{ env.IMAGE}}:${{ steps.tag.outputs.tag }} + KUBERNETES_NAMESPACE_OVERWRITE: ${{ env.K8S_NAMESPACE }} + run: | + # Ensure namespace exists + kubectl get namespace ${KUBERNETES_NAMESPACE_OVERWRITE} || kubectl create namespace ${KUBERNETES_NAMESPACE_OVERWRITE} + + # Apply configuration with error checking + if ! cat kubernetes/staging.yaml | envsubst | kubectl apply -f -; then + echo "Failed to apply Kubernetes configuration" + exit 1 + fi + + # Wait for deployment to roll out + kubectl rollout status deployment/app --namespace ${KUBERNETES_NAMESPACE_OVERWRITE} --timeout=300s + + - name: Verify deployment + run: | + kubectl wait --for=condition=available deployment/app --namespace ${KUBERNETES_NAMESPACE_OVERWRITE} --timeout=60s + kubectl get pods --namespace ${KUBERNETES_NAMESPACE_OVERWRITE} --selector app=libraries -o jsonpath='{.items[*].status.containerStatuses[*].ready}' | grep -q true diff --git a/docs/release b/docs/release index 709df07e..4d23f97c 100755 --- a/docs/release +++ b/docs/release @@ -21,6 +21,7 @@ else if test "$argv[1]" = prod; or test "$argv[1]" = production end git tag $NEXT_VERSION_TAG else + # ! This section needs a rewrite to work with new staging tags # Staging release: ep-full-N pushes to staging cluster. No other tags, no release in GH. set LATEST_EP_TAG (git tags | grep 'ep-full-' | sort | tail -n 1) set EP_VERSION (string split - $LATEST_EP_TAG) diff --git a/kubernetes/staging.yaml b/kubernetes/staging.yaml index 7b3c57ab..7581c871 100644 --- a/kubernetes/staging.yaml +++ b/kubernetes/staging.yaml @@ -1,7 +1,6 @@ --- -# this is the file used by [ep|mg]-full-... tagged releases -# create the namespace for libraries - +# This file is used by stg-deploy-X tags, see staging.yml GH Action +# Create the namespace for libraries kind: Namespace apiVersion: v1 metadata: @@ -17,33 +16,20 @@ metadata: name: app namespace: ${KUBERNETES_NAMESPACE_OVERWRITE} data: - # TODO review k8s secrets — some are unused (secret-key) and others should be converted to Secret Manager - # Generic - DJANGO_SETTINGS_MODULE: libraries.settings KUBERNETES_NAMESPACE: ${KUBERNETES_NAMESPACE_OVERWRITE} - - # CAS + # Most of these are references in libraries/libraries/settings/base.py + DJANGO_SETTINGS_MODULE: libraries.settings CAS_SERVER_URL: "https://sso5-stage.cca.edu/cas/login" - - # Media MEDIA_URL: "https://storage.googleapis.com/libraries-staging-${KUBERNETES_NAMESPACE_OVERWRITE}/" - - # Review Apps - DEPLOY_RELEASE: ${KUBERNETES_NAMESPACE_OVERWRITE} - # DEPLOY_RELEASE: ${CI_COMMIT_REF_NAME} - # DEPLOY_COMMIT_HASH: ${CI_COMMIT_SHORT_SHA} - # DEPLOY_BRANCH_NAME: ${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} - # DEPLOY_SERVER_NAME: ${KUBERNETES_NAMESPACE_OVERWRITE} - - # Elastic Search ES_INDEX_PREFIX: ${KUBERNETES_NAMESPACE_OVERWRITE} - - # Google Cloud Storage + DB_NAME: libraries-${KUBERNETES_NAMESPACE_OVERWRITE} + # For static files in Google Cloud Storage & secrets in Secret Manager GS_PROJECT_ID: cca-web-staging GS_BUCKET_NAME: libraries-media-staging-${KUBERNETES_NAMESPACE_OVERWRITE} - # Database - DB_NAME: libraries-${KUBERNETES_NAMESPACE_OVERWRITE} + # Review Apps + # ? Where is this used? + DEPLOY_RELEASE: ${KUBERNETES_NAMESPACE_OVERWRITE} --- # The application itself. @@ -69,7 +55,7 @@ spec: - name: gcr-json-key initContainers: - name: init-app - image: us.gcr.io/cca-web-staging/libraries:${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA} + image: ${IMAGE} imagePullPolicy: Always command: [ @@ -104,7 +90,7 @@ spec: key: credentials containers: - name: app - image: us.gcr.io/cca-web-staging/libraries:${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA} + image: ${IMAGE} imagePullPolicy: Always # Env variables form ConfigMap envFrom: @@ -279,7 +265,7 @@ spec: secretName: summon-sftp-secrets containers: - name: summon - image: us.gcr.io/cca-web-staging/libraries:${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA} + image: ${IMAGE} imagePullPolicy: IfNotPresent args: - python @@ -335,7 +321,7 @@ spec: restartPolicy: Never containers: - name: publish-scheduled-pages - image: us.gcr.io/cca-web-staging/libraries:${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA} + image: ${IMAGE} imagePullPolicy: IfNotPresent args: - python