config

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// FileStrategyAuto derives the route from the rulesets the configuration
	// declares: a pull request where a direct push would be rejected, a direct
	// commit where it would not. This is the default.
	FileStrategyAuto = "auto"
	// FileStrategyDirect always commits straight to the target branch, which is
	// what gomgr did before there was a choice.
	FileStrategyDirect = "direct"
	// FileStrategyPullRequest always routes through a branch and pull request.
	FileStrategyPullRequest = "pull_request"
)

How gomgr lands the files it writes.

View Source
const (
	PresetBranchProtection       = "branch-protection"
	PresetStrictBranchProtection = "strict-branch-protection"
	PresetTagProtection          = "tag-protection"
	PresetNoForcePush            = "no-force-push"
	PresetRequireSignedCommits   = "require-signed-commits"
	PresetRequireDCO             = "require-dco"
	PresetNoCommittedKeys        = "no-committed-keys"
)

Built-in preset names. These are the guard rails gomgr ships with, so a config can ask for "the usual protection" without restating a dozen rules.

View Source
const (
	RulesetTargetBranch = "branch"
	RulesetTargetTag    = "tag"
	RulesetTargetPush   = "push"
)

Ruleset targets. A ruleset protects refs (branch/tag) or the push itself.

View Source
const (
	RulesetEnforcementActive   = "active"
	RulesetEnforcementEvaluate = "evaluate"
	RulesetEnforcementDisabled = "disabled"
)

Ruleset enforcement levels. "evaluate" reports what would have been blocked without blocking it — the safe way to roll a new guardrail out.

View Source
const (
	BypassActorTypeIntegration       = "Integration"
	BypassActorTypeOrganizationAdmin = "OrganizationAdmin"
	BypassActorTypeEnterpriseOwner   = "EnterpriseOwner"
	BypassActorTypeRepositoryRole    = "RepositoryRole"
	BypassActorTypeTeam              = "Team"
	BypassActorTypeDeployKey         = "DeployKey"
)

Bypass actor types accepted in YAML. These mirror the GitHub API values and are case-sensitive there, so they are matched case-insensitively here and normalized on the way out.

View Source
const (
	BypassModeAlways      = "always"
	BypassModePullRequest = "pull_request"
)

Bypass modes. "pull_request" lets the actor bypass only via a pull request.

View Source
const (
	NotificationsEnabled  = "notifications_enabled"
	NotificationsDisabled = "notifications_disabled"
)

The two values GitHub accepts for a team's notification setting. They are compared against what the API reports and sent back to it, so they are constants rather than literals repeated at each end.

Variables

This section is empty.

Functions

func BootstrapTeamYAML

func BootstrapTeamYAML(path string, name string) error

func BypassActorTypeNames added in v0.3.0

func BypassActorTypeNames() string

BypassActorTypeNames returns the recognized actor types, for error messages.

func FindRepoDefinitionFile added in v0.5.0

func FindRepoDefinitionFile(dir, repo string) (path, container string, err error)

FindRepoDefinitionFile returns the file a repository's definition should be written to, and the key its entries sit under.

repos.yaml is preferred when it declares the repository, so an imported ruleset lands with the rest of that repository's definition. A configuration still keeping its definitions in team files gets the team file, because writing to repos.yaml instead would define the repository in two places at once, which the loader refuses.

func FindTeamFileForRepo added in v0.4.0

func FindTeamFileForRepo(dir, repo string) (string, error)

FindTeamFileForRepo returns the team file under <dir>/teams that declares repo, or "" when no file does. When several declare it the first by filename wins, which keeps the choice stable across runs.

func InsertOrgRulesets added in v0.4.0

func InsertOrgRulesets(path string, rulesets []RulesetConfig) error

InsertOrgRulesets adds ruleset entries to the top-level `rulesets:` block of an org.yaml, creating the block when the file has none.

The file is edited as text rather than re-encoded from a parsed tree. yaml.v3 can round-trip a document, but it normalizes as it goes — blank lines between entries disappear, quoting style shifts, long values re-wrap — and a config repository's diff is something people have to read. Parsing is used only to find the line to splice at, which is exactly what a parser is good for and a regex is not.

func InsertRepoRulesets added in v0.4.0

func InsertRepoRulesets(path, container, repo string, rulesets []RulesetConfig) error

InsertRepoRulesets adds ruleset entries to a repository's entry, handling the three shapes a repository entry can take: one that already has a `rulesets:` block, one that is a settings map without it, and one that is the bare permission string (`infra: push`), which has to grow into a map first.

container is the key the entries sit under: "repos" in repos.yaml, or "repositories" in a team file.

func NearestRepoKey added in v0.4.5

func NearestRepoKey(k string) string

NearestRepoKey returns a known key the given one is probably a slip of, which for the mistakes that actually happen — a plural, a missing letter, a transposition — means one edit away.

func PresetNames added in v0.3.0

