reposetup

package
v8.59.0 Latest Latest
Warning

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

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

Documentation

Overview

Package reposetup is the front half of the repository set-up engine: it reads a team file of giantswarm/github, validates the entries about to create a repository against the repositories schema and the creation rules, derives the template each repository is scaffolded from and returns the dry-run value the clients render — the team-file entry as it would be written, the implied template, the verdict of the name check and the guard notices.

The package is imported by `devctl repo validate` (and `repo create`), by the validation workflow of giantswarm/github and by giantswarm-repo-manager's validate_repository tool, so validation and rendering exist once. It keeps no CLI state: every input is a value of Request, every output a value of Result, and GitHub is reached only through the small FileGetter and RepositoryGetter interfaces, which *githubclient.Client satisfies.

Declarations

A TeamFile is one repositories/<team>.yaml. Its team is the file's base name (repositories/team-bumblebee.yaml → team-bumblebee), the GitHub team slug CODEOWNERS names for the file; an entry needs no team field. Each Declaration is one entry, kept as the YAML node it was read from so the rendered entry keeps the author's key order and comments.

Rules

Every entry named in Request.Names (all entries when nil) is treated as a repository the reconciler creates and must satisfy, on top of the schema:

  • gen.flavours and gen.language are set; gen.ci.generate defaults to true and is written into the rendered entry;
  • the name is lowercase and free on GitHub — an existing repository or a redirect from a renamed one counts as taken;
  • the chart-name convention holds where a chart exists (the app or cluster-app flavour): the repository is named after its chart, without an -app suffix, and gen.ci.chartName, when set, equals the name;
  • a template exists for the language: language node is refused until giantswarm/template-node ships.

Every refusal is a Problem naming the field in dotted form (gen.ci.chartName, gen.flavours[1]).

Templates

DeriveTemplate maps a declaration to its template without a template field: language go → giantswarm/template; language generic with the app flavour → giantswarm/template-app; the customer flavour or component type, the languages python and kyverno-policy, and generic repositories without a chart → the minimal scaffold (README, LICENSE, DCO, SECURITY.md, CODEOWNERS, .gitignore plus the generated files). Rendering the scaffold is the engine's back half.

Guards

Two notices bound the machine approval of a creation-only pull request: an author outside the owning team (and outside FallbackTeam, the CODEOWNERS fallback) keeps the team's review, and more than MaxMachineApprovedEntries added entries get a person. Author and membership are inputs; the package resolves neither.

Schema

FetchSchema reads the schema from giantswarm/github main so a run follows the schema as it is; EmbeddedSchema is the copy shipped with this devctl version, for tests and as the fallback when GitHub cannot be reached. Result.Schema says which one a result was validated against.

Index

Constants

View Source
const (
	SchemaRepositoryOwner = "giantswarm"
	SchemaRepository      = "github"
	SchemaPath            = ".github/repositories.schema.json"
	SchemaRef             = "main"
)

Where the live schema lives: giantswarm/github's main branch.

View Source
const (
	// DefaultOwner is the GitHub organisation repositories are created in.
	DefaultOwner = "giantswarm"
	// FallbackTeam is the CODEOWNERS fallback owner of giantswarm/github; its
	// members may add entries to any team file without the team's review.
	FallbackTeam = "team-planeteers"
	// MaxMachineApprovedEntries is how many entries a creation-only pull
	// request may add and still be approved by the machine; more is a
	// migration and gets a person.
	MaxMachineApprovedEntries = 3
)

Variables

This section is empty.

Functions

func IsEntryNotFound

func IsEntryNotFound(err error) bool

IsEntryNotFound asserts entryNotFoundError: a requested entry name is not in the team file.

func IsInvalidConfig

func IsInvalidConfig(err error) bool

IsInvalidConfig asserts invalidConfigError.

func IsInvalidSchema

func IsInvalidSchema(err error) bool

IsInvalidSchema asserts invalidSchemaError: the repositories schema does not parse or compile.

func IsInvalidTeamFile

func IsInvalidTeamFile(err error) bool

IsInvalidTeamFile asserts invalidTeamFileError: the team file is not a YAML list of mappings.

func IsTemplateUnavailable

func IsTemplateUnavailable(err error) bool

IsTemplateUnavailable asserts templateUnavailableError: the declaration needs a template that does not exist yet.

func TeamOf

func TeamOf(path string) string

TeamOf returns the team slug a team-file path stands for: the base name without extension.

Types

type Declaration

type Declaration struct {
	// Name is the entry's name field; empty when the entry has none.
	Name string
	// contains filtered or unexported fields
}

Declaration is one entry of a team file: the desired state of one repository. It keeps the YAML node it was read from, so a rendered entry preserves the author's key order and comments.

func (Declaration) Instance

func (d Declaration) Instance() (any, error)

