Skip to content
Draft
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
215 changes: 215 additions & 0 deletions .github/workflows/test-scenario-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
name: Test Scenario Cleanup

on:
pull_request:
types: [closed]
issue_comment:
types: [created]

permissions:
contents: read
pull-requests: write

jobs:
cleanup-playground:
# Run when a PR is closed OR when the comment is /test-scenario-cleanup on a PR
if: >-
${{
github.event_name == 'pull_request' ||
(startsWith(github.event.comment.body, '/test-scenario-cleanup') &&
github.event.issue.pull_request)
}}
runs-on: ubuntu-latest
env:
REPO_OWNER: dotnet
REPO_NAME: aspire-playground
Copy link
Member

Choose a reason for hiding this comment

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

Can we rename this to PLAYGROUND_REPO_NAME to avoid confusion when reading the code?

GH_CLI_VERSION: 2.81.0
GH_PLAYGROUND_TOKEN: ${{ secrets.GH_PLAYGROUND_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Get PR number
id: get_pr_number
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
PR_NUMBER="${{ github.event.pull_request.number }}"
else
PR_NUMBER="${{ github.event.issue.number }}"
fi
echo "PR number: $PR_NUMBER"
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT

- name: Download and install GitHub CLI
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to install this manually? The runners will have the cli installed.

run: |
CURRENT_VERSION=""
if command -v gh &> /dev/null; then
CURRENT_VERSION=$(gh --version | \
grep -oP 'gh version \K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
echo "Current GitHub CLI version: $CURRENT_VERSION"
fi

if [ "$CURRENT_VERSION" = "$GH_CLI_VERSION" ]; then
echo "GitHub CLI v${GH_CLI_VERSION} already installed"
else
echo "Downloading GitHub CLI v${GH_CLI_VERSION}..."
DOWNLOAD_URL="https://github.com/cli/cli/releases/download/v${GH_CLI_VERSION}"
ARCHIVE_NAME="gh_${GH_CLI_VERSION}_linux_amd64.tar.gz"
curl -fsSL "${DOWNLOAD_URL}/${ARCHIVE_NAME}" -o gh.tar.gz
tar -xzf gh.tar.gz
sudo mv "gh_${GH_CLI_VERSION}_linux_amd64/bin/gh" /usr/local/bin/
rm -rf gh.tar.gz "gh_${GH_CLI_VERSION}_linux_amd64"

echo "Verifying GitHub CLI installation..."
gh --version
fi

- name: Find and close linked playground items
id: cleanup
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER="${{ steps.get_pr_number.outputs.pr_number }}"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
PR_NUMBER="${{ steps.get_pr_number.outputs.pr_number }}"
set -euo pipefail # Exit on error, undefined vars, pipe failures
PR_NUMBER="${{ steps.get_pr_number.outputs.pr_number }}"

SOURCE_REPO="${{ github.repository }}"
SOURCE_REPO_NAME=$(echo "$SOURCE_REPO" | cut -d'/' -f2)

echo "Finding linked issues/PRs in aspire-playground for PR #$PR_NUMBER..."

# Get the PR body and comments to find references to playground issues/PRs
# Use GraphQL to get the timeline and linked issues
QUERY='query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
closingIssuesReferences(first: 100) {
Copy link
Member

Choose a reason for hiding this comment

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

We don't seem to be using the results from matching the issues. Maybe jq could be used to extract the information from the result?

nodes {
number
url
repository {
owner {
login
}
name
}
labels(first: 100) {
nodes {
name
}
}
}
}
body
comments(first: 100) {
nodes {
body
}
}
}
}
}'

# Execute GraphQL query
RESULT=$(gh api graphql -f query="$QUERY" \
-F owner="${{ github.repository_owner }}" \
-F repo="$SOURCE_REPO_NAME" \
-F number="$PR_NUMBER")

echo "GraphQL response: $RESULT"

# Extract playground issues/PRs from body and comments
PR_BODY=$(echo "$RESULT" | jq -r '.data.repository.pullRequest.body // ""')
COMMENTS=$(echo "$RESULT" | jq -r '.data.repository.pullRequest.comments.nodes[].body // ""')

