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 ¶
Renderer.Render renders the scaffold of an accepted Entry into a directory: the template's tree (the tarball of its main branch, or a TemplateSource of the caller's) with its placeholders replaced -- what `devctl replace` does by hand -- CODEOWNERS for the team, the chart's team annotation and default icon, and the files the generators write for the declared flavours and language: Makefile, workflows including auto-release, LLM rules, pre-commit, and CircleCI and Renovate when gen.ci.generate is on. The generators run through the same `devctl gen` commands align-files runs, in align-files' order and with its flags, so the generated files are byte-identical to what the first align run would write and that run changes nothing. Scaffold.Commands lists the command lines. The chart-only template offers [Option]s -- the vendir sync and patch-script scaffolding `devctl app bootstrap` used to write by flag -- listed on Entry.Options by the dry run and chosen through RenderRequest.Options.
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
- func Codeowners(team string) string
- func HasChart(flavours []string) bool
- func IsEntryNotFound(err error) bool
- func IsInvalidConfig(err error) bool
- func IsInvalidSchema(err error) bool
- func IsInvalidTeamFile(err error) bool
- func IsRenderFailed(err error) bool
- func IsTemplateFetch(err error) bool
- func IsTemplateUnavailable(err error) bool
- func TeamOf(path string) string
- type CIFields
- type Declaration
- type DirTemplates
- type Entry
- type Fields
- type FileGetter
- type GenFields
- type GitHubNameChecker
- type GitHubTemplates
- type NameCheck
- type NameChecker
- type Notice
- type NoticeKind
- type Option
- type Problem
- type RenderRequest
- type Renderer
- type RepositoryGetter
- type Request
- type Result
- type Scaffold
- type Schema
- type SchemaOrigin
- type TeamFile
- type Template
- type TemplateSource
- type Validator
- type Verdict
Constants ¶
const ( // OptionSync is how the chart's templates follow an upstream chart. OptionSync = "sync" // OptionPatch is how local changes are applied on top of the synced // templates. OptionPatch = "patch" // OptionUpstreamRepo is the git URL of the upstream repository the // chart is synced from. OptionUpstreamRepo = "upstream-repo" // OptionUpstreamChart is the path of the chart inside the upstream // repository. OptionUpstreamChart = "upstream-chart" // SyncNone renders no sync configuration. SyncNone = "none" // SyncVendir renders vendir.yml, which vendors the upstream chart's // templates into helm/<name>/templates. SyncVendir = "vendir" // PatchNone renders no patch scaffolding. PatchNone = "none" // PatchScript renders sync/sync.sh, which runs the sync, applies // sync/patches/*/patch.sh and stores the resulting diffs. PatchScript = "script" )
The chart-only template's options: what `devctl app bootstrap` scaffolded by flag, offered per declaration instead.
const ( SchemaRepositoryOwner = "giantswarm" SchemaRepository = "github" SchemaPath = ".github/repositories.schema.json" SchemaRef = "main" )
Where the live schema lives: giantswarm/github's main branch.
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 )
const DefaultTemplateRef = "main"
DefaultTemplateRef is the branch templates are rendered from.
Variables ¶
This section is empty.
Functions ¶
func Codeowners ¶ added in v8.62.0
Codeowners is the CODEOWNERS file of a repository owned by team (a team slug such as team-bumblebee), byte-identical to what align-files writes.
func HasChart ¶ added in v8.62.0
HasChart says whether the declared flavours produce a Helm chart, which is where the chart-name convention applies.
func IsEntryNotFound ¶
IsEntryNotFound asserts entryNotFoundError: a requested entry name is not in the team file.
func IsInvalidConfig ¶
IsInvalidConfig asserts invalidConfigError.
func IsInvalidSchema ¶
IsInvalidSchema asserts invalidSchemaError: the repositories schema does not parse or compile.
func IsInvalidTeamFile ¶
IsInvalidTeamFile asserts invalidTeamFileError: the team file is not a YAML list of mappings.
func IsRenderFailed ¶ added in v8.61.0
IsRenderFailed asserts renderFailedError: a generator failed while the scaffold was rendered.
func IsTemplateFetch ¶ added in v8.61.0
IsTemplateFetch asserts templateFetchError: a template repository could not be fetched.
func IsTemplateUnavailable ¶
IsTemplateUnavailable asserts templateUnavailableError: the declaration needs a template that does not exist yet.
Types ¶
type CIFields ¶ added in v8.62.0
type CIFields struct {
Generate *bool `yaml:"generate"`
ReleaseWorkflow string `yaml:"releaseWorkflow"`
AppCatalog string `yaml:"appCatalog"`
AppCatalogTest string `yaml:"appCatalogTest"`
ChartName string `yaml:"chartName"`
OverrideChartAppVersion *bool `yaml:"overrideChartAppVersion"`
ForcePublic bool `yaml:"forcePublic"`
Image *struct {
PreBuildJob string `yaml:"preBuildJob"`
PrivateOnly bool `yaml:"privateOnly"`
Name string `yaml:"name"`
Platforms string `yaml:"platforms"`
Dockerfile string `yaml:"dockerfile"`
NativeBuilds bool `yaml:"nativeBuilds"`
ResourceClasses map[string]string `yaml:"resourceClasses"`
} `yaml:"image"`
BranchPublish bool `yaml:"branchPublish"`
SkipAppCatalog bool `yaml:"skipAppCatalog"`
SkipATS bool `yaml:"skipATS"`
ATSOnRelease bool `yaml:"atsOnRelease"`
ATSVersion string `yaml:"atsVersion"`
ATSResourceClass string `yaml:"atsResourceClass"`
BuildConcurrency string `yaml:"buildConcurrency"`
ResourceClass string `yaml:"resourceClass"`
Go *struct {
TestArtifacts string `yaml:"testArtifacts"`
} `yaml:"go"`
Node *struct {
TestTarget string `yaml:"testTarget"`
BuildTarget string `yaml:"buildTarget"`
BuildOutput string `yaml:"buildOutput"`
} `yaml:"node"`
}
CIFields is the gen.ci block: the CircleCI generator's knobs.
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) Fields ¶ added in v8.62.0
func (d Declaration) Fields() (Fields, error)
fields decodes the fields the creation rules read; a type mismatch is an error the schema validation already names.
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 DirTemplates ¶ added in v8.61.0
type DirTemplates struct {
Root string
}
DirTemplates serves templates from local checkouts under Root, one directory per template named after the repository without its owner (<Root>/template, <Root>/template-app). Tests and offline use.
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"`
// Options the template's scaffold offers, selected by name through
// [RenderRequest.Options]; nil when the template has none.
Options []Option `json:"options,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 Fields ¶ added in v8.62.0
type Fields struct {
Name string `yaml:"name"`
ComponentType string `yaml:"componentType"`
Description string `yaml:"description"`
Visibility string `yaml:"visibility"`
Lifecycle string `yaml:"lifecycle"`
ChoreReviewers []string `yaml:"choreReviewers"`
Replace *struct {
Precommit bool `yaml:"precommit"`
} `yaml:"replace"`
Gen *GenFields `yaml:"gen"`
}
Fields are the fields of an entry the creation rules and the scaffold rendering read: what align-files reads when it runs the generators for the repository.
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 GenFields ¶ added in v8.62.0
type GenFields struct {
Flavours []string `yaml:"flavours"`
Language string `yaml:"language"`
InstallUpdateChart bool `yaml:"installUpdateChart"`
HelmDocsRegen bool `yaml:"helmDocsRegen"`
RunSecurityScorecard *bool `yaml:"runSecurityScorecard"`
GenerateLlmRules *bool `yaml:"generateLlmRules"`
GoGenerate bool `yaml:"goGenerate"`
PreCommit []string `yaml:"preCommit"`
EnableUpstreamSyncAutomation bool `yaml:"enableUpstreamSyncAutomation"`
DispatchUpdateChartEventsRepo string `yaml:"dispatchUpdateChartEventsRepo"`
CI *CIFields `yaml:"ci"`
}
GenFields is the gen block: the generators' inputs.
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 ¶
CheckName implements NameChecker.
type GitHubTemplates ¶ added in v8.61.0
type GitHubTemplates struct {
// Ref is the branch or tag; default main.
Ref string
// Token authenticates the download when set; the templates are public,
// so it only raises the rate limit.
Token string
// Client is the HTTP client; default [http.DefaultClient].
Client *http.Client
}
GitHubTemplates fetches the tarball of a template's branch from GitHub: what the scaffold is rendered from at run time.
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 Option ¶ added in v8.61.0
type Option struct {
Name string `json:"name"`
Description string `json:"description"`
Values []string `json:"values,omitempty"`
Default string `json:"default,omitempty"`
}
Option is a choice a template's scaffold offers. The dry run lists the options of the derived template on Entry.Options; RenderRequest.Options selects among them by name. An option without Values is free text.
type Problem ¶
Problem is one refusal: the field in dotted form (gen.ci.chartName, gen.flavours[1]; "(entry)" for the entry as a whole) and why.
type RenderRequest ¶ added in v8.61.0
type RenderRequest struct {
// Team is the slug of the team file the entry lives in (team-bumblebee):
// CODEOWNERS names it and the chart's team annotation carries its short
// name.
Team string
// Entry is an accepted entry of [Validator.Validate]; its Rendered
// declaration and Template are read.
Entry Entry
// Dir is the directory the scaffold is rendered into. It is created;
// an existing directory has to be empty.
Dir string
// Options selects among [Entry.Options] by name; an option left out
// takes its default.
Options map[string]string
}
RenderRequest is one scaffold to render.
type Renderer ¶ added in v8.61.0
type Renderer struct {
// Templates fetches the template repositories; nil fetches the tarball
// of each template's main branch from GitHub ([GitHubTemplates]).
Templates TemplateSource
// Log receives the generators' output; nil discards it.
Log io.Writer
}
Renderer renders the scaffold of an accepted declaration: the template checkout with its placeholders replaced, the files devctl generates for the declared flavours and language, CODEOWNERS for the team and the chart's team annotation. The generated files are the ones align-files writes for the same declaration with the same devctl, so the first align run after the scaffold is pushed changes nothing.
func (Renderer) Render ¶ added in v8.61.0
Render renders the scaffold of req.Entry into req.Dir.
The generators read the repository from the process working directory, as `devctl gen` does; Render changes it to req.Dir while they run, under a lock, and restores it. Concurrent renders serialize on that lock.
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 Scaffold ¶ added in v8.61.0
type Scaffold struct {
// Dir the scaffold was rendered into.
Dir string `json:"dir"`
// Template it was rendered from.
Template Template `json:"template"`
// Options in effect, defaults included.
Options map[string]string `json:"options,omitempty"`
// Commands are the `devctl gen …` command lines that produced the
// generated files, as align-files runs them.
Commands []string `json:"commands"`
// Files are the scaffold's paths relative to Dir, sorted.
Files []string `json:"files"`
}
Scaffold describes a rendered scaffold.
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 ¶
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.
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 ¶
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 ¶
ReadTeamFile reads a team file; the team is TeamOf the path.
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 ¶
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 ¶
Repository is the owner/name of the template repository, or empty for the minimal scaffold.
type TemplateSource ¶ added in v8.61.0
type TemplateSource interface {
// Fetch writes the tree of the template repository (owner/name) into
// dir, which exists and is empty.
Fetch(ctx context.Context, repository, dir string) error
}
TemplateSource fetches template repositories.
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.
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" )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package reconcile is the back half of the repository set-up engine: every set-up step of a declared repository as a check and a repair, run in order by Runner.Run against GitHub and CircleCI, with the outcome as one structured Result the inventory stores.
|
Package reconcile is the back half of the repository set-up engine: every set-up step of a declared repository as a check and a repair, run in order by Runner.Run against GitHub and CircleCI, with the outcome as one structured Result the inventory stores. |