Build inventory dynamically from the GitHub API (#55) - #74
Conversation
A repolist file can now define puppetsync::repos_source instead of a hand-maintained puppetsync::repos_config; the sync plan builds the repo list from the GitHub org listing at run time. New list_github_repos task (stdlib net/http, no new gems): - Excludes archived and empty repos; excludes forks unless they match include_forks — the escape hatch for forks the org actively maintains (rubygem-simp-rspec-puppet-facts, pupmod-voxpupuli-selinux) - Excludes repos carrying the 'puppetsync-ignore' GitHub topic, so a repo can be opted out on the GitHub side without touching puppetsync - include/exclude name globs (no pupmod-simp-* assumption: the shipped github-org.yaml also matches puppet-* like puppet-gpasswd, and project_type filtering after the clone remains the safety net) - Each branch comes from the API's default_branch (e.g. pupmod-voxpupuli-selinux syncs simp-master automatically) The plan snapshots the generated list to data/sync/repolists/generated-<config>.yaml (a normal repolist file) so approve/merge plans run against the exact same inventory via repolist=generated-<config>; static repos_config entries merge on top. Verified with 9 fixture-driven specs plus live runs against the real simp org: 79 of 258 repos selected with all known exceptions handled (maintained forks kept incl. the simp-master branch, plain forks and the augeasproviders family excluded, archived pupmod-simp-ntpd excluded, puppet-gpasswd included), and a live plan e2e that listed, cloned, checked out, and produced a snapshot that loads as a normal repolist. Closes simp#55. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Clean implementation of #55, and — worth calling out after the last two task PRs — this one is token-safe: no
next false if repo['fork'] && exclude_forks && !glob_match?(name, include_forks)
glob_match?(name, include_globs) || topics.any? { |t| include_topics.include?(t) }So a maintained fork must match
Since listing a fork explicitly reads as "I want this fork," I'd have an Minor: the inventory fetch is a hard dependency with no retry ( Everything else — plan wiring, |
- A fork matching include_forks now bypasses the include-glob/topic gate entirely: listing a fork means "I want this fork", whatever its name. Previously a fork exception with an off-pattern name would be silently dropped by the final include check — exactly the escape- hatch case. New spec mirrors the shipped-config shape (narrow include globs + an off-pattern fork name); non-forks still need to pass the include filters - Retry transient GitHub API failures (network errors, 5xx) with backoff, 3 attempts per page, failing fast on 4xx — so a blip doesn't abort a whole (possibly scheduled, per simp#56) run Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Agreed on the semantics call, and it's in 49414e9:
10 examples, 0 failures. 🤖 Generated with Claude Code |
Closes #55
A repolist file can now define
puppetsync::repos_sourceinstead of a hand-maintainedpuppetsync::repos_config— the sync plan builds the repo list from the GitHub org listing at run time.data/sync/repolists/github-org.yamlships ready to use (repolist=github-org).Filtering — built around the real org's shape
Surveyed live: the simp org has 258 repos, 86 archived, and a large fork population (the
augeasprovidersfamily,puppetlabs-*, etc.). The task (list_github_repos, stdlibnet/http, no new gems):exclude_archiveddefaults true)include_forks— the escape hatch for forks the org actively maintains. The shipped list carries the two known exceptions:rubygem-simp-rspec-puppet-factsandpupmod-voxpupuli-selinuxpuppetsync-ignoretopic are excluded by default, so a repo can be excluded (or a future fork exception could be marked with aninclude_topicsconvention) without touching puppetsyncpupmod-simp-*assumption: the shipped include list ispupmod-*,puppet-*,rubygem-*, which catchespuppet-gpasswd;filter_permitted_repos' project_type check after the clone remains the safety net for name-pattern false positivesdefault_branch— which the survey proved essential:pupmod-voxpupuli-selinux's default branch issimp-master, something a hand-maintained list has to know and a dynamic one gets for freeReproducibility: the snapshot
The plan writes the generated list to
data/sync/repolists/generated-<config>.yaml— a normal repolist file — and tells you so:The approve/merge plans need no changes at all: run them with
repolist=generated-<config>and they operate on the exact inventory the sync ran against. Snapshots are left untracked for the operator to commit when a session is handed off (matching the existing convention of committed dated repolists). Staticrepos_configentries merge on top of the generated list (they win for duplicate URLs), so per-repo overrides remain possible.Verification
simp-masterfor selinux),puppetlabs-apacheand allaugeasproviders*forks excluded, archivedpupmod-simp-ntpdexcluded,puppet-gpasswdincludedrepos_sourcenarrowed to one repo → real API listing → real clone → feature branch checked out → snapshot written and verified to load through the normal repolist Hiera path viabolt lookup --plan-hierarchypuppet parser validate --tasks,bolt plan show, the stage-list dry run (which skips generation, as intended), full rspec (166 examples, 0 failures), all greenWith #49's idempotency merged, this is the pairing the issue called for: an org-wide
repolist=github-orgrun is now safe — repos needing nothing pass through asunchanged. The remaining step toward #56 (scheduled runs) is CI-friendly pushing (HTTPS + token), the long-standing stretch-goal TODO ininit.pp.🤖 Generated with Claude Code