Instance returns the entry as a JSON-compatible value (maps, slices, strings, json.Number, bools), the shape the schema validates.

func (Declaration) YAML

func (d Declaration) YAML() (string, error)

YAML renders the entry as it stands in a team file: one list item.

type Entry

type Entry struct {
	// Name of the repository.
	Name string `json:"name"`
	// Rendered is the entry as it would be written to the team file, defaults
	// applied (gen.ci.generate: true), as a one-item YAML list.
	Rendered string `json:"rendered"`
	// Template the repository would be scaffolded from; empty when the
	// declaration does not derive one.
	Template Template `json:"template,omitempty"`
	// NameCheck is the verdict of the GitHub name check.
	NameCheck NameCheck `json:"nameCheck"`
	// Problems are the refusals, each naming the field. Empty when accepted.
	Problems []Problem `json:"problems,omitempty"`
	// Accepted is true when the entry has no problems.
	Accepted bool `json:"accepted"`
}

Entry is the dry run of one declaration.

type FileGetter

type FileGetter interface {
	GetFile(ctx context.Context, owner, repo, path, ref string) (githubclient.RepositoryFile, error)
}

FileGetter reads one file of a GitHub repository at a ref. *githubclient.Client is a FileGetter.

type GitHubNameChecker

type GitHubNameChecker struct {
	Repositories RepositoryGetter
}

GitHubNameChecker checks names against GitHub. GitHub answers a request for a renamed repository with a redirect the client follows, so the repository that comes back under a different full name is the redirect — and the old name is taken as much as an existing one.

func (GitHubNameChecker) CheckName

func (c GitHubNameChecker) CheckName(ctx context.Context, owner, name string) (NameCheck, error)

CheckName implements NameChecker.

type NameCheck

type NameCheck struct {
	Verdict Verdict `json:"verdict"`
	Detail  string  `json:"detail,omitempty"`
}

NameCheck is the verdict of the name check with the reason.

type NameChecker

type NameChecker interface {
	CheckName(ctx context.Context, owner, name string) (NameCheck, error)
}

NameChecker says whether owner/name is free on GitHub.

type Notice

type Notice struct {
	Kind    NoticeKind `json:"kind"`
	Message string     `json:"message"`
}

Notice is a guard notice about the change as a whole. A notice does not refuse; it tells the author what review the change will get.

type NoticeKind

type NoticeKind string

NoticeKind classifies a guard notice.

const (
	// NoticeTeamReview: the author is outside the owning team and the
	// fallback team, so the team's review is required.
	NoticeTeamReview NoticeKind = "team-review"
	// NoticeBatchReview: more than MaxMachineApprovedEntries entries are
	// added, so a person reviews.
	NoticeBatchReview NoticeKind = "batch-review"
	// NoticeNamesUnchecked: no NameChecker was configured.
	NoticeNamesUnchecked NoticeKind = "names-unchecked"
)

type Problem

type Problem struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

Problem is one refusal: the field in dotted form (gen.ci.chartName, gen.flavours[1]; "(entry)" for the entry as a whole) and why.

func (Problem) String

func (p Problem) String() string

type RepositoryGetter

type RepositoryGetter interface {
	GetRepository(ctx context.Context, owner, repo string) (*github.Repository, error)
}

RepositoryGetter reads one repository. *githubclient.Client is one; a missing repository is reported through githubclient.IsNotFound or a go-github 404.

type Request

type Request struct {
	// TeamFile the entries live in; its team is the owning team. Required.
	TeamFile *TeamFile
	// Names of the entries being added, the ones the creation rules apply
	// to. Nil means every entry of the file.
	Names []string
	// Author is the GitHub login of the person opening the change; empty
	// when unknown, which skips the team guard.
	Author string
	// AuthorTeams are the GitHub team slugs the author is a member of
	// (team-bumblebee; a giantswarm/ or @giantswarm/ prefix is accepted).
	AuthorTeams []string
}

Request is one validation: the team file, the entries being added and the author of the change.

type Result

type Result struct {
	// Team that owns the entries: the team file's.
	Team string `json:"team"`
	// Schema the entries were validated against.
	Schema SchemaOrigin `json:"schema"`
	// Entries in request order.
	Entries []Entry `json:"entries"`
	// Notices are the guard notices that apply to the change as a whole.
	Notices []Notice `json:"notices,omitempty"`
	// Accepted is true when every entry is accepted.
	Accepted bool `json:"accepted"`
}

Result is the dry-run value: what the reconciler would do with the entries and why it would refuse. `devctl repo validate` prints it as JSON, giantswarm-repo-manager's validate_repository returns it and the validation workflow renders it.

type Schema

type Schema struct {
	Origin SchemaOrigin
	// contains filtered or unexported fields
}

Schema is a compiled repositories schema: the JSON schema of the team files, whose top level is the list of entries.

func CompileSchema

