Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ files.

## Repository layout

- `data/<org>/*.yaml` — one file per managed repo (the source of truth)
- `data/<org>/*.yaml` — one file per managed repo (the source of truth);
`data/<org>/_teams.yaml` is optional per-org metadata, and leading-underscore
filenames are reserved for it rather than read as repos
- `modules/github-repo/` — the reusable repo module; `variables.tf` lists
every supported field and its default
- `modules/org/` — iterates an org's `data/` files
Expand Down Expand Up @@ -45,6 +47,14 @@ files.
- **Private repos on the free-tier personal org:** rulesets and secret
scanning are paywalled — omit them. Keep `vulnerability_alerts` and
`dependabot_security_updates`.
- **Teams are optional and per-org.** `data/<org>/_teams.yaml` is a map keyed
by team slug, each with `description`, `members`, and an optional `privacy`
(default `closed`). The key is used verbatim as the team name, and GitHub
derives the slug from it, so keys must be lowercase and hyphenated for the
two to agree. Membership is authoritative — a member added in the UI is
removed on the next apply. A repo grants to a team with
`collaborators.teams: [{permission: admin, slug: admins}]`; a slug with no
team in the same org's `_teams.yaml` fails the plan.

## Applying changes

Expand Down Expand Up @@ -73,6 +83,11 @@ task plan # terraform plan -out tfplan (read-only)
task apply # terraform apply tfplan (only after reviewing the plan)
```

`task plan ORG=<org>` scopes the plan to one org
(`-target=module.org_<org>`, hyphens become underscores). Targeting skips the
excluded org's `check "unmanaged_repos"` and its filename/`name:` validation,
so it is for scoping a known change; unscoped `task plan` stays the default.

Inspect state with `task state:list` (all instance addresses) and
`task state:show REPO=<name>` (one repo's instances). Always review the plan
before applying; plan files can contain sensitive values and are gitignored.
Expand Down
22 changes: 19 additions & 3 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,33 @@ tasks:
fi
fi

# An unmatched -target is only a soft warning, and the resulting empty plan
# reads like "nothing to do". A data directory doesn't prove module.org_<org>
# exists, but it's a cheap proxy that catches a plain typo in ORG before
# terraform silently no-ops.
org-guard:
internal: true
silent: true
cmds:
- |
if [ -n "{{.ORG}}" ] && [ ! -d "data/{{.ORG}}" ]; then
echo "ERROR: no data/{{.ORG}} directory; ORG must name a managed org." >&2
exit 1
fi

init:
desc: Initialise terraform against the Stategraph HTTP backend
deps: [preflight]
cmds:
- terraform init

plan:
desc: Generate a plan file via `terraform plan -out {{.PLAN_FILE}}` (read-only)
deps: [preflight]
desc: Generate a plan file via `terraform plan` (ORG=<org> scopes it to one org)
deps: [preflight, org-guard]
vars:
TARGET: '{{if .ORG}}-target=module.org_{{.ORG | replace "-" "_"}}{{end}}'
cmds:
- terraform plan -out={{.PLAN_FILE}}
- terraform plan -out={{.PLAN_FILE}} {{.TARGET}}

apply:
desc: Apply the previously generated plan file
Expand Down
6 changes: 6 additions & 0 deletions data/ycst-org-uk/_teams.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
admins:
description: Administrators for York City Supporters Trust repositories
members:
- robinbowes
- PlanetSeth
Loading