Skip to main content

YAML-config-driven GitHub repository manager

Project description

Repo Management

Declarative, YAML-driven management of GitHub repository configuration. Describe how a repo should be configured in a YAML file; repo-management reads the live state, shows you the diff, and reconciles it through the GitHub API (via PyGithub).

It is declarative and idempotent: re-running when nothing has changed does nothing. A section you don't mention is left unmanaged; a section you do declare is authoritative — it's the complete desired set, so anything on the repo not listed in it is removed.

Install

uv sync                      # create the venv + install everything

Authentication uses a GitHub token, read from $GITHUB_TOKEN (or --token). The token needs the scopes for whatever you manage (repo administration, Actions secrets, etc.).

Usage

export GITHUB_TOKEN=ghp_...

repo-management validate  -c examples/repos.yaml   # check the YAML (no network)
repo-management plan      -c examples/repos.yaml   # show the diff (read-only)
repo-management apply     -c examples/repos.yaml   # reconcile (prompts before writing)
repo-management list-repos                         # the managed fleet across config/*.yml (no network)

Useful flags:

  • --repo owner/name — limit the run to a single repo from the config.
  • --yes / -y — skip the confirmation prompt on apply.
  • --token — pass a token explicitly instead of using $GITHUB_TOKEN.

plan prints one line per change: + create, ~ update, - delete. Secret values are always redacted.

