Workspace Tests #128
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Workspace Tests | |
| on: | |
| workflow_run: | |
| workflows: [Pull Request Actions] | |
| types: [completed] | |
| paths: | |
| - 'workspaces/**' | |
| jobs: | |
| resolve: | |
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| contents: read | |
| env: | |
| TRIGGERING_RUN_ID: ${{ github.event.workflow_run.id }} | |
| outputs: | |
| workspace: ${{ steps.meta.outputs.workspace }} | |
| overlayBranch: ${{ steps.meta.outputs.overlayBranch }} | |
| overlayRepo: ${{ steps.meta.outputs.overlayRepo }} | |
| overlayCommit: ${{ steps.meta.outputs.overlayCommit }} | |
| prNumber: ${{ steps.meta.outputs.pr }} | |
| publishedExports: ${{ steps.meta.outputs.publishedExports }} | |
| steps: | |
| # the artifact of the current run contains PR number | |
| - name: Download context artifact | |
| uses: dawidd6/action-download-artifact@v6 | |
| with: | |
| name: context-${{ env.TRIGGERING_RUN_ID }} | |
| run_id: ${{ env.TRIGGERING_RUN_ID }} | |
| path: ./context | |
| if_no_artifact_found: fail | |
| # note: the 'pr:' filter is intentionally omitted as it is unreliable for comment-triggered | |
| # workflows on forks. PR is verified manually in the next step. | |
| - name: Download latest published-exports artifact | |
| uses: dawidd6/action-download-artifact@v6 | |
| with: | |
| name: published-exports | |
| workflow: pr-actions.yaml | |
| workflow_conclusion: success | |
| workflow_search: true | |
| search_artifacts: true | |
| allow_forks: true | |
| if_no_artifact_found: fail | |
| - name: Verify published-exports artifact belongs to triggering PR | |
| run: | | |
| triggering_pr=$(jq -r .pr ./context/meta.json) | |
| artifact_pr=$(jq -r .pr ./meta.json) | |
| if [[ "$triggering_pr" != "$artifact_pr" ]]; then | |
| echo "::error::Mismatch: published-exports artifact does not belong to triggering PR" | |
| echo "Triggering PR: $triggering_pr" | |
| echo "Published-exports artifact PR: $artifact_pr" | |
| exit 1 | |
| fi | |
| - name: Read published-exports artifact metadata | |
| id: meta | |
| run: | | |
| echo "Contents of meta.json:" | |
| cat meta.json || echo "meta.json not found" | |
| workspace=$(jq -r .workspace meta.json) | |
| overlayBranch=$(jq -r .overlayBranch meta.json) | |
| overlayRepo=$(jq -r .overlayRepo meta.json) | |
| overlayCommit=$(jq -r .overlayCommit meta.json) | |
| pr=$(jq -r '.pr // "null"' meta.json) | |
| publishedExports=$(cat published-exports.txt) | |
| echo "Parsed values: workspace=$workspace, overlayBranch=$overlayBranch, overlayRepo=$overlayRepo, overlayCommit=$overlayCommit, pr=$pr" | |
| test -z "$workspace" && { echo "Missing workspace in meta.json"; exit 1; } | |
| echo "workspace=$workspace" >> $GITHUB_OUTPUT | |
| echo "overlayBranch=$overlayBranch" >> $GITHUB_OUTPUT | |
| echo "overlayRepo=$overlayRepo" >> $GITHUB_OUTPUT | |
| echo "overlayCommit=$overlayCommit" >> $GITHUB_OUTPUT | |
| echo "pr=$pr" >> $GITHUB_OUTPUT | |
| echo "publishedExports<<EOF" >> $GITHUB_OUTPUT | |
| echo "$publishedExports" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| - name: Debug resolved metadata | |
| run: | | |
| echo "Workspace: ${{ steps.meta.outputs.workspace }}" | |
| echo "PR: ${{ steps.meta.outputs.pr }}, Overlay SHA: ${{ steps.meta.outputs.overlayCommit }}" | |
| prepare-test-config: | |
| needs: resolve | |
| if: ${{ needs.resolve.outputs.workspace != '' }} | |
| runs-on: ubuntu-latest | |
| outputs: | |
| plugins_metadata_complete: ${{ steps.build-dynamic-plugins.outputs.plugins_metadata_complete }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.resolve.outputs.overlayBranch }} | |
| repository: ${{ needs.resolve.outputs.overlayRepo }} | |
| - name: Setup yq | |
| uses: mikefarah/yq@v4 | |
| - name: Build dynamic-plugins.test.yaml | |
| id: build-dynamic-plugins | |
| run: | | |
| WORKSPACE_PATH="${{ needs.resolve.outputs.workspace }}" | |
| PUBLISHED_EXPORTS="${{ needs.resolve.outputs.publishedExports }}" | |
| PLUGINS_FOUND=0 | |
| TOTAL_PLUGINS=0 | |
| PLUGINS_METADATA_COMPLETE="false" | |
| if [ -z "$PUBLISHED_EXPORTS" ]; then | |
| echo "No published exports provided." | |
| echo "plugins_metadata_complete=$PLUGINS_METADATA_COMPLETE" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Count total plugins from exports | |
| for export in $PUBLISHED_EXPORTS; do | |
| TOTAL_PLUGINS=$((TOTAL_PLUGINS + 1)) | |
| done | |
| OUT_DIR="$WORKSPACE_PATH/tests" | |
| OUT_FILE="$OUT_DIR/dynamic-plugins.test.yaml" | |
| mkdir -p "$OUT_DIR" | |
| # Build map of <stripped packageName> -> metadata file path | |
| declare -A META_MAP | |
| for file in "$WORKSPACE_PATH"/metadata/*.yaml; do | |
| [ -e "$file" ] || continue | |
| pkg=$(yq -r '.packageName // ""' "$file") | |
| if [ -n "$pkg" ]; then | |
| stripped=$(echo "$pkg" | sed 's|^@||; s|/|-|') | |
| META_MAP["$stripped"]="$file" | |
| fi | |
| done | |
| # Always include the root-level default config | |
| ROOT_CONFIG="$GITHUB_WORKSPACE/tests/app-config.yaml" | |
| [ -f "$ROOT_CONFIG" ] && cp "$ROOT_CONFIG" "$OUT_DIR/app-config.yaml" | |
| # Start the resulting YAML file | |
| echo "plugins:" > "$OUT_FILE" | |
| # For each published export, extract plugin name and read its metadata | |
| # Expected export format: ghcr.io/<repo_path>/<plugin_name>:<tag> | |
| for export in $PUBLISHED_EXPORTS; do | |
| if [[ "$export" =~ ^ghcr\.io/(.+):([^[:space:]]+)$ ]]; then | |
| IMAGE_PATH_AND_PLUGIN="${BASH_REMATCH[1]}" # <repo_path>/<plugin_name> | |
| NEW_TAG="${BASH_REMATCH[2]}" # <tag> | |
| PLUGIN_NAME="${IMAGE_PATH_AND_PLUGIN##*/}" | |
| METADATA_FILE="${META_MAP[$PLUGIN_NAME]}" | |
| if [ -z "$METADATA_FILE" ]; then | |
| echo "Metadata mapping not found for $PLUGIN_NAME, skipping" | |
| continue | |
| fi | |
| PACKAGE_NAME=$(yq -r '.packageName' "$METADATA_FILE") | |
| if [ -z "$PACKAGE_NAME" ] || [ "$PACKAGE_NAME" = "null" ]; then | |
| echo "packageName not found in $METADATA_FILE, skipping" | |
| continue | |
| fi | |
| # Extract plugin config block under configs.default | |
| if ! yq -e '.configs.default' "$METADATA_FILE" >/dev/null 2>&1; then | |
| echo "configs.default not found in $METADATA_FILE, skipping" | |
| continue | |
| fi | |
| STRIPPED=$(echo "$PACKAGE_NAME" | sed 's|^@||; s|/|-|') | |
| echo "- package: \"oci://ghcr.io/${IMAGE_PATH_AND_PLUGIN}:${NEW_TAG}!${STRIPPED}\"" >> "$OUT_FILE" | |
| echo " disabled: false" >> "$OUT_FILE" | |
| echo " pluginConfig:" >> "$OUT_FILE" | |
| # Append the configs.default block indented by 4 spaces under pluginConfig | |
| yq -o=yaml '.configs.default' "$METADATA_FILE" | sed 's/^/ /' >> "$OUT_FILE" | |
| PLUGINS_FOUND=$((PLUGINS_FOUND + 1)) | |
| else | |
| echo "Export did not match expected format, skipping: $export" | |
| fi | |
| done | |
| if [ "$PLUGINS_FOUND" -eq 0 ]; then | |
| echo "[]" >> "$OUT_FILE" | |
| fi | |
| if [ "$PLUGINS_FOUND" -eq "$TOTAL_PLUGINS" ] && [ "$TOTAL_PLUGINS" -gt 0 ]; then | |
| PLUGINS_METADATA_COMPLETE="true" | |
| fi | |
| echo "plugins_metadata_complete=$PLUGINS_METADATA_COMPLETE" >> "$GITHUB_OUTPUT" | |
| - name: Upload integration-test artefact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: integration-test-artifacts | |
| path: ${{ format('{0}/tests', needs.resolve.outputs.workspace) }} | |
| if-no-files-found: error | |
| integration-tests: | |
| needs: | |
| - resolve | |
| - prepare-test-config | |
| if: ${{ needs.prepare-test-config.outputs.plugins_metadata_complete == 'true' }} | |
| uses: ./.github/workflows/run-plugin-integration-tests.yaml | |
| with: | |
| workspace-path: ${{ needs.resolve.outputs.workspace }} | |
| add-skipped-test-comment: | |
| if: ${{ needs.prepare-test-config.outputs.plugins_metadata_complete == 'false' && needs.resolve.outputs.prNumber != 'null' }} | |
| needs: | |
| - resolve | |
| - prepare-test-config | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| issues: write | |
| steps: | |
| - name: Post skipped-test comment | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const pr = Number('${{ needs.resolve.outputs.prNumber }}'); | |
| if (!pr) { | |
| console.log('No PR associated; skipping'); | |
| return; | |
| } | |
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | |
| const body = `:warning: \n[Test workflow](${runUrl}) skipped because some plugin metadata files (\`<workspace>/metadata/*.yaml\`) were not found.\n\nAdd the corresponding metadata files to enable testing for this workspace.\n`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr, | |
| body, | |
| }); | |
| add-test-result-comment: | |
| if: ${{ needs.prepare-test-config.outputs.plugins_metadata_complete == 'true' }} | |
| needs: | |
| - resolve | |
| - prepare-test-config | |
| - integration-tests | |
| concurrency: | |
| group: addTestResultComment-${{ github.ref_name }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| statuses: write | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Post test result comment and status | |
| uses: actions/github-script@v7 | |
| env: | |
| SUCCESS: ${{ needs.integration-tests.outputs.success }} | |
| FAILED_PLUGINS: ${{ needs.integration-tests.outputs.failed-plugins }} | |
| PR_NUMBER: ${{ needs.resolve.outputs.prNumber }} | |
| OVERLAY_COMMIT: ${{ needs.resolve.outputs.overlayCommit }} | |
| with: | |
| script: | | |
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | |
| const success = process.env.SUCCESS === 'true'; | |
| const failed = process.env.FAILED_PLUGINS.trim(); | |
| const pr = Number(process.env.PR_NUMBER); | |
| const overlayCommit = process.env.OVERLAY_COMMIT; | |
| if (!pr) { | |
| console.log('No PR associated; skipping'); | |
| return; | |
| } | |
| // Get current PR head SHA | |
| const { data: prData } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr, | |
| }); | |
| // Use PR head if different from overlayCommit (re-test), else use overlayCommit (immediate publish) | |
| const sha = prData.head.sha !== overlayCommit ? prData.head.sha : overlayCommit; | |
| console.log(`Status SHA: ${sha} (PR head: ${prData.head.sha}, overlay: ${overlayCommit})`); | |
| await github.rest.repos.createCommitStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| sha, | |
| description: 'Workspace Tests', | |
| state: success ? 'success' : 'failure', | |
| target_url: runUrl, | |
| context: 'test', | |
| }); | |
| const body = success | |
| ? `:white_check_mark: [Test workflow](${runUrl}) passed. All plugins loaded successfully.\n` | |
| : `:x: \n[Test workflow](${runUrl}) failed. These plugins failed to load:${failed ? `\n${failed}` : ''}\n`; | |
| await github.rest.issues.createComment({ | |
| issue_number: pr, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body, | |
| }); |