Commit Graph

3 Commits

Author SHA1 Message Date
hatiyildiz
354ddd5711 chore(ci): add auto-bump-images + pkg/** path filter to all build-*-controller workflows (Closes #2006)
TBD-A69. PR #2005 fixed build-organization-controller.yaml only. The
other six controller workflows (application, blueprint, continuum,
environment, sandbox, useraccess) had the same gaps that caused the
#1997 18h deploy gap:

- application-controller: missing pkg/** in path filter (auto-bump
  already present from earlier work).
- blueprint, continuum, environment, useraccess: missing BOTH pkg/**
  path filter AND auto-bump pipeline (permissions promotion +
  values.yaml bump + commit/push + blueprint-release dispatch).
- sandbox: already complete (pkg/** + auto-bump to platform/sandbox
  chart) — left untouched.

Each updated workflow inherits the canonical shape from
build-organization-controller.yaml (PR #2005):

  1. `core/controllers/pkg/**` added to BOTH push.paths and
     pull_request.paths. Without this, a fix that only touches the
     shared HTTP-client tree (gitea/keycloak/kc-mappers) silently
     fails to rebuild the controller image.
  2. `permissions.contents: write` + `actions: write` so the build
     job can push the values.yaml bump and dispatch the downstream
     chart re-publish.
  3. An awk-scoped `Bump controllers.<who>.image.tag in values.yaml`
     step that updates ONLY the targeted controller's tag (verified
     locally — sibling tags remain untouched).
  4. A commit/push step that bumps
     products/catalyst/chart/values.yaml (or
     products/continuum/chart/values.yaml for continuum, which has
     its own chart).
  5. A `gh workflow run blueprint-release.yaml` dispatch so the
     bot-pushed commit fires the downstream chart re-publish
     (GitHub Actions silently filters bot pushes from path-trigger
     workflows otherwise).

Adds two new files to lock the shape in:

  - `scripts/check-controller-workflow-uniformity.sh` — a CI
    regression test that grep-asserts every controller workflow has
    the canonical pkg/** filter + auto-bump pipeline. Fails loudly
    if any new controller workflow ships without the canonical shape,
    or if an existing one regresses.
  - `.github/workflows/check-controller-workflow-uniformity.yaml` —
    push-on-touch + pull_request-on-touch event-driven wrapper that
    runs the script. Mirrors the shape of check-vendor-coupling.yaml.

Verified locally:
  - YAML syntax valid for all 7 controller workflows + the new check
    workflow.
  - Regression script passes on all 7 controller workflows.
  - Simulated awk bumps against products/catalyst/chart/values.yaml
    and products/continuum/chart/values.yaml — each script bumps
    ONLY the targeted controller's tag, sibling tags untouched.

No chart bumps. No Go/chart changes. CI-workflow-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 02:08:14 +02:00
e3mrah
66fd0bbae3
refactor(controllers): promote duplicated internal/ packages to shared core/controllers/internal/ (CC1, #1095) (#1135)
Slice CC1 of EPIC-0 (#1095) — Coordinator-led consolidation. The 5 Group C
controllers (slices C1-C5: organization, environment, blueprint, application,
useraccess) all merged with their own per-controller go.mod + per-controller
internal/ tree. This PR canonicalizes the shared layout per
`02-implementer-canon.md` §1+§2:

  * One go.mod at core/controllers/go.mod (Path A — single shared module)
  * Shared helpers under core/controllers/internal/:
      - semver/    (was: blueprint/internal/semver + application/internal/semver,
                    now exposes blueprint's IsValidRange + app's IsExact, with
                    the union of both test corpora)
      - placement/ (was: application/internal/placement; promoted per seam map)
      - render/    (was: application/internal/render; promoted per seam map)
      - labels/    (was: useraccess/internal/labels; promoted per seam map —
                    Manara-style scope matcher, owner-of-record C5)

Module-discipline decision (Path A vs Path B): Path A. The 5 controllers'
go.mod files use the same controller-runtime v0.19.0, k8s.io/* @ 0.31.x,
sigs.k8s.io/yaml v1.4.0, etc. The only drift was organization-controller
on k8s.io/api 0.31.0 vs the others on 0.31.1 — a trivial bump.
Independent dep-version pinning would only be valuable if a controller
needed a hostile dep the others shouldn't pull; nothing in the current
tree is hostile.

Containerfiles + workflows updated:
  * 5 Containerfiles now COPY core/controllers/{go.mod,go.sum,internal/}
    plus the per-controller tree from a repo-root build context.
  * 4 per-controller workflows (application/environment/organization/
    useraccess; blueprint-controller has no dedicated workflow yet) now
    trigger on core/controllers/{<name>/**, internal/**, go.mod, go.sum}
    and run go vet + go test scoped to their own tree + shared internal.
  * useraccess workflow context flipped from core/controllers/useraccess
    to . (repo root) so the Containerfile can reach the shared go.mod.

Subpackages NOT promoted in this PR (compromise — flagged for follow-up):
  * gitea/ — 4 of 5 controllers each ship a Gitea HTTP client. The APIs
    DIVERGE (organization has Org+Repo CRUD with Repo struct return values;
    application/blueprint/environment have File CRUD with Org-not-found
    sentinel). A SUPERSET package would require renaming methods (e.g.
    EnsureRepo collides on signature) which crosses the brief's "no API
    redesign" line. CC2 follow-up slice should design the unified surface
    before promoting.
  * validate/ — application's package validates Application.spec.parameters
    against a JSON Schema (santhosh-tekuri lib); blueprint's validates
    Blueprint CR business rules (semver-backed). Same dir name, completely
    different functions — not actually duplicates.
  * gitops/ — environment's renders Flux GitRepository for an Environment;
    organization's renders HelmRelease+Namespace for an Org. Same dir name,
    different inputs and outputs.

Test-coverage delta: pre-consolidation 134 root-level tests (sum across
5 modules); post-consolidation 133 tests. Net delta -1: blueprint and
application each had their own TestIsValidRange in their semver pkg; the
shared semver pkg's TestIsValidRange now exercises the union of both
controllers' valid+invalid input corpora — coverage strictly improved
even though one redundant test name disappeared.

Verified locally: go build + go vet + `go test -count=1 -race ./...`
all clean; all 5 controller binaries (cmd/) link successfully.

Co-authored-by: hatiyildiz <hatiyildiz@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:54:42 +04:00
e3mrah
2ab442544e
feat(controllers): land environment-controller (slice C2, #1095) (#1127)
Implements slice C2 of EPIC-0 #1095 — the environment-controller Go
binary. Watches Environment.catalyst.openova.io/v1 CRs (cluster-scoped)
and reconciles each Environment to:

1. Verify the per-Org Gitea Org exists (parent Organization gate).
   Missing org surfaces GiteaOrgReady=False + Pending phase, never
   panics or crashloops.

2. Track the canonical branch name for this Environment in
   status.giteaRepoRef.{org,branch} per NAMING-CONVENTION.md §11.2
   item 1 (develop/staging/main ↔ dev/stg/prod; uat/poc map to their
   own branch name).

3. Idempotently write per-vCluster Flux GitRepository manifests into
   the Org's Gitea repo at the canonical path
   `clusters/<host-cluster>/environments/<env-name>/gitrepository.yaml`
   per NAMING §11.2 item 3. Multi-region Environments fan out one
   commit per spec.regions[]. Identical bytes short-circuit (zero
   spurious commits in repo history); drift triggers an overwrite
   with the existing blob SHA.

4. Surface the canonical JetStream subject prefix
   `ws.{organizationRef}-{envType}.>` on
   status.jetstreamSubjectPrefix per NAMING §11.2 item 4 +
   ARCHITECTURE.md §5. Per-Environment NATS Stream CR creation is
   OUT OF SCOPE here — NACK isn't installed yet (future slice).

5. Set status.phase, status.regionCount (printer column),
   status.vclusters[], status.observedGeneration, and the
   Ready/GiteaOrgReady/GitRepositoryWritten conditions.

Architecture rules honored (per docs/INVIOLABLE-PRINCIPLES.md +
docs/adr/0001-catalyst-control-plane-architecture.md):

- Flux is the only reconciler in production. The controller writes
  manifests to Gitea; Flux applies them. NO kubectl apply, NO
  helm install, NO exec.Command in the codebase.
- Crossplane is cloud-only. This controller is K8s-to-K8s native
  via controller-runtime + client-go.
- DR is a Placement, not an Env Type. The controller treats
  spec.envType as the schema-validated enum {prod|stg|uat|dev|poc}
  with no special-case for DR (per NAMING §11.1).
- Sovereign-independent. The Gitea base URL, secret ref, branch
  suffix, commit author, and Flux interval are ALL runtime config
  (per Inviolable Principle #4 — never hardcode).

Files:
- core/controllers/environment/api/v1/types.go — Environment
  Go types matching the CRD; hand-written DeepCopy to avoid
  build-time codegen tool dependency.
- core/controllers/environment/internal/gitea/client.go — minimal
  GitHub-compatible REST client targeting Gitea's /api/v1
  (GET /orgs/{org}, GET/POST/PUT /repos/{org}/{repo}/contents/{path}).
  Idempotent UpsertFile with byte-equality short-circuit + blob-SHA
  conflict refusal.
- core/controllers/environment/internal/gitops/render.go — pure
  template rendering of the Flux GitRepository CR. Deterministic
  field ordering for byte-equality idempotency.
- core/controllers/environment/internal/controller/environment_controller.go
  — reconciler: validate spec, gate on Gitea Org, fan out per-region
  manifest writes, set status + conditions.
- core/controllers/environment/cmd/main.go — controller-runtime
  manager entry point with leader election.
- core/controllers/environment/Containerfile — two-stage build,
  alpine:3.20 runtime, non-root UID 65534, ENTRYPOINT.
- core/controllers/environment/deploy/rbac.yaml — ClusterRole
  watching Environments + status subresource + leader election lease.
- .github/workflows/build-environment-controller.yaml — CI mirrors
  build-cert-manager-dynadot-webhook.yaml: vet + race tests,
  docker buildx + cosign keyless sign + SBOM attest, push to
  ghcr.io/openova-io/openova/environment-controller.

Tests (35 total, all GREEN, race-detector enabled):

- internal/controller (T1–T11):
  T1 happy-path single-region reconcile
  T2 idempotent re-reconcile (zero spurious commits)
  T3 parent Org missing → Pending + GiteaOrgReady=False (no panic)
  T4 multi-region fan-out (3 commits, 3 regions)
  T5 drift detection — operator hand-edit gets overwritten
  T6 placement-vs-regions cardinality violations → Failed
  T7 env_type→branch mapping table
  T8 Gitea repo missing → Pending + GiteaRepoMissing reason
  T9 partial-failure one region → Degraded with that region Failed
  T10 Config.Defaults applies the documented defaults
  T11 NotFound between dequeue and Get is benign

- internal/gitea: GET /orgs OK + 404 + 500; UpsertFile create / idempotent /
  update with SHA / repo-not-found; pathEscape preserves slashes;
  arg-validation.

- internal/gitops: BranchForEnvType / JetStreamSubjectPrefix /
  HostClusterName (with override) / GitRepositoryPath /
  RenderGitRepository (deterministic + complete + anonymous +
  default interval + required-field validation) / EnvironmentName.

go vet ./... clean. go test -count=1 -race ./... GREEN.

Out of scope per slice brief: organization-controller (C1),
blueprint-controller (C3), application-controller (C4),
useraccess-controller (C5), catalyst-api codebase changes, NACK
install, per-Environment NATS Stream CRs.

Co-authored-by: hatiyildiz <hati@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:05:53 +04:00