list-repos prints the managed fleet — the union of the repos: lists across every applied config/*.yml (the *.yaml layer files are bases, not configs, and are skipped). --format names emits a single comma-separated line of owner-relative names, used to scope the central Renovate runner's App token (see Fleet automation).

Config format

A config file lists the repositories to manage and one shared block of config sections applied to every one of them:

repos:
  - owner/repo
  - owner/other
settings: { ... }
rulesets: [ ... ]
labels: [ ... ]

Composing configs with extends

A file may extends: one or more base files (a string or a list, relative paths, resolved recursively). The bases are merged underneath, then this file is merged on top:

  • scalars — the override wins;
  • list sections (rulesets, labels.items, collaborators, webhooks, secrets) — merge by each item's natural key (ruleset/label/secret name, collaborator username, webhook url): a same-key item in the override replaces the base's item, and new items are appended.
# repos.yaml
extends: base.yaml
repos: [owner/repo]
settings:
  description: Managed by repo-management   # added on top of base.settings

See examples/base.yaml + examples/repos.yaml for a fully-annotated, working pair.

extends: reads and merges local files by relative path, so only point it at config you trust — treat a base file the same as the config that includes it.

What it manages

Section Manages
settings description, homepage, topics, visibility, features (issues/wiki/projects/discussions), merge options (squash/merge/rebase, auto-merge, delete-branch-on-merge), default branch
rulesets repository rulesets (branch/tag): the full rule set (pull_request, required_status_checks, required_linear_history, non_fast_forward, deletion, creation, update, required_deployments, merge_queue, required_signatures, the *_pattern rules, file_path/extension/size restrictions, workflows, code_scanning), plus bypass_actors and ref-name conditions
labels create/update/delete labels to match the listed set exactly
collaborators add/re-permission direct collaborators; remove those not listed
webhooks create/update/delete webhooks, matched by URL
secrets Actions secrets (write-only; libsodium-encrypted by PyGithub); delete those not listed
variables Actions repository variables (plain text; updated only when the value differs); delete those not listed

Design notes / limitations

  • A declared section is authoritative. Each section you include is the complete desired set: items present on the repo but absent from the section are removed (labels, webhooks, secrets, variables deleted; direct collaborators removed; repo rulesets deleted). A section you omit is left entirely unmanaged. This means a declared section can revoke access or delete a secret by omission — declare deliberately.
  • Only directly-granted collaborator access is managed. Pruning uses affiliation="direct", so access inherited from org or team membership is never listed and never touched (the repo API can't revoke it anyway).
  • Rulesets are matched by name and updated to the full declared spec. PyGithub has no ruleset support, so this is driven through its authenticated requester against the REST rulesets API. On update, a ruleset's rules/conditions/bypass actors are PUT to exactly what the config declares. A declared ruleset's lists (rules, bypass actors, ref-name patterns) must match the live ones exactly — a manually-added rule is drift that triggers an update — while server-supplied metadata the config never sets (item integration_id, bypass actor_id, timestamps) is ignored, so it can't cause churn. Listing passes includes_parents=false, so inherited org/enterprise rulesets are never matched or deleted through the repo.
  • Secrets and webhook secrets are write-only. The API never returns a secret value, so a change to only a secret can't be diffed: an Actions secret is re-sent on every apply, and a webhook with a configured secret is always re-sent (shown as (set) in the plan). Values are sourced from the environment (value_from_env / secret_from_env) and never printed. A literal value: is supported for secrets but should never be committed.
  • Variables, by contrast, are readable. An Actions variable value is returned by the API, so a variable is updated only when its value actually differs and the value is shown in plain text in the plan. Variables take a literal value: or value_from_env: (same shape as secrets, minus the secrecy).

Fleet automation

Beyond the CLI, this control-plane repo runs scheduled GitHub Actions that operate on the whole fleet, authenticating as the CI GitHub App (see each workflow's header comment for the details):

  • apply-config / plan-config — reconcile the live repos to config/*.yml: apply on push to main, plan as a read-only PR preview.
  • renovate — a central, self-hosted Renovate runner that opens dependency-update PRs across the managed fleet on a schedule, replacing the hosted Renovate app (and unlocking postUpgradeTasks, so a bumped binary's checksum is refreshed inside Renovate's own commit). It scopes its App token to exactly the fleet — repo-management list-repos --format names derives that set from config/*.yml, so the same config that drives apply is the single source of truth for what Renovate touches. Its global behaviour (autodiscover, onboarding off, the postUpgradeTasks command allowlist) lives in .github/renovate-global.json, separate from this repo's own dependency policy in .github/renovate.json. Dispatch it manually with dryRun to preview the scope and proposed changes without opening any PRs.

Development

uv run pytest                # tests + coverage (gate: 90%; currently 100%)
uvx prek run --all-files     # the full quality gate (ruff, format, REUSE, typos, …)

The project carries the shared quality spine: prek hooks (git hygiene, gitleaks, typos, rumdl, SPDX/REUSE headers, ruff) that run identically locally and in CI. Conventional Commits (gitmoji) are enforced at commit-msg time. A few hooks shell out to system tools prek can't bootstrap — install them locally too (most are in Homebrew): hawkeye, taplo, osv-scanner.

License

MIT — and REUSE-compliant.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

repo_management-1.2.2.tar.gz (114.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

repo_management-1.2.2-py3-none-any.whl (32.9 kB view details)

Uploaded Python 3

File details

Details for the file repo_management-1.2.2.tar.gz.

File metadata

  • Download URL: repo_management-1.2.2.tar.gz
  • Upload date:
  • Size: 114.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for repo_management-1.2.2.tar.gz
Algorithm Hash digest
SHA256 3e5fb2b6137da9bd83dc7a99551a34a5a10fd6d850105d3e3249af7acb21eb77
MD5 9bb2c2a210866666ec18dc7f2d93e304
BLAKE2b-256 52b8d6b7d032b76c6c208b3cc08d10e196ce331bc4dcf494c3000d591ee36247

See more details on using hashes here.

File details

Details for the file repo_management-1.2.2-py3-none-any.whl.

File metadata

  • Download URL: repo_management-1.2.2-py3-none-any.whl
  • Upload date:
  • Size: 32.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for repo_management-1.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d3c77cca6d26f17fff21b6cb0090ba9df5b62450532df294ec7c51846525e430
MD5 eda86d650f980155e6fd2a1bfcb8f363
BLAKE2b-256 f12e1c8ac3e22258a3956ea9d47647b5fb52cf49fc5f1563d7ae16d88baddbf3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page