func PresetNames() []string

PresetNames returns the built-in preset names in sorted order, for error messages and documentation.

func RejectUnknownRepoKeys added in v0.4.5

func RejectUnknownRepoKeys(m map[string]any) error

RejectUnknownRepoKeys refuses a key gomgr does not understand.

Ignoring one is not harmless. A configuration writing "permissions: admin" instead of "permission: admin" declared no permission at all, so gomgr granted the empty string, GitHub read that as its default of read, and the team sat on read access to a repository the configuration said was admin — for as long as nobody read the plan closely, because gomgr re-granted it on every run and reported success each time.

func RulesetPresets added in v0.3.0

func RulesetPresets() map[string]RulesetConfig

RulesetPresets returns the built-in guard rails, freshly constructed on each call. Presets hand out pointers into RulesetRules and callers merge those pointers into their own config, so sharing one package-level map between rulesets would let them alias each other's rule structs.

A preset supplies target, conditions and rules; a RulesetConfig naming one overrides any of those it sets itself. Presets deliberately do not set bypass actors — who may bypass a guard rail is a decision for the org, not a default.

func Slugify added in v0.6.0

func Slugify(name string) string

Slugify turns a team name into the slug GitHub would derive from it.

func ValidateCodeOwner added in v0.2.5

func ValidateCodeOwner(co string) error

ValidateCodeOwner checks that a CODEOWNERS entry is well-formed. Accepts bare usernames (octocat), explicit user refs (@octocat), and team refs (@org/team-slug). Emails are not yet supported.

func ValidateRulesets added in v0.3.0

func ValidateRulesets(scope RulesetScope, where string, rulesets []RulesetConfig) error

ValidateRulesets checks a ruleset list for the mistakes GitHub would only report as an opaque 422 at apply time: unknown presets, invalid enumerations, duplicate names, and conditions used at the wrong scope.

where labels the source ("org.yaml" or "team X, repo Y") in error messages.

func WriteTeamFile added in v0.4.0

func WriteTeamFile(dir string, team TeamConfig) (string, error)

WriteTeamFile writes an imported team to <dir>/teams/<slug>.yaml and returns the path it wrote.

Unlike the ruleset import, which has to splice into files somebody else wrote, every team file here is new — so this is a plain encode, and the only thing worth guarding is not landing on top of a file that already exists.

Types

type AppConfig

