diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml new file mode 100644 index 0000000..dbe4778 --- /dev/null +++ b/.github/workflows/cd.yaml @@ -0,0 +1,40 @@ +name: ๐Ÿš€ CD + +# Publishes the org's declarative GitHub state (deploy/) as a cosign-signed OCI +# artifact consumed by the platform cluster's `github-config` tenant. There is +# NO container image here โ€” only manifests โ€” so it delegates to the shared +# manifests-only publish workflow in devantler-tech/reusable-workflows (the +# manifests-only sibling of publish-app.yaml). +# +# The OCI path is `github-config` (not the repo name) because `.github` is an +# invalid OCI path component (leading dot) โ€” hence the oci-name override. +# +# Because signing runs INSIDE the reusable workflow, the cosign certificate +# identity (OIDC subject) is that workflow's path โ€” +# https://github.com/devantler-tech/reusable-workflows/.github/workflows/publish-manifests.yaml@ +# โ€” NOT this repo's cd.yaml. The platform `github-config` OCIRepository's +# verify.matchOIDCIdentity.subject must match that (see +# k8s/providers/hetzner/github/sync.yaml in devantler-tech/platform). + +on: + push: + tags: + - "v*" + +permissions: {} + +jobs: + publish-manifests: + name: ๐Ÿ—ณ๏ธ Publish manifests + permissions: + contents: read # checkout + packages: write # push the OCI artifact to GHCR + id-token: write # keyless cosign signing (Fulcio + Rekor via GitHub OIDC) + # Pinned to the released commit SHA (zizmor blanket policy requires a hash; + # the SHA must be reachable from a tag, or zizmor flags it as an impostor). + # Renovate manages this pin, as it does for publish-app.yaml elsewhere. + # The platform verifier matches `@.+$`, so the ref does not affect cosign + # verification. + uses: devantler-tech/reusable-workflows/.github/workflows/publish-manifests.yaml@f974f62a8cb396130fa084d209ad1fc17e99b9fc # v5.6.1 + with: + oci-name: devantler-tech/github-config diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..31f48cc --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,43 @@ +name: CI + +# Validates the declarative GitHub state in deploy/ on every PR, and reports the +# `CI - Required Checks` status the org ruleset requires before merge. (cd.yaml +# only runs on v* tags, so without this no PR-time required check is produced and +# the PR stays merge-BLOCKED.) + +on: + pull_request: + merge_group: + +permissions: {} + +jobs: + validate-manifests: + name: ๐Ÿงน Validate manifests + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: ๐Ÿ“‘ Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: ๐Ÿงน Validate deploy/ kustomize build + # kubectl (with built-in kustomize) is preinstalled on the runner. A + # successful build proves the manifests are well-formed; the Crossplane + # CRDs are applied/validated on-cluster, not here. + run: kubectl kustomize deploy/ > /dev/null + + ci-required-checks: + name: CI - Required Checks + runs-on: ubuntu-latest + needs: [validate-manifests] + if: always() + permissions: {} + steps: + - name: ๐Ÿ“Š Evaluate job results + uses: devantler-tech/actions/aggregate-job-checks@8ce19528eda1ef50f3d028b685c53aebaed6ae62 # v5.4.1 + with: + job-results: >- + ${{ needs.validate-manifests.result }} diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..10eb80b --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,22 @@ +# `deploy/` โ€” declarative GitHub state + +This directory is the source of truth for the devantler-tech org's GitHub +configuration, expressed as [Crossplane](https://crossplane.io) managed +resources (via +[provider-upjet-github](https://github.com/crossplane-contrib/provider-upjet-github)). + +On every `v*` tag, [`cd.yaml`](../.github/workflows/cd.yaml) publishes this +directory as a **cosign-signed OCI artifact** to +`ghcr.io/devantler-tech/github-config/manifests`. The +[platform](https://github.com/devantler-tech/platform) cluster onboards it as +the **`github-config` tenant**: it verifies the signature, then Flux + Crossplane +reconcile the live GitHub org to match these manifests โ€” including reverting +out-of-band changes made in the GitHub UI. + +- `repositories/` โ€” one `Repository` per managed repo. + +See the platform repo's +[`docs/github-management.md`](https://github.com/devantler-tech/platform/blob/main/docs/github-management.md) +for the architecture, the GitHub App credential setup, and the **Observe-first** +adoption flow for bringing an existing repository under management without any +risk of recreating or deleting it. diff --git a/deploy/kustomization.yaml b/deploy/kustomization.yaml new file mode 100644 index 0000000..51ba1ac --- /dev/null +++ b/deploy/kustomization.yaml @@ -0,0 +1,10 @@ +# Root of the manifests artifact published to +# oci://ghcr.io/devantler-tech/github-config/manifests and applied by the +# platform's `github-config` tenant Kustomization (path: .). It carries the +# devantler-tech org's desired GitHub state as Crossplane managed resources. +# The target namespace (github-config) is injected by the platform-side +# Kustomization's targetNamespace, so manifests here stay namespace-agnostic. +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - repositories/ diff --git a/deploy/repositories/ksail.yaml b/deploy/repositories/ksail.yaml new file mode 100644 index 0000000..3336f16 --- /dev/null +++ b/deploy/repositories/ksail.yaml @@ -0,0 +1,15 @@ +apiVersion: repo.github.m.upbound.io/v1alpha1 +kind: Repository +metadata: + name: ksail + annotations: + # Binds this resource to the EXISTING devantler-tech/ksail repository + # (owner comes from the ProviderConfig credentials). + crossplane.io/external-name: ksail +spec: + # Observe-only while adopting โ€” see repositories/kustomization.yaml. + managementPolicies: ["Observe"] + forProvider: {} + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/repositories/kustomization.yaml b/deploy/repositories/kustomization.yaml new file mode 100644 index 0000000..7788c04 --- /dev/null +++ b/deploy/repositories/kustomization.yaml @@ -0,0 +1,12 @@ +# One file per managed repository. Adoption is Observe-first: a repo starts with +# managementPolicies: ["Observe"] (read-only โ€” Crossplane mirrors live state into +# status.atProvider and never writes), then forProvider is backfilled and the +# policy promoted to the full set EXCEPT Delete. Namespaced managed resources +# have no deletionPolicy; omitting Delete is what guarantees Crossplane can never +# delete a real GitHub repository. See devantler-tech/platform +# docs/github-management.md for the full adoption flow. +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - platform.yaml + - ksail.yaml diff --git a/deploy/repositories/platform.yaml b/deploy/repositories/platform.yaml new file mode 100644 index 0000000..b325825 --- /dev/null +++ b/deploy/repositories/platform.yaml @@ -0,0 +1,17 @@ +apiVersion: repo.github.m.upbound.io/v1alpha1 +kind: Repository +metadata: + name: platform + annotations: + # Binds this resource to the EXISTING devantler-tech/platform repository + # (owner comes from the ProviderConfig credentials). + crossplane.io/external-name: platform +spec: + # Observe-only while adopting: Crossplane fills status.atProvider and never + # writes to GitHub. Backfill forProvider from status.atProvider, then promote + # to ["Observe","Create","Update","LateInitialize"] (everything except Delete). + managementPolicies: ["Observe"] + forProvider: {} + providerConfigRef: + kind: ProviderConfig + name: default