Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI {{ app_name }} PR Environment Checks
on:
workflow_dispatch:
inputs:
pr_number:
required: true
type: string
commit_hash:
required: true
type: string
{% if app_has_dev_env_setup %}
pull_request:
{% else %}
# !! Once you've set up the dev environment and are ready to enable PR
# environments, run:
#
# nava-platform infra update --answers-only --data app_has_dev_env_setup=true .
#
# to enable these lines. They are here as comments for context.
#
# pull_request:
{% endif %}

jobs:
update:
name: " " # GitHub UI is noisy when calling reusable workflows, so use whitespace for name to reduce noise
uses: ./.github/workflows/pr-environment-checks.yml
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.state == 'open'
with:
app_name: "{{ app_name }}"
environment: "dev"
pr_number: ${{'{{'}} inputs.pr_number || github.event.number {{'}}'}}
commit_hash: ${{'{{'}} inputs.commit_hash || github.event.pull_request.head.sha {{'}}'}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CI {{ app_name }} PR Environment Destroy
on:
workflow_dispatch:
inputs:
pr_number:
required: true
type: string
{% if app_has_dev_env_setup %}
pull_request_target:
types: [closed]
{% else %}
# !! Once you've set up the dev environment and are ready to enable PR
# environments, run:
#
# nava-platform infra update --answers-only --data app_has_dev_env_setup=true .
#
# to enable these lines. They are here as comments for context.
#
# pull_request:
# types: [closed]
{% endif %}

jobs:
destroy:
name: " " # GitHub UI is noisy when calling reusable workflows, so use whitespace for name to reduce noise
uses: ./.github/workflows/pr-environment-destroy.yml
with:
app_name: "{{ app_name }}"
environment: "dev"
pr_number: ${{'{{'}} inputs.pr_number || github.event.number {{'}}'}}
63 changes: 63 additions & 0 deletions .github/workflows/pr-environment-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: PR Environment Update
run-name: Update PR Environment ${{ inputs.pr_number }}
on:
workflow_call:
inputs:
app_name:
required: true
type: string
environment:
required: true
type: string
pr_number:
required: true
type: string
commit_hash:
required: true
type: string

concurrency: pr-environment-${{ inputs.app_name }}-${{ inputs.pr_number }}

jobs:
build-and-publish:
name: " " # GitHub UI is noisy when calling reusable workflows, so use whitespace for name to reduce noise
uses: ./.github/workflows/build-and-publish.yml
with:
app_name: ${{ inputs.app_name }}
ref: ${{ inputs.commit_hash }}

update:
name: Update environment
needs: [build-and-publish]
runs-on: ubuntu-latest

permissions:
contents: read
id-token: write
pull-requests: write # Needed to comment on PR
repository-projects: read # Workaround for GitHub CLI bug https://github.com/cli/cli/issues/6274

outputs:
service_endpoint: ${{ steps.update-environment.outputs.service_endpoint }}

steps:
- uses: actions/checkout@v4

- name: Set up Terraform
uses: ./.github/actions/setup-terraform

- name: Configure Azure credentials
uses: ./.github/actions/configure-azure-credentials
with:
app_name: ${{ inputs.app_name }}
environment: ${{ inputs.environment }}

- name: Update environment
id: update-environment
run: |
./bin/update-pr-environment "${{ inputs.app_name }}" "${{ inputs.environment }}" "${{ inputs.pr_number }}" "${{ inputs.commit_hash }}"
service_endpoint=$(terraform -chdir="infra/${{ inputs.app_name }}/service" output -raw service_endpoint)
echo "service_endpoint=${service_endpoint}"
echo "service_endpoint=${service_endpoint}" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
44 changes: 44 additions & 0 deletions .github/workflows/pr-environment-destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: PR Environment Destroy
run-name: Destroy PR Environment ${{ inputs.pr_number }}
on:
workflow_call:
inputs:
app_name:
required: true
type: string
environment:
required: true
type: string
pr_number:
required: true
type: string

concurrency: pr-environment-${{ inputs.app_name }}-${{ inputs.pr_number }}

jobs:
destroy:
name: Destroy environment
runs-on: ubuntu-latest

permissions:
contents: read
id-token: write
pull-requests: write # Needed to comment on PR
repository-projects: read # Workaround for GitHub CLI bug https://github.com/cli/cli/issues/6274

steps:
- uses: actions/checkout@v4

- name: Set up Terraform
uses: ./.github/actions/setup-terraform

- name: Configure Azure credentials
uses: ./.github/actions/configure-azure-credentials
with:
app_name: ${{ inputs.app_name }}
environment: ${{ inputs.environment }}

- name: Destroy environment
run: ./bin/destroy-pr-environment "${{ inputs.app_name }}" "${{ inputs.environment }}" "${{ inputs.pr_number }}"
env:
GH_TOKEN: ${{ github.token }}
70 changes: 70 additions & 0 deletions .github/workflows/scan-orphaned-environments.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# This workflow scans for temporary environments that were not properly cleaned up
# This can happen if the PR environment destroy workflow failed or didn't run
# or if the temporary environments created by the infra service tests were not cleaned up
name: Scan orphaned environments

on:
workflow_dispatch:
schedule:
# Run every day at 07:30 UTC (3:30am ET, 12:30am PT) after engineers are likely done with work
- cron: "30 7 * * *"

jobs:
get-app-names:
name: Get app names
runs-on: ubuntu-latest

outputs:
app_names: ${{ steps.get-app-names.outputs.app_names }}

steps:
- uses: actions/checkout@v4

- name: Get app names
id: get-app-names
run: |
source bin/util.sh
app_names="$(get_app_names)"
# turn app_names into a json list using jq
app_names="$(echo "${app_names}" | jq -R -s -c 'split("\n")[:-1]')"
echo "App names retrieved: ${app_names}"
echo "app_names=${app_names}" >> "$GITHUB_OUTPUT"
shell: bash

scan:
name: Scan
runs-on: ubuntu-latest
needs: get-app-names

strategy:
fail-fast: false
matrix:
app_name: ${{ fromJson(needs.get-app-names.outputs.app_names) }}
scan_script: [orphaned-pr-environments, stale-test-environments]

permissions:
contents: read
id-token: write
pull-requests: read

steps:
- uses: actions/checkout@v4

- name: Set up Terraform
uses: ./.github/actions/setup-terraform

- name: Configure Azure credentials
uses: ./.github/actions/configure-azure-credentials
with:
app_name: ${{ matrix.app_name }}
environment: dev

- name: List PR workspaces
run: |
./bin/${{ matrix.scan_script }} ${{ matrix.app_name }}
env:
GH_TOKEN: ${{ github.token }}
TF_IN_AUTOMATION: "true"

# TODO(#50): Add a notify job that posts to Slack on failure. Until then,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Link the full URL of the issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed the TODO comment to include the full URL in commit 59dbd28: 59dbd28

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Don't see that change in this PR?

# orphaned-environment alerts only show up in the Actions UI.
59 changes: 59 additions & 0 deletions bin/destroy-pr-environment
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# Destroy the temporary environment that was created for the pull request.
#
# Positional parameters:
# app_name (required) – the name of subdirectory of /infra that holds the
# application's infrastructure code.
# environment - the name of the application environment (e.g. dev, staging, prod)
# pr_number - the pull request number in GitHub
# -----------------------------------------------------------------------------
set -euo pipefail

app_name="$1"
environment="$2"
pr_number="$3"

workspace="p-${pr_number}"

echo "::group::Initialize Terraform with backend for environment: ${environment}"
terraform -chdir="infra/${app_name}/service" init -backend-config="${environment}.azurerm.tfbackend"
echo "::endgroup::"

echo "Select Terraform workspace: ${workspace}"
terraform -chdir="infra/${app_name}/service" workspace select "${workspace}"

echo "::group::Destroy resources"
terraform -chdir="infra/${app_name}/service" destroy -var="environment_name=${environment}" -input=false -auto-approve
echo "::endgroup::"

echo "Select default workspace"
terraform -chdir="infra/${app_name}/service" workspace select default

echo "Delete workspace: ${workspace}"
terraform -chdir="infra/${app_name}/service" workspace delete "${workspace}"

pr_info=$(cat <<EOF
<!-- ${app_name} - begin PR environment info -->
## Preview environment for ${app_name}
♻️ Environment destroyed ♻️
<!-- ${app_name} - end PR environment info -->
EOF
)

pr_body="$(gh pr view "${pr_number}" --json body | jq --raw-output .body)"

# clean up older single-app section if present
if [[ $pr_body == *"<!-- begin PR environment info -->"*"<!-- end PR environment info -->"* ]]; then
pr_body="${pr_body//<!-- begin PR environment info -->*<!-- end PR environment info -->}"
fi

if [[ $pr_body == *"<!-- ${app_name} - begin PR environment info -->"*"<!-- ${app_name} - end PR environment info -->"* ]]; then
pr_body="${pr_body//<!-- ${app_name} - begin PR environment info -->*<!-- ${app_name} - end PR environment info -->/$pr_info}"
else
pr_body="${pr_body}"$'\n\n'"${pr_info}"
fi

echo "Update PR description with PR environment info"
echo "${pr_info}"
gh pr edit "${pr_number}" --body "${pr_body}"
49 changes: 49 additions & 0 deletions bin/orphaned-pr-environments
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# This script checks for orphaned PR environments by listing all PR workspaces
# and checking if the associated PR is closed. If the PR is closed the
# resources in the workspace should have been destroyed and the workspace
# deleted, so existing workspaces for closed PRs are considered orphaned.
# -----------------------------------------------------------------------------
set -euo pipefail

GITHUB_STEP_SUMMARY=${GITHUB_STEP_SUMMARY:-/dev/null}

app_name="$1"

echo "::group::Initialize Terraform"
echo terraform -chdir="infra/${app_name}/service" init -input=false -reconfigure -backend-config="dev.azurerm.tfbackend"
terraform -chdir="infra/${app_name}/service" init -input=false -reconfigure -backend-config="dev.azurerm.tfbackend"
echo "::endgroup::"

echo "::group::List PRs with PR environments"
echo terraform -chdir="infra/${app_name}/service" workspace list
workspaces="$(terraform -chdir="infra/${app_name}/service" workspace list)"
# grep will exit with code `1` if there's no match, so ignore that for when
# there are no PR workspaces for the application
pr_nums="$(echo "${workspaces}" | { grep -o 'p-[0-9]\+$' || test $? = 1; } | sed 's/p-//')"
echo "PRs"
echo "${pr_nums}"
echo "::endgroup::"

echo "::group::Check status of each PR"
closed_prs=()
for pr_num in $pr_nums; do
pr_status="$(gh pr view "$pr_num" --json state --jq ".state")"
echo "PR ${pr_num}: ${pr_status}"

if [ "$pr_status" == "CLOSED" ] || [ "$pr_status" == "MERGED" ]; then
closed_prs+=("$pr_num")
fi
done
echo "::endgroup::"

# if closed_prs is not empty exit with 1 otherwise exit with 0
if [ ${#closed_prs[@]} -gt 0 ]; then
echo "🧹 Found orphaned PR environments for the following PRs: **${closed_prs[*]}**"
echo "🧹 Found orphaned PR environments for the following PRs: **${closed_prs[*]}**" >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi

echo "✅ No orphaned PR environments"
echo "✅ No orphaned PR environments" >> "${GITHUB_STEP_SUMMARY}"
Loading
Loading