feat(app): forward compose build.args; wait for Zitadel pod before setup #340

Closed
johnride wants to merge 0 commits from feat/compose-build-args-zitadel-readiness into master
Owner

build.args support (harmony_app):

  • Parse build.args (advanced build form) into ComposeService.build_args,
    mirroring parse_env (Simple/List/KvPair).
  • build_images() forwards them as docker build --build-arg K=V, so an app can
    bake per-environment build-time config (e.g. Quarkus profile, a Vite API URL).

Zitadel readiness (harmony):

  • ZitadelSetupScore failed at 0.0s on a cold cluster ("no Running pod for
    service 'zitadel'") because it checked once, before the Zitadel Deployment
    pod was Running. Poll for a Running pod (300s/3s) so a one-shot ship
    converges instead of needing a re-run.
build.args support (harmony_app): - Parse `build.args` (advanced build form) into ComposeService.build_args, mirroring parse_env (Simple/List/KvPair). - build_images() forwards them as `docker build --build-arg K=V`, so an app can bake per-environment build-time config (e.g. Quarkus profile, a Vite API URL). Zitadel readiness (harmony): - ZitadelSetupScore failed at 0.0s on a cold cluster ("no Running pod for service 'zitadel'") because it checked once, before the Zitadel Deployment pod was Running. Poll for a Running pod (300s/3s) so a one-shot `ship` converges instead of needing a re-run.
johnride added 1 commit 2026-06-24 22:14:54 +00:00
feat(app): forward compose build.args; wait for Zitadel pod before setup
Some checks failed
Run Check Script / check (pull_request) Failing after 57s
cbf3570f12
build.args support (harmony_app):
- Parse `build.args` (advanced build form) into ComposeService.build_args,
  mirroring parse_env (Simple/List/KvPair).
- build_images() forwards them as `docker build --build-arg K=V`, so an app can
  bake per-environment build-time config (e.g. Quarkus profile, a Vite API URL).

Zitadel readiness (harmony):
- ZitadelSetupScore failed at 0.0s on a cold cluster ("no Running pod for
  service 'zitadel'") because it checked once, before the Zitadel Deployment
  pod was Running. Poll for a Running pod (300s/3s) so a one-shot `ship`
  converges instead of needing a re-run.
johnride added 2 commits 2026-06-25 15:37:13 +00:00
ZitadelJwtBearer posted Zitadel's access_token to OpenBao's jwt/login. Zitadel
issues opaque access tokens by default, so OpenBao rejected it with "compact JWS
format must have three parts". The token endpoint already returns an id_token (a
JWT) because build_scope requests `openid`; use it. It carries the service user
in `sub` and the project id in `aud` (the latter via the project🆔aud scope) —
exactly what the OpenBao JWT role binds on. No Zitadel-side change needed (the
manual id_token exchange already proved this works).
feat(github-runner): install GitHub self-hosted runners (ARC) via HelmChartScore
Some checks failed
Run Check Script / check (pull_request) Failing after 55s
0094a966a7
New modules::github_runner. GithubRunnerScore yields two HelmChartScores — the
ARC controller (gha-runner-scale-set-controller) and a runner scale set
(gha-runner-scale-set) — as OCI charts. dind enabled (so docker build works),
configurable resource limits applied to both the runner and dind containers, and
the GitHub credential referenced by a pre-created Secret name (kept out of code).
The scale-set name becomes the workflow runs-on label.
johnride added 1 commit 2026-06-26 01:50:24 +00:00
docs(github-runner): platform guide for hosting client runners (org-scoped App)
Some checks failed
Run Check Script / check (pull_request) Failing after 1m31s
09ed0d81db
NationTech-hosted model: one NationTech-owned GitHub App (org permissions
Self-hosted runners: R/W + Metadata: RO), installed once per client org — that
single authorization covers all the org's repos. Per client: a Secret with that
org's installation ID + a runner scale set whose githubConfigUrl is the org URL.
Cited from the official GitHub ARC + Apps docs. Point the module's example/docs
at the org URL.
johnride added 1 commit 2026-06-26 01:52:09 +00:00
docs(github-runner): drop the runner setup guide from harmony
Some checks failed
Run Check Script / check (pull_request) Failing after 1m23s
3d2fd728e0
Hosting client CI runners + the GitHub-App auth is platform/operations, to live
in a NationTech private repo (TBD by the operator), not in harmony. Keep the
reusable module; reference the auth setup generically instead of by repo path.
johnride added 1 commit 2026-06-26 18:59:10 +00:00
fix(k8s): skip cluster-scope operator checks when access is restricted
Some checks failed
Run Check Script / check (pull_request) Failing after 55s
e92e263a42
A namespace-scoped deployer (e.g. a tenant CI service account) cannot list pods
cluster-wide, so the cert-manager and CNPG operator readiness checks 403'd and
failed the whole deploy — pushing operators to grant the tenant cluster-admin,
which is a security anti-pattern.

Both checks now treat a 403 Forbidden as "this cluster-wide platform operator is
managed out-of-band": log a clean info ("Skipped … due to restricted access")
and continue, assuming the operator is present. Non-403 errors still propagate.
johnride added 1 commit 2026-06-26 19:05:09 +00:00
fix(zitadel): don't patch the namespace if it already exists
Some checks failed
Run Check Script / check (pull_request) Failing after 1m0s
a8809c0838
ZitadelScore applied the (cluster-scoped) Namespace object unconditionally, so a
namespace-scoped tenant deployer 403'd on "patch namespaces" even though the
namespace was already provisioned by platform onboarding. Mirror the Postgres
module: check namespace_exists (a GET, which the deployer is allowed) and only
create when genuinely missing.
johnride added 1 commit 2026-06-26 19:14:31 +00:00
refactor(k8s): centralize namespace creation in K8sClient::ensure_namespace
Some checks failed
Run Check Script / check (pull_request) Failing after 55s
c18aa5d178
Add K8sClient::ensure_namespace(ns): a GET decides — no-op if the namespace
already exists (e.g. provisioned by platform onboarding), create only when
genuinely missing. Never patches the cluster-scoped Namespace object, so a
namespace-scoped tenant deployer doesn't need cluster RBAC.

Route the tenant-deploy path through it and stop helm from patching the
namespace:
- postgresql K8sPostgreSQLScore: use ensure_namespace (was inline).
- zitadel: use ensure_namespace; HelmChartScore create_namespace=false.
- app ComposeAppScore: ensure_namespace before install; create_namespace=false.

Fixes the prod deploy 403 "cannot patch resource namespaces" under a
namespace-scoped service account.
johnride added 1 commit 2026-06-26 21:37:55 +00:00
fix(zitadel): idempotent install — durable masterkey + install-once
Some checks failed
Run Check Script / check (pull_request) Failing after 54s
4ba5c46bc6
Two re-run hazards under the install/converge model:

1. Masterkey was generated fresh every run and only survived via the 409 on the
   k8s Secret — a deleted Secret would mint a new key and orphan the existing
   Zitadel DB. Now resolve it durably: prefer the in-cluster Secret (matches the
   running DB), else the value persisted in harmony_secret, else generate; then
   mirror into harmony_secret so a namespace reset reuses it. (New ZitadelMasterkey
   persisted secret, mirroring ZitadelAdmin.)

2. The chart's `zitadel-setup` init Job is a pre-upgrade hook that fails when
   re-run on `helm upgrade`. Set install_only=true: once the release exists, skip
   the helm op entirely. Zitadel is install-once + stateful; re-deploys converge
   via ZitadelSetupScore, not by re-running the chart.
johnride added 1 commit 2026-06-26 21:46:45 +00:00
fix(zitadel): default the Zitadel CNPG cluster to a single instance
Some checks failed
Run Check Script / check (pull_request) Failing after 53s
75aed3677f
Storage durability is provided by the backing layer (Ceph); CNPG-level replication added ops/cost (and a stuck second replica on single-node clusters) without buying availability. Default instances 2 -> 1.
johnride added 1 commit 2026-06-26 22:29:25 +00:00
feat(registry-pull): pull-secret Score + app imagePullSecrets + config fill
Some checks failed
Run Check Script / check (pull_request) Failing after 52s
0127558468
Lets a namespace-scoped deploy pull private images declaratively, derived from
context (no procedural step):

- harmony: RegistryPullSecretScore — builds a kubernetes.io/dockerconfigjson
  Secret in the namespace from pull-only registry creds (idempotent, namespaced).
- harmony_app: DeployConfig.image_pull_secrets renders into each pod's
  spec.template.spec.imagePullSecrets (mirrors extra_env / secret_file_mounts).
- harmony_config: get_or_prompt now fills only the MISSING fields of a partial
  stored value (e.g. when a Secret struct gains a field) and persists the merge,
  instead of failing or re-prompting everything. Prompting is behind a
  FieldPrompter seam so the flow is unit-tested (no terminal).
johnride added 1 commit 2026-07-03 22:01:19 +00:00
feat(app): add --force-conflicts argument to app cli that will be passed down to helm upgrade --install
Some checks failed
Run Check Script / check (pull_request) Failing after 2m2s
17b10b47e5
johnride force-pushed feat/compose-build-args-zitadel-readiness from 17b10b47e5 to 735547cfec 2026-07-03 22:06:34 +00:00 Compare
johnride force-pushed feat/compose-build-args-zitadel-readiness from 735547cfec to 1fb1e7e811 2026-07-03 22:09:44 +00:00 Compare
johnride force-pushed feat/compose-build-args-zitadel-readiness from 1fb1e7e811 to 49ffea99a5 2026-07-03 22:22:25 +00:00 Compare
johnride closed this pull request 2026-07-14 10:53:45 +00:00
johnride deleted branch feat/compose-build-args-zitadel-readiness 2026-07-14 10:54:02 +00:00
All checks were successful
Run Check Script / check (pull_request) Successful in 2m13s

Pull request closed

Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: NationTech/harmony#340
No description provided.