# Find playground issue/PR references in format: https://github.com/dotnet/aspire-playground/issues/123 or /pull/123
PLAYGROUND_REFS=$(echo -e "$PR_BODY\n$COMMENTS" | \
grep -oP "https://github\.com/${REPO_OWNER}/${REPO_NAME}/(issues|pull)/\K\d+" | \
sort -u)
Comment on lines +122 to +125
Copy link
Member

Choose a reason for hiding this comment

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

We don't seem to be using the results from matching the issues with closingIssuesReferences. Maybe jq could be used to extract the information from the result?


echo "Found potential playground references: $PLAYGROUND_REFS"

# Auth using the playground token
gh auth login --with-token <<< "$GH_PLAYGROUND_TOKEN"

CLOSED_ITEMS=""
CLOSED_COUNT=0

# Process each referenced item
for ITEM_NUMBER in $PLAYGROUND_REFS; do
echo "Checking item #$ITEM_NUMBER in aspire-playground..."

# Check if it's an issue or PR and get labels
ITEM_INFO=$(gh api "/repos/${REPO_OWNER}/${REPO_NAME}/issues/$ITEM_NUMBER" 2>/dev/null || echo "")

if [ -z "$ITEM_INFO" ]; then
echo " Item #$ITEM_NUMBER not found, skipping"
continue
fi

# Check if item is already closed
STATE=$(echo "$ITEM_INFO" | jq -r '.state')
if [ "$STATE" = "closed" ]; then
echo " Item #$ITEM_NUMBER is already closed, skipping"
continue
fi

# Check for auto-close label
HAS_AUTO_CLOSE=$(echo "$ITEM_INFO" | jq -r '.labels[] | select(.name == "auto-close") | .name')

if [ -n "$HAS_AUTO_CLOSE" ]; then
echo " Item #$ITEM_NUMBER has auto-close label, closing..."

# Determine if it's a PR or issue
IS_PR=$(echo "$ITEM_INFO" | jq -r '.pull_request // empty')

if [ -n "$IS_PR" ]; then
# Close PR
gh pr close "$ITEM_NUMBER" \
--repo "${REPO_OWNER}/${REPO_NAME}" \
--comment "Automatically closed because the linked PR ${SOURCE_REPO}#${PR_NUMBER} was closed."

ITEM_TYPE="PR"
else
# Close issue
gh issue close "$ITEM_NUMBER" \
--repo "${REPO_OWNER}/${REPO_NAME}" \
--comment "Automatically closed because the linked PR ${SOURCE_REPO}#${PR_NUMBER} was closed."

ITEM_TYPE="issue"
fi

ITEM_URL=$(echo "$ITEM_INFO" | jq -r '.html_url')
CLOSED_ITEMS="${CLOSED_ITEMS}\n- ${ITEM_TYPE} [#${ITEM_NUMBER}](${ITEM_URL})"
CLOSED_COUNT=$((CLOSED_COUNT + 1))

echo " Successfully closed $ITEM_TYPE #$ITEM_NUMBER"
else
echo " Item #$ITEM_NUMBER does not have auto-close label, skipping"
fi
done

echo "closed_items<<EOF" >> $GITHUB_OUTPUT
echo -e "$CLOSED_ITEMS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "closed_count=$CLOSED_COUNT" >> $GITHUB_OUTPUT

echo "Cleanup complete. Closed $CLOSED_COUNT item(s)."

- name: Comment on PR with cleanup summary
if: steps.cleanup.outputs.closed_count > 0 && github.event_name == 'issue_comment'
Copy link
Member

Choose a reason for hiding this comment

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

It might be useful to comment even when closed_count == 0, just so the user knows what happened.

uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const closedItems = `${{ steps.cleanup.outputs.closed_items }}`;
const closedCount = '${{ steps.cleanup.outputs.closed_count }}';

const comment = `🧹 **Playground Cleanup Complete**

Closed ${closedCount} item(s) in aspire-playground with the auto-close label:
${closedItems}`;

await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});