Outbound Bridge #211
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
| # Digital Service Orchestra — Outbound Bridge | |
| # Pushes local ticket changes to Jira when the tickets branch is updated. | |
| name: Outbound Bridge | |
| on: | |
| # Push trigger removed (71fa-c068): GitHub Actions does not detect workflow | |
| # files on orphan branches, so push-to-tickets never fired (all 100 recent | |
| # runs were workflow_dispatch). merge-to-main.sh dispatches explicitly. | |
| workflow_dispatch: {} | |
| concurrency: | |
| group: jira-bridge | |
| cancel-in-progress: false | |
| jobs: | |
| bridge: | |
| name: Push tickets to Jira | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: write | |
| # Skip commits made by the bridge bot itself (echo prevention). | |
| # Fall back to 'dso-bridge[bot]' when BRIDGE_BOT_LOGIN is unset so the | |
| # guard is never silently bypassed (unset var would evaluate to '' and | |
| # always pass, causing an infinite push loop on the tickets branch). | |
| if: github.actor != (vars.BRIDGE_BOT_LOGIN || 'dso-bridge[bot]') | |
| steps: | |
| - name: Checkout main (scripts and plugin code) | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| ref: main | |
| - name: Mount tickets branch as worktree | |
| run: | | |
| git fetch origin tickets --depth=2 | |
| rm -rf .tickets-tracker | |
| git worktree add .tickets-tracker tickets | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.x' | |
| - name: Cache ACLI binary | |
| id: cache-acli | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.acli | |
| key: acli-${{ runner.os }}-${{ vars.ACLI_VERSION || 'latest' }} | |
| - name: Validate ACLI version before download | |
| if: steps.cache-acli.outputs.cache-hit != 'true' | |
| run: | | |
| ACLI_VERSION="${{ vars.ACLI_VERSION }}" | |
| if [ -z "$ACLI_VERSION" ] || [ "$ACLI_VERSION" = "latest" ]; then | |
| echo "ERROR: ACLI_VERSION is unset or 'latest' — cannot proceed with download." | |
| echo " Pin ACLI_VERSION to a specific release and set ACLI_SHA256 to its" | |
| echo " expected SHA256 digest before running this workflow." | |
| exit 1 | |
| fi | |
| - name: Download ACLI | |
| if: steps.cache-acli.outputs.cache-hit != 'true' | |
| run: | | |
| mkdir -p ~/.acli | |
| # ACLI v1.3+ is a Go binary distributed as tar.gz from acli.atlassian.com. | |
| curl -sSL "https://acli.atlassian.com/linux/${{ vars.ACLI_VERSION }}/acli_${{ vars.ACLI_VERSION }}_linux_amd64.tar.gz" \ | |
| -o ~/.acli/acli.tar.gz | |
| - name: Verify ACLI checksum | |
| if: steps.cache-acli.outputs.cache-hit != 'true' | |
| run: | | |
| ACLI_VERSION="${{ vars.ACLI_VERSION }}" | |
| ACLI_SHA256="${{ vars.ACLI_SHA256 }}" | |
| ARTIFACT=~/.acli/acli.tar.gz | |
| if [ -z "$ACLI_VERSION" ] || [ "$ACLI_VERSION" = "latest" ]; then | |
| echo "ERROR: ACLI_VERSION is unset or 'latest' — checksum verification cannot proceed." | |
| echo " Pin ACLI_VERSION to a specific release and set ACLI_SHA256 to its expected" | |
| echo " SHA256 digest before running this workflow." | |
| exit 1 | |
| elif [ -z "$ACLI_SHA256" ]; then | |
| COMPUTED_HASH=$(sha256sum "$ARTIFACT" | awk '{print $1}') | |
| echo "WARNING: ACLI_SHA256 is not set. Computed SHA256 of downloaded artifact:" | |
| echo "" | |
| echo " ACLI_SHA256=${COMPUTED_HASH}" | |
| echo "" | |
| echo "Set this value as a GitHub repository variable:" | |
| echo " gh variable set ACLI_SHA256 --body '${COMPUTED_HASH}'" | |
| echo "" | |
| echo "Proceeding WITHOUT checksum verification (first-run bootstrap)." | |
| else | |
| echo "${ACLI_SHA256} ${ARTIFACT}" | sha256sum -c --strict | |
| echo "Checksum verified: ${ARTIFACT}" | |
| fi | |
| - name: Extract ACLI binary | |
| if: steps.cache-acli.outputs.cache-hit != 'true' | |
| run: | | |
| # ACLI v1.3+ is a Go binary in a tar.gz with a version-prefixed directory. | |
| # --strip-components=1 removes the top-level dir (e.g., acli_1.3.14-stable_linux_amd64/). | |
| tar xzf ~/.acli/acli.tar.gz -C ~/.acli/ --strip-components=1 | |
| chmod +x ~/.acli/acli | |
| rm -f ~/.acli/acli.tar.gz | |
| echo "Extracted acli binary from tar.gz." | |
| - name: Add ACLI to PATH | |
| run: | | |
| if [ ! -f "$HOME/.acli/acli" ]; then | |
| echo "ERROR: $HOME/.acli/acli not found — download or extraction must have failed." >&2 | |
| exit 1 | |
| fi | |
| ln -sf "$HOME/.acli/acli" /usr/local/bin/acli | |
| - name: Authenticate ACLI | |
| run: | | |
| echo "$JIRA_API_TOKEN" | acli jira auth login \ | |
| --site "$JIRA_URL" \ | |
| --email "$JIRA_USER" \ | |
| --token | |
| env: | |
| JIRA_URL: ${{ vars.JIRA_URL }} | |
| JIRA_USER: ${{ vars.JIRA_USER }} | |
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | |
| - name: Run outbound bridge | |
| id: run-bridge | |
| run: | | |
| python3 plugins/dso/scripts/bridge-outbound.py | |
| env: | |
| JIRA_URL: ${{ vars.JIRA_URL }} | |
| JIRA_USER: ${{ vars.JIRA_USER }} | |
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | |
| JIRA_PROJECT: ${{ vars.JIRA_PROJECT }} | |
| BRIDGE_ENV_ID: ${{ vars.BRIDGE_ENV_ID }} | |
| GH_RUN_ID: ${{ github.run_id }} | |
| - name: Commit SYNC events back to tickets branch | |
| run: | | |
| cd .tickets-tracker | |
| if [ -z "$(git status --porcelain)" ]; then | |
| echo "No SYNC events to commit — skipping." | |
| exit 0 | |
| fi | |
| # Configure bridge bot identity (not the default github-actions[bot]). | |
| # Values are read from env vars (set via the env: map below) rather than | |
| # interpolated directly into the run: block to prevent expression injection. | |
| git config user.name "${BRIDGE_BOT_NAME}" | |
| git config user.email "${BRIDGE_BOT_EMAIL}" | |
| git add -A | |
| git commit -m "chore: sync SYNC events from Jira bridge [run ${{ github.run_id }}]" | |
| git push origin HEAD:tickets | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| BRIDGE_BOT_NAME: ${{ vars.BRIDGE_BOT_NAME || 'dso-bridge[bot]' }} | |
| BRIDGE_BOT_EMAIL: ${{ vars.BRIDGE_BOT_EMAIL || 'dso-bridge@users.noreply.github.com' }} | |
| - name: Job timing report | |
| if: always() | |
| run: echo "outbound-bridge completed at $(date -u +%Y-%m-%dT%H:%M:%SZ)" |