diff --git a/.github/workflows/deploy-kotlin-v2.yml b/.github/workflows/deploy-kotlin-v2.yml index 8819633..cb4ede9 100644 --- a/.github/workflows/deploy-kotlin-v2.yml +++ b/.github/workflows/deploy-kotlin-v2.yml @@ -205,6 +205,9 @@ on: CHANGELOG_GITHUB_TOKEN: required: false description: 'GitHub token for changelog generation' + TAILSCALE_AUTHKEY: + required: false + description: 'Tailscale auth key. Lets the changelog job join the tailnet to reach the internal identity service (repo/org variable CHANGELOG_IDENTITY_RESOLVE_URL) used to tag release notification contributors; leave unset to skip' ARGOCD_TOKEN: required: false description: 'ArgoCD authentication token for API access' @@ -330,7 +333,45 @@ jobs: if: always() && inputs.enable-changelog && needs.deploy.result == 'success' runs-on: ${{ github.event.repository.private && 'arc-arm64-1cpu-2gb-nodocker' || 'ubuntu-24.04-arm' }} timeout-minutes: 5 + env: + TAILSCALE_AUTHKEY: ${{ secrets.TAILSCALE_AUTHKEY }} steps: + # The identity service that maps GitHub logins to Slack users is VPN-only, so the + # runner joins the tailnet first. Only needed when a release notification is enabled + # and an identity URL is configured. Best-effort: a failure here must never fail the + # changelog job, the CLI falls back to less reliable email lookups instead. + - name: Tailscale + id: tailscale + if: ${{ inputs.changelog-release-notify-channel != '' && vars.CHANGELOG_IDENTITY_RESOLVE_URL != '' && env.TAILSCALE_AUTHKEY != '' }} + continue-on-error: true + uses: tailscale/github-action@780049a30b6ff5c378a9e7b389d15ece7a204888 # v4.1.3 + with: + authkey: ${{ env.TAILSCALE_AUTHKEY }} + hostname: "github-changelog-${{ github.run_id }}" + # v4 passes --accept-routes itself; repeating it in args fails `tailscale up` + args: "--login-server https://headscale.monta.com" + # `tailscale up` returns before routes are usable, so the first request can hit a + # half-open path. Poll the identity service until it answers, otherwise the CLI's + # single best-effort lookup could silently fall back to GitHub profile emails. + # Output stays quiet on purpose: the URL is an internal hostname. + - name: Wait for the identity service to be reachable over the tailnet + if: ${{ steps.tailscale.outcome == 'success' }} + continue-on-error: true + shell: bash + env: + IDENTITY_RESOLVE_URL: ${{ vars.CHANGELOG_IDENTITY_RESOLVE_URL }} + run: | + MAX_ATTEMPTS=8 + for attempt in $(seq 1 "${MAX_ATTEMPTS}"); do + if curl --silent --fail --connect-timeout 3 --max-time 5 \ + --output /dev/null "${IDENTITY_RESOLVE_URL}?github=probe" 2>/dev/null; then + echo "✅ Identity service reachable (attempt ${attempt}/${MAX_ATTEMPTS})" + exit 0 + fi + echo "… attempt ${attempt}/${MAX_ATTEMPTS} missed; retrying in 3s" + sleep 3 + done + echo "⚠️ Identity service not reachable; contributors may fall back to GitHub profile emails" - name: Run changelog cli action uses: monta-app/changelog-cli-action@main with: @@ -358,3 +399,4 @@ jobs: jira-project-blacklist: ${{ vars.CHANGELOG_JIRA_PROJECT_BLACKLIST }} monitoring-urls: ${{ inputs.changelog-monitoring-urls }} release-notify-channel: ${{ inputs.changelog-release-notify-channel }} + identity-resolve-url: ${{ vars.CHANGELOG_IDENTITY_RESOLVE_URL }} diff --git a/.github/workflows/deploy-kotlin.yml b/.github/workflows/deploy-kotlin.yml index 47a3e06..9375829 100644 --- a/.github/workflows/deploy-kotlin.yml +++ b/.github/workflows/deploy-kotlin.yml @@ -193,6 +193,9 @@ on: CHANGELOG_GITHUB_TOKEN: required: false description: 'GitHub token for changelog generation' + TAILSCALE_AUTHKEY: + required: false + description: 'Tailscale auth key. Lets the changelog job join the tailnet to reach the internal identity service (repo/org variable CHANGELOG_IDENTITY_RESOLVE_URL) used to tag release notification contributors; leave unset to skip' ARGOCD_TOKEN: required: false description: 'ArgoCD authentication token for API access' @@ -315,7 +318,45 @@ jobs: if: always() && inputs.enable-changelog && needs.deploy.result == 'success' runs-on: ${{ github.event.repository.private && 'arc-arm64-1cpu-2gb-nodocker' || 'ubuntu-24.04-arm' }} timeout-minutes: 5 + env: + TAILSCALE_AUTHKEY: ${{ secrets.TAILSCALE_AUTHKEY }} steps: + # The identity service that maps GitHub logins to Slack users is VPN-only, so the + # runner joins the tailnet first. Only needed when a release notification is enabled + # and an identity URL is configured. Best-effort: a failure here must never fail the + # changelog job, the CLI falls back to less reliable email lookups instead. + - name: Tailscale + id: tailscale + if: ${{ inputs.changelog-release-notify-channel != '' && vars.CHANGELOG_IDENTITY_RESOLVE_URL != '' && env.TAILSCALE_AUTHKEY != '' }} + continue-on-error: true + uses: tailscale/github-action@780049a30b6ff5c378a9e7b389d15ece7a204888 # v4.1.3 + with: + authkey: ${{ env.TAILSCALE_AUTHKEY }} + hostname: "github-changelog-${{ github.run_id }}" + # v4 passes --accept-routes itself; repeating it in args fails `tailscale up` + args: "--login-server https://headscale.monta.com" + # `tailscale up` returns before routes are usable, so the first request can hit a + # half-open path. Poll the identity service until it answers, otherwise the CLI's + # single best-effort lookup could silently fall back to GitHub profile emails. + # Output stays quiet on purpose: the URL is an internal hostname. + - name: Wait for the identity service to be reachable over the tailnet + if: ${{ steps.tailscale.outcome == 'success' }} + continue-on-error: true + shell: bash + env: + IDENTITY_RESOLVE_URL: ${{ vars.CHANGELOG_IDENTITY_RESOLVE_URL }} + run: | + MAX_ATTEMPTS=8 + for attempt in $(seq 1 "${MAX_ATTEMPTS}"); do + if curl --silent --fail --connect-timeout 3 --max-time 5 \ + --output /dev/null "${IDENTITY_RESOLVE_URL}?github=probe" 2>/dev/null; then + echo "✅ Identity service reachable (attempt ${attempt}/${MAX_ATTEMPTS})" + exit 0 + fi + echo "… attempt ${attempt}/${MAX_ATTEMPTS} missed; retrying in 3s" + sleep 3 + done + echo "⚠️ Identity service not reachable; contributors may fall back to GitHub profile emails" - name: Run changelog cli action uses: monta-app/changelog-cli-action@main with: @@ -343,3 +384,4 @@ jobs: jira-project-blacklist: ${{ vars.CHANGELOG_JIRA_PROJECT_BLACKLIST }} monitoring-urls: ${{ inputs.changelog-monitoring-urls }} release-notify-channel: ${{ inputs.changelog-release-notify-channel }} + identity-resolve-url: ${{ vars.CHANGELOG_IDENTITY_RESOLVE_URL }} diff --git a/CLAUDE.md b/CLAUDE.md index 7cc8b08..4dd983d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,7 @@ - **Changes**: Small shared-workflow jobs run on the Docker-less ARC runner ## Recent Changes (2026-10-06) +1. **Changelog job can join the tailnet to resolve Slack users**: `deploy-kotlin.yml` and `deploy-kotlin-v2.yml` `create-changelog` jobs pass the repo/org variable `CHANGELOG_IDENTITY_RESOLVE_URL` to `changelog-cli-action` (`identity-resolve-url`), so the CLI can map GitHub logins to Slack user IDs for the release notification. The endpoint is VPN-only, so when `changelog-release-notify-channel`, that variable and the new optional `TAILSCALE_AUTHKEY` secret are all set, the job first joins the tailnet (`tailscale/github-action`, same login server and pinned version as `pull-request-kotlin`) and polls the service until it answers (`tailscale up` returns before routes are usable). Both steps are `continue-on-error` and best-effort: any failure just leaves the CLI on its GitHub-profile-email fallback, never failing the changelog job. The URL is deliberately a variable, not in the workflow, because this repo is public. Needs a `changelog-cli` release that includes the identity resolver (pinned in `changelog-cli-action`); the passthrough is inert until then. 1. **Small jobs on `arc-arm64-1cpu-2gb-nodocker`**: the shared workflows' small jobs that need no Docker moved there from `arc-arm64-2cpu-4gb` (kube-manifests#7784 added the set: 0.5 CPU guaranteed, 2 GiB, no dind, ~14 per node): the Setup/converter jobs (`component-build`, `component-test-kotlin`, `component-test-python`, `pull-request-{bun,kotlin,react}`, `sonar-cloud`), `check-migration-order`, the `component-deploy(-v2)` deploy job, `create-release-tag`, the changelog and Slack-token deprecation jobs, `track-pending-release`, `pr-digest`, `allow-/block-deploys` and rollback's `push-manifest`. Jobs that need Docker stay on `arc-arm64-2cpu-4gb`: rollback's `slackNotification` (`rtCamp/action-slack-notify` runs a `docker://` image), this repo's `lint-workflows` (`reviewdog/action-actionlint` is a Docker action), and `publish-tech-docs` (a docs build). Anything added to these jobs must not use `container:`, `services:` or a Docker-based action. 1. **The shared workflows' own jobs run on ARC**: the 17 jobs that named a GitHub-hosted label (`component-deploy(-v2)` deploy, `create-release-tag`, the deploy workflows' changelog and Slack-token deprecation jobs, `rollback`, `track-pending-release`, `pr-digest`, `publish-tech-docs`, `allow-/block-deploys`, `semgrep-security-scan`, and this repo's own `pull-request.yml`) use `${{ github.event.repository.private && 'arc-arm64-2cpu-4gb' || 'ubuntu-24.04-arm' }}`; semgrep gets `arc-arm64-4cpu-12gb`. Public callers, and events without a repository in the payload (`schedule`), get GitHub's standard arm64 runner. The deploy and rollback jobs rely on `yq` v4 and `jq` from the runner, both in the ARC image; `argocd` is downloaded by `argocd-wait-sync`, and nothing calls the `helm` CLI. diff --git a/docs/workflow-guide.md b/docs/workflow-guide.md index 78446e5..0180979 100644 --- a/docs/workflow-guide.md +++ b/docs/workflow-guide.md @@ -393,9 +393,12 @@ This conditional logic ensures the workflow continues properly even when optiona | `SLACK_APP_TOKEN` | Yes | Slack token for notifications | | `MANIFEST_REPO_PAT` | Yes | GitHub PAT for updating kube-manifests | | `SENTRY_AUTH_TOKEN` | No | Sentry authentication token | +| `TAILSCALE_AUTHKEY` | No | Tailscale auth key. With `changelog-release-notify-channel` and the `CHANGELOG_IDENTITY_RESOLVE_URL` variable set, the changelog job joins the tailnet to resolve contributors' Slack accounts (see below) | | `AWS_CDN_ACCESS_KEY_ID` | No | CDN access key for S3 access | | `AWS_CDN_SECRET_ACCESS_KEY` | No | CDN secret key for S3 access | +**Tagging contributors in the release notification:** the notification tags PR authors, co-authors and approvers with real Slack mentions. Resolving a GitHub login to a Slack user goes through an internal, VPN-only identity service, so the URL is *not* in the workflow - set it as a repo/org **variable** `CHANGELOG_IDENTITY_RESOLVE_URL` (full endpoint URL) and pass the `TAILSCALE_AUTHKEY` secret (`secrets: inherit` covers it). When all three of `changelog-release-notify-channel`, the variable and the secret are set, the changelog job joins the tailnet and waits for the service to answer before running the CLI. Without them, or if joining fails, the job still succeeds and the CLI falls back to less reliable GitHub-profile-email lookups. + ### Example Usage: #### Basic Deployment: @@ -591,6 +594,7 @@ jobs: | `SLACK_APP_TOKEN` | Yes | Slack token for notifications | | `MANIFEST_REPO_PAT` | Yes | GitHub PAT for updating service repository | | `SENTRY_AUTH_TOKEN` | No | Sentry authentication token | +| `TAILSCALE_AUTHKEY` | No | Tailscale auth key. With `changelog-release-notify-channel` and the `CHANGELOG_IDENTITY_RESOLVE_URL` variable set, the changelog job joins the tailnet to resolve contributors' Slack accounts (see below) | | `AWS_CDN_ACCESS_KEY_ID` | No | CDN access key for S3 access | | `AWS_CDN_SECRET_ACCESS_KEY` | No | CDN secret key for S3 access | | `LOKALISE_TOKEN` | No | Lokalise API token | @@ -600,6 +604,8 @@ jobs: | `CHANGELOG_GITHUB_TOKEN` | No | GitHub token for changelog generation | | `ARGOCD_TOKEN` | No | ArgoCD authentication token | +**Tagging contributors in the release notification:** the notification tags PR authors, co-authors and approvers with real Slack mentions. Resolving a GitHub login to a Slack user goes through an internal, VPN-only identity service, so the URL is *not* in the workflow - set it as a repo/org **variable** `CHANGELOG_IDENTITY_RESOLVE_URL` (full endpoint URL) and pass the `TAILSCALE_AUTHKEY` secret (`secrets: inherit` covers it). When all three of `changelog-release-notify-channel`, the variable and the secret are set, the changelog job joins the tailnet and waits for the service to answer before running the CLI. Without them, or if joining fails, the job still succeeds and the CLI falls back to less reliable GitHub-profile-email lookups. + ### Outputs: - `slack-message-id`: Slack message ID for notifications - `stage`: Stage that was deployed to