Adopt a8c-secrets in WordPress iOS - #25772
Conversation
There was a problem hiding this comment.
Pull request overview
This PR deduplicates the Buildkite “install CI secrets” step by extracting the repeated bundle exec fastlane run configure_apply invocation into a shared script, so the upcoming secrets migration can be done by updating a single file.
Changes:
- Introduces
.buildkite/commands/install-secrets.shto centralize secrets installation. - Updates Buildkite command scripts and release pipelines to call the shared script instead of inlining the Fastlane step.
- Unifies pipeline log labeling around “Installing Secrets”.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| .buildkite/release-pipelines/update-app-store-strings.yml | Swaps inline secrets install for shared script call in the pipeline command block. |
| .buildkite/release-pipelines/publish-release.yml | Swaps inline secrets install for shared script call in the pipeline command block. |
| .buildkite/release-pipelines/new-hotfix.yml | Swaps inline secrets install for shared script call in the pipeline command block. |
| .buildkite/release-pipelines/new-beta-release.yml | Swaps inline secrets install for shared script call in the pipeline command block. |
| .buildkite/release-pipelines/code-freeze.yml | Swaps inline secrets install for shared script call in the pipeline command block. |
| .buildkite/commands/release-build-wordpress.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/release-build-jetpack.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/prototype-build-wordpress.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/prototype-build-jetpack.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/promote-nightly.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/promote-build-to-public.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/install-secrets.sh | New shared script that performs the secrets install via Fastlane with a consistent log label. |
| .buildkite/commands/gather-testflight-candidates.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/finalize-release.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/finalize-hotfix.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/complete-code-freeze.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/build-for-testing.sh | Replaces duplicated secrets install lines with shared script invocation. |
| .buildkite/commands/build-and-upload-testflight.sh | Replaces duplicated secrets install lines with shared script invocation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
| App Name | Jetpack | |
| Configuration | Release-Alpha | |
| Build Number | 33615 | |
| Version | PR #25772 | |
| Bundle ID | com.jetpack.alpha | |
| Commit | 3064fe3 | |
| Installation URL | 4tavtjrogbl5g |
|
| App Name | WordPress | |
| Configuration | Release-Alpha | |
| Build Number | 33615 | |
| Version | PR #25772 | |
| Bundle ID | org.wordpress.alpha | |
| Commit | 3064fe3 | |
| Installation URL | 1cv7johfdvro0 |
| # dev | ||
| age1srcq3hl92ym9jk3ezj5prwhche3w8szc0cssy8t7afrkjmtkxu2qkjsdfn | ||
| # ci | ||
| age1a7xcr6qzwnzgcxq95sq33p58xdzsmful8w7mp2zktvuy5434yuss9rmv8f |
There was a problem hiding this comment.
CI public key validated by using age-keygen -y on the private key stored in our CI secrets 👍
| curl -fsSL https://raw.githubusercontent.com/Automattic/a8c-secrets/main/install.sh | bash | ||
| # The installer puts the binary in ~/.local/bin, which isn't on the agent's PATH. | ||
| export PATH="$HOME/.local/bin:$PATH" |
There was a problem hiding this comment.
Note that in the future we plan to have a8c-secrets pre-installed and provisionned on all of our CI agents.
We're not there yet so for the iterative migration we might still need this curl and export PATH calls indeed, but that's good to keep in mind and track in some Linear issue to remember to follow-up on this later.
Alternatively, maybe we could start deploying the a8c-secrets (and PATH addition) to our CI agents first, so that PRs that will start to adopt it don't have to do that curl themselves already?
| ${HOME}/.configure/wordpress-ios/secrets/WordPress-Secrets.swift | ||
| ${HOME}/.configure/wordpress-ios/secrets/WordPress-Secrets-Internal.swift | ||
| ${HOME}/.configure/wordpress-ios/secrets/WordPress-Secrets-Alpha.swift | ||
| ${HOME}/.configure/wordpress-ios/secrets/Jetpack-Secrets.swift | ||
| ${HOME}/.configure/wordpress-ios/secrets/Reader-Secrets.swift | ||
| ${HOME}/.a8c-secrets/wordpress-ios@github.com@wordpress-mobile/WordPress-Secrets.swift | ||
| ${HOME}/.a8c-secrets/wordpress-ios@github.com@wordpress-mobile/Jetpack-Secrets.swift | ||
| ${HOME}/.a8c-secrets/wordpress-ios@github.com@wordpress-mobile/Reader-Secrets.swift |
There was a problem hiding this comment.
Is it intentional that the 3 .swift files that were previously split per configuration (Secrets/Secrets-Internal/Secrets-Alpha) for the WordPress target are now unified into a single one, not making the distinction anymore?
I looked at the current 3 files in our ~/.mobile-secrets (the ones used by configure_apply currently) and their content seem to be distinct for all 3:
- Mostly the same between
Secrets.swiftandSecrets-Alpha.swift… except a different SentryDSN - Different SentryDSN but also different ClientID for
Internal
So in practice this migration to have a single WordPress-Secrets.swift file for all 3 Xcode configurations of the WordPress target changes this behavior. Just wanted to check if this was intentional and had been discussed with the team (e.g. we wouldn't want crashes from Internal and Alpha to be reported in the same Sentry project as prod)
There was a problem hiding this comment.
Ok reading the current content of the GenerateCredentials.sh script file I see that maybe the Secrets-Internal and Secrets-Alpha files were in practice never used (never copied by the script in the first place)? Or am I missing something? Or maybe this has been the case for ages and we never cleaned things up?
(I couldn't find a mention of this important gotcha in the PR description, so asking here so we can confirm a common understanding to be sure)
There was a problem hiding this comment.
We no longer need any of the differentiated secrets files – everything can use the same credentials.
There was a problem hiding this comment.
We no longer need any of the differentiated secrets files – everything can use the same credentials.
Consolidation to a single secret file felt like a bigger change than what this PR ought to hold. I reverted this to draft and opened #25803 to work on it.
Generated by 🚫 Danger |
|
Converted to draft as explain in #25772 (comment) |
|
Version |
4346fee to
dd98fd5
Compare
🤖 Build Failure AnalysisThis build has failures. Claude has analyzed them - check the build annotations for details. |
Centralize the `configure_apply` invocation, duplicated across 12 command scripts and 5 release pipelines, into `install-secrets.sh`. The upcoming move to a8c-secrets then swaps a single call site instead of seventeen. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swap the secrets store from `.configure`/`.configure-files` to `.a8c-secrets`, tracking the encrypted `*.age` blobs alongside the already-committed repo id and public keys, and updating the `.gitignore` allowlist, `.gitattributes`, and `CODEOWNERS` to match. Part of AINFRA-1538. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hint named a `configure_secrets` fastlane lane this repo never gained. Name the tool's own command and link its repo, so the message stays correct as the setup steps evolve. Part of AINFRA-1538. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI installs the tool through the toolkit's `install_a8c-secrets_binary`, added in plugin 6.2.0, which pins the version and checksums the download. `rake dependencies` now decrypts only when the tool is present, so external contributors bootstrap unchanged, and treats a failed decrypt as a warning: the build phase already falls back to the example secrets and errors on its own for Release builds. Part of AINFRA-1538. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flow existed to get developers a key for the `mobile-secrets` repo. a8c-secrets authenticates with age identities instead, so nothing in this repo reads a GPG key any more. Part of AINFRA-1538. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Neither file now names the tool's storage layout: the input list declares the committed `.age` blob it can actually see, and the script asks `a8c-secrets which` for the plaintext. A repo id or decrypt root spelled out here would silently rot the day either changes. `copy_if_changed` keeps a checkout that only bumps an input's mtime from forcing a recompile of `Secrets.swift`. Mirrors Gravatar-SDK-iOS `2fc29684`. Part of AINFRA-1538. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`a8c-secrets decrypt` produces a single file, `Secrets.swift`, and its only consumer is the `GenerateCredentials.sh` Xcode build phase. The release-management and TestFlight-promotion jobs never invoke a compile, and take their App Store Connect, GitHub, and Slack credentials from Buildkite environment variables, so the decryption was dead weight in those jobs. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>
Xcode collapses `..` when it resolves an `.xcfilelist`, so the entry landed in
the processed list rooted at the repo while the script still held the
`WordPress/..` form of the same path.
`ensure_is_in_input_files_list` compares the two as strings, so every target
running `Generate Credentials` failed the phase.
The paths this replaced were rooted at `${HOME}` and had no `..` to collapse,
which is why the check held up until now.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>
The `Generate Credentials` build phase resolves `a8c-secrets` off PATH, and the CI agents don't carry `~/.local/bin` — the installer says so itself on the way past. Running the script as a child process left the export behind with it, so the phase would have fallen through to the example secrets and failed every Release build. Gravatar-SDK-iOS keeps the install and the export inline in the job script for the same reason; sourcing gets there without copying them into six callers. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>
A comment asking callers to source the script is easy to miss, and the failure mode is silent: the build phase falls through to the example secrets and only breaks later, in a Release build. The guard matches the one in `set-up-git-to-fetch-private-packages.sh` in `dayone-apple` and `use-bot-for-git.sh` in the CI agent images. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>
34b18e2 to
448b9a2
Compare
Matches the shape agreed in Gravatar-SDK-iOS, whose build phase copies without comparing first: Automattic/Gravatar-SDK-iOS#824 The phase declares its inputs and output, so Xcode already skips it when nothing changed. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>
Each call site already echoes which secrets it is applying. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>
The AI use `xcodeproj` to make its changes, which smudged some of the comments and references. I opened the project in Xcode and added then removed a file to trigger a project file update.
The script installs the `a8c-secrets` binary; naming it after the secrets invited reading it as the step that puts them on disk. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>
The `Decrypt Secrets` aggregate target now decrypts once per build, so doing it here as well only repeated the work. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>
The `Decrypt Secrets` build phase covers local builds too, and it cannot go stale between a bootstrap and a build the way this task could. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>


Description
Migrate the WordPress/Jetpack iOS secrets store off
.configure/mobile-secrets ontoa8c-secrets(AINFRA-1538), part of the fleet-wide rollout. wpios is outside-only, so this is a decrypt-mechanism swap — no app-code changes.The branch opens with a tidy (
Extract secret install into shared script) that centralised the ~17configure_applycall sites into.buildkite/commands/install-secrets.sh, so the mechanism swap here touches that one file instead of every pipeline. The build phase now reads the three Swift secrets from~/.a8c-secrets/<repo-id>/(decrypted from the committed*.ageblobs), and a newconfigure_secretsfastlane lane wrapsa8c-secrets decryptbehind a tool preflight.Follows the woocommerce-ios (AINFRA-1534) and pocket-casts-ios (AINFRA-1541) adoptions, carrying over the pcios refinements:
FastlaneCore::CommandExecutor.which, theis_cigraceful-skip, and abefore_alldecrypt.Validated in CI
Build #33110 is green (26/26). The build-for-testing log confirms the full path on real agents: install
a8c-secrets→a8c-secrets decryptwrites the three secrets to~/.a8c-secrets/wordpress-ios@github.com@wordpress-mobile/→ the build reads them and compiles.A8C_SECRETS_IDENTITYis already provisioned on the agents (the decrypt succeeded), so there is no outstanding provisioning work.Gotchas
ainfra-2643-...— merge that first; this PR targets it, nottrunk.before_alldecrypt must skip in CI:install-secrets.shexportsa8c-secretsonto PATH in its own process, which does not reach the laterfastlane build_*process, so an unguarded decrypt hard-fails every CI build (pcios probe My Profile Network logic #4732). Theis_cibranch handles this — confirmed firing in #33110 with the AINFRA-2681 message, the secrets already on disk frominstall-secrets.sh.How to test
bundle exec fastlane configure_secretslocally (with the dev age identity imported) decrypts the three files into~/.a8c-secrets/wordpress-ios@github.com@wordpress-mobile/. CI exercises the CI path end-to-end (see build #33110).