From f3482bc8924eea6261d418e08d020ed8676b54ef Mon Sep 17 00:00:00 2001 From: Morten Andersen Date: Tue, 29 Sep 2026 16:50:08 +0200 Subject: [PATCH] feat(deploy): let the changelog job resolve Slack users via identity API [CPONETOPS-1176] The release notification tags contributors with Slack mentions, but mapping a GitHub login to a Slack user relies on an internal, VPN-only identity service (the GitHub-profile-email fallback reaches very few people). In deploy-kotlin and deploy-kotlin-v2, pass the repo/org variable CHANGELOG_IDENTITY_RESOLVE_URL to changelog-cli-action. The URL is a variable rather than workflow code because this repo is public. When changelog-release-notify-channel, that variable and the new optional TAILSCALE_AUTHKEY secret are all set, the create-changelog job first joins the tailnet (same pinned tailscale/github-action and login server as the Kotlin PR workflow) and polls the service until it answers, since `tailscale up` returns before routes are usable. Both steps are continue-on-error: on any failure the CLI just falls back to its email lookups and the job still succeeds. Documented in workflow-guide.md and CLAUDE.md. Inert until the pinned changelog-cli release includes the identity resolver. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/deploy-kotlin-v2.yml | 42 ++++++++++++++++++++++++++ .github/workflows/deploy-kotlin.yml | 42 ++++++++++++++++++++++++++ CLAUDE.md | 1 + docs/workflow-guide.md | 6 ++++ 4 files changed, 91 insertions(+) diff --git a/.github/workflows/deploy-kotlin-v2.yml b/.github/workflows/deploy-kotlin-v2.yml index f7e4177..fd472ed 100644 --- a/.github/workflows/deploy-kotlin-v2.yml +++ b/.github/workflows/deploy-kotlin-v2.yml @@ -210,6 +210,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' @@ -336,7 +339,45 @@ jobs: if: always() && inputs.enable-changelog && needs.deploy.result == 'success' runs-on: ubuntu-latest 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: @@ -364,3 +405,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 829321d..8f9754e 100644 --- a/.github/workflows/deploy-kotlin.yml +++ b/.github/workflows/deploy-kotlin.yml @@ -198,6 +198,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' @@ -321,7 +324,45 @@ jobs: if: always() && inputs.enable-changelog && needs.deploy.result == 'success' runs-on: linux-arm64 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: @@ -349,3 +390,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 7e93bc3..13aba02 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,7 @@ - **Changes**: Added `use-arc-runners` to `deploy-kotlin(-v2)`, `component-test-kotlin` and `component-build`; added the `arc-job-report` composite action ## Recent Changes (2026-09-29) +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. **Dockerfile cache mounts persisted on ARC image builds**: when `use-arc-runners` is set, `component-build.yml`'s buildx job restores and saves `RUN --mount=type=cache` contents (all Kotlin service Dockerfiles mount `/root/.gradle` around `./gradlew buildLayers`) via `reproducible-containers/buildkit-cache-dance`, which finds the mounts by parsing the Dockerfile. ARC builders start empty and the ECR registry cache (`cache-to ... mode=max`) does not carry cache mounts, so without this every deploy re-downloads all Gradle dependencies straight from Maven Central (the in-Docker Gradle has neither the runner's CodeArtifact init script nor AWS credentials). Storage follows the PR workflows: `runs-on/cache` on S3 when the caller passes the `GH_ACTION_*` secrets (new optional secrets on `component-build` and `deploy-kotlin(-v2)`), `actions/cache` otherwise. It must be the combined `runs-on/cache`, not `restore`/`save`: cache-dance extracts in its post step, and only a post-step save runs after that. The S3 step blanks `AWS_SESSION_TOKEN`, because the job's `ecr-put-image` credentials are exported into the environment before it. Key: Dockerfile path + hash of the Gradle build files, with a prefix restore-key. 1. **`use-arc-runners` for the Kotlin deploy workflows**: `deploy-kotlin.yml` and `deploy-kotlin-v2.yml` accept `use-arc-runners` and pass it to `component-test-kotlin.yml` and `component-build.yml`, which both gained the input. Tests run on the converter's ARC runner, with Setup on `arc-arm64-2cpu-4gb` and `LC_ALL=C.UTF-8`, and keep `setup-java`'s GitHub-backed Gradle cache. The image build runs the existing `build-self-hosted` job (docker buildx + ECR registry layer cache, the ARC README's recommendation) instead of `build-blacksmith`. The deploy, service-profile, release-tag and changelog jobs never used Blacksmith and are unchanged. Callers that don't opt in are unaffected. 2. **`arc-job-report` action**: New composite action at `.github/actions/arc-job-report` that keeps one PR comment with each job's ARC runner, duration, peak memory and CPU, a table of suggested runner-size changes, and a per-run history (commit, wall time, slowest job). Callers run it from a `workflow_run` trigger on their PR workflow (so it reports on the finished run and adds no job to the PR checks) and pass `github-token` + `grafana-token` (e.g. `secrets.GRAFANA_CLOUD_TOKEN`) as inputs; no checkout needed, the action ships its scripts. `job_resources.py` is kube-manifests' `infra/arc/scripts/job-resources.py` plus a `GRAFANA_TOKEN` path through Grafana's datasource proxy (CI has no `gcx`); keep the two in sync. Metrics are scraped every 60 s, so jobs under 2 min are left out of the suggestions and jobs under 1 min show no CPU at all (`rate()` needs two samples); waiting does not help because the pod is deleted when the job ends. Only bot-authored comments are updated, so local `--dry-run` testing never collides with CI. Built and trialled in service-ocpi#2599. diff --git a/docs/workflow-guide.md b/docs/workflow-guide.md index 92e6ccd..4bd0f1e 100644 --- a/docs/workflow-guide.md +++ b/docs/workflow-guide.md @@ -448,9 +448,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: @@ -647,6 +650,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 | @@ -656,6 +660,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