Skip to content

[codex] Apply generated cpflow GitHub Actions flow #1

[codex] Apply generated cpflow GitHub Actions flow

[codex] Apply generated cpflow GitHub Actions flow #1

name: Delete Review App
on:
pull_request_target:
types: [closed]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: Pull request number targeted for deletion
required: true
type: number
permissions:
contents: read
deployments: write
issues: write
pull-requests: write
concurrency:
group: cpflow-delete-review-app-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
# Deletions must not cancel each other mid-flight — a cancelled `cpln` delete can leave
# partial state behind. Let the in-progress deletion finish before the next run starts.
cancel-in-progress: false
env:
APP_NAME: ${{ vars.REVIEW_APP_PREFIX }}-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
jobs:
delete-review-app:
if: |
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
github.event.comment.body == '/delete-review-app' &&
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request_target' && github.event.action == 'closed') ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
# pull_request_target is intentional: PR-close events from forks need access
# to staging secrets so this workflow can delete review apps and update PR
# comments. This checkout is safe because it does not set `ref:`; GitHub checks
# out the base branch's trusted workflow code, not the fork head. Do not add
# `ref: ${{ github.event.pull_request.head.sha }}` here without re-evaluating
# the trust boundary. All local composite actions below are therefore loaded from
# trusted base-branch code; keep them that way when changing this workflow.
- name: Checkout repository
uses: actions/checkout@v4
with:
# Delete only invokes `cpln`/`cpflow`; no git push happens, so drop the
# GITHUB_TOKEN credential helper to keep the token out of .git/config under
# `pull_request_target`, which has access to repository secrets.
persist-credentials: false
- name: Validate required secrets and variables
id: config
uses: ./.github/actions/cpflow-validate-config
env:
CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }}
CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }}
REVIEW_APP_PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
with:
required: |
secret:CPLN_TOKEN_STAGING
variable:CPLN_ORG_STAGING
variable:REVIEW_APP_PREFIX
pull_request_friendly: "true"
- name: Setup environment
if: steps.config.outputs.ready == 'true'
uses: ./.github/actions/cpflow-setup-environment
with:
token: ${{ secrets.CPLN_TOKEN_STAGING }}
org: ${{ vars.CPLN_ORG_STAGING }}
cpln_cli_version: ${{ vars.CPLN_CLI_VERSION }}
cpflow_version: ${{ vars.CPFLOW_VERSION }}
- name: Set workflow links
if: steps.config.outputs.ready == 'true'
uses: actions/github-script@v7
with:
script: |
const workflowUrl = `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
core.exportVariable("WORKFLOW_URL", workflowUrl);
core.exportVariable(
"CONSOLE_URL",
`https://console.cpln.io/console/org/${process.env.CPLN_ORG}/-info`
);
- name: Create initial PR comment
if: steps.config.outputs.ready == 'true'
id: create-comment
uses: actions/github-script@v7
with:
script: |
const comment = await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(process.env.PR_NUMBER),
body: "🗑️ Deleting Control Plane review app..."
});
core.setOutput("comment-id", comment.data.id);
- name: Delete review app
if: steps.config.outputs.ready == 'true'
uses: ./.github/actions/cpflow-delete-control-plane-app
env:
CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }}
with:
app_name: ${{ env.APP_NAME }}
cpln_org: ${{ vars.CPLN_ORG_STAGING }}
review_app_prefix: ${{ vars.REVIEW_APP_PREFIX }}
- name: Mark GitHub deployment inactive
if: steps.config.outputs.ready == 'true'
uses: actions/github-script@v7
with:
script: |
const environment = `review/${process.env.APP_NAME}`;
const deployments = await github.paginate(github.rest.repos.listDeployments, {
owner: context.repo.owner,
repo: context.repo.repo,
environment,
per_page: 100
});
if (deployments.length === 0) {
core.info(`No GitHub deployments found for ${environment}.`);
return;
}
for (const deployment of deployments) {
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deployment.id,
state: "inactive",
environment,
log_url: process.env.WORKFLOW_URL,
description: "Review app deleted"
});
}
- name: Finalize delete status
if: always() && steps.config.outputs.ready == 'true'
uses: actions/github-script@v7
with:
script: |
const commentId = Number("${{ steps.create-comment.outputs.comment-id }}");
const success = "${{ job.status }}" === "success";
const body = success
? [
`✅ Review app for PR #${process.env.PR_NUMBER} is deleted`,
"",
`[Open organization console](${process.env.CONSOLE_URL})`,
`[View workflow logs](${process.env.WORKFLOW_URL})`
].join("\n")
: [
`❌ Failed to delete review app for PR #${process.env.PR_NUMBER}`,
"",
`[Open organization console](${process.env.CONSOLE_URL})`,
`[View workflow logs](${process.env.WORKFLOW_URL})`
].join("\n");
if (!Number.isFinite(commentId) || commentId <= 0) {
core.warning("Skipping delete status comment update because no comment id was created.");
return;
}
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: commentId,
body
});