Skip to content

Run Integration Tests #44

Run Integration Tests

Run Integration Tests #44

Workflow file for this run

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