From 22a302204bf58123b351ac0c37387aa31a460bcd Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Thu, 25 Jun 2026 19:40:37 +0200 Subject: [PATCH 1/3] feat(github): manage org/repo rulesets declaratively + add v* tag protection Adopt the org's existing rulesets as Crossplane managed resources under deploy/rulesets/ (provider-upjet-github), the same Observe-first read-only flow used for repositories/ and teams/: - 10 org rulesets -> OrganizationRuleset (Observe), bound by ruleset id - ksail "Restrict deletions" + platform "Require merge queue" -> RepositoryRuleset (Observe), bound by : - net-new "Protect release tags" OrganizationRuleset (managed, the only write): block tag deletion + force-move + require v on ~ALL repos/tags provider-upjet-github v0.19.1 cannot express the other 10 org rulesets (repository_property conditions, code_quality/copilot_code_review rules, repository-target rulesets) nor push rulesets nor the new Actions workflow-execution-protections; these stay UI-managed and are documented in deploy/rulesets/README.md and tracked in #69. Validated: kubectl kustomize deploy/ builds; every manifest passes kubectl apply --dry-run=server against the live CRDs. Co-Authored-By: Claude Opus 4.8 --- deploy/README.md | 5 + deploy/kustomization.yaml | 1 + deploy/rulesets/README.md | 85 +++++++++ deploy/rulesets/kustomization.yaml | 11 ++ deploy/rulesets/organization-rulesets.yaml | 189 +++++++++++++++++++++ deploy/rulesets/repository-rulesets.yaml | 49 ++++++ deploy/rulesets/tag-protection.yaml | 62 +++++++ 7 files changed, 402 insertions(+) create mode 100644 deploy/rulesets/README.md create mode 100644 deploy/rulesets/kustomization.yaml create mode 100644 deploy/rulesets/organization-rulesets.yaml create mode 100644 deploy/rulesets/repository-rulesets.yaml create mode 100644 deploy/rulesets/tag-protection.yaml diff --git a/deploy/README.md b/deploy/README.md index bfa9433..68134c3 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -20,6 +20,11 @@ out-of-band changes made in the GitHub UI. every repo); each `.yaml` adds only that repo's Dependabot/Renovate ecosystem extras. Authoritative — out-of-band label drift is reverted. This is the Crossplane replacement for the old EndBug/label-sync workflow. +- `rulesets/` — org & repository rulesets (branch/tag protection). 12 existing + rulesets are adopted **Observe-first** (read-only) + 1 net-new `v*` tag-protection + ruleset is managed. The 10 org rulesets the provider can't yet express stay + UI-managed — see [`rulesets/README.md`](rulesets/README.md) for the full + importability matrix and the push/tag/Actions-policy analysis. See the platform repo's [`docs/github-management.md`](https://github.com/devantler-tech/platform/blob/main/docs/github-management.md) diff --git a/deploy/kustomization.yaml b/deploy/kustomization.yaml index f5511ca..328afe4 100644 --- a/deploy/kustomization.yaml +++ b/deploy/kustomization.yaml @@ -18,3 +18,4 @@ resources: - repositories/ - teams/ - labels/ + - rulesets/ diff --git a/deploy/rulesets/README.md b/deploy/rulesets/README.md new file mode 100644 index 0000000..f673e2d --- /dev/null +++ b/deploy/rulesets/README.md @@ -0,0 +1,85 @@ +# `rulesets/` — org & repository rulesets as code + +Declarative management of the devantler-tech org's **rulesets** (branch/tag protection +and policies) as Crossplane managed resources, via +[provider-upjet-github](https://github.com/crossplane-contrib/provider-upjet-github) +(`OrganizationRuleset` / `RepositoryRuleset`, from the `integrations/github` Terraform +provider). Reconciled by the platform `github-config` tenant like the rest of `deploy/`. + +## How adoption works + +- **Observe-first (read-only).** Existing rulesets are bound with + `managementPolicies: ["Observe"]` — Crossplane mirrors live GitHub state into + `status.atProvider` and **never writes, reverts, or deletes**. This is pure GitOps + *visibility*, with zero behaviour change (the same flow `repositories/` and `teams/` + used). `Delete` is omitted everywhere, so a CR/Flux prune can never delete a real + ruleset. +- **external-name = the import id.** `OrganizationRuleset` → the numeric ruleset id + (`gh api orgs/devantler-tech/rulesets`); `RepositoryRuleset` → `:`. +- **forProvider is identity-only** on the Observe imports (`name`/`target`/ + `enforcement`). ⚠️ **Do not** promote an import past `Observe` without first + backfilling its full `rules`/`conditions`/`bypassActors` from the observed + `status.atProvider` — the provider's round-trip is lossy, so a partial `forProvider` + under `Update` would wipe live rules. + +## What is managed here + +| File | Rulesets | Policy | +|---|---|---| +| `organization-rulesets.yaml` | 10 org rulesets | Observe (read-only import) | +| `repository-rulesets.yaml` | `ksail` "Restrict deletions", `platform` "Require merge queue" | Observe (read-only import) | +| `tag-protection.yaml` | **Protect release tags** (net-new) | Managed (Create) — block tag delete + force-move + require `v` | + +The 10 imported org rulesets: Block force pushes · Require a pull request before +merging · Require conversation resolution before merging · Require linear history · +Require signed commits · Require status checks to pass · Restrict deletions · Restrict +branch names · Restrict commit metadata · Require workflows (DependencyReview). + +## What stays UI-managed, and why + +`provider-upjet-github` v0.19.1 has a **narrower** ruleset schema than GitHub's API. +Verified against the live CRDs, it does **not** support: + +- **`repository_property` conditions** (custom-property scoping) — only `refName`, + `repositoryName`, `repositoryId`. +- **Rule types** `code_quality`, `copilot_code_review`, `repository_transfer`, + `repository_name`, and the push-file rules (`file_path_restriction`, `max_file_size`, + `file_extension_restriction`, `max_file_path_length`). +- **Target `repository`** (only `branch`, `tag`, `push`). +- **Bypass actor `EnterpriseOwner`**. + +So **10 of the 20 org rulesets cannot be faithfully expressed** and remain UI-managed: + +| Ruleset (org) | Blocked by | +|---|---| +| Require code scanning results | `repository_property` condition (custom property `Type`) | +| Require workflows … EnableAutoMerge | `repository_property` condition | +| Require workflows … LintDocumentation | `repository_property` condition | +| Require workflows … ScanGitHubActions | `repository_property` condition | +| Require workflows for .NET | `repository_property` condition (`language`) | +| Require workflows for Go | `repository_property` condition (`language`) | +| Require code quality results | rule type `code_quality` unsupported | +| Automatically request Copilot code review | rule type `copilot_code_review` unsupported (also disabled) | +| restrict-names | target `repository` unsupported | +| restrict-transfers | target `repository` unsupported (+ `EnterpriseOwner` bypass) | + +These are tracked for re-adoption as the provider gains support in +[#69](https://github.com/devantler-tech/.github/issues/69) (`roadmap`). Re-home each +here Observe-first once expressible. + +## Push / tag / Actions-policy considerations + +- **Push rulesets** — none exist, and **not adoptable**: the provider supports + `target: push` (beta) but none of the push-file rule types above, so a push ruleset + can't be expressed. Tracked in [#69](https://github.com/devantler-tech/.github/issues/69). +- **Tag rulesets** — none existed; **added** here (`tag-protection.yaml`). Makes release + tags immutable (block delete + force-move) and well-formed (`v`). See that + file's header for the team-vs-enterprise tier caveat on the name-pattern rule and its + fallback. +- **Actions policies** — the 2026-06-18 + [workflow execution protections](https://github.blog/changelog/2026-06-18-control-who-and-what-triggers-github-actions-workflows/) + (actor + event allow-lists controlling who/what triggers workflows, delivered as org + rulesets scoped by **custom properties**) are **not adoptable**: the new rule types + aren't in the provider and the feature relies on the `repository_property` scoping the + provider lacks. Tracked in [#69](https://github.com/devantler-tech/.github/issues/69); + revisit when the provider catches up. diff --git a/deploy/rulesets/kustomization.yaml b/deploy/rulesets/kustomization.yaml new file mode 100644 index 0000000..5da611d --- /dev/null +++ b/deploy/rulesets/kustomization.yaml @@ -0,0 +1,11 @@ +# Org & repository rulesets as Crossplane managed resources (provider-upjet-github). +# 12 existing rulesets are adopted Observe-first (read-only) and 1 net-new tag ruleset +# is managed (Create). The 10 org rulesets the provider cannot express remain +# UI-managed — see ./README.md for the full importability matrix and the push/tag/ +# Actions-policy analysis. Included by ../kustomization.yaml. +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - organization-rulesets.yaml + - repository-rulesets.yaml + - tag-protection.yaml diff --git a/deploy/rulesets/organization-rulesets.yaml b/deploy/rulesets/organization-rulesets.yaml new file mode 100644 index 0000000..8e62253 --- /dev/null +++ b/deploy/rulesets/organization-rulesets.yaml @@ -0,0 +1,189 @@ +# Org-level rulesets — adopted Observe-first (read-only). +# +# These bind the org's EXISTING UI-created organization rulesets to Crossplane +# `OrganizationRuleset` managed resources (provider-upjet-github, terraform +# github_organization_ruleset). `managementPolicies: ["Observe"]` means Crossplane +# only MIRRORS live state into status.atProvider — it never writes, never reverts, +# never deletes. This brings the org's branch-protection posture under GitOps +# VISIBILITY with zero behaviour change, exactly how repositories/ and teams/ were +# adopted (see ../README.md and the platform repo's docs/github-management.md). +# +# external-name = the numeric ruleset ID (the provider's terraform import id: +# `terraform import github_organization_ruleset.example `). Find an id with +# `gh api orgs/devantler-tech/rulesets`. +# +# forProvider is intentionally MINIMAL (identity only: name/target/enforcement) — +# Observe never reads it. Do NOT add the full rules/conditions/bypassActors here +# and do NOT promote managementPolicies past Observe without FIRST backfilling +# forProvider from the observed `status.atProvider` (the provider's round-trip is +# lossy; a partial forProvider under Update would wipe live rules). The rules/ +# README documents this and the importability matrix. +# +# Only the 10 org rulesets the provider can FAITHFULLY express live here. The other +# 10 (repository_property conditions, code_quality/copilot_code_review rule types, +# and repository-target rulesets) are NOT expressible by provider-upjet-github +# v0.19.1 and remain UI-managed — see ./README.md and the tracking issue. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: block-force-pushes + annotations: + crossplane.io/external-name: "6986186" +spec: + managementPolicies: ["Observe"] + forProvider: + name: Block force pushes + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-pull-request + annotations: + crossplane.io/external-name: "4134020" +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require a pull request before merging + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-conversation-resolution + annotations: + crossplane.io/external-name: "6986190" +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require conversation resolution before merging + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-linear-history + annotations: + crossplane.io/external-name: "6986179" +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require linear history + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-signed-commits + annotations: + crossplane.io/external-name: "5397812" +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require signed commits + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-status-checks + annotations: + crossplane.io/external-name: "14974342" +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require status checks to pass + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: restrict-deletions + annotations: + crossplane.io/external-name: "6986171" +spec: + managementPolicies: ["Observe"] + forProvider: + name: Restrict deletions + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: restrict-branch-names + annotations: + crossplane.io/external-name: "4133993" +spec: + managementPolicies: ["Observe"] + forProvider: + name: Restrict branch names + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: restrict-commit-metadata + annotations: + crossplane.io/external-name: "4134015" +spec: + managementPolicies: ["Observe"] + forProvider: + name: Restrict commit metadata + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default +--- +# Newer "Require workflows to pass" rule (API rule type `workflows`, exposed by the +# provider as `requiredWorkflows`). Only the org-wide DependencyReview variant is +# importable — it conditions on repository_name `~ALL`. The other "Require +# workflows …" rulesets condition on repository_property (custom properties), which +# the provider cannot express; they stay UI-managed (see ./README.md). +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-workflows-dependency-review + annotations: + crossplane.io/external-name: "17213449" +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require workflows to pass before merging - DependencyReview + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/repository-rulesets.yaml b/deploy/rulesets/repository-rulesets.yaml new file mode 100644 index 0000000..484233d --- /dev/null +++ b/deploy/rulesets/repository-rulesets.yaml @@ -0,0 +1,49 @@ +# Repository-level rulesets — adopted Observe-first (read-only). +# +# Two repos carry their OWN ruleset (beyond the inherited org rulesets), bound here +# to Crossplane `RepositoryRuleset` managed resources (provider-upjet-github, +# terraform github_repository_ruleset). As with the org rulesets, +# `managementPolicies: ["Observe"]` is read-only — Crossplane mirrors live state and +# never writes. external-name = `:` (the provider's terraform +# import id: `terraform import github_repository_ruleset.example :`) — +# matching the `maintainers:` convention already used in ../teams/maintainers.yaml. +# +# forProvider is identity-only (repository/name/target/enforcement); see the warning +# in ./organization-rulesets.yaml before promoting past Observe. +apiVersion: repo.github.m.upbound.io/v1alpha1 +kind: RepositoryRuleset +metadata: + name: ksail-restrict-deletions + annotations: + # ksail's own "Restrict deletions" rule, scoped to refs/heads/benchmark-data + # (the long-lived benchmark-data branch) — narrower than the org-wide rule. + crossplane.io/external-name: "ksail:14699634" +spec: + managementPolicies: ["Observe"] + forProvider: + repository: ksail + name: Restrict deletions + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: repo.github.m.upbound.io/v1alpha1 +kind: RepositoryRuleset +metadata: + name: platform-require-merge-queue + annotations: + # platform's "Require merge queue" rule on the default branch (merge_queue is a + # repo-only rule type; org rulesets cannot carry it). + crossplane.io/external-name: "platform:5275020" +spec: + managementPolicies: ["Observe"] + forProvider: + repository: platform + name: Require merge queue + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/tag-protection.yaml b/deploy/rulesets/tag-protection.yaml new file mode 100644 index 0000000..300a3fd --- /dev/null +++ b/deploy/rulesets/tag-protection.yaml @@ -0,0 +1,62 @@ +# Protect release tags — the ONE net-new, actually-managed ruleset in deploy/rulesets/. +# +# The org ships every product by tagging a `v*` release (each repo's cd.yaml triggers +# on `v*`), yet had NO tag ruleset — release tags could be deleted or force-moved. +# This `OrganizationRuleset` (target: tag, ~ALL repos, ~ALL tags) makes release tags +# immutable and well-formed: +# - deletion: block deleting a tag +# - nonFastForward: block force-updating (re-pointing) a tag +# - tagNamePattern: require `v` (e.g. v1.2.3, v1.2.3-rc.1) +# Creating a NEW v-semver tag is still allowed (deletion/nonFastForward only restrict +# delete/move) so normal releases are unaffected; only tampering is blocked. +# +# Unlike everything else in this directory this is NOT an import — it has no +# external-name and is managed (Observe + Create + Update + LateInitialize, never +# Delete), so Crossplane CREATES it on first reconcile and then keeps it in sync. +# +# No bypassActors on purpose: clean immutability, consistent with the org's other +# protective rulesets ("Block force pushes", "Restrict branch names", which also have +# none), and it avoids the OrganizationAdmin bypass actor_id quirk. An org admin can +# still edit/disable the ruleset itself. If release automation ever needs to move or +# delete a tag, add a bypassActors entry here (declaratively) rather than in the UI. +# +# TIER CAVEAT (tagNamePattern): provider/GitHub docs historically flag the tag name +# pattern rule as "enterprise only". devantler-tech is on the `team` plan, NOT an +# enterprise — BUT this same org already runs ACTIVE org-level branch_name_pattern +# ("Restrict branch names") and commit_message_pattern ("Restrict commit metadata") +# rulesets, so pattern rules demonstrably work here and tagNamePattern is expected to +# as well. FALLBACK: if GitHub rejects this ruleset on apply because of the tag name +# pattern, delete just the `tagNamePattern` block below — `deletion` + `nonFastForward` +# still deliver immutable release tags and have no tier requirement. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: protect-release-tags +spec: + managementPolicies: + - Observe + - Create + - Update + - LateInitialize + forProvider: + name: Protect release tags + target: tag + enforcement: active + conditions: + - refName: + - include: ["~ALL"] + exclude: [] + repositoryName: + - include: ["~ALL"] + exclude: [] + rules: + - deletion: true + nonFastForward: true + tagNamePattern: + - operator: regex + pattern: '^v\d+\.\d+\.\d+(-.+)?$' + negate: false + name: "Tags must be v (e.g. v1.2.3 or v1.2.3-rc.1)" + providerConfigRef: + kind: ProviderConfig + name: default From 758fa39b112b155feaf8f3e07a241c161227cd76 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Thu, 25 Jun 2026 19:58:13 +0200 Subject: [PATCH 2/3] refactor(github): one ruleset resource per file in deploy/rulesets/ Per the maintainer's one-resource-per-file preference: split the multi-doc organization-rulesets.yaml (10) and repository-rulesets.yaml (2) into one file per resource (filename = resource name, matching ../repositories/), and rename tag-protection.yaml -> protect-release-tags.yaml to match its resource name. Behaviour-preserving: the rendered set of 13 ruleset resources is byte-identical before/after the split (verified via kustomize build diff). kustomization.yaml + README.md updated for the new layout. Co-Authored-By: Claude Opus 4.8 --- deploy/rulesets/README.md | 18 +- deploy/rulesets/block-force-pushes.yaml | 18 ++ deploy/rulesets/ksail-restrict-deletions.yaml | 22 ++ deploy/rulesets/kustomization.yaml | 27 ++- deploy/rulesets/organization-rulesets.yaml | 189 ------------------ .../platform-require-merge-queue.yaml | 22 ++ ...tection.yaml => protect-release-tags.yaml} | 0 deploy/rulesets/repository-rulesets.yaml | 49 ----- .../require-conversation-resolution.yaml | 18 ++ deploy/rulesets/require-linear-history.yaml | 18 ++ deploy/rulesets/require-pull-request.yaml | 18 ++ deploy/rulesets/require-signed-commits.yaml | 18 ++ deploy/rulesets/require-status-checks.yaml | 18 ++ .../require-workflows-dependency-review.yaml | 23 +++ deploy/rulesets/restrict-branch-names.yaml | 18 ++ deploy/rulesets/restrict-commit-metadata.yaml | 18 ++ deploy/rulesets/restrict-deletions.yaml | 18 ++ 17 files changed, 259 insertions(+), 253 deletions(-) create mode 100644 deploy/rulesets/block-force-pushes.yaml create mode 100644 deploy/rulesets/ksail-restrict-deletions.yaml delete mode 100644 deploy/rulesets/organization-rulesets.yaml create mode 100644 deploy/rulesets/platform-require-merge-queue.yaml rename deploy/rulesets/{tag-protection.yaml => protect-release-tags.yaml} (100%) delete mode 100644 deploy/rulesets/repository-rulesets.yaml create mode 100644 deploy/rulesets/require-conversation-resolution.yaml create mode 100644 deploy/rulesets/require-linear-history.yaml create mode 100644 deploy/rulesets/require-pull-request.yaml create mode 100644 deploy/rulesets/require-signed-commits.yaml create mode 100644 deploy/rulesets/require-status-checks.yaml create mode 100644 deploy/rulesets/require-workflows-dependency-review.yaml create mode 100644 deploy/rulesets/restrict-branch-names.yaml create mode 100644 deploy/rulesets/restrict-commit-metadata.yaml create mode 100644 deploy/rulesets/restrict-deletions.yaml diff --git a/deploy/rulesets/README.md b/deploy/rulesets/README.md index f673e2d..cc2b3c2 100644 --- a/deploy/rulesets/README.md +++ b/deploy/rulesets/README.md @@ -24,11 +24,13 @@ provider). Reconciled by the platform `github-config` tenant like the rest of `d ## What is managed here -| File | Rulesets | Policy | +**One resource per file** (filename = resource name), matching `../repositories/`: + +| Files | Rulesets | Policy | |---|---|---| -| `organization-rulesets.yaml` | 10 org rulesets | Observe (read-only import) | -| `repository-rulesets.yaml` | `ksail` "Restrict deletions", `platform` "Require merge queue" | Observe (read-only import) | -| `tag-protection.yaml` | **Protect release tags** (net-new) | Managed (Create) — block tag delete + force-move + require `v` | +| 10 `OrganizationRuleset` files | the 10 org rulesets below | Observe (read-only import) | +| `ksail-restrict-deletions.yaml`, `platform-require-merge-queue.yaml` | `ksail` "Restrict deletions", `platform` "Require merge queue" | Observe (read-only import) | +| `protect-release-tags.yaml` | **Protect release tags** (net-new) | Managed (Create) — block tag delete + force-move + require `v` | The 10 imported org rulesets: Block force pushes · Require a pull request before merging · Require conversation resolution before merging · Require linear history · @@ -72,10 +74,10 @@ here Observe-first once expressible. - **Push rulesets** — none exist, and **not adoptable**: the provider supports `target: push` (beta) but none of the push-file rule types above, so a push ruleset can't be expressed. Tracked in [#69](https://github.com/devantler-tech/.github/issues/69). -- **Tag rulesets** — none existed; **added** here (`tag-protection.yaml`). Makes release - tags immutable (block delete + force-move) and well-formed (`v`). See that - file's header for the team-vs-enterprise tier caveat on the name-pattern rule and its - fallback. +- **Tag rulesets** — none existed; **added** here (`protect-release-tags.yaml`). Makes + release tags immutable (block delete + force-move) and well-formed (`v`). See + that file's header for the team-vs-enterprise tier caveat on the name-pattern rule and + its fallback. - **Actions policies** — the 2026-06-18 [workflow execution protections](https://github.blog/changelog/2026-06-18-control-who-and-what-triggers-github-actions-workflows/) (actor + event allow-lists controlling who/what triggers workflows, delivered as org diff --git a/deploy/rulesets/block-force-pushes.yaml b/deploy/rulesets/block-force-pushes.yaml new file mode 100644 index 0000000..bd5f19d --- /dev/null +++ b/deploy/rulesets/block-force-pushes.yaml @@ -0,0 +1,18 @@ +# Existing org ruleset, adopted Observe-first (read-only). Identity-only +# forProvider — don't promote past Observe without backfilling from +# status.atProvider (lossy round-trip). See ./README.md for the convention. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: block-force-pushes + annotations: + crossplane.io/external-name: "6986186" # numeric ruleset id +spec: + managementPolicies: ["Observe"] + forProvider: + name: Block force pushes + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/ksail-restrict-deletions.yaml b/deploy/rulesets/ksail-restrict-deletions.yaml new file mode 100644 index 0000000..68555df --- /dev/null +++ b/deploy/rulesets/ksail-restrict-deletions.yaml @@ -0,0 +1,22 @@ +# Existing repository ruleset, adopted Observe-first (read-only). external-name = +# `:` (cf. the `maintainers:` convention in +# ../teams/). Identity-only forProvider — see ./README.md for the convention. +# +# ksail's own "Restrict deletions" rule, scoped to refs/heads/benchmark-data (the +# long-lived benchmark-data branch) — narrower than the org-wide rule. +apiVersion: repo.github.m.upbound.io/v1alpha1 +kind: RepositoryRuleset +metadata: + name: ksail-restrict-deletions + annotations: + crossplane.io/external-name: "ksail:14699634" +spec: + managementPolicies: ["Observe"] + forProvider: + repository: ksail + name: Restrict deletions + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/kustomization.yaml b/deploy/rulesets/kustomization.yaml index 5da611d..e11b351 100644 --- a/deploy/rulesets/kustomization.yaml +++ b/deploy/rulesets/kustomization.yaml @@ -1,11 +1,24 @@ # Org & repository rulesets as Crossplane managed resources (provider-upjet-github). -# 12 existing rulesets are adopted Observe-first (read-only) and 1 net-new tag ruleset -# is managed (Create). The 10 org rulesets the provider cannot express remain -# UI-managed — see ./README.md for the full importability matrix and the push/tag/ -# Actions-policy analysis. Included by ../kustomization.yaml. +# One resource per file. 12 existing rulesets are adopted Observe-first (read-only) +# and 1 net-new tag ruleset is managed (Create). The 10 org rulesets the provider +# cannot express remain UI-managed — see ./README.md for the full importability +# matrix and the push/tag/Actions-policy analysis. Included by ../kustomization.yaml. apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - - organization-rulesets.yaml - - repository-rulesets.yaml - - tag-protection.yaml + # Org rulesets — adopted Observe-first (read-only). + - block-force-pushes.yaml + - require-pull-request.yaml + - require-conversation-resolution.yaml + - require-linear-history.yaml + - require-signed-commits.yaml + - require-status-checks.yaml + - restrict-deletions.yaml + - restrict-branch-names.yaml + - restrict-commit-metadata.yaml + - require-workflows-dependency-review.yaml + # Repository rulesets — adopted Observe-first (read-only). + - ksail-restrict-deletions.yaml + - platform-require-merge-queue.yaml + # Net-new, managed (Create). + - protect-release-tags.yaml diff --git a/deploy/rulesets/organization-rulesets.yaml b/deploy/rulesets/organization-rulesets.yaml deleted file mode 100644 index 8e62253..0000000 --- a/deploy/rulesets/organization-rulesets.yaml +++ /dev/null @@ -1,189 +0,0 @@ -# Org-level rulesets — adopted Observe-first (read-only). -# -# These bind the org's EXISTING UI-created organization rulesets to Crossplane -# `OrganizationRuleset` managed resources (provider-upjet-github, terraform -# github_organization_ruleset). `managementPolicies: ["Observe"]` means Crossplane -# only MIRRORS live state into status.atProvider — it never writes, never reverts, -# never deletes. This brings the org's branch-protection posture under GitOps -# VISIBILITY with zero behaviour change, exactly how repositories/ and teams/ were -# adopted (see ../README.md and the platform repo's docs/github-management.md). -# -# external-name = the numeric ruleset ID (the provider's terraform import id: -# `terraform import github_organization_ruleset.example `). Find an id with -# `gh api orgs/devantler-tech/rulesets`. -# -# forProvider is intentionally MINIMAL (identity only: name/target/enforcement) — -# Observe never reads it. Do NOT add the full rules/conditions/bypassActors here -# and do NOT promote managementPolicies past Observe without FIRST backfilling -# forProvider from the observed `status.atProvider` (the provider's round-trip is -# lossy; a partial forProvider under Update would wipe live rules). The rules/ -# README documents this and the importability matrix. -# -# Only the 10 org rulesets the provider can FAITHFULLY express live here. The other -# 10 (repository_property conditions, code_quality/copilot_code_review rule types, -# and repository-target rulesets) are NOT expressible by provider-upjet-github -# v0.19.1 and remain UI-managed — see ./README.md and the tracking issue. -apiVersion: enterprise.github.m.upbound.io/v1alpha1 -kind: OrganizationRuleset -metadata: - name: block-force-pushes - annotations: - crossplane.io/external-name: "6986186" -spec: - managementPolicies: ["Observe"] - forProvider: - name: Block force pushes - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default ---- -apiVersion: enterprise.github.m.upbound.io/v1alpha1 -kind: OrganizationRuleset -metadata: - name: require-pull-request - annotations: - crossplane.io/external-name: "4134020" -spec: - managementPolicies: ["Observe"] - forProvider: - name: Require a pull request before merging - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default ---- -apiVersion: enterprise.github.m.upbound.io/v1alpha1 -kind: OrganizationRuleset -metadata: - name: require-conversation-resolution - annotations: - crossplane.io/external-name: "6986190" -spec: - managementPolicies: ["Observe"] - forProvider: - name: Require conversation resolution before merging - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default ---- -apiVersion: enterprise.github.m.upbound.io/v1alpha1 -kind: OrganizationRuleset -metadata: - name: require-linear-history - annotations: - crossplane.io/external-name: "6986179" -spec: - managementPolicies: ["Observe"] - forProvider: - name: Require linear history - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default ---- -apiVersion: enterprise.github.m.upbound.io/v1alpha1 -kind: OrganizationRuleset -metadata: - name: require-signed-commits - annotations: - crossplane.io/external-name: "5397812" -spec: - managementPolicies: ["Observe"] - forProvider: - name: Require signed commits - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default ---- -apiVersion: enterprise.github.m.upbound.io/v1alpha1 -kind: OrganizationRuleset -metadata: - name: require-status-checks - annotations: - crossplane.io/external-name: "14974342" -spec: - managementPolicies: ["Observe"] - forProvider: - name: Require status checks to pass - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default ---- -apiVersion: enterprise.github.m.upbound.io/v1alpha1 -kind: OrganizationRuleset -metadata: - name: restrict-deletions - annotations: - crossplane.io/external-name: "6986171" -spec: - managementPolicies: ["Observe"] - forProvider: - name: Restrict deletions - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default ---- -apiVersion: enterprise.github.m.upbound.io/v1alpha1 -kind: OrganizationRuleset -metadata: - name: restrict-branch-names - annotations: - crossplane.io/external-name: "4133993" -spec: - managementPolicies: ["Observe"] - forProvider: - name: Restrict branch names - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default ---- -apiVersion: enterprise.github.m.upbound.io/v1alpha1 -kind: OrganizationRuleset -metadata: - name: restrict-commit-metadata - annotations: - crossplane.io/external-name: "4134015" -spec: - managementPolicies: ["Observe"] - forProvider: - name: Restrict commit metadata - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default ---- -# Newer "Require workflows to pass" rule (API rule type `workflows`, exposed by the -# provider as `requiredWorkflows`). Only the org-wide DependencyReview variant is -# importable — it conditions on repository_name `~ALL`. The other "Require -# workflows …" rulesets condition on repository_property (custom properties), which -# the provider cannot express; they stay UI-managed (see ./README.md). -apiVersion: enterprise.github.m.upbound.io/v1alpha1 -kind: OrganizationRuleset -metadata: - name: require-workflows-dependency-review - annotations: - crossplane.io/external-name: "17213449" -spec: - managementPolicies: ["Observe"] - forProvider: - name: Require workflows to pass before merging - DependencyReview - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default diff --git a/deploy/rulesets/platform-require-merge-queue.yaml b/deploy/rulesets/platform-require-merge-queue.yaml new file mode 100644 index 0000000..3d3a296 --- /dev/null +++ b/deploy/rulesets/platform-require-merge-queue.yaml @@ -0,0 +1,22 @@ +# Existing repository ruleset, adopted Observe-first (read-only). external-name = +# `:` (cf. the `maintainers:` convention in +# ../teams/). Identity-only forProvider — see ./README.md for the convention. +# +# platform's "Require merge queue" rule on the default branch (merge_queue is a +# repo-only rule type; org rulesets cannot carry it). +apiVersion: repo.github.m.upbound.io/v1alpha1 +kind: RepositoryRuleset +metadata: + name: platform-require-merge-queue + annotations: + crossplane.io/external-name: "platform:5275020" +spec: + managementPolicies: ["Observe"] + forProvider: + repository: platform + name: Require merge queue + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/tag-protection.yaml b/deploy/rulesets/protect-release-tags.yaml similarity index 100% rename from deploy/rulesets/tag-protection.yaml rename to deploy/rulesets/protect-release-tags.yaml diff --git a/deploy/rulesets/repository-rulesets.yaml b/deploy/rulesets/repository-rulesets.yaml deleted file mode 100644 index 484233d..0000000 --- a/deploy/rulesets/repository-rulesets.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# Repository-level rulesets — adopted Observe-first (read-only). -# -# Two repos carry their OWN ruleset (beyond the inherited org rulesets), bound here -# to Crossplane `RepositoryRuleset` managed resources (provider-upjet-github, -# terraform github_repository_ruleset). As with the org rulesets, -# `managementPolicies: ["Observe"]` is read-only — Crossplane mirrors live state and -# never writes. external-name = `:` (the provider's terraform -# import id: `terraform import github_repository_ruleset.example :`) — -# matching the `maintainers:` convention already used in ../teams/maintainers.yaml. -# -# forProvider is identity-only (repository/name/target/enforcement); see the warning -# in ./organization-rulesets.yaml before promoting past Observe. -apiVersion: repo.github.m.upbound.io/v1alpha1 -kind: RepositoryRuleset -metadata: - name: ksail-restrict-deletions - annotations: - # ksail's own "Restrict deletions" rule, scoped to refs/heads/benchmark-data - # (the long-lived benchmark-data branch) — narrower than the org-wide rule. - crossplane.io/external-name: "ksail:14699634" -spec: - managementPolicies: ["Observe"] - forProvider: - repository: ksail - name: Restrict deletions - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default ---- -apiVersion: repo.github.m.upbound.io/v1alpha1 -kind: RepositoryRuleset -metadata: - name: platform-require-merge-queue - annotations: - # platform's "Require merge queue" rule on the default branch (merge_queue is a - # repo-only rule type; org rulesets cannot carry it). - crossplane.io/external-name: "platform:5275020" -spec: - managementPolicies: ["Observe"] - forProvider: - repository: platform - name: Require merge queue - target: branch - enforcement: active - providerConfigRef: - kind: ProviderConfig - name: default diff --git a/deploy/rulesets/require-conversation-resolution.yaml b/deploy/rulesets/require-conversation-resolution.yaml new file mode 100644 index 0000000..cd9c45b --- /dev/null +++ b/deploy/rulesets/require-conversation-resolution.yaml @@ -0,0 +1,18 @@ +# Existing org ruleset, adopted Observe-first (read-only). Identity-only +# forProvider — don't promote past Observe without backfilling from +# status.atProvider (lossy round-trip). See ./README.md for the convention. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-conversation-resolution + annotations: + crossplane.io/external-name: "6986190" # numeric ruleset id +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require conversation resolution before merging + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/require-linear-history.yaml b/deploy/rulesets/require-linear-history.yaml new file mode 100644 index 0000000..6d19b45 --- /dev/null +++ b/deploy/rulesets/require-linear-history.yaml @@ -0,0 +1,18 @@ +# Existing org ruleset, adopted Observe-first (read-only). Identity-only +# forProvider — don't promote past Observe without backfilling from +# status.atProvider (lossy round-trip). See ./README.md for the convention. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-linear-history + annotations: + crossplane.io/external-name: "6986179" # numeric ruleset id +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require linear history + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/require-pull-request.yaml b/deploy/rulesets/require-pull-request.yaml new file mode 100644 index 0000000..1465f94 --- /dev/null +++ b/deploy/rulesets/require-pull-request.yaml @@ -0,0 +1,18 @@ +# Existing org ruleset, adopted Observe-first (read-only). Identity-only +# forProvider — don't promote past Observe without backfilling from +# status.atProvider (lossy round-trip). See ./README.md for the convention. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-pull-request + annotations: + crossplane.io/external-name: "4134020" # numeric ruleset id +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require a pull request before merging + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/require-signed-commits.yaml b/deploy/rulesets/require-signed-commits.yaml new file mode 100644 index 0000000..1eec790 --- /dev/null +++ b/deploy/rulesets/require-signed-commits.yaml @@ -0,0 +1,18 @@ +# Existing org ruleset, adopted Observe-first (read-only). Identity-only +# forProvider — don't promote past Observe without backfilling from +# status.atProvider (lossy round-trip). See ./README.md for the convention. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-signed-commits + annotations: + crossplane.io/external-name: "5397812" # numeric ruleset id +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require signed commits + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/require-status-checks.yaml b/deploy/rulesets/require-status-checks.yaml new file mode 100644 index 0000000..438074f --- /dev/null +++ b/deploy/rulesets/require-status-checks.yaml @@ -0,0 +1,18 @@ +# Existing org ruleset, adopted Observe-first (read-only). Identity-only +# forProvider — don't promote past Observe without backfilling from +# status.atProvider (lossy round-trip). See ./README.md for the convention. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-status-checks + annotations: + crossplane.io/external-name: "14974342" # numeric ruleset id +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require status checks to pass + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/require-workflows-dependency-review.yaml b/deploy/rulesets/require-workflows-dependency-review.yaml new file mode 100644 index 0000000..441f876 --- /dev/null +++ b/deploy/rulesets/require-workflows-dependency-review.yaml @@ -0,0 +1,23 @@ +# Existing org ruleset, adopted Observe-first (read-only). Identity-only +# forProvider — don't promote past Observe without backfilling from +# status.atProvider (lossy round-trip). See ./README.md for the convention. +# +# Newer "Require workflows to pass" rule (API type `workflows`, exposed by the +# provider as `requiredWorkflows`). Only this DependencyReview variant is +# importable — it conditions on repository_name `~ALL`. The other "Require +# workflows …" rulesets condition on repository_property and stay UI-managed. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-workflows-dependency-review + annotations: + crossplane.io/external-name: "17213449" # numeric ruleset id +spec: + managementPolicies: ["Observe"] + forProvider: + name: Require workflows to pass before merging - DependencyReview + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/restrict-branch-names.yaml b/deploy/rulesets/restrict-branch-names.yaml new file mode 100644 index 0000000..04fc667 --- /dev/null +++ b/deploy/rulesets/restrict-branch-names.yaml @@ -0,0 +1,18 @@ +# Existing org ruleset, adopted Observe-first (read-only). Identity-only +# forProvider — don't promote past Observe without backfilling from +# status.atProvider (lossy round-trip). See ./README.md for the convention. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: restrict-branch-names + annotations: + crossplane.io/external-name: "4133993" # numeric ruleset id +spec: + managementPolicies: ["Observe"] + forProvider: + name: Restrict branch names + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/restrict-commit-metadata.yaml b/deploy/rulesets/restrict-commit-metadata.yaml new file mode 100644 index 0000000..a4a4193 --- /dev/null +++ b/deploy/rulesets/restrict-commit-metadata.yaml @@ -0,0 +1,18 @@ +# Existing org ruleset, adopted Observe-first (read-only). Identity-only +# forProvider — don't promote past Observe without backfilling from +# status.atProvider (lossy round-trip). See ./README.md for the convention. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: restrict-commit-metadata + annotations: + crossplane.io/external-name: "4134015" # numeric ruleset id +spec: + managementPolicies: ["Observe"] + forProvider: + name: Restrict commit metadata + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/deploy/rulesets/restrict-deletions.yaml b/deploy/rulesets/restrict-deletions.yaml new file mode 100644 index 0000000..da27886 --- /dev/null +++ b/deploy/rulesets/restrict-deletions.yaml @@ -0,0 +1,18 @@ +# Existing org ruleset, adopted Observe-first (read-only). Identity-only +# forProvider — don't promote past Observe without backfilling from +# status.atProvider (lossy round-trip). See ./README.md for the convention. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: restrict-deletions + annotations: + crossplane.io/external-name: "6986171" # numeric ruleset id +spec: + managementPolicies: ["Observe"] + forProvider: + name: Restrict deletions + target: branch + enforcement: active + providerConfigRef: + kind: ProviderConfig + name: default From 43fa366f782b8db0e3fb23ad379a6a1d7a4e1d08 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Thu, 25 Jun 2026 20:22:58 +0200 Subject: [PATCH 3/3] refactor(github): split rulesets into per-kind subfolders with active-verb names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deploy/rulesets/ mixed OrganizationRuleset and RepositoryRuleset. Split into one subfolder per CR kind, each with an active-verb filename convention: - organization-rulesets/ — OrganizationRuleset, named after the rule it enforces (e.g. require-pull-request.yaml); carries the convention README + importability matrix. - repository-rulesets/ — RepositoryRuleset as -on-.yaml (ksail-restrict-deletions -> restrict-deletions-on-ksail; platform-require-merge-queue -> require-merge-queue-on-platform). Pure rename + kustomization/README re-wiring; resource bodies (metadata.name, crossplane.io/external-name) unchanged. `kubectl kustomize deploy/` validated (11 OrganizationRuleset, 2 RepositoryRuleset). Co-Authored-By: Claude Opus 4.8 --- deploy/README.md | 11 +++++---- deploy/kustomization.yaml | 3 ++- .../README.md | 17 +++++++------ .../block-force-pushes.yaml | 0 .../organization-rulesets/kustomization.yaml | 21 ++++++++++++++++ .../protect-release-tags.yaml | 0 .../require-conversation-resolution.yaml | 0 .../require-linear-history.yaml | 0 .../require-pull-request.yaml | 0 .../require-signed-commits.yaml | 0 .../require-status-checks.yaml | 0 .../require-workflows-dependency-review.yaml | 0 .../restrict-branch-names.yaml | 0 .../restrict-commit-metadata.yaml | 0 .../restrict-deletions.yaml | 0 deploy/repository-rulesets/README.md | 17 +++++++++++++ deploy/repository-rulesets/kustomization.yaml | 9 +++++++ .../require-merge-queue-on-platform.yaml} | 0 .../restrict-deletions-on-ksail.yaml} | 0 deploy/rulesets/kustomization.yaml | 24 ------------------- 20 files changed, 66 insertions(+), 36 deletions(-) rename deploy/{rulesets => organization-rulesets}/README.md (83%) rename deploy/{rulesets => organization-rulesets}/block-force-pushes.yaml (100%) create mode 100644 deploy/organization-rulesets/kustomization.yaml rename deploy/{rulesets => organization-rulesets}/protect-release-tags.yaml (100%) rename deploy/{rulesets => organization-rulesets}/require-conversation-resolution.yaml (100%) rename deploy/{rulesets => organization-rulesets}/require-linear-history.yaml (100%) rename deploy/{rulesets => organization-rulesets}/require-pull-request.yaml (100%) rename deploy/{rulesets => organization-rulesets}/require-signed-commits.yaml (100%) rename deploy/{rulesets => organization-rulesets}/require-status-checks.yaml (100%) rename deploy/{rulesets => organization-rulesets}/require-workflows-dependency-review.yaml (100%) rename deploy/{rulesets => organization-rulesets}/restrict-branch-names.yaml (100%) rename deploy/{rulesets => organization-rulesets}/restrict-commit-metadata.yaml (100%) rename deploy/{rulesets => organization-rulesets}/restrict-deletions.yaml (100%) create mode 100644 deploy/repository-rulesets/README.md create mode 100644 deploy/repository-rulesets/kustomization.yaml rename deploy/{rulesets/platform-require-merge-queue.yaml => repository-rulesets/require-merge-queue-on-platform.yaml} (100%) rename deploy/{rulesets/ksail-restrict-deletions.yaml => repository-rulesets/restrict-deletions-on-ksail.yaml} (100%) delete mode 100644 deploy/rulesets/kustomization.yaml diff --git a/deploy/README.md b/deploy/README.md index 68134c3..0c86169 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -20,11 +20,14 @@ out-of-band changes made in the GitHub UI. every repo); each `.yaml` adds only that repo's Dependabot/Renovate ecosystem extras. Authoritative — out-of-band label drift is reverted. This is the Crossplane replacement for the old EndBug/label-sync workflow. -- `rulesets/` — org & repository rulesets (branch/tag protection). 12 existing - rulesets are adopted **Observe-first** (read-only) + 1 net-new `v*` tag-protection - ruleset is managed. The 10 org rulesets the provider can't yet express stay - UI-managed — see [`rulesets/README.md`](rulesets/README.md) for the full +- `organization-rulesets/` — one `OrganizationRuleset` per file (org-wide branch/tag + protection). 10 existing org rulesets are adopted **Observe-first** (read-only) + 1 + net-new `v*` tag-protection ruleset is managed. The 10 org rulesets the provider + can't yet express stay UI-managed — see + [`organization-rulesets/README.md`](organization-rulesets/README.md) for the full importability matrix and the push/tag/Actions-policy analysis. +- `repository-rulesets/` — one `RepositoryRuleset` per file (`-on-.yaml`), + each a repo-scoped rule adopted Observe-first. See the platform repo's [`docs/github-management.md`](https://github.com/devantler-tech/platform/blob/main/docs/github-management.md) diff --git a/deploy/kustomization.yaml b/deploy/kustomization.yaml index 328afe4..975451b 100644 --- a/deploy/kustomization.yaml +++ b/deploy/kustomization.yaml @@ -18,4 +18,5 @@ resources: - repositories/ - teams/ - labels/ - - rulesets/ + - organization-rulesets/ + - repository-rulesets/ diff --git a/deploy/rulesets/README.md b/deploy/organization-rulesets/README.md similarity index 83% rename from deploy/rulesets/README.md rename to deploy/organization-rulesets/README.md index cc2b3c2..90cd3ff 100644 --- a/deploy/rulesets/README.md +++ b/deploy/organization-rulesets/README.md @@ -1,10 +1,11 @@ -# `rulesets/` — org & repository rulesets as code +# `organization-rulesets/` — org rulesets as code -Declarative management of the devantler-tech org's **rulesets** (branch/tag protection -and policies) as Crossplane managed resources, via +Declarative management of the devantler-tech org's **`OrganizationRuleset`** resources +(org-wide branch/tag protection and policies) as Crossplane managed resources, via [provider-upjet-github](https://github.com/crossplane-contrib/provider-upjet-github) -(`OrganizationRuleset` / `RepositoryRuleset`, from the `integrations/github` Terraform -provider). Reconciled by the platform `github-config` tenant like the rest of `deploy/`. +(from the `integrations/github` Terraform provider). Repo-scoped `RepositoryRuleset` +resources live in the sibling [`../repository-rulesets/`](../repository-rulesets/). +Reconciled by the platform `github-config` tenant like the rest of `deploy/`. ## How adoption works @@ -24,13 +25,15 @@ provider). Reconciled by the platform `github-config` tenant like the rest of `d ## What is managed here -**One resource per file** (filename = resource name), matching `../repositories/`: +**One `OrganizationRuleset` per file**, named after the rule it enforces (an active +verb — e.g. `require-pull-request.yaml`). Repo-scoped rulesets live next door in +[`../repository-rulesets/`](../repository-rulesets/) as `-on-.yaml`. | Files | Rulesets | Policy | |---|---|---| | 10 `OrganizationRuleset` files | the 10 org rulesets below | Observe (read-only import) | -| `ksail-restrict-deletions.yaml`, `platform-require-merge-queue.yaml` | `ksail` "Restrict deletions", `platform` "Require merge queue" | Observe (read-only import) | | `protect-release-tags.yaml` | **Protect release tags** (net-new) | Managed (Create) — block tag delete + force-move + require `v` | +| (in `../repository-rulesets/`) `restrict-deletions-on-ksail.yaml`, `require-merge-queue-on-platform.yaml` | `ksail` "Restrict deletions", `platform` "Require merge queue" | Observe (read-only import) | The 10 imported org rulesets: Block force pushes · Require a pull request before merging · Require conversation resolution before merging · Require linear history · diff --git a/deploy/rulesets/block-force-pushes.yaml b/deploy/organization-rulesets/block-force-pushes.yaml similarity index 100% rename from deploy/rulesets/block-force-pushes.yaml rename to deploy/organization-rulesets/block-force-pushes.yaml diff --git a/deploy/organization-rulesets/kustomization.yaml b/deploy/organization-rulesets/kustomization.yaml new file mode 100644 index 0000000..2e9bffa --- /dev/null +++ b/deploy/organization-rulesets/kustomization.yaml @@ -0,0 +1,21 @@ +# `OrganizationRuleset` resources — one per file, named after the rule (an active verb). +# 10 existing org rulesets are adopted Observe-first (read-only) and 1 net-new tag ruleset +# is managed (Create). The 10 org rulesets the provider cannot express remain UI-managed — +# see ./README.md for the importability matrix and the push/tag/Actions-policy analysis. +# Repo-scoped rulesets live in ../repository-rulesets/. Included by ../kustomization.yaml. +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + # Adopted Observe-first (read-only). + - block-force-pushes.yaml + - require-pull-request.yaml + - require-conversation-resolution.yaml + - require-linear-history.yaml + - require-signed-commits.yaml + - require-status-checks.yaml + - restrict-deletions.yaml + - restrict-branch-names.yaml + - restrict-commit-metadata.yaml + - require-workflows-dependency-review.yaml + # Net-new, managed (Create). + - protect-release-tags.yaml diff --git a/deploy/rulesets/protect-release-tags.yaml b/deploy/organization-rulesets/protect-release-tags.yaml similarity index 100% rename from deploy/rulesets/protect-release-tags.yaml rename to deploy/organization-rulesets/protect-release-tags.yaml diff --git a/deploy/rulesets/require-conversation-resolution.yaml b/deploy/organization-rulesets/require-conversation-resolution.yaml similarity index 100% rename from deploy/rulesets/require-conversation-resolution.yaml rename to deploy/organization-rulesets/require-conversation-resolution.yaml diff --git a/deploy/rulesets/require-linear-history.yaml b/deploy/organization-rulesets/require-linear-history.yaml similarity index 100% rename from deploy/rulesets/require-linear-history.yaml rename to deploy/organization-rulesets/require-linear-history.yaml diff --git a/deploy/rulesets/require-pull-request.yaml b/deploy/organization-rulesets/require-pull-request.yaml similarity index 100% rename from deploy/rulesets/require-pull-request.yaml rename to deploy/organization-rulesets/require-pull-request.yaml diff --git a/deploy/rulesets/require-signed-commits.yaml b/deploy/organization-rulesets/require-signed-commits.yaml similarity index 100% rename from deploy/rulesets/require-signed-commits.yaml rename to deploy/organization-rulesets/require-signed-commits.yaml diff --git a/deploy/rulesets/require-status-checks.yaml b/deploy/organization-rulesets/require-status-checks.yaml similarity index 100% rename from deploy/rulesets/require-status-checks.yaml rename to deploy/organization-rulesets/require-status-checks.yaml diff --git a/deploy/rulesets/require-workflows-dependency-review.yaml b/deploy/organization-rulesets/require-workflows-dependency-review.yaml similarity index 100% rename from deploy/rulesets/require-workflows-dependency-review.yaml rename to deploy/organization-rulesets/require-workflows-dependency-review.yaml diff --git a/deploy/rulesets/restrict-branch-names.yaml b/deploy/organization-rulesets/restrict-branch-names.yaml similarity index 100% rename from deploy/rulesets/restrict-branch-names.yaml rename to deploy/organization-rulesets/restrict-branch-names.yaml diff --git a/deploy/rulesets/restrict-commit-metadata.yaml b/deploy/organization-rulesets/restrict-commit-metadata.yaml similarity index 100% rename from deploy/rulesets/restrict-commit-metadata.yaml rename to deploy/organization-rulesets/restrict-commit-metadata.yaml diff --git a/deploy/rulesets/restrict-deletions.yaml b/deploy/organization-rulesets/restrict-deletions.yaml similarity index 100% rename from deploy/rulesets/restrict-deletions.yaml rename to deploy/organization-rulesets/restrict-deletions.yaml diff --git a/deploy/repository-rulesets/README.md b/deploy/repository-rulesets/README.md new file mode 100644 index 0000000..a005e1b --- /dev/null +++ b/deploy/repository-rulesets/README.md @@ -0,0 +1,17 @@ +# `repository-rulesets/` — repo-scoped rulesets as code + +Declarative management of individual repositories' **`RepositoryRuleset`** resources as +Crossplane managed resources, via +[provider-upjet-github](https://github.com/crossplane-contrib/provider-upjet-github). +Org-wide `OrganizationRuleset` resources live in the sibling +[`../organization-rulesets/`](../organization-rulesets/), whose +[`README.md`](../organization-rulesets/README.md) documents the shared **Observe-first** +adoption convention and the provider importability matrix. + +**One `RepositoryRuleset` per file**, named `-on-.yaml` so the rule and its +target repo are clear from the filename. external-name = `:`. + +| File | Repo | Ruleset | Policy | +|---|---|---|---| +| `restrict-deletions-on-ksail.yaml` | `ksail` | Restrict deletions (scoped to `refs/heads/benchmark-data`) | Observe (read-only import) | +| `require-merge-queue-on-platform.yaml` | `platform` | Require merge queue | Observe (read-only import) | diff --git a/deploy/repository-rulesets/kustomization.yaml b/deploy/repository-rulesets/kustomization.yaml new file mode 100644 index 0000000..4e18c1f --- /dev/null +++ b/deploy/repository-rulesets/kustomization.yaml @@ -0,0 +1,9 @@ +# `RepositoryRuleset` resources — one per file, named `-on-.yaml`, each a +# repo-scoped rule adopted Observe-first (read-only). external-name = `:`. See +# ../organization-rulesets/README.md for the shared adoption convention and the provider +# importability matrix. Included by ../kustomization.yaml. +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - restrict-deletions-on-ksail.yaml + - require-merge-queue-on-platform.yaml diff --git a/deploy/rulesets/platform-require-merge-queue.yaml b/deploy/repository-rulesets/require-merge-queue-on-platform.yaml similarity index 100% rename from deploy/rulesets/platform-require-merge-queue.yaml rename to deploy/repository-rulesets/require-merge-queue-on-platform.yaml diff --git a/deploy/rulesets/ksail-restrict-deletions.yaml b/deploy/repository-rulesets/restrict-deletions-on-ksail.yaml similarity index 100% rename from deploy/rulesets/ksail-restrict-deletions.yaml rename to deploy/repository-rulesets/restrict-deletions-on-ksail.yaml diff --git a/deploy/rulesets/kustomization.yaml b/deploy/rulesets/kustomization.yaml deleted file mode 100644 index e11b351..0000000 --- a/deploy/rulesets/kustomization.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# Org & repository rulesets as Crossplane managed resources (provider-upjet-github). -# One resource per file. 12 existing rulesets are adopted Observe-first (read-only) -# and 1 net-new tag ruleset is managed (Create). The 10 org rulesets the provider -# cannot express remain UI-managed — see ./README.md for the full importability -# matrix and the push/tag/Actions-policy analysis. Included by ../kustomization.yaml. -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - # Org rulesets — adopted Observe-first (read-only). - - block-force-pushes.yaml - - require-pull-request.yaml - - require-conversation-resolution.yaml - - require-linear-history.yaml - - require-signed-commits.yaml - - require-status-checks.yaml - - restrict-deletions.yaml - - restrict-branch-names.yaml - - restrict-commit-metadata.yaml - - require-workflows-dependency-review.yaml - # Repository rulesets — adopted Observe-first (read-only). - - ksail-restrict-deletions.yaml - - platform-require-merge-queue.yaml - # Net-new, managed (Create). - - protect-release-tags.yaml