feat(github): manage GitHub org declaratively via Crossplane provider-upjet-github#2039
Merged
Conversation
…-upjet-github GitHub repos/rulesets/labels become Kubernetes custom resources reconciled by provider-upjet-github, replacing manual GitHub UI administration. - crossplane core HelmRelease (prod-only controllers overlay, chart 2.3.2) with provider.defaultActivations=[] so only explicitly activated MRDs become CRDs (~3MiB apiserver each; the provider ships 77) - dedicated `github` Flux Kustomization (dependsOn: infrastructure) so a GitHub-side stall never holds the infrastructure→apps deploy chain red - GitHub App auth: PushSecret seeds OpenBao from variables-cluster (SOPS), ExternalSecret renders the provider's app_auth JSON credential - Observe-first adoption: first two Repository MRs (platform, ksail) bound by external-name with managementPolicies=[Observe], deletionPolicy=Orphan - docs/github-management.md: architecture, credential setup, adoption flow Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…spaced MRs Rework: instead of holding cluster-scoped Repository CRs in the platform tree, the org's desired GitHub state lives in devantler-tech/.github under deploy/ and is onboarded as an isolated tenant. - switch the activation policy + ProviderConfig + managed resources to the Crossplane v2 NAMESPACED variants (repo.github.m.upbound.io), so a namespace-scoped tenant SA with a plain Role can apply them — no cluster RBAC, no ClusterRoleBinding - add the github-config tenant: namespace, least-privilege SA + Role (NOT aggregated into edit, so other tenants don't inherit GitHub powers), ghcr + GitHub App credential ExternalSecrets, namespaced ProviderConfig, and a cosign-verified OCIRepository + Kustomization that pull .github's artifact and apply it as the tenant SA - engine (provider package + activation policy + credential seed) stays platform-side; desired-state managed resources move to .github/deploy/ - remove the cluster-scoped repositories/ CRs (now in .github) - docs rewritten for the tenant + namespaced architecture Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # k8s/bases/infrastructure/vault-config/job.yaml # k8s/providers/hetzner/infrastructure/controllers/kustomization.yaml
…signer The .github repo's cd.yaml now publishes the github-config OCI artifact by calling the shared publish-manifests reusable workflow (reusable-workflows#319) instead of signing inline. Cosign keyless signing runs INSIDE that reusable workflow, so the certificate's OIDC subject is the reusable workflow's path — not .github's own cd.yaml. Update the github-config OCIRepository's verify.matchOIDCIdentity.subject to match the new signer: ^…/devantler-tech/reusable-workflows/.github/workflows/publish-manifests.yaml@.+$ (same convention as the publish-app.yaml-signed app artifacts). This must land together with .github#39 (the caller cutover) so the verified identity always matches the signer. The github-config artifact is not yet in production (both PRs are drafts), so there is no live verification to break. Validated: `kubectl kustomize k8s/providers/hetzner/github/` builds clean with the updated subject. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bespoke Flux Kustomization The dedicated `github` Flux Kustomization (clusters/prod/github-flux-kustomization.yaml + providers/hetzner/github/) diverged from the proven pattern. Rework to the standard tiers: - Crossplane core HelmRelease stays in infrastructure/controllers/crossplane/ - Provider package + ManagedResourceActivationPolicy move to the infrastructure layer (providers/hetzner/infrastructure/crossplane/), one tier after the controller — same controller-then-CR ordering as Coroot/Flagger - the GitHub state is onboarded as a normal app, bases/apps/github-config/ (namespace, SA, least-priv Role, ESO creds, namespaced ProviderConfig, seed PushSecret, cosign-verified OCIRepository+Kustomization), consumed by the existing `apps` Flux Kustomization — no bespoke kustomization - the credential seed PushSecret lives with the app (leaf apps layer) so a missing-key stall can never block the infrastructure→apps chain - delete the bespoke github layer + github-flux-kustomization.yaml; update docs/github-management.md + AGENTS.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… SOPS PushSecret Replace the variables-cluster (SOPS) PushSecret seed with a declarative, idempotent placeholder seed in the vault-config bootstrap Job: it writes REPLACE_WITH_* values to secret/infrastructure/github/app only when the secret is absent, so the maintainer overwrites them in place via the OpenBao UI/CLI and a later Job re-run never clobbers the real values. OpenBao is backed up (Raft snapshots → R2), so the manually-set values are durable without a GitOps source. - add the only-if-absent kv seed to vault-config/job.yaml (section 1b) - remove seed-github-app.yaml PushSecret + its kustomization entry - drop the now-unused vault-seed-write github paths (keep infra-github-readonly, which the github-app-credentials ExternalSecret still needs to read) - docs: credential setup is now 'overwrite the OpenBao placeholders', no SOPS Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hcr-auth Tighten the github-config tenant's privileges and move tenant-specific manifests out of the platform: - add a namespaced SecretStore (k8s/bases/apps/github-config/secretstore.yaml) backed by a dedicated 'github-config' OpenBao role scoped to infrastructure/github/* only (mirrors the wedding-app pattern); the tenant no longer uses the broad cluster-scoped openbao ClusterSecretStore - remove infra-github-readonly from the shared external-secrets role bundle (now carried solely by the dedicated github-config role) - move the github-app-credentials ExternalSecret + the namespaced ProviderConfig to devantler-tech/.github deploy/ (tenant-applied); the app SA Role gains verbs on external-secrets.io + github.m.upbound.io accordingly - drop ghcr-auth entirely: the github-config/manifests package is public, so the OCIRepository pulls anonymously (no secretRef, no ExternalSecret, no SA imagePullSecrets) - docs updated Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
|
🎉 This PR is included in version 1.64.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
4 tasks
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 free
to 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.
What
Brings the devantler-tech GitHub org under GitOps: GitHub repositories (and over time rulesets, labels, branch protection, …) become Crossplane managed resources reconciled against the GitHub API by provider-upjet-github — no more GitHub UI fiddling; out-of-band UI edits get reverted.
The org's desired state lives in devantler-tech/.github under
deploy/(merged, published as the cosign-signedgithub-configOCI artifact) and is onboarded here as a standard app (github-config) — the same pattern as every other tenant. No bespoke Flux Kustomization.Architecture (standard tiers — controller → its CRs → app)
k8s/providers/hetzner/infrastructure/controllers/crossplane/k8s/providers/hetzner/infrastructure/crossplane/github-configapp (ns, SA, least-priv Role, ESO creds, namespaced ProviderConfig, seed PushSecret, cosign-verified OCIRepository+Kustomization) — consumed by the existingappsFlux Kustomizationk8s/bases/apps/github-config/deploy/k8s/bases/infrastructure/vault-config/job.yamldocs/github-management.mdKey decisions:
appslayer like wedding-app/ascoachingogvaner; the earlier bespokegithubFlux Kustomization was removed (it diverged from the proven pattern).repo.github.m.upbound.io) — appliable by a namespace-scoped app SA with a plain Role (no ClusterRoleBinding); the Role is not aggregated intoedit, so no other tenant inherits GitHub powers.appslayer) so a missing-credential stall can never block the infrastructure→apps chain.provider.defaultActivations: []+ explicitManagedResourceActivationPolicy— only the namespaced MRDs we use become CRDs (~3MiB apiserver each).DeletefrommanagementPoliciesso neither CR deletion nor a Flux prune can delete a real repo.Maintainer follow-up (one-time, before/with rollout)
Create the org GitHub App (Repository Administration R/W, Contents R, Organization Administration R/W; install on all repos; no webhook) and add
github_app_id/github_app_installation_id/github_app_pemtok8s/clusters/prod/bootstrap/variables-cluster-secret.enc.yaml. Seed these before rollout so the app reconciles green immediately (the seed PushSecret otherwise sits red in the leaf apps layer — isolated, non-blocking).Validation
ksail workload validate(local, 337 files ✔) andksail --config ksail.prod.yaml workload validate(all github-config + crossplane groups ✔).github-configis correctly excluded from the local/docker cluster (prod-only).🤖 Generated with Claude Code