func CompileSchema(doc []byte, origin SchemaOrigin) (*Schema, error)

CompileSchema compiles a repositories schema document.

func EmbeddedSchema

func EmbeddedSchema() (*Schema, error)

EmbeddedSchema compiles the schema copy shipped with this devctl version. It carries the fields of the repository set-up plan (description, visibility, lifecycle archived) and is the fallback when GitHub cannot be reached.

func FetchSchema

func FetchSchema(ctx context.Context, files FileGetter) (*Schema, error)

FetchSchema reads and compiles the schema from giantswarm/github main, so a validation follows the schema as it is today.

func (*Schema) Problems

func (s *Schema) Problems(entry any) []Problem

Problems validates one entry — a JSON-compatible object, see Declaration.Instance — against the schema and returns a problem per violation, each naming the field.

type SchemaOrigin

type SchemaOrigin string

SchemaOrigin says where the schema a result was validated against came from.

const (
	// SchemaOriginGitHub is the schema as fetched from giantswarm/github main.
	SchemaOriginGitHub SchemaOrigin = "giantswarm/github@main"
	// SchemaOriginEmbedded is the copy shipped with this devctl version.
	SchemaOriginEmbedded SchemaOrigin = "embedded"
	// SchemaOriginFile is a schema read from a local file.
	SchemaOriginFile SchemaOrigin = "file"
)

type TeamFile

type TeamFile struct {
	// Team is the GitHub team slug the file belongs to: the file's base name
	// without extension (repositories/team-bumblebee.yaml → team-bumblebee).
	Team string
	// Path is where the file was read from; empty for a parsed reader.
	Path string
	// Entries in file order.
	Entries []Declaration
}

TeamFile is one repositories/<team>.yaml of giantswarm/github: the team that owns every entry in it and the entries themselves.

func ParseTeamFile

func ParseTeamFile(team string, r io.Reader) (*TeamFile, error)

ParseTeamFile parses a team file for the named team. The file is a YAML list of mappings; an empty file is a team with no entries.

func ReadTeamFile

func ReadTeamFile(path string) (*TeamFile, error)

ReadTeamFile reads a team file; the team is TeamOf the path.

func (*TeamFile) Entry

func (t *TeamFile) Entry(name string) (Declaration, bool)

Entry returns the entry with the given name.

type Template

type Template string

Template is what a created repository is scaffolded from: a template repository of the giantswarm organisation, or the minimal scaffold.

const (
	// TemplateGo is giantswarm/template, the Go service and CLI template.
	TemplateGo Template = "giantswarm/template"
	// TemplateChart is giantswarm/template-app, the chart-only template.
	TemplateChart Template = "giantswarm/template-app"
	// TemplateMinimal is the minimal scaffold: README, LICENSE, DCO,
	// SECURITY.md, CODEOWNERS and .gitignore plus the generated files.
	TemplateMinimal Template = "minimal"
)

func DeriveTemplate

func DeriveTemplate(componentType string, flavours []string, language string) (Template, error)

DeriveTemplate returns the template a declaration is scaffolded from. There is no template field: the component type, flavours and language decide. Language go → TemplateGo; language generic with the app flavour → TemplateChart; the customer flavour or component type, the languages python and kyverno-policy, and a generic repository without a chart → TemplateMinimal. Language node has no template yet and is refused with an error IsTemplateUnavailable asserts. An unknown flavour or language is refused with the error gen.NewFlavour or gen.NewLanguage returns.

func (Template) Repository

func (t Template) Repository() string

Repository is the owner/name of the template repository, or empty for the minimal scaffold.

func (Template) String

func (t Template) String() string

type Validator

type Validator struct {
	// Schema the entries are validated against. Required.
	Schema *Schema
	// Names checks whether a repository name is free on GitHub. Optional:
	// without one every name is reported unchecked and the result says so.
	Names NameChecker
	// Owner is the GitHub organisation; DefaultOwner when empty.
	Owner string
}

Validator validates the entries of a team file that are about to create repositories and returns the dry-run value.

func (Validator) Validate

func (v Validator) Validate(ctx context.Context, req Request) (*Result, error)

Validate validates the requested entries and returns the dry-run value. A refused entry is data in the result, not an error; an error means the validation itself could not run (a requested entry that is not in the file, GitHub unreachable).

type Verdict

type Verdict string

Verdict is the outcome of a repository name check on GitHub.

const (
	// VerdictFree: no repository of that name, no redirect to another one.
	VerdictFree Verdict = "free"
	// VerdictTaken: a repository exists under the name, or the name redirects
	// to a renamed repository.
	VerdictTaken Verdict = "taken"
	// VerdictUnchecked: the name was not checked (no GitHub client, or the
	// name is invalid anyway).
	VerdictUnchecked Verdict = "unchecked"
)

Jump to

Keyboard shortcuts

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