From 15cd558c1435637c6113713cff981304a36e34a7 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Wed, 22 Jan 2025 15:58:21 -1000 Subject: [PATCH 1/6] Works with deployment --- .controlplane/Dockerfile | 8 +- .controlplane/readme.md | 29 +++ .controlplane/shakacode-team.md | 6 + .dockerignore | 6 +- .github/actions/build-docker-image/action.yml | 32 +++ .../delete-control-plane-app/action.yml | 20 ++ .../deploy-to-control-plane/action.yml | 105 ++++++--- .../scripts/delete-app.sh | 36 +++ .../deploy-to-control-plane/scripts/deploy.sh | 43 ++++ .../scripts/get-commit-sha.sh | 34 +++ .github/actions/setup-environment/action.yml | 23 +- .github/workflows/delete-review-app.yml | 85 +++++++ .../deploy-to-control-plane-review.yml | 85 ------- .../deploy-to-control-plane-staging.yml | 8 +- .github/workflows/deploy-to-control-plane.yml | 210 ++++++++++++++++++ .github/workflows/help-command.yml | 52 +++++ .../nightly-remove-stale-review-apps.yml | 34 ++- .../promote-staging-to-production.yml | 56 +++++ .gitignore | 2 +- CHANGELOG.md | 6 + 20 files changed, 742 insertions(+), 138 deletions(-) create mode 100644 .controlplane/shakacode-team.md create mode 100644 .github/actions/build-docker-image/action.yml create mode 100644 .github/actions/delete-control-plane-app/action.yml create mode 100755 .github/actions/deploy-to-control-plane/scripts/delete-app.sh create mode 100755 .github/actions/deploy-to-control-plane/scripts/deploy.sh create mode 100755 .github/actions/deploy-to-control-plane/scripts/get-commit-sha.sh create mode 100644 .github/workflows/delete-review-app.yml delete mode 100644 .github/workflows/deploy-to-control-plane-review.yml create mode 100644 .github/workflows/deploy-to-control-plane.yml create mode 100644 .github/workflows/help-command.yml create mode 100644 .github/workflows/promote-staging-to-production.yml diff --git a/.controlplane/Dockerfile b/.controlplane/Dockerfile index 3ca0447e..8a809700 100644 --- a/.controlplane/Dockerfile +++ b/.controlplane/Dockerfile @@ -2,6 +2,10 @@ ARG RUBY_VERSION=3.3.4 FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base +# Current commit hash environment variable +ARG GIT_COMMIT +ENV GIT_COMMIT_SHA=${GIT_COMMIT} + # Install packages needed to build gems and node modules RUN apt-get update -qq && \ apt-get install --no-install-recommends -y build-essential curl git libpq-dev libvips node-gyp pkg-config python-is-python3 @@ -76,7 +80,3 @@ ENTRYPOINT ["./.controlplane/entrypoint.sh"] # Default args to pass to the entry point that can be overridden # For Kubernetes and ControlPlane, these are the "workload args" CMD ["./bin/rails", "server"] - -# Current commit hash environment variable -ARG GIT_COMMIT_SHA -ENV GIT_COMMIT_SHA=${GIT_COMMIT_SHA} diff --git a/.controlplane/readme.md b/.controlplane/readme.md index cfab19a8..d3fe1850 100644 --- a/.controlplane/readme.md +++ b/.controlplane/readme.md @@ -123,3 +123,32 @@ cpflow build-image -a $APP_NAME --commit ABCD ### `entrypoint.sh` - waits for Postgres and Redis to be available - runs `rails db:prepare` to create/seed or migrate the database + +## CI Automation, Review Apps and Staging + +_Note, some of the URL references are internal for the ShakaCode team._ + + Review Apps (deployment of apps based on a PR) are done via Github Actions. + +The review apps work by creating isolated deployments for each branch through this automated process. When a branch is pushed, the action: + +1. Sets up the necessary environment and tools +2. Creates a unique deployment for that branch if it doesn't exist +3. Builds a Docker image tagged with the branch's commit SHA +4. Deploys this image to Control Plane with its own isolated environment + +This allows teams to: +- Preview changes in a production-like environment +- Test features independently +- Share working versions with stakeholders +- Validate changes before merging to main branches + +The system uses Control Plane's infrastructure to manage these deployments, with each branch getting its own resources as defined in the controlplane.yml configuration. + + +### Workflow for Developing Github Actions for Review Apps + +1. Create a PR with changes to the Github Actions workflow +2. Make edits to file such as `.github/actions/deploy-to-control-plane/action.yml` +3. Run a script like `ga .github && gc -m fixes && gp` to commit and push changes (ga = git add, gc = git commit, gp = git push) +4. Check the Github Actions tab in the PR to see the status of the workflow diff --git a/.controlplane/shakacode-team.md b/.controlplane/shakacode-team.md new file mode 100644 index 00000000..0a6273a0 --- /dev/null +++ b/.controlplane/shakacode-team.md @@ -0,0 +1,6 @@ +# Internal Notes to the Shakacode Team + +## Links + +- [Control Plane Org for Staging and Review Apps](https://console.cpln.io/console/org/shakacode-open-source-examples-staging/-info) +- [Control Plane Org for Deployed App](https://console.cpln.io/console/org/shakacode-open-source-examples/-info) diff --git a/.dockerignore b/.dockerignore index 8ee7cce9..4ddbcbc5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -19,7 +19,10 @@ dump.rdb .DS_Store # Ignore bundle dependencies -vendor/ruby +vendor/bundle + +# Ignore GitHub Actions and workflows +.github/ # RVM gemset .ruby-gemset @@ -45,6 +48,5 @@ yarn-debug.log* ################################################### # Specific to .dockerignore .git/ -.github/ spec/ scripts/ diff --git a/.github/actions/build-docker-image/action.yml b/.github/actions/build-docker-image/action.yml new file mode 100644 index 00000000..e1b5df73 --- /dev/null +++ b/.github/actions/build-docker-image/action.yml @@ -0,0 +1,32 @@ +name: Build Docker Image +description: 'Builds a Docker image for the application' + +inputs: + app_name: + description: 'Name of the application' + required: true + org: + description: 'Organization name' + required: true + commit: + description: 'Commit SHA to tag the image with' + required: true + PR_NUMBER: + description: 'PR number' + required: true + +runs: + using: "composite" + steps: + - name: Build Docker Image + id: build + shell: bash + run: | + echo "🏗️ Building Docker image for PR #${PR_NUMBER} (commit ${{ inputs.commit }})..." + + if cpflow build-image -a "${{ inputs.app_name }}" --commit="${{ inputs.commit }}" --org="${{ inputs.org }}"; then + echo "✅ Docker image build successful for PR #${PR_NUMBER} (commit ${{ inputs.commit }})" + else + echo "❌ Docker image build failed for PR #${PR_NUMBER} (commit ${{ inputs.commit }})" + exit 1 + fi diff --git a/.github/actions/delete-control-plane-app/action.yml b/.github/actions/delete-control-plane-app/action.yml new file mode 100644 index 00000000..d5d13ef7 --- /dev/null +++ b/.github/actions/delete-control-plane-app/action.yml @@ -0,0 +1,20 @@ +name: Delete Control Plane App +description: 'Deletes a Control Plane application and all its resources' + +inputs: + app_name: + description: 'Name of the application to delete' + required: true + org: + description: 'Organization name' + required: true + +runs: + using: "composite" + steps: + - name: Delete Application + shell: bash + run: ${{ github.action_path }}/../deploy-to-control-plane/scripts/delete-app.sh + env: + APP_NAME: ${{ inputs.app_name }} + CPLN_ORG: ${{ inputs.org }} diff --git a/.github/actions/deploy-to-control-plane/action.yml b/.github/actions/deploy-to-control-plane/action.yml index 43a9eb97..7093ed94 100644 --- a/.github/actions/deploy-to-control-plane/action.yml +++ b/.github/actions/deploy-to-control-plane/action.yml @@ -1,56 +1,87 @@ # Control Plane GitHub Action -name: Deploy-To-Control-Plane -description: 'Deploys both to staging and to review apps' +name: Deploy to Control Plane +description: 'Deploys an application to Control Plane' inputs: app_name: - description: 'The name of the app to deploy' + description: 'Name of the application' required: true - default: org: - description: 'The org of the app to deploy' + description: 'Organization name' required: true - default: + github_token: + description: 'GitHub token' + required: true + wait_timeout: + description: 'Timeout in seconds for waiting for workloads to be ready' + required: false + default: 600 + +outputs: + review_app_url: + description: 'URL of the deployed application' + value: ${{ steps.deploy.outputs.review_app_url }} runs: - using: 'composite' + using: "composite" steps: - name: Setup Environment uses: ./.github/actions/setup-environment - - name: Set Short SHA - id: vars + - name: Get Commit SHA + id: get_sha shell: bash - run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" - - # Caching step - - uses: actions/cache@v2 - with: - path: /tmp/.docker-cache - key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile', '**/package.json', '**/yarn.lock') }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile', '**/package.json', '**/yarn.lock') }} - ${{ runner.os }}-docker- + run: ${{ github.action_path }}/scripts/get-commit-sha.sh + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + PR_NUMBER: ${{ env.PR_NUMBER }} - - name: cpflow setup-app - shell: bash - run: | - if ! cpflow exists -a ${{ inputs.app_name }} ; then - cpflow setup-app -a ${{ inputs.app_name }} - fi - # Provision all infrastructure on Control Plane. - # app react-webpack-rails-tutorial will be created per definition in .controlplane/controlplane.yml - - name: cpflow build-image - shell: bash - run: | - cpln image docker-login - # Use BUILDKIT_PROGRESS=plain to get more verbose logging of the build - # BUILDKIT_PROGRESS=plain cpflow build-image -a ${{ inputs.app_name }} --commit ${{steps.vars.outputs.sha_short}} --org ${{inputs.org}} - cpflow build-image -a ${{ inputs.app_name }} --commit ${{steps.vars.outputs.sha_short}} --org ${{inputs.org}} - # --cache /tmp/.docker-cache - name: Deploy to Control Plane + id: deploy shell: bash run: | - echo "Deploying to Control Plane" - cpflow deploy-image -a ${{ inputs.app_name }} --run-release-phase --org ${{inputs.org}} --verbose + echo "🚀 Deploying app for PR #${PR_NUMBER}..." + + # Create temp file for output + TEMP_OUTPUT=$(mktemp) + trap 'rm -f "${TEMP_OUTPUT}"' EXIT + + # Deploy the application and show output in real-time while capturing it + if ! cpflow deploy-image -a "${{ inputs.app_name }}" --run-release-phase --org "${{ inputs.org }}" 2>&1 | tee "${TEMP_OUTPUT}"; then + echo "❌ Deployment failed for PR #${PR_NUMBER}" + echo "Error output:" + cat "${TEMP_OUTPUT}" + exit 1 + fi + + # Extract app URL from captured output + REVIEW_APP_URL=$(grep -oP 'https://rails-[^[:space:]]*\.cpln\.app(?=\s|$)' "${TEMP_OUTPUT}" | head -n1) + if [ -z "${REVIEW_APP_URL}" ]; then + echo "❌ Failed to get app URL from deployment output" + echo "Deployment output:" + cat "${TEMP_OUTPUT}" + exit 1 + fi + + # Wait for all workloads to be ready + WAIT_TIMEOUT=${WAIT_TIMEOUT:-${{ inputs.wait_timeout }}} + echo "⏳ Waiting for all workloads to be ready (timeout: ${WAIT_TIMEOUT}s)..." + + # Use timeout command with ps:wait and show output in real-time + if ! timeout "${WAIT_TIMEOUT}" bash -c "cpflow ps:wait -a \"${{ inputs.app_name }}\"" 2>&1 | tee -a "${TEMP_OUTPUT}"; then + TIMEOUT_EXIT=$? + if [ ${TIMEOUT_EXIT} -eq 124 ]; then + echo "❌ Timed out waiting for workloads after ${WAIT_TIMEOUT} seconds" + else + echo "❌ Workloads did not become ready for PR #${PR_NUMBER} (exit code: ${TIMEOUT_EXIT})" + fi + echo "Full output:" + cat "${TEMP_OUTPUT}" + exit 1 + fi + + echo "✅ Deployment successful for PR #${PR_NUMBER}" + echo "🌐 App URL: ${REVIEW_APP_URL}" + echo "review_app_url=${REVIEW_APP_URL}" >> $GITHUB_OUTPUT + echo "REVIEW_APP_URL=${REVIEW_APP_URL}" >> $GITHUB_ENV diff --git a/.github/actions/deploy-to-control-plane/scripts/delete-app.sh b/.github/actions/deploy-to-control-plane/scripts/delete-app.sh new file mode 100755 index 00000000..92e8fbc3 --- /dev/null +++ b/.github/actions/deploy-to-control-plane/scripts/delete-app.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Script to delete a Control Plane application +# Required environment variables: +# - APP_NAME: Name of the application to delete +# - CPLN_ORG: Organization name + +set -e + +# Validate required environment variables +: "${APP_NAME:?APP_NAME environment variable is required}" +: "${CPLN_ORG:?CPLN_ORG environment variable is required}" + +# Safety check: prevent deletion of production or staging apps +if echo "$APP_NAME" | grep -iqE '(production|staging)'; then + echo "❌ ERROR: Cannot delete apps containing 'production' or 'staging' in their name" >&2 + echo "🛑 This is a safety measure to prevent accidental deletion of production or staging environments" >&2 + echo " App name: $APP_NAME" >&2 + exit 1 +fi + +# Check if app exists before attempting to delete +echo "🔍 Checking if application exists: $APP_NAME" +if ! cpflow exists -a "$APP_NAME"; then + echo "⚠️ Application does not exist: $APP_NAME" + exit 0 +fi + +# Delete the application +echo "🗑️ Deleting application: $APP_NAME" +if ! cpflow delete -a "$APP_NAME" --force; then + echo "❌ Failed to delete application: $APP_NAME" >&2 + exit 1 +fi + +echo "✅ Successfully deleted application: $APP_NAME" diff --git a/.github/actions/deploy-to-control-plane/scripts/deploy.sh b/.github/actions/deploy-to-control-plane/scripts/deploy.sh new file mode 100755 index 00000000..4f12a52f --- /dev/null +++ b/.github/actions/deploy-to-control-plane/scripts/deploy.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# This script handles the deployment to Control Plane and extracts the Rails URL +# +# Required environment variables: +# - APP_NAME: Name of the application to deploy +# - CPLN_ORG: Control Plane organization +# +# Outputs: +# - rails_url: URL of the deployed Rails application + +set -e + +# Validate required environment variables +: "${APP_NAME:?APP_NAME environment variable is required}" +: "${CPLN_ORG:?CPLN_ORG environment variable is required}" + +# Set deployment timeout (15 minutes) +TIMEOUT=900 + +TEMP_OUTPUT=$(mktemp) +trap 'rm -f "$TEMP_OUTPUT"' EXIT + +# Deploy the application +echo "🚀 Deploying to Control Plane..." +if timeout "$TIMEOUT" cpflow deploy-image -a "$APP_NAME" --run-release-phase --org "$CPLN_ORG" --verbose | tee "$TEMP_OUTPUT"; then + # Extract Rails URL from deployment output + RAILS_URL=$(grep -oP 'https://rails-[^[:space:]]*\.cpln\.app(?=\s|$)' "$TEMP_OUTPUT" | head -n1) + if [ -n "$RAILS_URL" ]; then + echo "rails_url=$RAILS_URL" >> "$GITHUB_OUTPUT" + echo "✅ Deployment successful" + echo "🚀 Rails URL: $RAILS_URL" + else + echo "❌ Failed to extract Rails URL from deployment output" + exit 1 + fi +elif [ $? -eq 124 ]; then + echo "❌ Deployment timed out after $TIMEOUT seconds" + exit 1 +else + echo "❌ Deployment to Control Plane failed" + exit 1 +fi diff --git a/.github/actions/deploy-to-control-plane/scripts/get-commit-sha.sh b/.github/actions/deploy-to-control-plane/scripts/get-commit-sha.sh new file mode 100755 index 00000000..9dd32cd0 --- /dev/null +++ b/.github/actions/deploy-to-control-plane/scripts/get-commit-sha.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# This script retrieves the commit SHA for deployment +# It handles both PR and direct branch deployments +# +# Required environment variables: +# - PR_NUMBER: Pull request number (optional) +# - GITHUB_TOKEN: GitHub token for API access +# +# Outputs: +# - sha: Full commit SHA +# - sha_short: Short (7 char) commit SHA + +set -e + +if [ -n "${PR_NUMBER}" ]; then + # If PR_NUMBER is set, get the PR's head SHA + if ! PR_SHA=$(gh pr view "${PR_NUMBER}" --json headRefOid --jq '.headRefOid'); then + echo "Failed to get PR head SHA" >&2 + exit 1 + fi + echo "sha=${PR_SHA}" >> "$GITHUB_OUTPUT" + echo "sha_short=${PR_SHA:0:7}" >> "$GITHUB_OUTPUT" + echo "Using PR head commit SHA: ${PR_SHA:0:7}" +else + # For direct branch deployments, use the current commit SHA + if ! CURRENT_SHA=$(git rev-parse HEAD); then + echo "Failed to get current SHA" >&2 + exit 1 + fi + echo "sha=${CURRENT_SHA}" >> "$GITHUB_OUTPUT" + echo "sha_short=${CURRENT_SHA:0:7}" >> "$GITHUB_OUTPUT" + echo "Using branch commit SHA: ${CURRENT_SHA:0:7}" +fi diff --git a/.github/actions/setup-environment/action.yml b/.github/actions/setup-environment/action.yml index 96185f7b..829a9498 100644 --- a/.github/actions/setup-environment/action.yml +++ b/.github/actions/setup-environment/action.yml @@ -14,12 +14,27 @@ runs: - name: Install Control Plane CLI and cpflow gem shell: bash run: | - sudo npm install -g @controlplane/cli@3.1.0 + sudo npm install -g @controlplane/cli@3.3.0 cpln --version - gem install cpflow -v 4.0.0 + gem install cpflow -v 4.1.0 cpflow --version - - name: cpln profile + - name: Setup Control Plane Profile shell: bash run: | - cpln profile update default + if [ -z "$CPLN_TOKEN" ]; then + echo " Error: CPLN_TOKEN environment variable is not set" + exit 1 + fi + + if [ -z "$CPLN_ORG" ]; then + echo " Error: CPLN_ORG environment variable is not set" + exit 1 + fi + + echo "Setting up Control Plane profile..." + echo "Organization: $CPLN_ORG" + cpln profile update default --org "$CPLN_ORG" --token "$CPLN_TOKEN" + + echo "Setting up Docker login for Control Plane registry..." + cpln image docker-login --org "$CPLN_ORG" diff --git a/.github/workflows/delete-review-app.yml b/.github/workflows/delete-review-app.yml new file mode 100644 index 00000000..7c8b5b73 --- /dev/null +++ b/.github/workflows/delete-review-app.yml @@ -0,0 +1,85 @@ +name: Delete Review App + +on: + issue_comment: + types: [created] + +permissions: + contents: read + deployments: write + pull-requests: write + issues: write + +env: + CPLN_ORG: ${{ secrets.CPLN_ORG_STAGING }} + CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} + +jobs: + Process-Delete-Command: + if: | + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/delete-review-app' + runs-on: ubuntu-latest + + steps: + - name: Get PR number + id: pr + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.payload.issue.number; + core.setOutput('pr_number', prNumber); + core.exportVariable('PR_NUMBER', prNumber); + + - name: Set App Name + run: echo "APP_NAME=qa-react-webpack-rails-tutorial-pr-${{ env.PR_NUMBER }}" >> $GITHUB_ENV + + - uses: actions/checkout@v4 + + - name: Setup Environment + uses: ./.github/actions/setup-environment + + - name: Create Initial Delete Comment + id: init-delete + uses: actions/github-script@v7 + with: + script: | + const comment = await github.rest.issues.createComment({ + issue_number: process.env.PR_NUMBER, + owner: context.repo.owner, + repo: context.repo.repo, + body: '🗑️ Starting app deletion...' + }); + return { commentId: comment.data.id }; + + - name: Delete Review App + uses: ./.github/actions/delete-control-plane-app + env: + APP_NAME: ${{ env.APP_NAME }} + CPLN_ORG: ${{ secrets.CPLN_ORG }} + CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} + + - name: Update Delete Status + if: always() + uses: actions/github-script@v7 + with: + script: | + const success = '${{ job.status }}' === 'success'; + const prNumber = process.env.PR_NUMBER; + const cpConsoleUrl = `https://console.cpln.io/org/${process.env.CPLN_ORG}/workloads/${process.env.APP_NAME}`; + + const message = success + ? '✅ Review app for PR #' + prNumber + ' was successfully deleted' + : [ + '❌ Review app for PR #' + prNumber + ' failed to be deleted', + '', + '[View in Control Plane Console](' + cpConsoleUrl + ')' + ].join('\n'); + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: ${{ fromJSON(steps.init-delete.outputs.result).commentId }}, + body: message + }); diff --git a/.github/workflows/deploy-to-control-plane-review.yml b/.github/workflows/deploy-to-control-plane-review.yml deleted file mode 100644 index 9d6a6d82..00000000 --- a/.github/workflows/deploy-to-control-plane-review.yml +++ /dev/null @@ -1,85 +0,0 @@ -# Control Plane GitHub Action - -name: Deploy Review App to Control Plane - -# Controls when the workflow will run -on: - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - - # Uncomment these lines to trigger the workflow on pull request events - # pull_request: - # branches: - # - master - - # deploy on comment "/deploy-review-app" - issue_comment: - types: [created, edited] - -# Convert the GitHub secret variables to environment variables for use by the Control Plane CLI -env: - CPLN_ORG: ${{secrets.CPLN_ORG_STAGING}} - CPLN_TOKEN: ${{secrets.CPLN_TOKEN_STAGING}} - # Uncomment this line to use the PR number from the pull requests trigger event (that trigger is commented) - # PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} - PR_NUMBER: ${{ github.event.issue.number }} - -jobs: - deploy-to-control-plane-review: - if: ${{ github.event_name != 'issue_comment' || (github.event.comment.body == '/deploy-review-app' && github.event.issue.pull_request) }} - runs-on: ubuntu-latest - - steps: - - name: Get PR HEAD Ref - if: ${{ github.event_name == 'issue_comment' }} - id: getRef - run: echo "PR_REF=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json headRefName | jq -r '.headRefName')" >> $GITHUB_OUTPUT - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Checkout source code from Github - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ steps.getRef.outputs.PR_REF || github.ref }} - - - name: Add GitHub Comment - if: ${{ github.event_name == 'issue_comment' }} - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: "We started working on your review-app deployment. You can track progress in the `Actions` Tab [here](https://github.com/shakacode/react-webpack-rails-tutorial/actions/workflows/deploy-to-control-plane-review.yml) on Github." - }) - - - name: Get PR number - if: ${{ github.event_name != 'issue_comment' }} - run: | - echo "GITHUB_REPOSITORY: \"$GITHUB_REPOSITORY\"" - if [ -z "$PR_NUMBER" ]; then - echo "PR_NUMBER is not in the trigger event. Fetching PR number from open PRs." - REF="${{ github.ref }}" - REF=${REF#refs/heads/} # Remove 'refs/heads/' prefix - echo "REF: \"$REF\"" - API_RESPONSE=$(curl --location --request GET "https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls?state=open" \ - --header 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}') - PR_NUMBER=$(echo "$API_RESPONSE" | jq '.[] | select(.head.ref=="'$REF'") | .number') - fi - echo "PR_NUMBER: $PR_NUMBER" - if [ -z "$PR_NUMBER" ]; then - echo "PR_NUMBER is not set. Aborting." - exit 1 - fi - echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV - - name: Get App Name - run: | - echo "PR_NUMBER: ${{ env.PR_NUMBER }}" - echo "APP_NAME=qa-react-webpack-rails-tutorial-pr-${{ env.PR_NUMBER }}" >> "$GITHUB_ENV" - echo "App Name: ${{ env.APP_NAME }}" - - uses: ./.github/actions/deploy-to-control-plane - with: - app_name: ${{ env.APP_NAME }} - org: ${{ env.CPLN_ORG }} diff --git a/.github/workflows/deploy-to-control-plane-staging.yml b/.github/workflows/deploy-to-control-plane-staging.yml index 1f276b5c..095c635a 100644 --- a/.github/workflows/deploy-to-control-plane-staging.yml +++ b/.github/workflows/deploy-to-control-plane-staging.yml @@ -1,6 +1,6 @@ # Control Plane GitHub Action -name: Deploy-To-Control-Plane-Staging +name: Deploy Main Branch to Control Plane Staging # Controls when the workflow will run on: @@ -21,8 +21,10 @@ jobs: runs-on: ubuntu-latest steps: - - name: Check out the repo - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for proper SHA handling + ref: master # Explicitly checkout master branch - uses: ./.github/actions/deploy-to-control-plane with: diff --git a/.github/workflows/deploy-to-control-plane.yml b/.github/workflows/deploy-to-control-plane.yml new file mode 100644 index 00000000..0ce9f7c3 --- /dev/null +++ b/.github/workflows/deploy-to-control-plane.yml @@ -0,0 +1,210 @@ +name: Deploy Review App to Control Plane + +run-name: ${{ (github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.issue.pull_request)) && 'Deploying Review App' || format('Deploying {0} to Staging App', github.ref_name) }} + +on: + pull_request: + types: [opened, synchronize, reopened] + issue_comment: + types: [created] + +# Use concurrency to cancel in-progress runs +concurrency: + group: deploy-${{ github.event.pull_request.number || github.event.issue.number }} + cancel-in-progress: true + +env: + APP_NAME: qa-react-webpack-rails-tutorial-pr-${{ github.event.pull_request.number || github.event.issue.number }} + CPLN_ORG: ${{ secrets.CPLN_ORG_STAGING }} + CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} + PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} + +jobs: + Process-Deployment-Command: + if: | + (github.event_name == 'pull_request') || + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy') + runs-on: ubuntu-latest + permissions: + contents: read + deployments: write + pull-requests: write + issues: write + + steps: + - name: Get PR HEAD Ref + if: github.event_name == 'issue_comment' + id: getRef + run: | + # For PR comments, get the actual PR head commit + PR_DATA=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json headRefName,headRefOid) + echo "PR_REF=$(echo "$PR_DATA" | jq -r '.headRefName')" >> $GITHUB_OUTPUT + echo "PR_SHA=$(echo "$PR_DATA" | jq -r '.headRefOid')" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || steps.getRef.outputs.PR_REF || github.ref }} + + - name: Setup Environment + uses: ./.github/actions/setup-environment + + - name: Initialize Deployment + id: init-deployment + uses: actions/github-script@v7 + with: + script: | + async function getWorkflowUrl(runId) { + // Get the current job ID + const jobs = await github.rest.actions.listJobsForWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: runId + }); + + const currentJob = jobs.data.jobs.find(job => job.status === 'in_progress'); + const jobId = currentJob?.id; + + if (!jobId) { + console.log('Warning: Could not find current job ID'); + return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; + } + + return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/job/${jobId}`; + } + + // Create initial deployment comment + const comment = await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: process.env.PR_NUMBER, + body: '⏳ Initializing deployment...' + }); + + // Create GitHub deployment + const deployment = await github.rest.repos.createDeployment({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: context.sha, + environment: 'review', + auto_merge: false, + required_contexts: [] + }); + + const workflowUrl = await getWorkflowUrl(context.runId); + + return { + deploymentId: deployment.data.id, + commentId: comment.data.id, + workflowUrl + }; + + - name: Set comment ID and workflow URL + run: | + echo "COMMENT_ID=${{ fromJSON(steps.init-deployment.outputs.result).commentId }}" >> $GITHUB_ENV + echo "WORKFLOW_URL=${{ fromJSON(steps.init-deployment.outputs.result).workflowUrl }}" >> $GITHUB_ENV + + - name: Set commit hash + run: | + FULL_COMMIT="${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || steps.getRef.outputs.PR_SHA || github.sha }}" + echo "COMMIT_HASH=${FULL_COMMIT:0:7}" >> $GITHUB_ENV + + - name: Update Status - Building + uses: actions/github-script@v7 + with: + script: | + const buildingMessage = [ + '🏗️ Building Docker image for PR #' + process.env.PR_NUMBER + ', commit ' + '${{ env.COMMIT_HASH }}', + '', + '[View Build Logs](' + process.env.WORKFLOW_URL + ')' + ].join('\n'); + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: process.env.COMMENT_ID, + body: buildingMessage + }); + + - name: Build Docker Image + uses: ./.github/actions/build-docker-image + with: + app_name: ${{ env.APP_NAME }} + org: ${{ env.CPLN_ORG }} + commit: ${{ env.COMMIT_HASH }} + PR_NUMBER: ${{ env.PR_NUMBER }} + + - name: Update Status - Deploying + uses: actions/github-script@v7 + with: + script: | + const deployingMessage = [ + '🚀 Deploying to Control Plane...', + '', + '⏳ Waiting for deployment to be ready...', + '', + '[View Deploy Logs](' + process.env.WORKFLOW_URL + ')' + ].join('\n'); + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: process.env.COMMENT_ID, + body: deployingMessage + }); + + - name: Deploy to Control Plane + uses: ./.github/actions/deploy-to-control-plane + with: + app_name: ${{ env.APP_NAME }} + org: ${{ env.CPLN_ORG }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Update Status - Deployment Complete + uses: actions/github-script@v7 + with: + script: | + const prNumber = process.env.PR_NUMBER; + const appUrl = process.env.REVIEW_APP_URL; + const workflowUrl = process.env.WORKFLOW_URL; + const isSuccess = '${{ job.status }}' === 'success'; + + // Create GitHub deployment status + const deploymentStatus = { + owner: context.repo.owner, + repo: context.repo.repo, + deployment_id: ${{ fromJSON(steps.init-deployment.outputs.result).deploymentId }}, + state: isSuccess ? 'success' : 'failure', + environment_url: isSuccess ? appUrl : undefined, + log_url: workflowUrl, + environment: 'review' + }; + + await github.rest.repos.createDeploymentStatus(deploymentStatus); + + // Define messages based on deployment status + const successMessage = [ + '✅ Deployment complete for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', + '', + '🚀 [Review App for PR #' + prNumber + '](' + appUrl + ')', + '', + '📋 [View Completed Action Build and Deploy Logs](' + workflowUrl + ')' + ].join('\n'); + + const failureMessage = [ + '❌ Deployment failed for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', + '', + '📋 [View Deployment Logs with Errors](' + workflowUrl + ')' + ].join('\n'); + + // Update the existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: process.env.COMMENT_ID, + body: isSuccess ? successMessage : failureMessage + }); \ No newline at end of file diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml new file mode 100644 index 00000000..6e77bf69 --- /dev/null +++ b/.github/workflows/help-command.yml @@ -0,0 +1,52 @@ +name: Show Help for Commands + +on: + issue_comment: + types: [created] + +permissions: + issues: write + pull-requests: write + +jobs: + show-help: + if: | + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/help' + runs-on: ubuntu-latest + + steps: + - name: Show Available Commands + uses: actions/github-script@v7 + with: + script: | + const helpMessage = [ + '## 📚 Available Commands', + '', + '### `/deploy`', + 'Deploys your PR branch to a review environment on Control Plane.', + '- Creates a new review app if one doesn\'t exist', + '- Updates the existing review app if it already exists', + '- Provides a unique URL to preview your changes', + '- Shows build and deployment progress in real-time', + '', + '### `/delete-review-app`', + 'Deletes the review app associated with this PR.', + '- Removes all resources from Control Plane', + '- Helpful for cleaning up when you\'re done testing', + '- Can be re-deployed later using `/deploy`', + '', + '### `/help`', + 'Shows this help message explaining available commands.', + '', + '---', + '_Note: These commands only work in pull request comments._' + ].join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: helpMessage + }); diff --git a/.github/workflows/nightly-remove-stale-review-apps.yml b/.github/workflows/nightly-remove-stale-review-apps.yml index 9f3985ba..c5f0376a 100644 --- a/.github/workflows/nightly-remove-stale-review-apps.yml +++ b/.github/workflows/nightly-remove-stale-review-apps.yml @@ -21,9 +21,39 @@ jobs: - name: Setup Environment uses: ./.github/actions/setup-environment - - name: Run cleanup-stale-apps script + - name: Get Stale PRs + id: stale_prs + uses: actions/github-script@v7 + with: + script: | + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + + const prs = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'closed', + sort: 'updated', + direction: 'desc' + }); + + const stalePRs = prs.data + .filter(pr => new Date(pr.updated_at) < thirtyDaysAgo) + .map(pr => pr.number); + + console.log('Found stale PRs:', stalePRs); + return stalePRs; + + - name: Delete Stale Review Apps + if: ${{ steps.stale_prs.outputs.result != '[]' }} run: | - cpflow cleanup-stale-apps -a qa-react-webpack-rails-tutorial -y + for pr in $(echo "${{ steps.stale_prs.outputs.result }}" | jq -r '.[]'); do + APP_NAME="qa-react-webpack-rails-tutorial-pr-$pr" + echo "🗑️ Deleting stale review app for PR #$pr: $APP_NAME" + ${{ github.workspace }}/.github/actions/deploy-to-control-plane/scripts/delete-app.sh + done + env: + APP_NAME: qa-react-webpack-rails-tutorial-pr-${{ steps.stale_prs.outputs.result }} - name: Run cleanup-images script run: | diff --git a/.github/workflows/promote-staging-to-production.yml b/.github/workflows/promote-staging-to-production.yml new file mode 100644 index 00000000..04148067 --- /dev/null +++ b/.github/workflows/promote-staging-to-production.yml @@ -0,0 +1,56 @@ +name: Promote Staging to Production + +on: + workflow_dispatch: + inputs: + confirm_promotion: + description: 'Type "promote" to confirm promotion of staging to production' + required: true + type: string + +jobs: + promote-to-production: + runs-on: ubuntu-latest + if: github.event.inputs.confirm_promotion == 'promote' + + env: + APP_NAME: react-webpack-rails-tutorial + CPLN_ORG: ${{ secrets.CPLN_ORG }} + UPSTREAM_TOKEN: ${{ secrets.STAGING_TOKEN }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Environment + uses: ./.github/actions/setup-environment + env: + CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} + + - name: Promote Staging to Production + id: promote + run: | + echo "🚀 Starting promotion from staging to production..." + + if ! cpflow promote-app-from-upstream -a "${APP_NAME}" -t "${UPSTREAM_TOKEN}" --org "${CPLN_ORG}"; then + echo "❌ Failed to promote staging to production" + exit 1 + fi + + echo "✅ Successfully promoted staging to production" + + - name: Create GitHub Release + if: success() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Get the current date in YYYY-MM-DD format + RELEASE_DATE=$(date '+%Y-%m-%d') + + # Create a release tag + RELEASE_TAG="production-${RELEASE_DATE}" + + # Create GitHub release + gh release create "${RELEASE_TAG}" \ + --title "Production Release ${RELEASE_DATE}" \ + --notes "🚀 Production deployment on ${RELEASE_DATE}" diff --git a/.gitignore b/.gitignore index 42b8d30f..7567a8b7 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ dump.rdb .DS_Store # Ignore bundle dependencies -vendor/ruby +vendor/bundle # RVM gemset .ruby-gemset diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed23da7..75972ae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. See: [merged pull requests](https://github.com/shakacode/react-webpack-rails-tutorial/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged). + +## 2025-01-22 +Improvements to control-plane-flow implementation. + + + ## [2.1.0] - 2016-03-06 ### Updated From ebf079561eec75bd1246d35eede19ff694ed841e Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Mon, 27 Jan 2025 15:57:24 -1000 Subject: [PATCH 2/6] updated production app name --- .controlplane/controlplane.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.controlplane/controlplane.yml b/.controlplane/controlplane.yml index 53f68730..f394fe1c 100644 --- a/.controlplane/controlplane.yml +++ b/.controlplane/controlplane.yml @@ -38,7 +38,7 @@ aliases: release_script: release_script.sh apps: - react-webpack-rails-tutorial: + react-webpack-rails-tutorial-production: # Simulate Production Version <<: *common # Don't allow overriding the org and app by ENV vars b/c production is sensitive! From b731dde7c11295247a875775d8cbb0fcaea2b35f Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Mon, 27 Jan 2025 17:02:30 -1000 Subject: [PATCH 3/6] brought in changes from master --- .../deploy-to-control-plane/action.yml | 17 +- .../deploy-to-control-plane/scripts/deploy.sh | 65 +++++--- .github/actions/setup-environment/action.yml | 25 ++- .../workflows/add-comment-on-pr-creation.yml | 34 ++-- .github/workflows/delete-review-app.yml | 117 ++++++++++++-- .github/workflows/deploy-to-control-plane.yml | 145 ++++++++++++++++-- .github/workflows/help-command.yml | 108 +++++++++---- .github/workflows/review-app-help.yml | 52 +++++++ 8 files changed, 467 insertions(+), 96 deletions(-) create mode 100644 .github/workflows/review-app-help.yml diff --git a/.github/actions/deploy-to-control-plane/action.yml b/.github/actions/deploy-to-control-plane/action.yml index 7093ed94..b5282ce8 100644 --- a/.github/actions/deploy-to-control-plane/action.yml +++ b/.github/actions/deploy-to-control-plane/action.yml @@ -16,7 +16,7 @@ inputs: wait_timeout: description: 'Timeout in seconds for waiting for workloads to be ready' required: false - default: 600 + default: '900' outputs: review_app_url: @@ -26,6 +26,21 @@ outputs: runs: using: "composite" steps: + - name: Validate Required Secrets + shell: bash + run: | + missing_secrets=() + for secret in "CPLN_TOKEN" "CPLN_ORG"; do + if [ -z "${!secret}" ]; then + missing_secrets+=("$secret") + fi + done + + if [ ${#missing_secrets[@]} -ne 0 ]; then + echo "Required secrets are not set: ${missing_secrets[*]}" + exit 1 + fi + - name: Setup Environment uses: ./.github/actions/setup-environment diff --git a/.github/actions/deploy-to-control-plane/scripts/deploy.sh b/.github/actions/deploy-to-control-plane/scripts/deploy.sh index 4f12a52f..73bb8968 100755 --- a/.github/actions/deploy-to-control-plane/scripts/deploy.sh +++ b/.github/actions/deploy-to-control-plane/scripts/deploy.sh @@ -1,11 +1,15 @@ #!/bin/bash # This script handles the deployment to Control Plane and extracts the Rails URL -# +# # Required environment variables: # - APP_NAME: Name of the application to deploy # - CPLN_ORG: Control Plane organization # +# Optional environment variables: +# - WAIT_TIMEOUT: Timeout in seconds for deployment (default: 900) +# Must be a positive integer +# # Outputs: # - rails_url: URL of the deployed Rails application @@ -15,29 +19,48 @@ set -e : "${APP_NAME:?APP_NAME environment variable is required}" : "${CPLN_ORG:?CPLN_ORG environment variable is required}" -# Set deployment timeout (15 minutes) -TIMEOUT=900 +# Set and validate deployment timeout +WAIT_TIMEOUT=${WAIT_TIMEOUT:-900} +if ! [[ "${WAIT_TIMEOUT}" =~ ^[0-9]+$ ]]; then + echo "❌ Invalid timeout value: ${WAIT_TIMEOUT}" + exit 1 +fi TEMP_OUTPUT=$(mktemp) trap 'rm -f "$TEMP_OUTPUT"' EXIT # Deploy the application -echo "🚀 Deploying to Control Plane..." -if timeout "$TIMEOUT" cpflow deploy-image -a "$APP_NAME" --run-release-phase --org "$CPLN_ORG" --verbose | tee "$TEMP_OUTPUT"; then - # Extract Rails URL from deployment output - RAILS_URL=$(grep -oP 'https://rails-[^[:space:]]*\.cpln\.app(?=\s|$)' "$TEMP_OUTPUT" | head -n1) - if [ -n "$RAILS_URL" ]; then - echo "rails_url=$RAILS_URL" >> "$GITHUB_OUTPUT" - echo "✅ Deployment successful" - echo "🚀 Rails URL: $RAILS_URL" - else - echo "❌ Failed to extract Rails URL from deployment output" - exit 1 - fi -elif [ $? -eq 124 ]; then - echo "❌ Deployment timed out after $TIMEOUT seconds" - exit 1 -else - echo "❌ Deployment to Control Plane failed" - exit 1 +echo "🚀 Deploying to Control Plane (timeout: ${WAIT_TIMEOUT}s)" +if ! timeout "${WAIT_TIMEOUT}" cpflow deploy-image -a "$APP_NAME" --run-release-phase --org "$CPLN_ORG" --verbose 2>&1 | tee "$TEMP_OUTPUT"; then + echo "❌ Deployment failed" + echo "Full output:" + cat "$TEMP_OUTPUT" + exit 1 +fi + +# Extract app URL from deployment output +RAILS_URL=$(grep -oP 'https://rails-[^[:space:]]*\.cpln\.app(?=\s|$)' "$TEMP_OUTPUT" | head -n1) +if [ -z "$RAILS_URL" ]; then + echo "❌ Failed to get app URL from deployment output" + echo "Full output:" + cat "$TEMP_OUTPUT" + exit 1 +fi + +# Wait for all workloads to be ready +echo "⏳ Waiting for all workloads to be ready (timeout: ${WAIT_TIMEOUT}s)" +if ! timeout "${WAIT_TIMEOUT}" bash -c "cpflow ps:wait -a \"$APP_NAME\"" 2>&1 | tee -a "$TEMP_OUTPUT"; then + TIMEOUT_EXIT=$? + if [ ${TIMEOUT_EXIT} -eq 124 ]; then + echo "❌ Timed out waiting for workloads after ${WAIT_TIMEOUT} seconds" + else + echo "❌ Workloads did not become ready" + fi + echo "Full output:" + cat "$TEMP_OUTPUT" + exit 1 fi + +echo "✅ Deployment successful" +echo "🌐 Rails URL: $RAILS_URL" +echo "rails_url=$RAILS_URL" >> "$GITHUB_OUTPUT" diff --git a/.github/actions/setup-environment/action.yml b/.github/actions/setup-environment/action.yml index 829a9498..1a086ee3 100644 --- a/.github/actions/setup-environment/action.yml +++ b/.github/actions/setup-environment/action.yml @@ -3,6 +3,14 @@ name: 'Setup Environment' description: 'Sets up Ruby, installs Control Plane CLI, cpflow gem, and sets up the default profile' +inputs: + token: + description: 'Control Plane token' + required: true + org: + description: 'Control Plane organization' + required: true + runs: using: 'composite' steps: @@ -22,19 +30,22 @@ runs: - name: Setup Control Plane Profile shell: bash run: | - if [ -z "$CPLN_TOKEN" ]; then - echo " Error: CPLN_TOKEN environment variable is not set" + TOKEN="${{ inputs.token }}" + ORG="${{ inputs.org }}" + + if [ -z "$TOKEN" ]; then + echo " Error: Control Plane token not provided" exit 1 fi - if [ -z "$CPLN_ORG" ]; then - echo " Error: CPLN_ORG environment variable is not set" + if [ -z "$ORG" ]; then + echo " Error: Control Plane organization not provided" exit 1 fi echo "Setting up Control Plane profile..." - echo "Organization: $CPLN_ORG" - cpln profile update default --org "$CPLN_ORG" --token "$CPLN_TOKEN" + echo "Organization: $ORG" + cpln profile update default --org "$ORG" --token "$TOKEN" echo "Setting up Docker login for Control Plane registry..." - cpln image docker-login --org "$CPLN_ORG" + cpln image docker-login --org "$ORG" diff --git a/.github/workflows/add-comment-on-pr-creation.yml b/.github/workflows/add-comment-on-pr-creation.yml index bd4b69de..fa686305 100644 --- a/.github/workflows/add-comment-on-pr-creation.yml +++ b/.github/workflows/add-comment-on-pr-creation.yml @@ -1,20 +1,30 @@ -name: Add helper Comment on PR creation +name: Add Comment on PR Creation on: pull_request: types: [opened] jobs: - comment-on-pr: + add-comment: runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - - name: Add GitHub Comment for review app instructions - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: "Hi 👋 To deploy a review app, please comment `/deploy-review-app`" - }) + - uses: actions/github-script@v7 + name: Add GitHub Comment for review app instructions + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: [ + "Hi 👋 Here are the commands available for this PR:", + "", + "- `/deploy-review-app`: Deploy your changes to a review environment", + "- `/delete-review-app`: Clean up the review environment when you're done", + "- `/help`: Show detailed information about all commands", + "", + "Use `/help` to see full documentation, including configuration options." + ].join("\n") + }); diff --git a/.github/workflows/delete-review-app.yml b/.github/workflows/delete-review-app.yml index 7c8b5b73..d2b1a267 100644 --- a/.github/workflows/delete-review-app.yml +++ b/.github/workflows/delete-review-app.yml @@ -1,6 +1,8 @@ name: Delete Review App on: + pull_request: + types: [closed] issue_comment: types: [created] @@ -12,14 +14,18 @@ permissions: env: CPLN_ORG: ${{ secrets.CPLN_ORG_STAGING }} - CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} + CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} + APP_NAME: qa-react-webpack-rails-tutorial-pr-${{ github.event.pull_request.number || github.event.issue.number }} + PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} jobs: Process-Delete-Command: if: | - github.event_name == 'issue_comment' && - github.event.issue.pull_request && - github.event.comment.body == '/delete-review-app' + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/delete-review-app') || + (github.event_name == 'pull_request' && + github.event.action == 'closed') runs-on: ubuntu-latest steps: @@ -37,24 +43,97 @@ jobs: - uses: actions/checkout@v4 + - name: Validate Required Secrets + run: | + missing_secrets=() + for secret in "CPLN_TOKEN" "CPLN_ORG"; do + if [ -z "${!secret}" ]; then + missing_secrets+=("$secret") + fi + done + + if [ ${#missing_secrets[@]} -ne 0 ]; then + echo "Required secrets are not set: ${missing_secrets[*]}" + exit 1 + fi + - name: Setup Environment uses: ./.github/actions/setup-environment + - name: Set shared functions + id: shared-functions + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('GET_CONSOLE_LINK', ` + function getConsoleLink(prNumber) { + return '🎮 [Control Plane Console](' + + 'https://console.cpln.io/console/org/' + process.env.CPLN_ORG + '/gvc/' + process.env.APP_NAME + '/-info)'; + } + `); + + - name: Setup Workflow URL + id: setup-workflow-url + uses: actions/github-script@v7 + with: + script: | + async function getWorkflowUrl(runId) { + // Get the current job ID + const jobs = await github.rest.actions.listJobsForWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: runId + }); + + const currentJob = jobs.data.jobs.find(job => job.status === 'in_progress'); + const jobId = currentJob?.id; + + if (!jobId) { + console.log('Warning: Could not find current job ID'); + return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; + } + + return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/job/${jobId}`; + } + + const workflowUrl = await getWorkflowUrl(context.runId); + core.exportVariable('WORKFLOW_URL', workflowUrl); + return { workflowUrl }; + - name: Create Initial Delete Comment - id: init-delete + id: create-delete-comment uses: actions/github-script@v7 with: script: | + eval(process.env.GET_CONSOLE_LINK); + + let message = '🗑️ Starting app deletion'; + if ('${{ github.event_name }}' === 'pull_request') { + const merged = '${{ github.event.pull_request.merged }}' === 'true'; + message += merged ? ' (PR merged)' : ' (PR closed)'; + } + const comment = await github.rest.issues.createComment({ issue_number: process.env.PR_NUMBER, owner: context.repo.owner, repo: context.repo.repo, body: '🗑️ Starting app deletion...' + body: [ + message, + '', + ' 🗑️ [View Delete Logs](' + process.env.WORKFLOW_URL + ')', + '', + getConsoleLink(process.env.PR_NUMBER) + ].join('\n') }); return { commentId: comment.data.id }; - name: Delete Review App uses: ./.github/actions/delete-control-plane-app + with: + app_name: ${{ env.APP_NAME }} + org: ${{ env.CPLN_ORG }} + github_token: ${{ secrets.GITHUB_TOKEN }} env: APP_NAME: ${{ env.APP_NAME }} CPLN_ORG: ${{ secrets.CPLN_ORG }} @@ -65,21 +144,31 @@ jobs: uses: actions/github-script@v7 with: script: | + eval(process.env.GET_CONSOLE_LINK); + const success = '${{ job.status }}' === 'success'; const prNumber = process.env.PR_NUMBER; const cpConsoleUrl = `https://console.cpln.io/org/${process.env.CPLN_ORG}/workloads/${process.env.APP_NAME}`; - const message = success - ? '✅ Review app for PR #' + prNumber + ' was successfully deleted' - : [ - '❌ Review app for PR #' + prNumber + ' failed to be deleted', - '', - '[View in Control Plane Console](' + cpConsoleUrl + ')' - ].join('\n'); + const successMessage = [ + '✅ Review app for PR #' + prNumber + ' was successfully deleted', + '', + ' [View Completed Delete Logs](' + process.env.WORKFLOW_URL + ')', + '', + ' [Control Plane Organization](https://console.cpln.io/console/org/' + process.env.CPLN_ORG + '/-info)' + ].join('\n'); + + const failureMessage = [ + '❌ Review app for PR #' + prNumber + ' failed to be deleted', + '', + ' [View Delete Logs with Errors](' + process.env.WORKFLOW_URL + ')', + '', + getConsoleLink(prNumber) + ].join('\n'); await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, - comment_id: ${{ fromJSON(steps.init-delete.outputs.result).commentId }}, - body: message + comment_id: ${{ fromJSON(steps.create-delete-comment.outputs.result).commentId }}, + body: success ? successMessage : failureMessage }); diff --git a/.github/workflows/deploy-to-control-plane.yml b/.github/workflows/deploy-to-control-plane.yml index 0ce9f7c3..516b3c2a 100644 --- a/.github/workflows/deploy-to-control-plane.yml +++ b/.github/workflows/deploy-to-control-plane.yml @@ -1,6 +1,6 @@ name: Deploy Review App to Control Plane -run-name: ${{ (github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.issue.pull_request)) && 'Deploying Review App' || format('Deploying {0} to Staging App', github.ref_name) }} +run-name: ${{ github.event_name == 'issue_comment' && 'Deploying Review App' || format('Updating Review App for {0}', github.ref_name) }} on: pull_request: @@ -10,7 +10,7 @@ on: # Use concurrency to cancel in-progress runs concurrency: - group: deploy-${{ github.event.pull_request.number || github.event.issue.number }} + group: deploy-pr-${{ github.event.pull_request.number || github.event.issue.number }} cancel-in-progress: true env: @@ -34,10 +34,22 @@ jobs: issues: write steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || steps.getRef.outputs.PR_REF || github.ref }} + + - name: Setup Environment + uses: ./.github/actions/setup-environment + with: + token: ${{ env.CPLN_TOKEN }} + org: ${{ env.CPLN_ORG }} + - name: Get PR HEAD Ref if: github.event_name == 'issue_comment' - id: getRef run: | + echo "PR_NUMBER=${{ github.event.issue.number }}" >> $GITHUB_ENV + echo "APP_NAME=qa-react-webpack-rails-tutorial-pr-${{ github.event.issue.number }}" >> $GITHUB_ENV # For PR comments, get the actual PR head commit PR_DATA=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json headRefName,headRefOid) echo "PR_REF=$(echo "$PR_DATA" | jq -r '.headRefName')" >> $GITHUB_OUTPUT @@ -45,13 +57,76 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/checkout@v4 + - name: Check if Review App Exists + id: check-app + if: github.event_name == 'push' + env: + CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} + run: | + if ! cpflow exists -a ${{ env.APP_NAME }}; then + echo "No review app exists for this PR" + exit 0 + fi + echo "app_exists=true" >> $GITHUB_OUTPUT + + + - name: Set Workflow URL + id: workflow-url + uses: actions/github-script@v7 with: - fetch-depth: 0 - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || steps.getRef.outputs.PR_REF || github.ref }} + script: | + async function getWorkflowUrl(runId) { + const jobs = await github.rest.actions.listJobsForWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: runId + }); + + const currentJob = jobs.data.jobs.find(job => job.status === 'in_progress'); + const jobId = currentJob?.id; + + if (!jobId) { + return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; + } + + return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/job/${jobId}`; + } + + const workflowUrl = await getWorkflowUrl(context.runId); + core.exportVariable('WORKFLOW_URL', workflowUrl); + core.exportVariable('GET_CONSOLE_LINK', ` + function getConsoleLink(prNumber) { + return '🎮 [Control Plane Console](' + + 'https://console.cpln.io/console/org/' + process.env.CPLN_ORG + '/gvc/' + process.env.APP_NAME + '/-info)'; + } + `); - - name: Setup Environment - uses: ./.github/actions/setup-environment + - name: Create Initial Comment + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + ( steps.check-app.outputs.app_exists == 'true') + id: create-comment + uses: actions/github-script@v7 + with: + script: | + const result = await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: process.env.PR_NUMBER, + body: '🚀 Starting deployment process...' + }); + console.log('Created comment:', result.data.id); + return { commentId: result.data.id }; + + - name: Set Comment ID + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.check-app.outputs.app_exists == 'true') + run: echo "COMMENT_ID=${{ fromJSON(steps.create-comment.outputs.result).commentId }}" >> $GITHUB_ENV - name: Initialize Deployment id: init-deployment @@ -59,7 +134,6 @@ jobs: with: script: | async function getWorkflowUrl(runId) { - // Get the current job ID const jobs = await github.rest.actions.listJobsForWorkflowRun({ owner: context.repo.owner, repo: context.repo.repo, @@ -114,13 +188,23 @@ jobs: echo "COMMIT_HASH=${FULL_COMMIT:0:7}" >> $GITHUB_ENV - name: Update Status - Building + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.check-app.outputs.app_exists == 'true') uses: actions/github-script@v7 with: script: | + eval(process.env.GET_CONSOLE_LINK); + const buildingMessage = [ '🏗️ Building Docker image for PR #' + process.env.PR_NUMBER + ', commit ' + '${{ env.COMMIT_HASH }}', + '🏗️ Building Docker image...', + '', + '📝 [View Build Logs](' + process.env.WORKFLOW_URL + ')', '', - '[View Build Logs](' + process.env.WORKFLOW_URL + ')' + getConsoleLink(process.env.PR_NUMBER) ].join('\n'); await github.rest.issues.updateComment({ @@ -130,7 +214,20 @@ jobs: body: buildingMessage }); + - name: Checkout PR Branch + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.check-app.outputs.app_exists == 'true') + run: git checkout ${{ steps.getRef.outputs.PR_REF }} + - name: Build Docker Image + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.check-app.outputs.app_exists == 'true') uses: ./.github/actions/build-docker-image with: app_name: ${{ env.APP_NAME }} @@ -139,15 +236,24 @@ jobs: PR_NUMBER: ${{ env.PR_NUMBER }} - name: Update Status - Deploying + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.check-app.outputs.app_exists == 'true') uses: actions/github-script@v7 with: script: | + eval(process.env.GET_CONSOLE_LINK); + const deployingMessage = [ '🚀 Deploying to Control Plane...', '', '⏳ Waiting for deployment to be ready...', '', - '[View Deploy Logs](' + process.env.WORKFLOW_URL + ')' + '📝 [View Deploy Logs](' + process.env.WORKFLOW_URL + ')', + '', + getConsoleLink(process.env.PR_NUMBER) ].join('\n'); await github.rest.issues.updateComment({ @@ -158,11 +264,20 @@ jobs: }); - name: Deploy to Control Plane + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.check-app.outputs.app_exists == 'true') uses: ./.github/actions/deploy-to-control-plane with: app_name: ${{ env.APP_NAME }} org: ${{ env.CPLN_ORG }} github_token: ${{ secrets.GITHUB_TOKEN }} + wait_timeout: ${{ vars.WAIT_TIMEOUT || 900 }} + env: + CPLN_TOKEN: ${{ env.CPLN_TOKEN }} + PR_NUMBER: ${{ env.PR_NUMBER }} - name: Update Status - Deployment Complete uses: actions/github-script@v7 @@ -173,6 +288,9 @@ jobs: const workflowUrl = process.env.WORKFLOW_URL; const isSuccess = '${{ job.status }}' === 'success'; + const consoleLink = '🎮 [Control Plane Console](https://console.cpln.io/console/org/' + + process.env.CPLN_ORG + '/gvc/' + process.env.APP_NAME + '/-info)'; + // Create GitHub deployment status const deploymentStatus = { owner: context.repo.owner, @@ -191,6 +309,7 @@ jobs: '✅ Deployment complete for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', '', '🚀 [Review App for PR #' + prNumber + '](' + appUrl + ')', + consoleLink, '', '📋 [View Completed Action Build and Deploy Logs](' + workflowUrl + ')' ].join('\n'); @@ -198,6 +317,8 @@ jobs: const failureMessage = [ '❌ Deployment failed for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', '', + consoleLink, + '', '📋 [View Deployment Logs with Errors](' + workflowUrl + ')' ].join('\n'); @@ -207,4 +328,4 @@ jobs: repo: context.repo.repo, comment_id: process.env.COMMENT_ID, body: isSuccess ? successMessage : failureMessage - }); \ No newline at end of file + }); diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 6e77bf69..f086966a 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -21,32 +21,82 @@ jobs: uses: actions/github-script@v7 with: script: | - const helpMessage = [ - '## 📚 Available Commands', - '', - '### `/deploy`', - 'Deploys your PR branch to a review environment on Control Plane.', - '- Creates a new review app if one doesn\'t exist', - '- Updates the existing review app if it already exists', - '- Provides a unique URL to preview your changes', - '- Shows build and deployment progress in real-time', - '', - '### `/delete-review-app`', - 'Deletes the review app associated with this PR.', - '- Removes all resources from Control Plane', - '- Helpful for cleaning up when you\'re done testing', - '- Can be re-deployed later using `/deploy`', - '', - '### `/help`', - 'Shows this help message explaining available commands.', - '', - '---', - '_Note: These commands only work in pull request comments._' - ].join('\n'); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.issue.number, - body: helpMessage - }); + try { + console.log('Creating detailed help message...'); + const helpMessage = [ + '# 📚 Detailed Review App Commands Guide', + '', + 'This is a detailed guide to using review app commands. For a quick reference, see the message posted when your PR was created.', + '', + '## Available Commands', + '', + '### `/deploy-review-app`', + 'Deploys your PR branch to a review environment on Control Plane.', + '- Creates a new review app if one doesn\'t exist', + '- Updates the existing review app if it already exists', + '- Provides a unique URL to preview your changes', + '- Shows build and deployment progress in real-time', + '', + '**Required Environment Variables:**', + '- `CPLN_TOKEN`: Control Plane authentication token', + '- `CPLN_ORG`: Control Plane organization name', + '', + '**Optional Configuration:**', + '- `WAIT_TIMEOUT`: Deployment timeout in seconds (default: 900)', + ' - Must be a positive integer', + ' - Can be set in GitHub Actions variables', + ' - Applies to both deployment and workload readiness checks', + '', + '### `/delete-review-app`', + 'Deletes the review app associated with this PR.', + '- Removes all resources from Control Plane', + '- Helpful for cleaning up when you\'re done testing', + '- Can be re-deployed later using `/deploy-review-app`', + '', + '**Required Environment Variables:**', + '- `CPLN_TOKEN`: Control Plane authentication token', + '- `CPLN_ORG`: Control Plane organization name', + '', + '### `/help`', + 'Shows this detailed help message.', + '', + '---', + '## Environment Setup', + '', + '1. Set required secrets in your repository settings:', + ' - `CPLN_TOKEN`', + ' - `CPLN_ORG`', + '', + '2. Optional: Configure `WAIT_TIMEOUT` in GitHub Actions variables to customize deployment timeout', + '', + '## Control Plane Integration', + '', + 'Review apps are deployed to Control Plane with the following configuration:', + '- App Name Format: `qa-react-webpack-rails-tutorial-pr-{PR_NUMBER}`', + '- Console URL: `https://console.cpln.io/console/org/{CPLN_ORG}/gvc/{APP_NAME}/-info`', + '', + '## Automatic Cleanup', + '', + 'Review apps are automatically deleted when:', + '- The PR is closed (merged or not merged)', + '- The PR is stale (via nightly cleanup job)', + '', + 'For more information, see the [React on Rails Tutorial documentation](https://github.com/shakacode/react-on-rails/tree/master/react-webpack-rails-tutorial)' + ].join('\n'); + + console.log('Issue number:', github.context.payload.issue.number); + console.log('Owner:', context.repo.owner); + console.log('Repo:', context.repo.repo); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: github.context.payload.issue.number, + body: helpMessage + }); + + console.log('Help message posted successfully'); + } catch (error) { + console.error('Error posting help message:', error); + core.setFailed(`Failed to post help message: ${error.message}`); + } diff --git a/.github/workflows/review-app-help.yml b/.github/workflows/review-app-help.yml new file mode 100644 index 00000000..313101e4 --- /dev/null +++ b/.github/workflows/review-app-help.yml @@ -0,0 +1,52 @@ +name: Show Quick Help on PR Creation + +on: + pull_request: + types: [opened] + +permissions: + issues: write + pull-requests: write + +jobs: + show-quick-help: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + + steps: + - name: Show Quick Reference + uses: actions/github-script@v7 + with: + script: | + try { + console.log('Creating quick reference message...'); + const helpMessage = [ + '# 🚀 Quick Review App Commands', + '', + 'Welcome! Here are the commands you can use in this PR:', + '', + '### `/deploy-review-app`', + 'Deploy your PR branch for testing', + '', + '### `/delete-review-app`', + 'Remove the review app when done', + '', + '### `/help`', + 'Show detailed documentation', + '', + '---', + '**Note:** Type `/help` for detailed instructions, environment setup, and configuration options.' + ].join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: helpMessage + }); + + console.log('Quick reference posted successfully'); + } catch (error) { + console.error('Error posting quick reference:', error); + core.setFailed(`Failed to post quick reference: ${error.message}`); + } From 625afa8bea4b336840044fa1b1301b3461de6a63 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Mon, 27 Jan 2025 17:07:16 -1000 Subject: [PATCH 4/6] small fixes --- .github/workflows/delete-review-app.yml | 7 +++++-- .../workflows/deploy-to-control-plane-staging.yml | 2 +- .github/workflows/deploy-to-control-plane.yml | 6 +++--- .github/workflows/help-command.yml | 14 ++++++-------- .../workflows/nightly-remove-stale-review-apps.yml | 2 +- .../workflows/promote-staging-to-production.yml | 2 +- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/delete-review-app.yml b/.github/workflows/delete-review-app.yml index d2b1a267..a7691e3e 100644 --- a/.github/workflows/delete-review-app.yml +++ b/.github/workflows/delete-review-app.yml @@ -58,7 +58,10 @@ jobs: fi - name: Setup Environment - uses: ./.github/actions/setup-environment + uses: ./.github/actions/setup-environment@justin808-working-for-deploys + with: + org: ${{ env.CPLN_ORG }} + token: ${{ env.CPLN_TOKEN }} - name: Set shared functions id: shared-functions @@ -129,7 +132,7 @@ jobs: return { commentId: comment.data.id }; - name: Delete Review App - uses: ./.github/actions/delete-control-plane-app + uses: ./.github/actions/delete-control-plane-app@justin808-working-for-deploys with: app_name: ${{ env.APP_NAME }} org: ${{ env.CPLN_ORG }} diff --git a/.github/workflows/deploy-to-control-plane-staging.yml b/.github/workflows/deploy-to-control-plane-staging.yml index 095c635a..5b178e66 100644 --- a/.github/workflows/deploy-to-control-plane-staging.yml +++ b/.github/workflows/deploy-to-control-plane-staging.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 0 # Fetch all history for proper SHA handling ref: master # Explicitly checkout master branch - - uses: ./.github/actions/deploy-to-control-plane + - uses: ./.github/actions/deploy-to-control-plane@justin808-working-for-deploys with: app_name: ${{ secrets.APP_NAME_STAGING }} org: ${{ secrets.CPLN_ORG_STAGING }} diff --git a/.github/workflows/deploy-to-control-plane.yml b/.github/workflows/deploy-to-control-plane.yml index 516b3c2a..92e97187 100644 --- a/.github/workflows/deploy-to-control-plane.yml +++ b/.github/workflows/deploy-to-control-plane.yml @@ -40,7 +40,7 @@ jobs: ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || steps.getRef.outputs.PR_REF || github.ref }} - name: Setup Environment - uses: ./.github/actions/setup-environment + uses: ./.github/actions/setup-environment@justin808-working-for-deploys with: token: ${{ env.CPLN_TOKEN }} org: ${{ env.CPLN_ORG }} @@ -228,7 +228,7 @@ jobs: github.event.issue.pull_request && github.event.comment.body == '/deploy-review-app') || (steps.check-app.outputs.app_exists == 'true') - uses: ./.github/actions/build-docker-image + uses: ./.github/actions/build-docker-image@justin808-working-for-deploys with: app_name: ${{ env.APP_NAME }} org: ${{ env.CPLN_ORG }} @@ -269,7 +269,7 @@ jobs: github.event.issue.pull_request && github.event.comment.body == '/deploy-review-app') || (steps.check-app.outputs.app_exists == 'true') - uses: ./.github/actions/deploy-to-control-plane + uses: ./.github/actions/deploy-to-control-plane@justin808-working-for-deploys with: app_name: ${{ env.APP_NAME }} org: ${{ env.CPLN_ORG }} diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index f086966a..f9376eeb 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -3,6 +3,7 @@ name: Show Help for Commands on: issue_comment: types: [created] + workflow_dispatch: permissions: issues: write @@ -11,9 +12,10 @@ permissions: jobs: show-help: if: | - github.event_name == 'issue_comment' && - github.event.issue.pull_request && - github.event.comment.body == '/help' + github.event_name == 'workflow_dispatch' || + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/help') runs-on: ubuntu-latest steps: @@ -84,14 +86,10 @@ jobs: 'For more information, see the [React on Rails Tutorial documentation](https://github.com/shakacode/react-on-rails/tree/master/react-webpack-rails-tutorial)' ].join('\n'); - console.log('Issue number:', github.context.payload.issue.number); - console.log('Owner:', context.repo.owner); - console.log('Repo:', context.repo.repo); - await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: github.context.payload.issue.number, + issue_number: context.issue.number, body: helpMessage }); diff --git a/.github/workflows/nightly-remove-stale-review-apps.yml b/.github/workflows/nightly-remove-stale-review-apps.yml index c5f0376a..0e8630f3 100644 --- a/.github/workflows/nightly-remove-stale-review-apps.yml +++ b/.github/workflows/nightly-remove-stale-review-apps.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Environment - uses: ./.github/actions/setup-environment + uses: ./.github/actions/setup-environment@justin808-working-for-deploys - name: Get Stale PRs id: stale_prs diff --git a/.github/workflows/promote-staging-to-production.yml b/.github/workflows/promote-staging-to-production.yml index 04148067..06cea9cd 100644 --- a/.github/workflows/promote-staging-to-production.yml +++ b/.github/workflows/promote-staging-to-production.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Environment - uses: ./.github/actions/setup-environment + uses: ./.github/actions/setup-environment@justin808-working-for-deploys env: CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} From cb5ce31813e9560f58ddf7abd241f4fd4512043c Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Mon, 27 Jan 2025 18:01:45 -1000 Subject: [PATCH 5/6] fix issues related to new secrets/variables --- .github/workflows/delete-review-app.yml | 4 +- .../deploy-to-control-plane-staging.yml | 8 +- .github/workflows/deploy-to-control-plane.yml | 4 +- .github/workflows/help-command.yml | 99 +++++++++++++++++++ 4 files changed, 107 insertions(+), 8 deletions(-) diff --git a/.github/workflows/delete-review-app.yml b/.github/workflows/delete-review-app.yml index a7691e3e..3f547579 100644 --- a/.github/workflows/delete-review-app.yml +++ b/.github/workflows/delete-review-app.yml @@ -13,8 +13,8 @@ permissions: issues: write env: - CPLN_ORG: ${{ secrets.CPLN_ORG_STAGING }} - CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} + CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }} + CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} APP_NAME: qa-react-webpack-rails-tutorial-pr-${{ github.event.pull_request.number || github.event.issue.number }} PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} diff --git a/.github/workflows/deploy-to-control-plane-staging.yml b/.github/workflows/deploy-to-control-plane-staging.yml index 5b178e66..b56511b1 100644 --- a/.github/workflows/deploy-to-control-plane-staging.yml +++ b/.github/workflows/deploy-to-control-plane-staging.yml @@ -13,8 +13,8 @@ on: # Convert the GitHub secret variables to environment variables for use by the Control Plane CLI env: - CPLN_ORG: ${{secrets.CPLN_ORG_STAGING}} - CPLN_TOKEN: ${{secrets.CPLN_TOKEN_STAGING}} + CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }} + CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} jobs: deploy-to-control-plane-staging: @@ -28,5 +28,5 @@ jobs: - uses: ./.github/actions/deploy-to-control-plane@justin808-working-for-deploys with: - app_name: ${{ secrets.APP_NAME_STAGING }} - org: ${{ secrets.CPLN_ORG_STAGING }} + app_name: ${{ vars.STAGING_APP_NAME }} + org: ${{ vars.CPLN_ORG_STAGING }} diff --git a/.github/workflows/deploy-to-control-plane.yml b/.github/workflows/deploy-to-control-plane.yml index 92e97187..35e26082 100644 --- a/.github/workflows/deploy-to-control-plane.yml +++ b/.github/workflows/deploy-to-control-plane.yml @@ -15,8 +15,8 @@ concurrency: env: APP_NAME: qa-react-webpack-rails-tutorial-pr-${{ github.event.pull_request.number || github.event.issue.number }} - CPLN_ORG: ${{ secrets.CPLN_ORG_STAGING }} CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} + CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }} PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} jobs: @@ -25,7 +25,7 @@ jobs: (github.event_name == 'pull_request') || (github.event_name == 'issue_comment' && github.event.issue.pull_request && - github.event.comment.body == '/deploy') + github.event.comment.body == '/deploy-review-app') runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index f9376eeb..682ffa1f 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -18,6 +18,105 @@ jobs: github.event.comment.body == '/help') runs-on: ubuntu-latest + steps: + - name: Show Available Commands + uses: actions/github-script@v7 + with: + script: | + try { + console.log('Creating detailed help message...'); + const helpMessage = [ + '# 📚 Detailed Review App Commands Guide', + '', + 'This is a detailed guide to using review app commands. For a quick reference, see the message posted when your PR was created.', + '', + '## Available Commands', + '', + '### `/deploy-review-app`', + 'Deploys your PR branch to a review environment on Control Plane.', + '- Creates a new review app if one doesn\'t exist', + '- Updates the existing review app if it already exists', + '- Provides a unique URL to preview your changes', + '- Shows build and deployment progress in real-time', + '', + '**Required Environment Secrets:**', + '- `CPLN_TOKEN_STAGING`: Control Plane authentication token', + '- `CPLN_TOKEN_PRODUCTION`: Control Plane authentication token', + '', + '**Required GitHub Actions Variables:**', + '- `CPLN_ORG_STAGING`: Control Plane authentication token', + '- `CPLN_ORG_PRODUCTION`: Control Plane authentication token', + '', + '**Required GitHub Actions Variables (these need to match your control_plane.yml file:**', + '- `PRODUCTION_APP_NAME`: Control Plane production app name', + '- `STAGING_APP_NAME`: Control Plane staging app name', + '- `REVIEW_APP_PREFIX`: Control Plane review app prefix', + '', + '**Optional Configuration:**', + '- `WAIT_TIMEOUT`: Deployment timeout in seconds (default: 900)', + ' - Must be a positive integer', + ' - Can be set in GitHub Actions variables', + ' - Applies to both deployment and workload readiness checks', + '', + '### `/delete-review-app`', + 'Deletes the review app associated with this PR.', + '- Removes all resources from Control Plane', + '- Helpful for cleaning up when you\'re done testing', + '- Can be re-deployed later using `/deploy-review-app`', + '', + '**Required Environment Variables:**', + '- `CPLN_TOKEN`: Control Plane authentication token', + '- `CPLN_ORG`: Control Plane organization name', + '', + '### `/help`', + 'Shows this detailed help message.', + '', + '---', + '## Environment Setup', + '', + '1. Set required secrets in your repository settings:', + ' - `CPLN_TOKEN`', + ' - `CPLN_ORG`', + '', + '2. Optional: Configure `WAIT_TIMEOUT` in GitHub Actions variables to customize deployment timeout', + '', + '## Control Plane Integration', + '', + 'Review apps are deployed to Control Plane with the following configuration:', + '- App Name Format: `qa-react-webpack-rails-tutorial-pr-{PR_NUMBER}`', + '- Console URL: `https://console.cpln.io/console/org/{CPLN_ORG}/gvc/{APP_NAME}/-info`', + '', + '## Automatic Cleanup', + '', + 'Review apps are automatically deleted when:', + '- The PR is closed (merged or not merged)', + '- The PR is stale (via nightly cleanup job)', + '', + 'For more information, see the [React on Rails Tutorial documentation](https://github.com/shakacode/react-on-rails/tree/master/react-webpack-rails-tutorial)' + ].join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, +name: Show Help for Commands + +on: + issue_comment: + types: [created] + +permissions: + issues: write + pull-requests: write + +jobs: + show-help: + if: | + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/help' + runs-on: ubuntu-latest + steps: - name: Show Available Commands uses: actions/github-script@v7 From 3ca76cdca3f79bb5747d496b121a36ac9495d218 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Mon, 27 Jan 2025 18:20:16 -1000 Subject: [PATCH 6/6] add log to what file is used --- .github/workflows/help-command.yml | 97 +++--------------------------- 1 file changed, 7 insertions(+), 90 deletions(-) diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 682ffa1f..90f19a2c 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -1,6 +1,9 @@ name: Show Help for Commands on: + push: + branches: + - "**" # Trigger on all branches, including feature branches issue_comment: types: [created] workflow_dispatch: @@ -19,6 +22,10 @@ jobs: runs-on: ubuntu-latest steps: + - name: Identify Workflow Branch + run: | + echo "Workflow branch ref: ${{ github.ref }}" + - name: Show Available Commands uses: actions/github-script@v7 with: @@ -95,96 +102,6 @@ jobs: 'For more information, see the [React on Rails Tutorial documentation](https://github.com/shakacode/react-on-rails/tree/master/react-webpack-rails-tutorial)' ].join('\n'); - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, -name: Show Help for Commands - -on: - issue_comment: - types: [created] - -permissions: - issues: write - pull-requests: write - -jobs: - show-help: - if: | - github.event_name == 'issue_comment' && - github.event.issue.pull_request && - github.event.comment.body == '/help' - runs-on: ubuntu-latest - - steps: - - name: Show Available Commands - uses: actions/github-script@v7 - with: - script: | - try { - console.log('Creating detailed help message...'); - const helpMessage = [ - '# 📚 Detailed Review App Commands Guide', - '', - 'This is a detailed guide to using review app commands. For a quick reference, see the message posted when your PR was created.', - '', - '## Available Commands', - '', - '### `/deploy-review-app`', - 'Deploys your PR branch to a review environment on Control Plane.', - '- Creates a new review app if one doesn\'t exist', - '- Updates the existing review app if it already exists', - '- Provides a unique URL to preview your changes', - '- Shows build and deployment progress in real-time', - '', - '**Required Environment Variables:**', - '- `CPLN_TOKEN`: Control Plane authentication token', - '- `CPLN_ORG`: Control Plane organization name', - '', - '**Optional Configuration:**', - '- `WAIT_TIMEOUT`: Deployment timeout in seconds (default: 900)', - ' - Must be a positive integer', - ' - Can be set in GitHub Actions variables', - ' - Applies to both deployment and workload readiness checks', - '', - '### `/delete-review-app`', - 'Deletes the review app associated with this PR.', - '- Removes all resources from Control Plane', - '- Helpful for cleaning up when you\'re done testing', - '- Can be re-deployed later using `/deploy-review-app`', - '', - '**Required Environment Variables:**', - '- `CPLN_TOKEN`: Control Plane authentication token', - '- `CPLN_ORG`: Control Plane organization name', - '', - '### `/help`', - 'Shows this detailed help message.', - '', - '---', - '## Environment Setup', - '', - '1. Set required secrets in your repository settings:', - ' - `CPLN_TOKEN`', - ' - `CPLN_ORG`', - '', - '2. Optional: Configure `WAIT_TIMEOUT` in GitHub Actions variables to customize deployment timeout', - '', - '## Control Plane Integration', - '', - 'Review apps are deployed to Control Plane with the following configuration:', - '- App Name Format: `qa-react-webpack-rails-tutorial-pr-{PR_NUMBER}`', - '- Console URL: `https://console.cpln.io/console/org/{CPLN_ORG}/gvc/{APP_NAME}/-info`', - '', - '## Automatic Cleanup', - '', - 'Review apps are automatically deleted when:', - '- The PR is closed (merged or not merged)', - '- The PR is stale (via nightly cleanup job)', - '', - 'For more information, see the [React on Rails Tutorial documentation](https://github.com/shakacode/react-on-rails/tree/master/react-webpack-rails-tutorial)' - ].join('\n'); - await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo,