Run Integration Tests #44
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: Run Integration Tests | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| fcli_version: | |
| description: 'Fcli version (tag from GitHub releases)' | |
| required: false | |
| default: 'dev_v3.x' | |
| products: | |
| description: 'Products to test (comma-separated: fod, ssc)' | |
| required: false | |
| default: 'fod,ssc' | |
| components: | |
| description: 'Components to test (comma-separated: setup, ast-scan)' | |
| required: false | |
| default: 'setup,ast-scan' | |
| source_dirs: | |
| description: 'Source directories to test (comma-separated)' | |
| required: false | |
| default: 'dvna' | |
| ci_systems: | |
| description: 'CI systems to test (comma-separated: github:v2, github:v3, gitlab:v2, ado:v1)' | |
| required: false | |
| default: 'github:feat/fcli-ci,gitlab:v2' | |
| os: | |
| description: 'Operating systems to test (comma-separated: linux, windows, mac)' | |
| required: false | |
| default: 'linux,windows' | |
| concurrency: | |
| group: fcli-ci-tests-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| prepare-matrix: | |
| name: Prepare Test Matrix | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.create-matrix.outputs.matrix }} | |
| summary: ${{ steps.create-matrix.outputs.summary }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Create test matrix | |
| id: create-matrix | |
| run: | | |
| set -x # Enable debug output | |
| # Parse inputs | |
| IFS=',' read -ra CI_SYSTEMS <<< "${{ github.event.inputs.ci_systems }}" | |
| IFS=',' read -ra PRODUCTS <<< "${{ github.event.inputs.products }}" | |
| IFS=',' read -ra COMPONENTS <<< "${{ github.event.inputs.components }}" | |
| IFS=',' read -ra SOURCE_DIRS <<< "${{ github.event.inputs.source_dirs }}" | |
| IFS=',' read -ra OS_LIST <<< "${{ github.event.inputs.os }}" | |
| echo "Input parameters:" | |
| echo " CI_SYSTEMS: ${CI_SYSTEMS[@]}" | |
| echo " PRODUCTS: ${PRODUCTS[@]}" | |
| echo " COMPONENTS: ${COMPONENTS[@]}" | |
| echo " SOURCE_DIRS: ${SOURCE_DIRS[@]}" | |
| echo " OS_LIST: ${OS_LIST[@]}" | |
| # Read sources configuration | |
| if [ -f "sources/config.json" ]; then | |
| echo "Loading sources/config.json for build tool configuration" | |
| sources_config=$(cat sources/config.json) | |
| else | |
| echo "WARNING: sources/config.json not found, build tool setup will be skipped" | |
| sources_config='{"sources": {}}' | |
| fi | |
| # Build matrix with filtering | |
| matrix_items=() | |
| filtered_count=0 | |
| filtered_items="" | |
| for ci_system in "${CI_SYSTEMS[@]}"; do | |
| platform=$(echo "$ci_system" | cut -d: -f1) | |
| version=$(echo "$ci_system" | cut -d: -f2) | |
| echo "Processing CI system: $ci_system (platform=$platform, version=$version)" | |
| # Check if config file exists | |
| if [ ! -f "ci/$platform/config.json" ]; then | |
| echo "ERROR: Config file ci/$platform/config.json not found" | |
| continue | |
| fi | |
| # Read supported OS from config | |
| supported_os=$(jq -r '.supportedOs[]' "ci/$platform/config.json" 2>/dev/null | tr '\n' ',') | |
| echo " Supported OS for $platform: $supported_os" | |
| for component in "${COMPONENTS[@]}"; do | |
| echo " Processing component: $component" | |
| # For setup component, no need to iterate products (product-agnostic) | |
| if [ "$component" = "setup" ]; then | |
| for os in "${OS_LIST[@]}"; do | |
| if echo "$supported_os" | grep -q "$os"; then | |
| echo " Adding: $ci_system / $component / $os" | |
| matrix_items+=("{\"ci_system\":\"$ci_system\",\"platform\":\"$platform\",\"version\":\"$version\",\"product\":\"none\",\"component\":\"$component\",\"source_dir\":\"none\",\"os\":\"$os\",\"github_setup_steps\":\"[]\",\"ado_setup_tasks\":\"[]\",\"gitlab_image\":\"\"}") | |
| else | |
| echo " Filtering: $ci_system + $os (not supported)" | |
| filtered_count=$((filtered_count + 1)) | |
| filtered_items="${filtered_items}- $ci_system + $os (not supported)\n" | |
| fi | |
| done | |
| else | |
| # For ast-scan, iterate products and source_dirs | |
| for product in "${PRODUCTS[@]}"; do | |
| for source_dir in "${SOURCE_DIRS[@]}"; do | |
| # Get build tool configuration for this source | |
| github_setup_steps=$(echo "$sources_config" | jq -c ".sources[\"$source_dir\"].githubSetupSteps // []") | |
| ado_setup_tasks=$(echo "$sources_config" | jq -c ".sources[\"$source_dir\"].adoSetupTasks // []") | |
| gitlab_image=$(echo "$sources_config" | jq -r ".sources[\"$source_dir\"].gitlabImage // \"\"") | |
| for os in "${OS_LIST[@]}"; do | |
| if echo "$supported_os" | grep -q "$os"; then | |
| echo " Adding: $ci_system / $product / $component / $source_dir / $os" | |
| # Escape JSON strings for inclusion in matrix item | |
| github_setup_escaped=$(echo "$github_setup_steps" | jq -c .) | |
| ado_setup_escaped=$(echo "$ado_setup_tasks" | jq -c .) | |
| matrix_items+=("{\"ci_system\":\"$ci_system\",\"platform\":\"$platform\",\"version\":\"$version\",\"product\":\"$product\",\"component\":\"$component\",\"source_dir\":\"$source_dir\",\"os\":\"$os\",\"github_setup_steps\":$github_setup_escaped,\"ado_setup_tasks\":$ado_setup_escaped,\"gitlab_image\":\"$gitlab_image\"}") | |
| else | |
| echo " Filtering: $ci_system + $os (not supported)" | |
| filtered_count=$((filtered_count + 1)) | |
| filtered_items="${filtered_items}- $ci_system + $os (not supported)\n" | |
| fi | |
| done | |
| done | |
| done | |
| fi | |
| done | |
| done | |
| echo "Total matrix items: ${#matrix_items[@]}" | |
| # Create JSON array | |
| if [ ${#matrix_items[@]} -eq 0 ]; then | |
| matrix_json="[]" | |
| else | |
| matrix_json=$(printf '%s\n' "${matrix_items[@]}" | jq -s -c .) | |
| fi | |
| echo "Matrix JSON: $matrix_json" | |
| echo "matrix<<EOF" >> $GITHUB_OUTPUT | |
| echo "$matrix_json" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| # Create summary | |
| summary="Created ${#matrix_items[@]} test combinations." | |
| if [ $filtered_count -gt 0 ]; then | |
| summary="$summary Filtered out $filtered_count incompatible combinations:\n$filtered_items" | |
| fi | |
| echo "summary<<EOF" >> $GITHUB_OUTPUT | |
| echo -e "$summary" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| - name: Display matrix summary | |
| run: | | |
| echo "## Test Matrix Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "${{ steps.create-matrix.outputs.summary }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Test Combinations" >> $GITHUB_STEP_SUMMARY | |
| echo '${{ steps.create-matrix.outputs.matrix }}' | jq -r '.[] | "- \(.ci_system) / \(.product) / \(.component) / \(.source_dir) / \(.os)"' >> $GITHUB_STEP_SUMMARY | |
| # Sync external CI repositories (GitLab, ADO) once before running tests | |
| sync-external-repos: | |
| name: Sync ${{ matrix.platform }} Repository | |
| needs: prepare-matrix | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| platform: [gitlab, ado] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Check if platform is needed | |
| id: check | |
| run: | | |
| if echo '${{ needs.prepare-matrix.outputs.matrix }}' | grep -q '"platform":"${{ matrix.platform }}"'; then | |
| echo "needed=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "needed=false" >> $GITHUB_OUTPUT | |
| echo "Skipping ${{ matrix.platform }} - not in test matrix" | |
| fi | |
| - name: Sync repository | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| set -e | |
| # Read platform config | |
| PLATFORM="${{ matrix.platform }}" | |
| REPO_URL=$(jq -r '.repoUrl' "ci/$PLATFORM/config.json") | |
| PIPELINE_FILE=$([ "$PLATFORM" = "gitlab" ] && echo ".gitlab-ci.yml" || echo "azure-pipelines.yml") | |
| echo "Syncing $PLATFORM repository: $REPO_URL" | |
| # Set up authentication based on platform | |
| if [ "$PLATFORM" = "gitlab" ]; then | |
| AUTH_URL="https://oauth2:$GITLAB_TOKEN@${REPO_URL#https://}" | |
| else | |
| AUTH_URL="https://anything:$ADO_PAT@${REPO_URL#https://}" | |
| fi | |
| # Clone repo to temp directory | |
| temp_dir=$(mktemp -d) | |
| git clone "$AUTH_URL" "$temp_dir" | |
| cd "$temp_dir" | |
| # Check if main branch exists | |
| echo "Checking for pipeline or source changes..." | |
| if git ls-remote --heads origin main | grep -q main; then | |
| git fetch origin main | |
| else | |
| echo "Repository is empty or main branch doesn't exist yet" | |
| git checkout -b main | |
| fi | |
| # Copy all CI files for this platform to root | |
| echo "Syncing ci/$PLATFORM files to root..." | |
| cp -r "$GITHUB_WORKSPACE/ci/$PLATFORM/"* . | |
| cp -r "$GITHUB_WORKSPACE/ci/$PLATFORM/".* . 2>/dev/null || true | |
| # Copy sources | |
| rm -rf sources | |
| cp -r "$GITHUB_WORKSPACE/sources" . | |
| # Check if anything changed (new files or modified files) | |
| if ! git diff --quiet || [ -n "$(git ls-files --others --exclude-standard)" ]; then | |
| echo "Changes detected, pushing to $PLATFORM..." | |
| git config user.name "fcli-ci-test" | |
| git config user.email "fcli-ci-test@fortify.github.io" | |
| git add . | |
| git commit -m "CI Test Run ${{ github.run_id }} - Sync from fcli-ci-test@${{ github.sha }}" | |
| git push --force origin HEAD:main | |
| else | |
| echo "No changes detected, skipping git push" | |
| fi | |
| cd "$GITHUB_WORKSPACE" | |
| rm -rf "$temp_dir" | |
| echo "✓ Repository synced successfully" | |
| env: | |
| GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} | |
| ADO_PAT: ${{ secrets.ADO_PAT }} | |
| - name: Sync GitLab secrets and variables | |
| if: steps.check.outputs.needed == 'true' && matrix.platform == 'gitlab' | |
| run: | | |
| set -e | |
| GITLAB_REPO_URL=$(jq -r '.repoUrl' ci/gitlab/config.json) | |
| GITLAB_PROJECT_ID=$(echo "$GITLAB_REPO_URL" | sed 's|https://gitlab.com/||' | sed 's|/|%2F|g') | |
| echo "Syncing variables to GitLab CI/CD variables..." | |
| # Sync regular variables (non-masked) | |
| for var_name in $(jq -r '.variableNames[]' ci/gitlab/config.json); do | |
| var_value="${!var_name}" | |
| if [ -n "$var_value" ]; then | |
| echo "Updating variable: $var_name" | |
| curl -s -X DELETE \ | |
| "https://gitlab.com/api/v4/projects/$GITLAB_PROJECT_ID/variables/$var_name" \ | |
| -H "PRIVATE-TOKEN: $GITLAB_TOKEN" 2>/dev/null || true | |
| curl -f -X POST \ | |
| "https://gitlab.com/api/v4/projects/$GITLAB_PROJECT_ID/variables" \ | |
| -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"key\": \"$var_name\", | |
| \"value\": \"$var_value\", | |
| \"masked\": false, | |
| \"protected\": false | |
| }" | |
| fi | |
| done | |
| echo "Syncing secrets to GitLab CI/CD variables..." | |
| # Sync secrets (masked) | |
| for secret_name in $(jq -r '.secretNames[]' ci/gitlab/config.json); do | |
| secret_value="${!secret_name}" | |
| if [ -n "$secret_value" ]; then | |
| echo "Updating variable: $secret_name" | |
| curl -s -X DELETE \ | |
| "https://gitlab.com/api/v4/projects/$GITLAB_PROJECT_ID/variables/$secret_name" \ | |
| -H "PRIVATE-TOKEN: $GITLAB_TOKEN" 2>/dev/null || true | |
| curl -f -X POST \ | |
| "https://gitlab.com/api/v4/projects/$GITLAB_PROJECT_ID/variables" \ | |
| -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"key\": \"$secret_name\", | |
| \"value\": \"$secret_value\", | |
| \"masked\": true, | |
| \"protected\": false | |
| }" | |
| fi | |
| done | |
| echo "✓ GitLab secrets and variables synced successfully" | |
| env: | |
| GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} | |
| FCLI_FT_FOD_URL: ${{ vars.FCLI_FT_FOD_URL }} | |
| FCLI_FT_SSC_URL: ${{ vars.FCLI_FT_SSC_URL }} | |
| FCLI_FT_FOD_CLIENT_ID: ${{ secrets.FCLI_FT_FOD_CLIENT_ID }} | |
| FCLI_FT_FOD_CLIENT_SECRET: ${{ secrets.FCLI_FT_FOD_CLIENT_SECRET }} | |
| FCLI_FT_SSC_TOKEN: ${{ secrets.FCLI_FT_SSC_TOKEN }} | |
| FCLI_FT_SC_SAST_TOKEN: ${{ secrets.FCLI_FT_SC_SAST_TOKEN }} | |
| - name: Sync ADO secrets | |
| if: steps.check.outputs.needed == 'true' && matrix.platform == 'ado' | |
| run: | | |
| set -e | |
| # Parse ADO org and project from config | |
| ADO_REPO_URL=$(jq -r '.repoUrl' ci/ado/config.json) | |
| ADO_ORG=$(echo "$ADO_REPO_URL" | sed 's|https://dev.azure.com/||' | cut -d'/' -f1) | |
| ADO_PROJ=$(echo "$ADO_REPO_URL" | sed 's|https://dev.azure.com/||' | cut -d'/' -f2) | |
| echo "Syncing secrets to Azure DevOps Variable Group ($ADO_ORG/$ADO_PROJ)..." | |
| # Create auth header | |
| AUTH_HEADER="Authorization: Basic $(printf ":%s" "$ADO_PAT" | base64 -w 0)" | |
| # Get existing variable group | |
| echo "Fetching fcli-ci-test variable group..." | |
| vg_response=$(curl -s \ | |
| "https://dev.azure.com/$ADO_ORG/$ADO_PROJ/_apis/distributedtask/variablegroups?api-version=7.1-preview.2" \ | |
| -H "$AUTH_HEADER") | |
| vg_id=$(echo "$vg_response" | jq -r '.value[] | select(.name == "fcli-ci-test") | .id') | |
| if [ -z "$vg_id" ] || [ "$vg_id" = "null" ]; then | |
| echo "Error: Variable group 'fcli-ci-test' not found. Please create it manually in Azure DevOps." | |
| exit 1 | |
| fi | |
| echo "Found variable group ID: $vg_id" | |
| # Get full variable group details to preserve project references | |
| vg_details=$(curl -s \ | |
| "https://dev.azure.com/$ADO_ORG/$ADO_PROJ/_apis/distributedtask/variablegroups/$vg_id?api-version=7.1-preview.2" \ | |
| -H "$AUTH_HEADER") | |
| # Extract existing project references | |
| project_refs=$(echo "$vg_details" | jq -c '.variableGroupProjectReferences // []') | |
| # Build variables JSON | |
| variables_json='{' | |
| first=true | |
| # Add regular variables (URLs) | |
| for var_name in $(jq -r '.variableNames[]' ci/ado/config.json); do | |
| var_value="${!var_name}" | |
| if [ -n "$var_value" ]; then | |
| if [ "$first" = false ]; then | |
| variables_json="$variables_json," | |
| fi | |
| variables_json="$variables_json\"$var_name\":{\"value\":\"$var_value\",\"isSecret\":false}" | |
| first=false | |
| fi | |
| done | |
| # Add secrets | |
| for secret_name in $(jq -r '.secretNames[]' ci/ado/config.json); do | |
| secret_value="${!secret_name}" | |
| if [ -n "$secret_value" ]; then | |
| if [ "$first" = false ]; then | |
| variables_json="$variables_json," | |
| fi | |
| variables_json="$variables_json\"$secret_name\":{\"value\":\"$secret_value\",\"isSecret\":true}" | |
| first=false | |
| fi | |
| done | |
| variables_json="$variables_json}" | |
| # Update variable group | |
| echo "Updating variable group with secrets..." | |
| update_response=$(curl -s -X PUT \ | |
| "https://dev.azure.com/$ADO_ORG/$ADO_PROJ/_apis/distributedtask/variablegroups/$vg_id?api-version=7.1-preview.2" \ | |
| -H "$AUTH_HEADER" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"id\": $vg_id, | |
| \"name\": \"fcli-ci-test\", | |
| \"type\": \"Vsts\", | |
| \"variables\": $variables_json, | |
| \"variableGroupProjectReferences\": $project_refs | |
| }") | |
| # Check if update was successful | |
| updated_id=$(echo "$update_response" | jq -r '.id // empty') | |
| if [ -z "$updated_id" ]; then | |
| echo "Error: Failed to update variable group" | |
| echo "Response: $update_response" | |
| exit 1 | |
| fi | |
| echo "✓ Azure DevOps secrets synced successfully" | |
| env: | |
| ADO_PAT: ${{ secrets.ADO_PAT }} | |
| FCLI_FT_FOD_URL: ${{ vars.FCLI_FT_FOD_URL }} | |
| FCLI_FT_FOD_CLIENT_ID: ${{ secrets.FCLI_FT_FOD_CLIENT_ID }} | |
| FCLI_FT_FOD_CLIENT_SECRET: ${{ secrets.FCLI_FT_FOD_CLIENT_SECRET }} | |
| FCLI_FT_SSC_URL: ${{ vars.FCLI_FT_SSC_URL }} | |
| FCLI_FT_SSC_TOKEN: ${{ secrets.FCLI_FT_SSC_TOKEN }} | |
| FCLI_FT_SC_SAST_TOKEN: ${{ secrets.FCLI_FT_SC_SAST_TOKEN }} | |
| test: | |
| name: Test fcli ${{ github.event.inputs.fcli_version }} / ${{ matrix.ci_system }} / ${{ matrix.product }} / ${{ matrix.component }} / ${{ matrix.source_dir }} / ${{ matrix.os }} | |
| needs: [prepare-matrix, sync-external-repos] | |
| runs-on: ubuntu-latest | |
| if: | | |
| always() && | |
| needs.prepare-matrix.outputs.matrix != '[]' && | |
| (needs.sync-external-repos.result == 'success' || needs.sync-external-repos.result == 'skipped') | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.prepare-matrix.outputs.matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Read CI config | |
| id: config | |
| run: | | |
| repo_url=$(jq -r '.repoUrl' "ci/${{ matrix.platform }}/config.json") | |
| echo "repo_url=$repo_url" >> $GITHUB_OUTPUT | |
| # Check if this is the same repo | |
| if [ "$repo_url" = "https://github.com/${{ github.repository }}" ]; then | |
| echo "is_local=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "is_local=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Construct fcli URL | |
| id: fcli-url | |
| run: | | |
| os="${{ matrix.os }}" | |
| version="${{ github.event.inputs.fcli_version }}" | |
| if [ "$os" = "windows" ]; then | |
| artifact="fcli-windows.zip" | |
| elif [ "$os" = "mac" ]; then | |
| artifact="fcli-mac.tgz" | |
| else | |
| artifact="fcli-linux.tgz" | |
| fi | |
| url="https://github.com/fortify/fcli/releases/download/$version/$artifact" | |
| echo "url=$url" >> $GITHUB_OUTPUT | |
| - name: Generate Fortify release/appversion name | |
| id: fortify-release | |
| run: | | |
| # Generate: fcli/ci-test/<platform>/<version>/<os>:<source_dir> | |
| # Skip source_dir if component is setup (uses 'none') | |
| if [ "${{ matrix.component }}" = "setup" ]; then | |
| release="fcli/ci-test/${{ matrix.platform }}/${{ matrix.version }}/${{ matrix.os }}" | |
| else | |
| release="fcli/ci-test/${{ matrix.platform }}/${{ matrix.version }}/${{ matrix.os }}:${{ matrix.source_dir }}" | |
| fi | |
| echo "name=$release" >> $GITHUB_OUTPUT | |
| - name: Generate pipeline name | |
| id: pipeline-name | |
| run: | | |
| # Generate consistent name: fcli version / version / component / product / os / source_dir | |
| # For setup component, use 'none' for product | |
| if [ "${{ matrix.component }}" = "setup" ]; then | |
| name="fcli ${{ github.event.inputs.fcli_version }} / ${{ matrix.version }} / ${{ matrix.component }} / none / ${{ matrix.os }} / ${{ matrix.source_dir }}" | |
| else | |
| name="fcli ${{ github.event.inputs.fcli_version }} / ${{ matrix.version }} / ${{ matrix.component }} / ${{ matrix.product }} / ${{ matrix.os }} / ${{ matrix.source_dir }}" | |
| fi | |
| echo "name=$name" >> $GITHUB_OUTPUT | |
| - name: Trigger GitHub test | |
| id: github-trigger | |
| if: matrix.platform == 'github' | |
| run: | | |
| # Trigger workflow and capture the time | |
| trigger_time=$(date -u +%s) | |
| # Convert setup steps JSON to string for workflow input | |
| setup_steps='${{ toJSON(matrix.github_setup_steps) }}' | |
| gh workflow run test-pipeline.yml \ | |
| --ref ${{ github.ref }} \ | |
| -f pipeline_name="${{ steps.pipeline-name.outputs.name }}" \ | |
| -f version=${{ matrix.version }} \ | |
| -f fcli_url=${{ steps.fcli-url.outputs.url }} \ | |
| -f product=${{ matrix.product }} \ | |
| -f component=${{ matrix.component }} \ | |
| -f source_dir=sources/${{ matrix.source_dir }} \ | |
| -f os=${{ matrix.os }} \ | |
| -f fortify_release=${{ steps.fortify-release.outputs.name }} \ | |
| -f setup_steps="$setup_steps" | |
| echo "Triggered GitHub workflow, waiting for run to start..." | |
| sleep 10 | |
| # Find the most recent run that started after our trigger | |
| run_id=$(gh run list \ | |
| --workflow=test-pipeline.yml \ | |
| --branch=${{ github.ref_name }} \ | |
| --limit=10 \ | |
| --json databaseId,createdAt,status \ | |
| --jq "[.[] | select(.createdAt | fromdateiso8601 > $trigger_time)] | .[0].databaseId") | |
| if [ -z "$run_id" ]; then | |
| echo "Error: Could not find triggered workflow run" | |
| exit 1 | |
| fi | |
| run_url="https://github.com/${{ github.repository }}/actions/runs/$run_id" | |
| echo "Found workflow run ID: $run_id" | |
| echo "Workflow URL: $run_url" | |
| echo "run_url=$run_url" >> $GITHUB_OUTPUT | |
| echo "Polling for completion (timeout: 30 minutes)..." | |
| # Poll for completion | |
| timeout=1800 # 30 minutes | |
| elapsed=0 | |
| interval=30 | |
| while [ $elapsed -lt $timeout ]; do | |
| status=$(gh run view $run_id --json status,conclusion --jq '.status + ":" + .conclusion') | |
| run_status=$(echo $status | cut -d: -f1) | |
| run_conclusion=$(echo $status | cut -d: -f2) | |
| echo "[$elapsed s] Status: $run_status, Conclusion: $run_conclusion" | |
| if [ "$run_status" = "completed" ]; then | |
| if [ "$run_conclusion" = "success" ]; then | |
| echo "✓ Test passed!" | |
| exit 0 | |
| else | |
| echo "✗ Test failed with conclusion: $run_conclusion" | |
| gh run view $run_id --log-failed | |
| exit 1 | |
| fi | |
| fi | |
| sleep $interval | |
| elapsed=$((elapsed + interval)) | |
| done | |
| echo "✗ Test timed out after $timeout seconds" | |
| exit 1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Trigger GitLab test | |
| id: gitlab-trigger | |
| if: matrix.platform == 'gitlab' | |
| run: | | |
| set -e | |
| # Read GitLab config | |
| GITLAB_REPO_URL=$(jq -r '.repoUrl' ci/gitlab/config.json) | |
| GITLAB_PROJECT_ID=$(echo "$GITLAB_REPO_URL" | sed 's|https://gitlab.com/||' | sed 's|/|%2F|g') | |
| echo "Triggering GitLab pipeline: ${{ steps.pipeline-name.outputs.name }}" | |
| pipeline_response=$(curl -f -X POST \ | |
| "https://gitlab.com/api/v4/projects/$GITLAB_PROJECT_ID/trigger/pipeline" \ | |
| -F "token=$GITLAB_TRIGGER_TOKEN" \ | |
| -F "ref=main" \ | |
| -F "variables[PIPELINE_NAME]=${{ steps.pipeline-name.outputs.name }}" \ | |
| -F "variables[VERSION]=${{ matrix.version }}" \ | |
| -F "variables[FCLI_URL]=${{ steps.fcli-url.outputs.url }}" \ | |
| -F "variables[PRODUCT]=${{ matrix.product }}" \ | |
| -F "variables[COMPONENT]=${{ matrix.component }}" \ | |
| -F "variables[SOURCE_DIR]=sources/${{ matrix.source_dir }}" \ | |
| -F "variables[OS]=${{ matrix.os }}" \ | |
| -F "variables[FORTIFY_RELEASE]=${{ steps.fortify-release.outputs.name }}" \ | |
| -F "variables[GITLAB_IMAGE]=${{ matrix.gitlab_image }}") | |
| pipeline_id=$(echo "$pipeline_response" | jq -r '.id') | |
| pipeline_url="$GITLAB_REPO_URL/-/pipelines/$pipeline_id" | |
| echo "Pipeline ID: $pipeline_id" | |
| echo "Pipeline URL: $pipeline_url" | |
| echo "pipeline_url=$pipeline_url" >> $GITHUB_OUTPUT | |
| echo "Polling for completion (timeout: 30 minutes)..." | |
| timeout=1800 | |
| elapsed=0 | |
| interval=30 | |
| while [ $elapsed -lt $timeout ]; do | |
| pipeline_status=$(curl -f -s \ | |
| "https://gitlab.com/api/v4/projects/$GITLAB_PROJECT_ID/pipelines/$pipeline_id" \ | |
| -H "PRIVATE-TOKEN: $GITLAB_TOKEN" | jq -r '.status') | |
| echo "[$elapsed s] Status: $pipeline_status" | |
| case "$pipeline_status" in | |
| success) | |
| echo "✓ Test passed!" | |
| exit 0 | |
| ;; | |
| failed|canceled|skipped) | |
| echo "✗ Test failed with status: $pipeline_status" | |
| curl -f -s \ | |
| "https://gitlab.com/api/v4/projects/$GITLAB_PROJECT_ID/pipelines/$pipeline_id/jobs" \ | |
| -H "PRIVATE-TOKEN: $GITLAB_TOKEN" | jq '.[] | select(.status == "failed") | {name, stage, status}' | |
| exit 1 | |
| ;; | |
| running|pending|created|waiting_for_resource|preparing) | |
| # Continue polling | |
| ;; | |
| *) | |
| echo "Unknown status: $pipeline_status" | |
| exit 1 | |
| ;; | |
| esac | |
| sleep $interval | |
| elapsed=$((elapsed + interval)) | |
| done | |
| echo "✗ Test timed out after $timeout seconds" | |
| exit 1 | |
| env: | |
| GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} | |
| GITLAB_TRIGGER_TOKEN: ${{ secrets.GITLAB_TRIGGER_TOKEN }} | |
| - name: Trigger ADO test | |
| id: ado-trigger | |
| if: matrix.platform == 'ado' | |
| run: | | |
| set -e | |
| # Parse ADO org and project from config | |
| ADO_REPO_URL=$(jq -r '.repoUrl' ci/${{ matrix.platform }}/config.json) | |
| ADO_ORG=$(echo "$ADO_REPO_URL" | sed 's|https://dev.azure.com/||' | cut -d'/' -f1) | |
| ADO_PROJ=$(echo "$ADO_REPO_URL" | sed 's|https://dev.azure.com/||' | cut -d'/' -f2) | |
| echo "Triggering Azure DevOps pipeline ($ADO_ORG/$ADO_PROJ)..." | |
| # Create auth header | |
| AUTH_HEADER="Authorization: Basic $(printf ":%s" "${{ secrets.ADO_PAT }}" | base64 -w 0)" | |
| # Convert ADO setup tasks to JSON string for pipeline variable | |
| ado_setup_tasks='${{ toJSON(matrix.ado_setup_tasks) }}' | |
| build_response=$(curl -s -X POST \ | |
| "https://dev.azure.com/$ADO_ORG/$ADO_PROJ/_apis/pipelines/1/runs?api-version=7.1-preview.1" \ | |
| -H "$AUTH_HEADER" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"resources\": { | |
| \"repositories\": { | |
| \"self\": { | |
| \"refName\": \"refs/heads/main\" | |
| } | |
| } | |
| }, | |
| \"templateParameters\": { | |
| \"PIPELINE_NAME\": \"${{ steps.pipeline-name.outputs.name }}\", | |
| \"VERSION\": \"${{ matrix.version }}\", | |
| \"FCLI_URL\": \"${{ steps.fcli-url.outputs.url }}\", | |
| \"PRODUCT\": \"${{ matrix.product }}\", | |
| \"COMPONENT\": \"${{ matrix.component }}\", | |
| \"SOURCE_DIR\": \"sources/${{ matrix.source_dir }}\", | |
| \"OS\": \"${{ matrix.os }}\", | |
| \"FORTIFY_RELEASE\": \"${{ steps.fortify-release.outputs.name }}\", | |
| \"SETUP_TASKS\": $ado_setup_tasks | |
| } | |
| }") | |
| # Check if response is valid JSON | |
| if ! echo "$build_response" | jq -e . >/dev/null 2>&1; then | |
| echo "Error: API returned invalid JSON response:" | |
| echo "$build_response" | |
| exit 1 | |
| fi | |
| build_id=$(echo "$build_response" | jq -r '.id') | |
| if [ "$build_id" = "null" ] || [ -z "$build_id" ]; then | |
| echo "Error: Failed to get build ID from response:" | |
| echo "$build_response" | jq . | |
| exit 1 | |
| fi | |
| build_url="https://dev.azure.com/$ADO_ORG/$ADO_PROJ/_build/results?buildId=$build_id" | |
| echo "Build ID: $build_id" | |
| echo "Build URL: $build_url" | |
| echo "build_url=$build_url" >> $GITHUB_OUTPUT | |
| echo "Polling for completion (timeout: 30 minutes)..." | |
| timeout=1800 | |
| elapsed=0 | |
| interval=30 | |
| while [ $elapsed -lt $timeout ]; do | |
| build_info=$(curl -s \ | |
| "https://dev.azure.com/$ADO_ORG/$ADO_PROJ/_apis/build/builds/$build_id?api-version=7.1-preview.7" \ | |
| -H "$AUTH_HEADER") | |
| build_status=$(echo "$build_info" | jq -r '.status') | |
| build_result=$(echo "$build_info" | jq -r '.result') | |
| echo "[$elapsed s] Status: $build_status, Result: $build_result" | |
| if [ "$build_status" = "completed" ]; then | |
| if [ "$build_result" = "succeeded" ]; then | |
| echo "✓ Test passed!" | |
| exit 0 | |
| else | |
| echo "✗ Test failed with result: $build_result" | |
| # Get failed job logs | |
| curl -s \ | |
| "https://dev.azure.com/$ADO_ORG/$ADO_PROJ/_apis/build/builds/$build_id/timeline?api-version=7.1-preview.2" \ | |
| -H "$AUTH_HEADER" | \ | |
| jq '.records[] | select(.result == "failed") | {name, type, result}' | |
| exit 1 | |
| fi | |
| fi | |
| sleep $interval | |
| elapsed=$((elapsed + interval)) | |
| done | |
| echo "✗ Test timed out after $timeout seconds" | |
| exit 1 | |
| env: | |
| ADO_PAT: ${{ secrets.ADO_PAT }} | |
| - name: Store pipeline info | |
| if: always() | |
| run: | | |
| mkdir -p pipeline-info | |
| platform="${{ matrix.platform }}" | |
| ci_system="${{ matrix.ci_system }}" | |
| product="${{ matrix.product }}" | |
| component="${{ matrix.component }}" | |
| source_dir="${{ matrix.source_dir }}" | |
| os="${{ matrix.os }}" | |
| # Get the appropriate URL based on platform | |
| if [ "$platform" = "github" ]; then | |
| url="${{ steps.github-trigger.outputs.run_url }}" | |
| elif [ "$platform" = "gitlab" ]; then | |
| url="${{ steps.gitlab-trigger.outputs.pipeline_url }}" | |
| elif [ "$platform" = "ado" ]; then | |
| url="${{ steps.ado-trigger.outputs.build_url }}" | |
| fi | |
| # Determine test result | |
| if [ "${{ job.status }}" = "success" ]; then | |
| status="✅ PASS" | |
| else | |
| status="❌ FAIL" | |
| fi | |
| # Create a unique filename for this test combination (sanitize special chars) | |
| safe_ci_system=$(echo "$ci_system" | tr ':/' '__') | |
| filename="${safe_ci_system}_${product}_${component}_${source_dir}_${os}.txt" | |
| echo "$status|$url" > "pipeline-info/$filename" | |
| - name: Upload pipeline info | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: pipeline-info-${{ matrix.platform }}-${{ matrix.version }}-${{ matrix.product }}-${{ matrix.component }}-${{ matrix.source_dir }}-${{ matrix.os }} | |
| path: pipeline-info/ | |
| retention-days: 1 | |
| summary: | |
| name: Test Summary | |
| needs: [prepare-matrix, test] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: Download all pipeline info | |
| if: needs.test.result != 'skipped' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: pipeline-info-* | |
| path: pipeline-info | |
| merge-multiple: true | |
| continue-on-error: true | |
| - name: Create summary | |
| run: | | |
| echo "## Integration Test Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Overall Status:** ${{ needs.test.result }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Add pipeline links if available | |
| if [ -d "pipeline-info" ] && [ "$(ls -A pipeline-info 2>/dev/null)" ]; then | |
| echo "### Test Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Count pass/fail | |
| pass_count=0 | |
| fail_count=0 | |
| for file in pipeline-info/*.txt; do | |
| if [ -f "$file" ]; then | |
| filename=$(basename "$file" .txt) | |
| content=$(cat "$file") | |
| # Parse status and URL | |
| status=$(echo "$content" | cut -d'|' -f1) | |
| url=$(echo "$content" | cut -d'|' -f2) | |
| # Parse the filename back to readable format | |
| readable_name=$(echo "$filename" | sed 's/_/ / g' | sed 's/github:/GitHub: /; s/gitlab:/GitLab: /; s/ado:/ADO: /') | |
| echo "- $status [$readable_name]($url)" >> $GITHUB_STEP_SUMMARY | |
| # Count results | |
| if echo "$status" | grep -q "PASS"; then | |
| pass_count=$((pass_count + 1)) | |
| else | |
| fail_count=$((fail_count + 1)) | |
| fi | |
| fi | |
| done | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Summary:** $pass_count passed, $fail_count failed" >> $GITHUB_STEP_SUMMARY | |
| fi |