type AppConfig struct {
	AppID      int64  `yaml:"app_id,omitempty"`
	PrivateKey string `yaml:"private_key,omitempty"`
	Org        string `yaml:"org"`

	DryWarnings struct {
		WarnUnmanagedTeams        bool `yaml:"warn_unmanaged_teams"`
		WarnMembersWithoutAnyTeam bool `yaml:"warn_members_without_any_team"`
		WarnUnmanagedRepos        bool `yaml:"warn_unmanaged_repos"`
		WarnUnmanagedCustomRoles  bool `yaml:"warn_unmanaged_custom_roles"`
		WarnUnmanagedRulesets     bool `yaml:"warn_unmanaged_rulesets"`
		WarnUnmanagedOwners       bool `yaml:"warn_unmanaged_owners"`
		WarnExcessCollaborators   bool `yaml:"warn_excess_collaborators"`
	} `yaml:"dry_warnings"`
	RemoveMembersWithoutTeam   bool `yaml:"remove_members_without_team"`
	DeleteUnconfiguredTeams    bool `yaml:"delete_unconfigured_teams"`
	DeleteUnmanagedRepos       bool `yaml:"delete_unmanaged_repos"`
	DeleteUnmanagedCustomRoles bool `yaml:"delete_unmanaged_custom_roles"`
	DeleteUnmanagedRulesets    bool `yaml:"delete_unmanaged_rulesets"`
	DeleteStaleCodeowners      bool `yaml:"delete_stale_codeowners"`
	CreateRepo                 bool `yaml:"create_repo"`

	// DemoteUnconfiguredOwners drops an organization owner that org.yaml does
	// not list back to plain member. Off by default, and inert unless
	// org.owners names at least one login: an empty list means "gomgr does not
	// manage owners here", never "this organization should have none". The
	// authenticated user is never demoted by their own run, because an
	// organization whose last owner demotes themselves cannot be repaired
	// through the API that did it.
	//
	// A demoted owner becomes an ordinary member, which puts them in reach of
	// RemoveMembersWithoutTeam on the same run if they belong to no team.
	DemoteUnconfiguredOwners bool `yaml:"demote_unconfigured_owners"`

	// RemoveExcessCollaborators revokes a direct repository grant that gives
	// someone more access than their team membership and the organization's
	// default repository permission already do.
	//
	// This is the one thing that closes the door the GitHub UI opens: a
	// repository's "add collaborator" button grants access that no YAML file
	// mentions, and no other part of gomgr looks at it. Revoking a direct
	// grant leaves team-derived access untouched, so someone who should have
	// push through a team keeps push; only the grant stapled on beside it goes.
	//
	// Off by default. With DryWarnings.WarnExcessCollaborators set instead,
	// the same grants are reported and left alone.
	RemoveExcessCollaborators bool `yaml:"remove_excess_collaborators"`

	// ArchiveUnmanagedRepos archives a repository no team names, instead of
	// leaving it alone.
	//
	// This is the reversible sibling of DeleteUnmanagedRepos, and the one to
	// reach for first. An archived repository can be un-archived by anyone with
	// admin on it; a deleted one is ninety days of GitHub support at best and
	// gone at worst. A configuration that has drifted — a repository created
	// last week that nobody has declared yet — costs an un-archive rather than
	// a restore request.
	//
	// If both this and DeleteUnmanagedRepos are set, this one wins and the
	// deletion is reported as skipped. Being wrong in the recoverable direction
	// is the point of having it.
	ArchiveUnmanagedRepos bool `yaml:"archive_unmanaged_repos"`

	// IgnoreArchived skips archived repositories when planning team permission
	// grants. GitHub refuses to change permissions on an archived repository,
	// so a configuration that still names one plans a grant that fails on every
	// run. Off by default, because silently skipping a repository is also a way
	// to not notice that it was archived out from under the config.
	IgnoreArchived bool `yaml:"ignore_archived"`

	// ReconcileVisibility allows gomgr to change the visibility of a repository
	// that already exists, and only one that explicitly declares a visibility.
	//
	// Off by default, and deliberately a second key rather than something an
	// org-wide default can trigger. Making a private repository public is the
	// most damaging thing this tool can do — a deleted repository can be
	// restored for ninety days, a disclosed one cannot be undisclosed — so it
	// takes a deliberate edit in two files, and can never happen to thirty-four
	// repositories because one line changed.
	ReconcileVisibility bool `yaml:"reconcile_visibility"`

	// SignOff is the identity used for the Signed-off-by trailer appended to
	// every commit gomgr writes, in "Name <email>" form. Set it when the org
	// enforces DCO — a ruleset with a commit_message_pattern rule requiring
	// "Signed-off-by:" rejects gomgr's file-sync pushes otherwise, because
	// those commits go straight to the default branch and never pass through
	// a pull request where a DCO check could run.
	//
	// Empty (the default) appends nothing, preserving prior behavior for orgs
	// that do not require sign-off.
	SignOff string `yaml:"sign_off,omitempty"`

	// FileChanges controls how gomgr lands the files it writes.
	FileChanges FileChangeConfig `yaml:"file_changes,omitempty"`

	// Files declares templated files that should exist in every managed
	// repository. Each entry's Content is rendered through text/template with
	// {Org, Repo} context. Only (optional) limits which repos an entry applies
	// to via path.Match-style globs.
	Files []FileSpec `yaml:"files,omitempty"`

	// Legacy convenience flags. These are still honored but are materialized
	// into Files entries at load time. Prefer Files for new configurations.
	AddRenovateConfig bool   `yaml:"add_renovate_config,omitempty"`
	RenovateConfig    string `yaml:"renovate_config,omitempty"`
	AddDefaultReadme  bool   `yaml:"add_default_readme,omitempty"`
}

func (AppConfig) ResolvedAppID added in v0.3.0

func (a AppConfig) ResolvedAppID() int64

ResolvedAppID returns the GitHub App ID in effect, falling back to GITHUB_APP_ID when app.yaml does not set one. It is 0 under PAT auth, where there is no app.

type BypassActorConfig added in v0.3.0

type BypassActorConfig struct {
	Type    string `yaml:"type"`
	Team    string `yaml:"team,omitempty"`
	App     string `yaml:"app,omitempty"`
	ActorID int64  `yaml:"actor_id,omitempty"`
	Mode    string `yaml:"mode,omitempty"` // always|pull_request (default always)
}

BypassActorConfig grants an actor the right to bypass the ruleset.

