Uh oh!
There was an error while loading. Please reload this page.
feat: move apps to targets, support env_refs as a list - #110
Merged
Conversation
Implements #108 and #104 together. apps moves out of vault manifests (which now hold only env/secrets) onto target manifests, where it belongs conceptually — it's a property of the deployment, not of any one secrets source. This removes #104's main blocker: with apps no longer rendered per-vault as an APPS= line, merging multiple env sources is just concatenation with fail-loud key-collision detection, no APPS-specific union logic needed. env_ref becomes env_refs (list, required non-empty), mirroring app_refs. ansible/deploy.yml decrypts every entry, merges them plus a synthesized APPS= line (built from the target's own apps field, seeded into the collision check first so a vault accidentally defining APPS collides too), and fails loud on any duplicate key across sources - matching the existing app-bundle-conflict philosophy. No external consumers exist yet, so this is a breaking schema change: no legacy support for vault-level apps or singular env_ref. #103 (dedup the now four near-identical ref-resolution blocks) stays deferred as its own follow-up, per plan.
There was a problem hiding this comment.
Pull request overview
This PR updates Flightdeck’s deployment schema and pipeline to make app selection a property of deployment targets (not vaults) and to support merging multiple encrypted env sources per target, aligning env composition with existing multi-bundle app composition.
Changes:
- Move
appsout ofvaults/*.ymlintotargets/*.ymland renderAPPSfrom the target’s desired app list. - Replace singular
env_refwith required non-emptyenv_refs(list) and implement fail-loud env key collision detection when merging decrypted env assets. - Update deploy workflows and the
encrypt-envaction/docs/tests to match the new manifest and target schemas.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| vaults/hawkeye.yml | Removes apps from the vault manifest so vaults contain only env/secrets. |
| targets/hawkeye.yml | Adds apps and switches env_ref → env_refs for target-driven app selection and env composition. |
| ansible/deploy.yml | Implements looped download/decrypt/merge for flightdeck_env_refs with collision detection and synthesized APPS from flightdeck_apps. |
| README.md | Documents new env_refs/apps target schema and updated Ansible/deploy usage examples. |
| AGENTS.md | Updates repo agent guidance to reflect env merging and target-owned app selection. |
| .github/workflows/release.yml | Passes env-refs and apps through to deploy-shared.yml from the targets matrix. |
| .github/workflows/deploy.yml | Passes env-refs and apps through to deploy-shared.yml from the targets matrix. |
| .github/workflows/deploy-shared.yml | Adds env-refs + apps inputs, validates them as non-empty JSON arrays, and forwards to Ansible as extra-vars. |
| .github/actions/encrypt-env/scripts/render-env.py | Drops apps from manifest schema and stops generating APPS in rendered env output. |
| .github/actions/encrypt-env/tests/test_render_env.py | Updates tests to reflect removal of apps/APPS from the encrypt-env action output and schema validation. |
| .github/actions/encrypt-env/README.md | Updates action documentation to remove apps and point app selection to targets. |
Suppressed comments (2)
README.md:129
- This multiple-env-source example also omits required playbook variables (
flightdeck_app_ref,flightdeck_path,flightdeck_keep_releases,flightdeck_sops_age_key_file), so it won't run successfully as written.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env","<owner>/<other-secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'
README.md:118
- This additional app-bundle example has the same issue as the earlier one: it doesn't pass required playbook variables like
flightdeck_app_ref,flightdeck_path,flightdeck_keep_releases, andflightdeck_sops_age_key_file, so it will fail the playbook's initialassert.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest","<owner>/<extra-repo>@latest"],"flightdeck_apps":["traefik","rybbit"]}'
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+90
to
+93
| ansible-playbook ansible/deploy.yml \ | ||
| -i mainframe, \ | ||
| -u root \ | ||
| -e flightdeck_env_ref=<owner>/<secrets-repo>@latest:<server>.sops.env \ | ||
| -e '{"flightdeck_app_refs":["rubykatzen/flightdeck@latest"]}' | ||
| -e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}' |
| ``` | ||
| The `flightdeck_env_ref` format is `owner/repo@tag:asset`. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads the asset, decrypts it with the server-local SOPS age key (`flightdeck_sops_age_key_file`), links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`. | ||
| Each `flightdeck_env_refs` entry is in `owner/repo@tag:asset` format. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads and decrypts every entry with the server-local SOPS age key (`flightdeck_sops_age_key_file`), merges them into one `.env` alongside a synthesized `APPS` line built from `flightdeck_apps`, links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`. |
…interface
deploy-shared.yml already exists specifically to hide flightdeck_*
Ansible variable names and -e JSON from callers, but the README's
"Ansible Deploy" section showed exactly that - three ansible-playbook
-e '{...}' examples, all of which actually fail at the playbook's
initial assert (missing flightdeck_app_ref/path/keep_releases/
sops_age_key_file), per Copilot's review on #110.
Replace it with a short pointer to deploy-shared.yml as the actual
interface and to the Vaults And Targets section for how multi-source
merging is configured, instead of patching three broken CLI examples
that shouldn't be a documented entry point in the first place.Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 20, 2026
This was referenced Aug 20, 2026
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
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements #108 and #104 together, leaving #103 (ref-resolution dedup) as a separate follow-up.
appsmoves out of vault manifests (which now hold only env/secrets) onto target manifests, where it belongs conceptually — it's a property of the deployment, not of any one secrets source.env_refbecomesenv_refs(list, required non-empty), mirroringapp_refs.Why together
appsliving in vaults was #104's main blocker: anAPPS=...line was rendered per-vault, so merging multiple env sources meant reconciling theirAPPSlists too (naive concatenation silently drops earlier sources' apps — dotenv parsing is last-value-wins on duplicate keys). Movingappsonto the target first removes that obstacle entirely: mergingenv_refsis now just concatenation of N decrypted sources plus one synthesizedAPPS=line built from the target's ownappsfield, with fail-loud collision detection across all of them — noAPPS-specific union logic needed.Cross-repo age-key distribution (the other obstacle #104 raised) is untouched and stays out of scope — not needed while every vault lives in this repo, encrypted for the same
keys/hawkeye.pubrecipient.No external consumers exist yet, so this is a breaking schema change: no legacy support for vault-level
appsor singularenv_ref.Schema changes
vaults/hawkeye.yml—appsdropped:targets/hawkeye.yml—appsadded,env_ref→env_refs:load-yaml-matrixneeded zero changes — confirms it's genuinely schema-agnostic, fields just flow through.ansible/deploy.ymlReplaces the single env pull/decrypt chain with a loop shaped like the existing app-bundle merge loop: for each ref in
flightdeck_env_refs, download +sops decrypt, then append every line to the merged output via a smalladd_line()function that rejects any key already seen — fails loud withEnv key conflicts with an existing source: $key. The "seen keys" set is seeded with a synthesizedAPPS={{ flightdeck_apps | join(',') }}line before the loop, so a vault accidentally emitting its ownAPPScollides too, uniformly, with no special-casing.This is the fourth near-identical "download + resolve
@latest" block in this file (after the machinery pull, the app-refs loop, and now this) — #103 tracks extracting a shared script, deliberately deferred rather than folded in here.Verification
encrypt-envunit tests updated (droppedapps/APPSfixtures and assertions, removed two now-obsolete tests) and passingload-yaml-matrixunit tests — unchanged, still passing (confirms schema-agnosticism)ansible-playbook --syntax-check ansible/deploy.ymlvaults/hawkeye.yml/targets/hawkeye.yml—env_refs/appsshow up correctly in the generated JSONadd_line()collision-detection logic: clean multi-source merge, ordinary key collision, and a source colliding with the synthesizedAPPSline — all three behave as intendedpre-commit run --all-filesCloses#108, closes#104.