Files
awx/.github/workflows/spec-sync-on-merge.yml
Rodrigo Toshiaki Horie 0dfc168a5f fix: pipe blob content via stdin to avoid ARG_MAX limit in spec sync (#16510)
fix: use GPG-signed commits in spec sync workflow

Switch from unsigned GitHub API commits to GPG-signed git commits
using the aap-api-bot GPG key (OPENAPI_SPEC_SYNC_GPG_PRIVATE_KEY).

The aap-openapi-specs repo requires signed commits via org ruleset.
The previous API-based approach didn't sign commits because GitHub
only auto-signs API commits for GitHub App tokens, not user PATs.

This matches the pattern used by EDA and Gateway teams for their
spec sync workflows.

Also fixes template injection risk by using env vars instead of
direct ${{ }} expansion in shell context.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-18 15:56:03 -03:00

207 lines
7.5 KiB
YAML

# Sync OpenAPI Spec on Merge
#
# This workflow runs when code is merged to the devel branch.
# It runs the dev environment to generate the OpenAPI spec, then syncs it to
# the central spec repository.
#
# FLOW: PR merged → push to branch → dev environment runs → spec synced to central repo
#
# NOTE: This is an inlined version for testing with private forks.
# Production version will use a reusable workflow from the org repos.
name: Sync OpenAPI Spec on Merge
env:
LC_ALL: "C.UTF-8"
DEV_DOCKER_OWNER: ${{ github.repository_owner }}
on:
push:
branches:
- devel
- 'stable-2.[6-9]'
- 'stable-2.[1-9][0-9]'
workflow_dispatch: # Allow manual triggering for testing
jobs:
sync-openapi-spec:
if: |
github.event_name == 'workflow_dispatch' ||
(github.repository == 'ansible/awx' && (github.ref_name == 'devel' || startsWith(github.ref_name, 'feature_'))) ||
(github.repository == 'ansible/tower' && (startsWith(github.ref_name, 'stable-') || startsWith(github.ref_name, 'release_')))
name: Sync OpenAPI spec to central repo
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Checkout Controller repository
uses: actions/checkout@v4
with:
show-progress: false
- name: Build awx_devel image to use for schema gen
uses: ./.github/actions/awx_devel_image
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
private-github-key: ${{ secrets.PRIVATE_GITHUB_KEY }}
- name: Generate API Schema
env:
REF_NAME: ${{ github.ref_name }}
BASE_REF: ${{ github.base_ref }}
run: |
DEV_DOCKER_TAG_BASE=ghcr.io/${OWNER_LC} \
COMPOSE_TAG=${BASE_REF:-${REF_NAME}} \
docker run -u $(id -u) --rm -v ${{ github.workspace }}:/awx_devel/:Z \
--workdir=/awx_devel `make print-DEVEL_IMAGE_NAME` /start_tests.sh genschema
- name: Verify spec file exists
run: |
SPEC_FILE="./schema.json"
if [ ! -f "$SPEC_FILE" ]; then
echo "❌ Spec file not found at $SPEC_FILE"
echo "Contents of workspace:"
ls -la .
exit 1
fi
echo "✅ Found spec file at $SPEC_FILE"
- name: Checkout spec repo
id: checkout_spec_repo
continue-on-error: true
uses: actions/checkout@v4
with:
repository: ansible-automation-platform/aap-openapi-specs
ref: ${{ github.ref_name }}
path: spec-repo
token: ${{ secrets.OPENAPI_SPEC_SYNC_TOKEN }}
- name: Fail if branch doesn't exist
if: steps.checkout_spec_repo.outcome == 'failure'
env:
REF_NAME: ${{ github.ref_name }}
run: |
echo "##[error]❌ Branch '${REF_NAME}' does not exist in the central spec repository."
echo "##[error]Expected branch: ${REF_NAME}"
echo "##[error]This branch must be created in the spec repo before specs can be synced."
exit 1
- name: Compare specs
id: compare
run: |
COMPONENT_SPEC="./schema.json"
SPEC_REPO_FILE="spec-repo/controller.json"
# Check if spec file exists in spec repo
if [ ! -f "$SPEC_REPO_FILE" ]; then
echo "Spec file doesn't exist in spec repo - will create new file"
echo "has_diff=true" >> $GITHUB_OUTPUT
echo "is_new_file=true" >> $GITHUB_OUTPUT
else
# Compare files
if diff -q "$COMPONENT_SPEC" "$SPEC_REPO_FILE" > /dev/null; then
echo "✅ No differences found - specs are identical"
echo "has_diff=false" >> $GITHUB_OUTPUT
else
echo "📝 Differences found - spec has changed"
echo "has_diff=true" >> $GITHUB_OUTPUT
echo "is_new_file=false" >> $GITHUB_OUTPUT
fi
fi
- name: Update spec file
if: steps.compare.outputs.has_diff == 'true'
run: |
cp "./schema.json" "spec-repo/controller.json"
echo "✅ Updated spec-repo/controller.json"
- name: Create PR in spec repo
if: steps.compare.outputs.has_diff == 'true'
working-directory: spec-repo
env:
GH_TOKEN: ${{ secrets.OPENAPI_SPEC_SYNC_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.OPENAPI_SPEC_SYNC_GPG_PRIVATE_KEY }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
SPEC_REPO: ansible-automation-platform/aap-openapi-specs
REF_NAME: ${{ github.ref_name }}
GITHUB_SHA_FULL: ${{ github.sha }}
GITHUB_REPO: ${{ github.repository }}
IS_NEW_FILE: ${{ steps.compare.outputs.is_new_file }}
run: |
# Import GPG key and configure git for signed commits
echo "$GPG_PRIVATE_KEY" | gpg --batch --import 2>/dev/null
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format long 2>/dev/null | grep sec | head -1 | awk '{print $2}' | cut -d'/' -f2)
if [ -z "$GPG_KEY_ID" ]; then
echo "❌ Failed to import GPG key or extract key ID"
exit 1
fi
git config user.name "aap-api-bot"
git config user.email "aap-api-bot@redhat.com"
git config commit.gpgsign true
git config user.signingkey "$GPG_KEY_ID"
# Configure git to use the token for push
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${SPEC_REPO}.git"
SHORT_SHA="${GITHUB_SHA_FULL:0:7}"
BRANCH_NAME="update-Controller-${REF_NAME}-${SHORT_SHA}"
git checkout -b "$BRANCH_NAME"
# Add and commit changes
git add "controller.json"
if [ "${IS_NEW_FILE}" == "true" ]; then
COMMIT_MSG="Add Controller OpenAPI spec for ${REF_NAME}"
else
COMMIT_MSG="Update Controller OpenAPI spec for ${REF_NAME}"
fi
git commit -m "${COMMIT_MSG}
Synced from ${GITHUB_REPO}@${GITHUB_SHA_FULL}
Source branch: ${REF_NAME}
Co-Authored-By: github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
# Push branch
git push origin "$BRANCH_NAME"
# Create PR
PR_TITLE="[${REF_NAME}] Update Controller spec from merged commit"
PR_BODY="## Summary
Automated OpenAPI spec sync from component repository merge.
**Source:** ${GITHUB_REPO}@${GITHUB_SHA_FULL}
**Branch:** \`${REF_NAME}\`
**Component:** \`Controller\`
**Spec File:** \`controller.json\`
## Changes
$(if [ "${IS_NEW_FILE}" == "true" ]; then echo "- 🆕 New spec file created"; else echo "- 📝 Spec file updated with latest changes"; fi)
## Source Commit
\`\`\`
${COMMIT_MESSAGE}
\`\`\`
---
🤖 This PR was automatically generated by the OpenAPI spec sync workflow."
gh pr create \
--repo "${SPEC_REPO}" \
--title "$PR_TITLE" \
--body "$PR_BODY" \
--base "${REF_NAME}" \
--head "$BRANCH_NAME"
echo "✅ Created PR in spec repo"
- name: Report results
if: always()
env:
HAS_DIFF: ${{ steps.compare.outputs.has_diff }}
run: |
if [ "${HAS_DIFF}" == "true" ]; then
echo "📝 Spec sync completed - PR created in spec repo"
else
echo "✅ Spec sync completed - no changes needed"
fi