Skip to content

Workspace Tests

Workspace Tests #128

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,
});