Exactly one identifier applies per Type:

  • Team: Team names a team slug, resolved to its ID against the org.
  • Integration: App is either "self" (gomgr's own app_id, so its file-sync pushes survive a pull-request rule) or a numeric GitHub App ID.
  • RepositoryRole: ActorID is the role ID, which the roles API reports.
  • OrganizationAdmin, DeployKey: no identifier; GitHub fixes the ID.

func (BypassActorConfig) BypassMode added in v0.3.0

func (b BypassActorConfig) BypassMode() string

BypassMode returns the actor's bypass mode, defaulting to "always".

func (BypassActorConfig) IdentifiedByTypeAlone added in v0.3.0

func (b BypassActorConfig) IdentifiedByTypeAlone() bool

IdentifiedByTypeAlone reports whether GitHub recognizes this actor from its type without an ID. Such actors must be sent without one.

func (BypassActorConfig) NormalizedType added in v0.3.0

func (b BypassActorConfig) NormalizedType() string

NormalizedType returns the canonical, correctly-cased actor type for the GitHub API, or "" if the configured type is not recognized.

type CodeScanningRule added in v0.3.0

type CodeScanningRule struct {
	Tools []CodeScanningTool `yaml:"tools"`
}

CodeScanningRule blocks merges when a scanning tool reports alerts at or above the configured thresholds.

type CodeScanningTool added in v0.3.0

type CodeScanningTool struct {
	Tool                    string `yaml:"tool"`
	AlertsThreshold         string `yaml:"alerts_threshold,omitempty"`          // none|errors|errors_and_warnings|all
	SecurityAlertsThreshold string `yaml:"security_alerts_threshold,omitempty"` // none|critical|high_or_higher|medium_or_higher|all
}

CodeScanningTool sets the thresholds for one scanning tool, e.g. CodeQL.

type ConfigError added in v0.4.3

type ConfigError struct {
	Where string
	Msg   string
}

ConfigError is a configuration problem with the place it was found.

func (*ConfigError) Error added in v0.4.3

func (e *ConfigError) Error() string

type CustomRoleConfig added in v0.2.1

type CustomRoleConfig struct {
	Name        string   `yaml:"name"`
	Description string   `yaml:"description,omitempty"`
	BaseRole    string   `yaml:"base_role"` // read, triage, write, maintain, admin
	Permissions []string `yaml:"permissions,omitempty"`
}

CustomRoleConfig defines a custom repository role for the organization Requires GitHub Enterprise Cloud

type FileChangeConfig added in v0.4.2

type FileChangeConfig struct {
	// Strategy is auto (default), direct, or pull_request.
	Strategy string `yaml:"strategy,omitempty"`
	// Branch is the head branch gomgr opens its pull requests from. One branch
	// is reused per repository, so a repeated sync updates the open pull
	// request rather than opening another.
	Branch string `yaml:"branch,omitempty"`
}

FileChangeConfig controls how the files gomgr writes reach their branch.

The strategy is derived rather than declared by default. gomgr already knows the organization's guard rails — it manages them — so it can work out whether a direct push to a given branch would be rejected instead of asking for a flag that has to be kept in step with the rulesets by hand.

Merge behavior is deliberately absent. gomgr decides that at repository creation, not per sync: applyRepoEnsure sets AllowAutoMerge true, AllowMergeCommit false and DeleteBranchOnMerge true, the same way repository visibility is decided there. Offering merge_method here would let a configuration ask for a merge commit that gomgr itself disabled.

func (FileChangeConfig) ResolvedBranch added in v0.4.2

func (f FileChangeConfig) ResolvedBranch() string

ResolvedBranch returns the head branch for gomgr's pull requests.

func (FileChangeConfig) ResolvedStrategy added in v0.4.2

func (f FileChangeConfig) ResolvedStrategy() string

ResolvedStrategy returns the configured strategy, defaulting to auto.

func (FileChangeConfig) Validate added in v0.4.2

func (f FileChangeConfig) Validate() error

Validate checks the file-change settings.

type FileExtensionRestrictionRule added in v0.3.0

type FileExtensionRestrictionRule struct {
	RestrictedFileExtensions []string `yaml:"restricted_file_extensions"`
}

FileExtensionRestrictionRule blocks pushes touching the listed extensions.

type FilePathRestrictionRule added in v0.3.0

type FilePathRestrictionRule struct {
	RestrictedFilePaths []string `yaml:"restricted_file_paths"`
}

FilePathRestrictionRule blocks pushes touching the listed paths.

type FileSpec added in v0.2.5

type FileSpec struct {
	Path      string   `yaml:"path"`
	Content   string   `yaml:"content"`
	Message   string   `yaml:"message,omitempty"`
	Branch    string   `yaml:"branch,omitempty"`
	Only      []string `yaml:"only,omitempty"`
	Reconcile bool     `yaml:"reconcile,omitempty"`
}

FileSpec declares a file that gomgr should ensure exists in managed repos. Content is a Go text/template; Path, Message and Branch are literal strings. Only restricts which repositories the file applies to (path.Match globs against the repo name). An empty Only matches every managed repo.

Reconcile controls drift handling for files that already exist. When false (the default) gomgr only writes the file when it is missing, leaving any hand-edited content alone. When true gomgr compares the rendered content to what is on the default branch and pushes an update commit if they differ — useful for config-derived files like CODEOWNERS that should track the YAML.

func RepoFileSpecs added in v0.5.0

func RepoFileSpecs(val any) ([]FileSpec, error)

RepoFileSpecs reads the `files:` block off a repository entry.

type MergeQueueRule added in v0.3.0

type MergeQueueRule struct {
	MergeMethod                  string `yaml:"merge_method,omitempty"`      // merge|squash|rebase
	GroupingStrategy             string `yaml:"grouping_strategy,omitempty"` // allgreen|headgreen
	CheckResponseTimeoutMinutes  int    `yaml:"check_response_timeout_minutes,omitempty"`
	MaxEntriesToBuild            int    `yaml:"max_entries_to_build,omitempty"`
	MaxEntriesToMerge            int    `yaml:"max_entries_to_merge,omitempty"`
	MinEntriesToMerge            int    `yaml:"min_entries_to_merge,omitempty"`
	MinEntriesToMergeWaitMinutes int    `yaml:"min_entries_to_merge_wait_minutes,omitempty"`
}

MergeQueueRule routes merges through GitHub's merge queue.

type OrgConfig

type OrgConfig struct {
	Owners      []string           `yaml:"owners"`
	CustomRoles []CustomRoleConfig `yaml:"custom_roles,omitempty"`

	// RepoDefaults are the repository settings every managed repository gets
	// unless its own entry in teams/*.yaml overrides them. Visibility is not
	// among them on purpose: see AppConfig.ReconcileVisibility.
	RepoDefaults RepoSettingsConfig `yaml:"repo_defaults,omitempty"`

	// Rulesets declares organization-wide rulesets — the guard rails that
	// apply across repositories, narrowed by their repository_name conditions.
	// Repository-specific rulesets live on the repository entry in teams/*.yaml
	// and stack on top of these; GitHub evaluates every ruleset that matches and
	// enforces the strictest outcome.
	Rulesets []RulesetConfig `yaml:"rulesets,omitempty"`
}

type OrgDir added in v0.6.1

type OrgDir struct {
	// Dir is the path to the directory holding app.yaml.
	Dir string
	// Org is the `org:` key from that app.yaml, lowercased. Empty when the
	// file parses but names no organization.
	Org string
}

OrgDir is a configuration directory found on disk and the organization it names.

func DiscoverOrgDirs added in v0.6.1

func DiscoverOrgDirs(root string) ([]OrgDir, error)

DiscoverOrgDirs walks root and returns every directory containing an app.yaml, paired with the organization that app.yaml names.

It reads only the `org:` key. A full config.Load would refuse a directory with an unrelated problem — a bad ruleset, a team file with a typo — and the question being asked here is which organizations are configured at all, which a broken team file does not change the answer to. Reporting those as missing would be worse than reporting them as present and broken.

The walk does not descend into a directory that has an app.yaml. One config directory does not contain another, and teams/ is full of YAML that would otherwise be probed for no reason.

type PatternRule added in v0.3.0

type PatternRule struct {
	Name     string `yaml:"name,omitempty"`
	Operator string `yaml:"operator"` // starts_with|ends_with|contains|regex
	Pattern  string `yaml:"pattern"`
	Negate   *bool  `yaml:"negate,omitempty"`
}

PatternRule matches a commit message, email, branch name or tag name. Negate inverts the match, so the rule fails when the pattern *does* match.

type PullRequestRule added in v0.3.0

type PullRequestRule struct {
	RequiredApprovingReviewCount   int      `yaml:"required_approving_review_count,omitempty"`
	DismissStaleReviewsOnPush      bool     `yaml:"dismiss_stale_reviews_on_push,omitempty"`
	RequireCodeOwnerReview         bool     `yaml:"require_code_owner_review,omitempty"`
	RequireLastPushApproval        bool     `yaml:"require_last_push_approval,omitempty"`
	RequiredReviewThreadResolution bool     `yaml:"required_review_thread_resolution,omitempty"`
	AllowedMergeMethods            []string `yaml:"allowed_merge_methods,omitempty"` // merge|squash|rebase
}

PullRequestRule requires changes to arrive through a pull request.

type RefNameCondition added in v0.3.0

type RefNameCondition struct {
	Include []string `yaml:"include,omitempty"`
	Exclude []string `yaml:"exclude,omitempty"`
}

RefNameCondition selects refs by fnmatch pattern. GitHub also accepts the magic values "~ALL" and "~DEFAULT_BRANCH".

type RepoConfig added in v0.1.13

type RepoConfig struct {
	Permission string `yaml:"permission,omitempty"` // pull|triage|push|maintain|admin

	// Archived declares whether this repository should be archived. It is a
	// pointer because omitting it and setting it to false are different
	// instructions: an absent key leaves GitHub's state alone, so a repository
	// somebody archived by hand stays archived until a configuration says
	// otherwise in as many words. Un-archiving never happens by omission.
	Archived   *bool              `yaml:"archived,omitempty"`
	Topics     []string           `yaml:"topics,omitempty"`
	Pinned     bool               `yaml:"pinned,omitempty"`
	Rulesets   []RulesetConfig    `yaml:"rulesets,omitempty"`
	Settings   RepoSettingsConfig `yaml:"settings,omitempty"`
	Visibility string             `yaml:"visibility,omitempty"` // public|private|internal

	// Files are this repository's own templated files. An entry here replaces
	// the app.files entry with the same path for this repository alone, so an
	// exception is stated by naming the repository rather than by relying on
	// where it sits in a list.
	Files []FileSpec `yaml:"files,omitempty"`
}

type RepoSettingsConfig added in v0.4.3

type RepoSettingsConfig struct {
	AllowAutoMerge      *bool `yaml:"allow_auto_merge,omitempty"`
	AllowSquashMerge    *bool `yaml:"allow_squash_merge,omitempty"`
	AllowMergeCommit    *bool `yaml:"allow_merge_commit,omitempty"`
	AllowRebaseMerge    *bool `yaml:"allow_rebase_merge,omitempty"`
	DeleteBranchOnMerge *bool `yaml:"delete_branch_on_merge,omitempty"`
	AllowUpdateBranch   *bool `yaml:"allow_update_branch,omitempty"`

	// Security analyses. GitHub keeps these in a nested security_and_analysis
	// object and reports each as a status string rather than a flag, but they
	// behave like every other setting here: stating one asks for it, omitting
	// one leaves GitHub's state alone.
	//
	// Secret scanning and push protection are free on public repositories and
	// need GitHub Advanced Security on private ones, so a private repository
	// in an organization without it will be refused by the API.
	//
	// Deliberately absent: advanced_security and code_security. Those turn
	// GHAS itself on, which is a billing decision rather than a hygiene one,
	// and gomgr does not spend money on your behalf. secret_scanning_ai_
	// detection and secret_scanning_non_provider_patterns are absent because
	// go-github does not model them yet.
	SecretScanning               *bool `yaml:"secret_scanning,omitempty"`
	SecretScanningPushProtection *bool `yaml:"secret_scanning_push_protection,omitempty"`
	SecretScanningValidityChecks *bool `yaml:"secret_scanning_validity_checks,omitempty"`
	DependabotSecurityUpdates    *bool `yaml:"dependabot_security_updates,omitempty"`
}

RepoSettingsConfig declares the repository settings gomgr keeps in step.

Every field is a pointer so that "not mentioned" is distinguishable from "set to false". An org-wide default that said false for everything it did not mention would switch settings off across the organization the first time anybody added the block.

The set is limited to settings that are idempotent, reversible, and cannot expose anything: merge and branch hygiene, and the security analyses below. Repository visibility is handled separately and far more carefully — see AppConfig.ReconcileVisibility.

func ParseRepoSettings added in v0.4.3

func ParseRepoSettings(v any) (RepoSettingsConfig, error)

ParseRepoSettings decodes a `settings:` block that arrived as untyped YAML, which is the shape a repository entry in teams/*.yaml is held in.

func (RepoSettingsConfig) IsEmpty added in v0.4.3

func (r RepoSettingsConfig) IsEmpty() bool

IsEmpty reports whether nothing at all is declared.

func (RepoSettingsConfig) MergedWith added in v0.4.3

MergedWith returns these settings overlaid on defaults: a field the override states wins, a field it omits falls through.

func (RepoSettingsConfig) Validate added in v0.4.3

func (r RepoSettingsConfig) Validate(where string) error

Validate rejects a combination GitHub would refuse.

type ReposFile added in v0.5.0

type ReposFile struct {
	Repos map[string]any `yaml:"repos"`
}

ReposFile is repos.yaml: repository definitions keyed by repository name.

Splitting these out of teams/*.yaml separates the two things a team entry was saying at once. A team file says which teams hold what access; repos.yaml says what the repository is. Keeping both in one place meant a repository named by two teams had two definitions and no rule for which won, and the answer turned out to be "whichever file sorted last", including for the permission granted.

Definitions in team files are still honored so a configuration can move across one repository at a time, but a repository defined in both places is refused rather than resolved by precedence, because a precedence rule is what this is getting rid of.

type RepositoryNameCondition added in v0.3.0

type RepositoryNameCondition struct {
	Include   []string `yaml:"include,omitempty"`
	Exclude   []string `yaml:"exclude,omitempty"`
	Protected *bool    `yaml:"protected,omitempty"`
}

RepositoryNameCondition selects repositories by fnmatch pattern, or "~ALL". Protected, when set, additionally restricts to repositories GitHub considers protected.

type RequiredDeploymentsRule added in v0.3.0

type RequiredDeploymentsRule struct {
	Environments []string `yaml:"environments"`
}

RequiredDeploymentsRule requires deployments to the named environments to succeed before merge.

type RequiredStatusChecksRule added in v0.3.0

type RequiredStatusChecksRule struct {
	Checks               []StatusCheck `yaml:"checks"`
	Strict               bool          `yaml:"strict,omitempty"`
	DoNotEnforceOnCreate *bool         `yaml:"do_not_enforce_on_create,omitempty"`
}

RequiredStatusChecksRule requires named checks to pass before merge. Strict additionally requires the branch to be up to date with its base.

type Root

type Root struct {
	App  AppConfig    `yaml:"app"`
	Org  OrgConfig    `yaml:"org"`
	Team []TeamConfig `yaml:"teams"`

	// Repos are repository definitions from repos.yaml: everything about a
	// repository that is not some team's permission on it. Values take the same
	// shape as an advanced entry under a team's `repositories:`, minus
	// `permission`, which belongs to the (team, repo) pair rather than to the
	// repository.
	//
	// A repository may be defined here or in a team file, but not in both — see
	// ReposFile.
	Repos map[string]any
}

func Load

func Load(dir string) (*Root, error)

func (*Root) Validate added in v0.2.3

func (r *Root) Validate() error

Validate checks that the loaded configuration is semantically correct.

type RuleWorkflow added in v0.3.0

type RuleWorkflow struct {
	Path         string `yaml:"path"`
	Repository   string `yaml:"repository,omitempty"` // repo name in this org; resolved to an ID
	RepositoryID *int64 `yaml:"repository_id,omitempty"`
	Ref          string `yaml:"ref,omitempty"`
	SHA          string `yaml:"sha,omitempty"`
}

RuleWorkflow points at a workflow file, optionally pinned to a ref or SHA in a specific repository.

type RulesetConditions added in v0.3.0

type RulesetConditions struct {
	RefName        *RefNameCondition        `yaml:"ref_name,omitempty"`
	RepositoryName *RepositoryNameCondition `yaml:"repository_name,omitempty"`
}

RulesetConditions narrows which refs and repositories a ruleset applies to. RepositoryName is only meaningful for organization-level rulesets; a repository-level ruleset already knows its repository.

type RulesetConfig added in v0.3.0

type RulesetConfig struct {
	Name         string              `yaml:"name"`
	Preset       string              `yaml:"preset,omitempty"`
	Target       string              `yaml:"target,omitempty"`      // branch|tag|push (default branch)
	Enforcement  string              `yaml:"enforcement,omitempty"` // active|evaluate|disabled (default active)
	Conditions   *RulesetConditions  `yaml:"conditions,omitempty"`
	BypassActors []BypassActorConfig `yaml:"bypass_actors,omitempty"`
	Rules        RulesetRules        `yaml:"rules,omitempty"`
}

RulesetConfig declares a GitHub ruleset — the successor to branch protection, and the mechanism behind org-wide guard rails.

The same shape is used at both scopes:

  • org.yaml `rulesets:` applies across the organization, narrowed by Conditions.RepositoryName.
  • a repository entry in teams/*.yaml may carry its own `rulesets:`, which apply to that repository alone and stack on top of the org's.

Preset names a built-in guard rail (see RulesetPresets) that supplies the target, conditions and rules. Anything set explicitly alongside it wins, at whole-rule granularity: naming a rule key replaces the preset's version of that rule outright rather than merging field by field.

func ParseRulesets added in v0.3.0

func ParseRulesets(v any) ([]RulesetConfig, error)

ParseRulesets decodes a `rulesets:` value that arrived as an untyped YAML node — the shape repository entries in teams/*.yaml are held in. Round-tripping through YAML keeps RulesetConfig the single definition of the schema instead of duplicating the field walk here.

func (RulesetConfig) Resolve added in v0.3.0

func (r RulesetConfig) Resolve() (RulesetConfig, error)

Resolve expands the ruleset's preset and fills in defaults, returning a configuration with Target and Enforcement always set. The receiver is not modified.

type RulesetRules added in v0.3.0

type RulesetRules struct {
	// Branch and tag rules.
	Creation                 *bool                     `yaml:"creation,omitempty"`
	Update                   *UpdateRule               `yaml:"update,omitempty"`
	Deletion                 *bool                     `yaml:"deletion,omitempty"`
	RequiredLinearHistory    *bool                     `yaml:"required_linear_history,omitempty"`
	RequiredSignatures       *bool                     `yaml:"required_signatures,omitempty"`
	NonFastForward           *bool                     `yaml:"non_fast_forward,omitempty"`
	PullRequest              *PullRequestRule          `yaml:"pull_request,omitempty"`
	RequiredStatusChecks     *RequiredStatusChecksRule `yaml:"required_status_checks,omitempty"`
	RequiredDeployments      *RequiredDeploymentsRule  `yaml:"required_deployments,omitempty"`
	MergeQueue               *MergeQueueRule           `yaml:"merge_queue,omitempty"`
	CommitMessagePattern     *PatternRule              `yaml:"commit_message_pattern,omitempty"`
	CommitAuthorEmailPattern *PatternRule              `yaml:"commit_author_email_pattern,omitempty"`
	CommitterEmailPattern    *PatternRule              `yaml:"committer_email_pattern,omitempty"`
	BranchNamePattern        *PatternRule              `yaml:"branch_name_pattern,omitempty"`
	TagNamePattern           *PatternRule              `yaml:"tag_name_pattern,omitempty"`
	Workflows                *WorkflowsRule            `yaml:"workflows,omitempty"`
	CodeScanning             *CodeScanningRule         `yaml:"code_scanning,omitempty"`

	// Push rules. These require GitHub Enterprise Cloud on private repositories.
	FileExtensionRestriction *FileExtensionRestrictionRule `yaml:"file_extension_restriction,omitempty"`
	FilePathRestriction      *FilePathRestrictionRule      `yaml:"file_path_restriction,omitempty"`
	MaxFilePathLength        *int                          `yaml:"max_file_path_length,omitempty"`
	MaxFileSize              *int64                        `yaml:"max_file_size,omitempty"`
}

RulesetRules is the YAML face of GitHub's rule list. Every field is a pointer so that "unset" is distinguishable from "explicitly off" — a preset can turn a rule on and the config can turn it back off with `deletion: false`.

Field order here is also the merge order used by mergeRules; adding a field requires no change there as long as it stays nil-able.

func (RulesetRules) IsEmpty added in v0.3.0

func (rr RulesetRules) IsEmpty() bool

IsEmpty reports whether no rule at all is enabled. A ruleset with no rules is accepted by GitHub but enforces nothing, so it is worth catching in validation.

type RulesetScope added in v0.3.0

type RulesetScope string

RulesetScope distinguishes the two places a ruleset can be declared, because a few rules are only legal at one of them.

const (
	// ScopeOrg is org.yaml `rulesets:` — applies across the organization.
	ScopeOrg RulesetScope = "org"
	// ScopeRepo is a `rulesets:` block on a repository in teams/*.yaml.
	ScopeRepo RulesetScope = "repo"
)

type StatusCheck added in v0.3.0

type StatusCheck struct {
	Context       string `yaml:"context"`
	IntegrationID *int64 `yaml:"integration_id,omitempty"`
}

StatusCheck names a required check. IntegrationID pins the check to a specific GitHub App, so an unrelated app cannot report the same context.

type TeamConfig

type TeamConfig struct {
	Name        string `yaml:"name"`
	Slug        string `yaml:"slug,omitempty"`
	Description string `yaml:"description,omitempty"`
	Privacy     string `yaml:"privacy,omitempty"` // closed, secret

	// Parents nests this team under another. GitHub allows exactly one parent,
	// so at most one entry is accepted; the field stays a list because it
	// always was one, and a config that wrote `parents: []` should keep
	// loading. The entry may be a team name or a slug — it is resolved to a
	// slug against the other teams in the config.
	//
	// Nesting grants access rather than membership: a child team inherits the
	// repository permissions of every team above it, while the members of the
	// two teams stay separate rosters. gomgr sets the relationship and lets
	// GitHub apply the inheritance; it does not write the parent's grants onto
	// the child.
	//
	// Neither a parent nor a child may be `secret` — GitHub rejects the
	// combination — which loader validation reports rather than discovering at
	// apply time.
	Parents []string `yaml:"parents,omitempty"`

	// NotificationSetting is "notifications_enabled" or
	// "notifications_disabled". Empty leaves GitHub's setting alone, which for
	// a team gomgr creates means enabled — so an org that wants its teams quiet
	// has to say so on every team, and a config that drops this field turns
	// notifications back on for everyone in it.
	NotificationSetting string `yaml:"notification_setting,omitempty"`

	Maintainers []string `yaml:"maintainers,omitempty"`
	Members     []string `yaml:"members,omitempty"`

	// repo => permission (pull|triage|push|maintain|admin) or RepoConfig for advanced settings
	// For backward compatibility, supports both:
	//   repositories:
	//     infra: maintain               # simple string permission
	//     api:                          # or advanced RepoConfig
	//       permission: push
	//       topics: [backend, api]
	//       pinned: true
	Repositories map[string]any `yaml:"repositories,omitempty"`
}

func (TeamConfig) ParentSlug added in v0.6.0

func (t TeamConfig) ParentSlug() string

ParentSlug returns the slug of the team this one is nested under, or "" when it is not nested. Validation has already rejected more than one entry, so only the first is consulted.

func (TeamConfig) ResolvedSlug added in v0.2.3

func (t TeamConfig) ResolvedSlug() string

ResolvedSlug returns the team's slug, deriving it from the name if not explicitly set.

type UpdateRule added in v0.3.0

type UpdateRule struct {
	AllowsFetchAndMerge bool `yaml:"allows_fetch_and_merge,omitempty"`
}

UpdateRule restricts updates to a matching ref.

type WorkflowsRule added in v0.3.0

type WorkflowsRule struct {
	Workflows            []RuleWorkflow `yaml:"workflows"`
	DoNotEnforceOnCreate *bool          `yaml:"do_not_enforce_on_create,omitempty"`
}

WorkflowsRule requires the named workflows to have run.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL