Onboarding flow: repo-created → scaffold PR + factory PR + cluster resources #10

Open
opened 2026-09-22 19:42:04 +00:00 by ginjiruu · 0 comments
Owner

What to build

The repo-creation onboarding flow (repurposing the existing create-repo-ci flow in the automation repo):

  1. Owner filter fix: the repo-created sensor's scope currently relies on a phantom field (Argo Workflows vocabulary that is silently dropped by argo-events) — replace it with a real data filter on the repository owner in the event payload, scoped to the lab org (designed so it can be widened to other orgs later without rework).
  2. Provisioning workflow on repo creation in lab:
    • create the app namespace (named <owner>-<repo>, RFC1123-safe) + destination service account + curated Role on mini (workloads, core resources, networkpolicy, gateway-api routes, cert-manager listener kinds; no RBAC or cluster-scoped kinds);
    • open a scaffold PR in the new repo: .ci/ (kustomization + repo-prefixed tag-build sensor + apps.yaml repo-declared app config) + k8s/ starter, in the shape proven by the authz-bridge ticket, parameterized by owner/repo (sensor filter, labels, prefixed names);
    • open a factory PR into lab/dev-apps-source: the per-repo directory with the CI Application (static: .ci/ → mini/ci, SA ci-argocd, allowEmpty: false), a per-repo ApplicationSet that generates the app Application from the repo's .ci/apps.yaml (see design notes below), and the AppProject (destinations: ci + <owner>-<repo>) with an appRBAC rule for the repo creator.
  3. Harbor robot step is out of scope — the PoC uses the shared admin account (tracked separately, blocked on the VSO work).

Design notes: app Application via ApplicationSet (repo-declared manifest location)

The app Application is not a static factory object — its source path is declared by the repo itself, so the repo stays the source of truth for its own layout. System-service repos (like authz-bridge, deployed by Flux from deployments/) have no app Application at all; app-style repos declare their manifest path in the repo.

  • Mechanism: one Argo CD ApplicationSet per repo (Git file generator, goTemplate: true, goTemplateOptions: ["missingkey=error"]) pointing at the app repo's .ci/apps.yaml. The file is a YAML array; each element generates one Application:

    # .ci/apps.yaml (in the app repo)
    - name: app
      sourcePath: k8s/
      namespace: <owner>-<repo>
    
  • Template: project pinned statically in the ApplicationSet (never repo-templated — with a templated project field, repos could grant themselves projects with excessive permissions; Argo CD docs require the source of truth to be admin-controlled in that case). destination.name: mini, source.path: '{{.sourcePath}}', destination.namespace: '{{.namespace}}', destination SA from the provisioning convention, allowEmpty: true (a fresh scaffold with an empty k8s/ is a valid state).

  • Placement: ApplicationSet objects live in the argocd namespace (the controller enforces generated-app namespace = appset namespace) — the existing projects/argocd Flux Kustomization from #9 covers this; the factory PR adds them alongside the AppProject.

  • Naming constraint: a file-content key literally named path overwrites the generator's own path params (verified in applicationset/generators/repo_path_utils.go) — use sourcePath.

  • Failure semantics (verified in argo-cd v3 source, applicationset/controllers/applicationset_controller.go): a broken config file (bad YAML / missing key under missingkey=error) fails generation and the reconcile aborts before the create/prune phase — last-good Applications keep running, the ApplicationSet gets an ErrorOccurred status condition, and it requeues on the next poll. A typo in the repo cannot delete a live app; apps are pruned only when their file entry is deliberately removed.

  • Latency: the Git generator polls every 3 min by default (requeueAfterSeconds tunable per ApplicationSet). Forgejo is not in the tested ApplicationSet webhook list (GitHub/GitLab only), so config changes propagate on the poll interval; the argocd.argoproj.io/application-set-refresh annotation forces an immediate refresh.

  • CI Application stays static in the factory entry (100% conventional: .ci/ci ns, SA ci-argocd) — the CI chain is decoupled from ApplicationSet generation, and the repo-declared file only covers the variable (app) half.

Acceptance criteria

  • Creating a throwaway repo in the lab org triggers the onboarding workflow
  • The app namespace + service account + Role exist on mini
  • The scaffold PR appears in the new repo, correctly parameterized (sensor filter matches the new repo, labels, names, .ci/apps.yaml present)
  • The factory PR appears in lab/dev-apps-source with the CI Application, the per-repo ApplicationSet, and the AppProject carrying the creator's appRBAC rule
  • After merging, the ApplicationSet generates the app Application from the repo's .ci/apps.yaml (picked up by Argo CD, empty starter tolerated via allowEmpty)
  • Editing the repo's .ci/apps.yaml (e.g. sourcePath) updates the generated Application within the poll interval; a broken file leaves the last-good Application running with an ErrorOccurred condition on the ApplicationSet
  • Repo creation in other orgs does not trigger the flow
  • After merging both PRs, the new repo's CI works end-to-end: tag push → sensor → build → Harbor image

Blocked by

  • #9 — authz-bridge: first end-to-end (hand-written scaffold + factory entry)
## What to build The repo-creation onboarding flow (repurposing the existing `create-repo-ci` flow in the automation repo): 1. **Owner filter fix**: the repo-created sensor's scope currently relies on a phantom field (Argo Workflows vocabulary that is silently dropped by argo-events) — replace it with a real data filter on the repository owner in the event payload, scoped to the `lab` org (designed so it can be widened to other orgs later without rework). 2. **Provisioning workflow** on repo creation in `lab`: - create the app namespace (named `<owner>-<repo>`, RFC1123-safe) + destination service account + curated Role on mini (workloads, core resources, networkpolicy, gateway-api routes, cert-manager listener kinds; no RBAC or cluster-scoped kinds); - open a **scaffold PR** in the new repo: `.ci/` (kustomization + repo-prefixed tag-build sensor + `apps.yaml` repo-declared app config) + `k8s/` starter, in the shape proven by the authz-bridge ticket, parameterized by owner/repo (sensor filter, labels, prefixed names); - open a **factory PR** into `lab/dev-apps-source`: the per-repo directory with the CI Application (static: `.ci/` → mini/`ci`, SA `ci-argocd`, `allowEmpty: false`), a **per-repo ApplicationSet** that generates the app Application from the repo's `.ci/apps.yaml` (see design notes below), and the AppProject (destinations: `ci` + `<owner>-<repo>`) with an `appRBAC` rule for the repo creator. 3. **Harbor robot step is out of scope** — the PoC uses the shared admin account (tracked separately, blocked on the VSO work). ### Design notes: app Application via ApplicationSet (repo-declared manifest location) The app Application is **not** a static factory object — its source path is declared by the repo itself, so the repo stays the source of truth for its own layout. System-service repos (like authz-bridge, deployed by Flux from `deployments/`) have no app Application at all; app-style repos declare their manifest path in the repo. - **Mechanism**: one Argo CD ApplicationSet per repo (Git **file** generator, `goTemplate: true`, `goTemplateOptions: ["missingkey=error"]`) pointing at the app repo's `.ci/apps.yaml`. The file is a YAML array; each element generates one Application: ```yaml # .ci/apps.yaml (in the app repo) - name: app sourcePath: k8s/ namespace: <owner>-<repo> ``` - **Template**: `project` pinned **statically** in the ApplicationSet (never repo-templated — with a templated project field, repos could grant themselves projects with excessive permissions; Argo CD docs require the source of truth to be admin-controlled in that case). `destination.name: mini`, `source.path: '{{.sourcePath}}'`, `destination.namespace: '{{.namespace}}'`, destination SA from the provisioning convention, `allowEmpty: true` (a fresh scaffold with an empty `k8s/` is a valid state). - **Placement**: ApplicationSet objects live in the `argocd` namespace (the controller enforces generated-app namespace = appset namespace) — the existing `projects/` → `argocd` Flux Kustomization from #9 covers this; the factory PR adds them alongside the AppProject. - **Naming constraint**: a file-content key literally named `path` overwrites the generator's own `path` params (verified in `applicationset/generators/repo_path_utils.go`) — use `sourcePath`. - **Failure semantics (verified in argo-cd v3 source, `applicationset/controllers/applicationset_controller.go`)**: a broken config file (bad YAML / missing key under `missingkey=error`) fails generation and the reconcile **aborts before the create/prune phase** — last-good Applications keep running, the ApplicationSet gets an `ErrorOccurred` status condition, and it requeues on the next poll. A typo in the repo cannot delete a live app; apps are pruned only when their file entry is deliberately removed. - **Latency**: the Git generator polls every 3 min by default (`requeueAfterSeconds` tunable per ApplicationSet). Forgejo is not in the tested ApplicationSet webhook list (GitHub/GitLab only), so config changes propagate on the poll interval; the `argocd.argoproj.io/application-set-refresh` annotation forces an immediate refresh. - **CI Application stays static** in the factory entry (100% conventional: `.ci/` → `ci` ns, SA `ci-argocd`) — the CI chain is decoupled from ApplicationSet generation, and the repo-declared file only covers the variable (app) half. ## Acceptance criteria - [ ] Creating a throwaway repo in the `lab` org triggers the onboarding workflow - [ ] The app namespace + service account + Role exist on mini - [ ] The scaffold PR appears in the new repo, correctly parameterized (sensor filter matches the new repo, labels, names, `.ci/apps.yaml` present) - [ ] The factory PR appears in `lab/dev-apps-source` with the CI Application, the per-repo ApplicationSet, and the AppProject carrying the creator's appRBAC rule - [ ] After merging, the ApplicationSet generates the app Application from the repo's `.ci/apps.yaml` (picked up by Argo CD, empty starter tolerated via `allowEmpty`) - [ ] Editing the repo's `.ci/apps.yaml` (e.g. `sourcePath`) updates the generated Application within the poll interval; a broken file leaves the last-good Application running with an `ErrorOccurred` condition on the ApplicationSet - [ ] Repo creation in other orgs does not trigger the flow - [ ] After merging both PRs, the new repo's CI works end-to-end: tag push → sensor → build → Harbor image ## Blocked by - #9 — authz-bridge: first end-to-end (hand-written scaffold + factory entry)
Sign in to join this conversation.
No labels
ready-for-agent
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lab/automation#10
No description provided.