diff --git a/.github/workflows/pypi_publish.yml b/.github/workflows/pypi_publish.yml index ba842c8d8..e87f009eb 100644 --- a/.github/workflows/pypi_publish.yml +++ b/.github/workflows/pypi_publish.yml @@ -81,7 +81,9 @@ jobs: uses: pypa/gh-action-pypi-publish@v1.13.0 # Prod cloud-mcp is bumped via Dependabot on airbytehq/airbyte-ops-mcp, not here. - # A PyPI publish only refreshes the cloud-mcp preview against the new version. + # A PyPI publish dispatches the just-released version to airbyte-ops-mcp, whose + # deploy-mcp-command.yml opens/refreshes a deterministic `airbyte==` + # bump PR and deploys the cloud-mcp preview off it. deploy_cloud_mcp_preview: name: Deploy Cloud MCP (Preview) needs: publish_to_pypi @@ -89,13 +91,65 @@ jobs: runs-on: ubuntu-latest permissions: {} steps: + # Read the raw ref/input via env (not inline `${{ }}` in the script) to + # avoid shell injection, then validate it is a plain PEP 440-ish version + # before it reaches the dispatch payload -- this both fails fast on a + # malformed tag/override and guarantees the value is safe to interpolate + # into the client-payload JSON string below (no quotes/backslashes). + - name: Resolve released version + id: version + env: + EVENT_NAME: ${{ github.event_name }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + VERSION_OVERRIDE: ${{ github.event.inputs.version_override }} + run: | + set -euo pipefail + if [ "$EVENT_NAME" = "release" ]; then + raw="$RELEASE_TAG" + else + raw="$VERSION_OVERRIDE" + fi + version="${raw#v}" + [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.a-zA-Z0-9-]*)?$ ]] \ + || { echo "::error::Refusing to dispatch an unexpected version string: '$version'"; exit 1; } + echo "value=$version" >> "$GITHUB_OUTPUT" + + # Poll the version-specific PyPI endpoint instead of a fixed sleep so the + # dispatch never races ahead of replication (and fails fast if the just- + # published version never shows up). - name: Wait for PyPI availability - run: sleep 120 + env: + AIRBYTE_VERSION: ${{ steps.version.outputs.value }} + run: | + set -euo pipefail + for _ in $(seq 1 30); do + if curl --fail --silent --show-error \ + "https://pypi.org/pypi/airbyte/${AIRBYTE_VERSION}/json" >/dev/null; then + exit 0 + fi + sleep 10 + done + echo "::error::airbyte==${AIRBYTE_VERSION} was not available on PyPI in time" + exit 1 + + # Cross-repo dispatch is authenticated with an Octavia GitHub App token, + # scoped to airbyte-ops-mcp and to contents:write only (least privilege; + # repository scoping limits where the token works, not its permission + # set), matching the pattern used by prerelease-command.yml. + - name: Authenticate as GitHub App + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: get-app-token + with: + owner: airbytehq + repositories: airbyte-ops-mcp + permission-contents: write + app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} - name: Trigger cloud-mcp preview deploy uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1 with: - token: ${{ secrets.GITHUB_CI_WORKFLOW_TRIGGER_PAT }} + token: ${{ steps.get-app-token.outputs.token }} repository: airbytehq/airbyte-ops-mcp event-type: deploy-cloud-mcp - client-payload: '{"mcp-server": "cloud-mcp", "preview": "true"}' + client-payload: '{"mcp-server": "cloud-mcp", "preview": "true", "airbyte-version": "${{ steps.version.outputs.value }}"}'