Skip to content

Fix Review App Deletion Workflows #639

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/actions/delete-control-plane-app/action.yml
Original file line number Diff line number Diff line change
@@ -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
cpln_org:
description: 'Organization name'
required: true

runs:
using: "composite"
steps:
- name: Delete Application
shell: bash
run: ${{ github.action_path }}/delete-app.sh
env:
APP_NAME: ${{ inputs.app_name }}
CPLN_ORG: ${{ inputs.cpln_org }}
36 changes: 36 additions & 0 deletions .github/actions/delete-control-plane-app/delete-app.sh
Original file line number Diff line number Diff line change
@@ -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" --org "$CPLN_ORG"; 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" --org "$CPLN_ORG" --yes; then
echo "❌ Failed to delete application: $APP_NAME" >&2
exit 1
fi

echo "✅ Successfully deleted application: $APP_NAME"
19 changes: 3 additions & 16 deletions .github/workflows/delete-review-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ env:
PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }}
CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }}
APP_NAME: ${{ vars.REVIEW_APP_PREFIX }}-pr-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }}
APP_NAME: ${{ vars.REVIEW_APP_PREFIX }}-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }}

jobs:
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
missing+=("Variable: CPLN_ORG_STAGING")
fi

if [ -z "$"PREFIX" }} ]; then
if [ -z "$PREFIX" ]; then
missing+=("Variable: REVIEW_APP_PREFIX")
fi

Expand Down Expand Up @@ -131,29 +131,16 @@ jobs:
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 }}
CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }}
cpln_org: ${{ vars.CPLN_ORG_STAGING }}

- name: Update Delete Status
if: always()
uses: actions/github-script@v7
with:
script: |
Expand Down
14 changes: 6 additions & 8 deletions .github/workflows/deploy-to-control-plane-review-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ concurrency:

env:
PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
APP_NAME: ${{ vars.REVIEW_APP_PREFIX }}-pr-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
APP_NAME: ${{ vars.REVIEW_APP_PREFIX }}-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }}
CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
Expand Down Expand Up @@ -181,17 +181,19 @@ jobs:
echo "Skipping deployment for non-PR comment"
fi
fi
if [[ "${{ env.DO_DEPLOY }}" == "false" ]]; then
exit 0
fi
Comment on lines +184 to +186
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Early-exit logic does not skip subsequent steps as intended
The exit 0 inside the Validate Deployment Request step only terminates that shell script—it does not prevent the rest of the job from executing. As a result, even when DO_DEPLOY is "false", all downstream steps will still run.

To properly gate the workflow, consider emitting a step output (e.g., echo "do_deploy=${DO_DEPLOY}" >> $GITHUB_OUTPUT in the validate script) and then adding:

if: steps.validate.outputs.do_deploy == 'true'

to every subsequent step. This ensures that all deployment steps are truly skipped when DO_DEPLOY is disabled.


- name: Setup Control Plane App if Not Existing
if: env.DO_DEPLOY == 'true' && env.APP_EXISTS == 'false'
if: env.APP_EXISTS == 'false'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Include DO_DEPLOY guard in the app setup condition
Right now the setup step only checks APP_EXISTS, so it can still run when deployments are disabled. It’s safer to combine both flags:

if: env.APP_EXISTS == 'false' && env.DO_DEPLOY != 'false'

This ensures that a new app is only provisioned when we explicitly want to deploy.

env:
CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }}
run: |
echo "🔧 Setting up new Control Plane app..."
cpflow setup-app -a ${{ env.APP_NAME }} --org ${{ vars.CPLN_ORG_STAGING }}

- name: Create Initial Comment
if: env.DO_DEPLOY != 'false'
uses: actions/github-script@v7
id: create-comment
with:
Expand All @@ -200,13 +202,12 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: process.env.PR_NUMBER,
body: '🚀 Starting deployment process...\n\n' + process.env.CONSOLE_LINK
body: '🚀 Starting deployment process...\n\n'
});
core.setOutput('comment-id', result.data.id);

- name: Set Deployment URLs
id: set-urls
if: env.DO_DEPLOY != 'false'
uses: actions/github-script@v7
with:
script: |
Expand Down Expand Up @@ -237,7 +238,6 @@ jobs:
);

- name: Initialize GitHub Deployment
if: env.DO_DEPLOY != 'false'
uses: actions/github-script@v7
id: init-deployment
with:
Expand Down Expand Up @@ -316,15 +316,13 @@ jobs:
});

- name: Deploy to Control Plane
if: env.DO_DEPLOY != 'false'
run: cpflow deploy-image -a ${{ env.APP_NAME }} --run-release-phase --org ${{ vars.CPLN_ORG_STAGING }} --verbose

- name: Retrieve App URL
id: workload
run: echo "WORKLOAD_URL=$(cpln workload get rails --gvc ${{ env.APP_NAME }} | tee | grep -oP 'https://[^[:space:]]*\.cpln\.app(?=\s|$)' | head -n1)" >> "$GITHUB_OUTPUT"

- name: Update Status - Deployment Complete
if: env.DO_DEPLOY != 'false'
uses: actions/github-script@v7
with:
script: |
Expand Down
37 changes: 1 addition & 36 deletions .github/workflows/nightly-remove-stale-review-apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,5 @@ jobs:
token: ${{ secrets.CPLN_TOKEN_STAGING }}
org: ${{ vars.CPLN_ORG_STAGING }}

- 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: |
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: |
cpflow cleanup-images -a qa-react-webpack-rails-tutorial -y
run: cpflow cleanup-stale-apps -a "qa-react-webpack-rails-tutorial" --yes