forge

package
v0.0.0-...-a3dd30d Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package forge is the platform abstraction. It owns the Forge interface, the domain types both implementations speak, the shared HTTP transport carrying the timeout and the bounded retry, and the sentinel errors callers classify on.

Index

Constants

View Source
const (
	// RequestTimeout bounds a single request, retries excluded.
	RequestTimeout = 30 * time.Second
	// MaxAttempts is the total number of attempts, so at most two retries
	// follow the first try.
	MaxAttempts = 3
	// BaseBackoff is the first pause between attempts; each subsequent pause
	// doubles it.
	BaseBackoff = 500 * time.Millisecond
)

The bounds every platform call carries (CLI-005, FR-046). They are forgectl's own, applied by one transport both clients are given, so the retry policy never comes from a client library (R5).

Variables

View Source
var (
	// ErrUnknownHost reports a remote host matching no instance. It is an alias
	// of the config sentinel so a caller can classify it without importing both
	// packages (FR-003, R11).
	ErrUnknownHost = config.ErrUnknownHost

	// ErrNoCredential reports an instance whose credential environment variable
	// is unset or empty, raised before any network call (FR-005, R11).
	ErrNoCredential = config.ErrNoCredential

	// ErrInsufficientRights reports a credential that may not perform the
	// operation. It makes the affected check SKIP with that reason rather than
	// fail, so a token without token-management rights does not turn a healthy
	// repository into a drifted one (FR-030).
	ErrInsufficientRights = errors.New("the credential lacks the rights for this operation")

	// ErrNotSupported reports an operation the platform has no equivalent for.
	// A generated variable on GitHub raises it, and is skipped with a warning
	// rather than failing the run (FR-029).
	ErrNotSupported = errors.New("not supported on this platform")

	// ErrTokenLifetime reports a requested token lifetime above the instance
	// maximum. Its message carries the platform's own wording, which states the
	// permitted maximum (FR-052).
	ErrTokenLifetime = errors.New("the requested token lifetime exceeds the instance maximum")

	// ErrMaskRejected reports a value the platform refuses to mask. It drives
	// the single unmasked retry of FR-043, and its message names the constraint
	// — never the value (R7).
	ErrMaskRejected = errors.New("the platform refuses to mask this value")
)

Functions

func NewClient

func NewClient() *http.Client

NewClient builds the *http.Client both platform clients are given: one explicit request timeout, one bounded and backed-off retry policy, and full context propagation (CLI-005, R5).

Types

type Forge

type Forge interface {
	Reader
	Writer
}

Forge is both halves: what apply is given, and what each platform package implements in full.

type ProjectToken

type ProjectToken struct {
	ID        int
	Name      string
	ExpiresAt time.Time
	Active    bool
	Revoked   bool
}

ProjectToken is one project access token as the platform reports it.

func (ProjectToken) DaysRemaining

func (t ProjectToken) DaysRemaining(now time.Time) int

DaysRemaining reports how many whole days are left before the token expires, counted from now. A token that has already expired reports zero.

type ProjectTokenRequest

type ProjectTokenRequest struct {
	Name      string
	Scopes    []string
	Role      config.AccessLevel
	ExpiresAt time.Time
}

ProjectTokenRequest is the token forgectl asks the platform to create.

type Protection

type Protection struct {
	// Exists is false when the branch carries no protection at all.
	Exists bool
	// AllowForcePush is GitHub's absent non_fast_forward rule, and GitLab's
	// allow_force_push flag.
	AllowForcePush bool
	// AllowDelete is GitHub's absent deletion rule. On GitLab it is ALWAYS
	// false: deleting a protected branch is denied with no toggle (R9), so a
	// configured allow_delete: false is satisfied by protection existing at
	// all and must never be reported as drift.
	AllowDelete bool
	// PushAccessLevel is GitLab only. It carries the zero value on GitHub,
	// which models no equivalent, and is therefore never compared there
	// (FR-026).
	PushAccessLevel config.AccessLevel
}

Protection is the state of a protected branch, expressed in the terms both platforms can be mapped onto.

type Reader

type Reader interface {
	// DefaultBranch reports the branch the platform serves as default (FR-023).
	DefaultBranch(ctx context.Context) (string, error)
	// BranchExists reports whether the named branch is on the platform. It is
	// what lets the protection check skip with a reason rather than fail when
	// the target branch is not there yet (FR-024).
	BranchExists(ctx context.Context, name string) (bool, error)
	// Protection reads the protection in force on a branch (FR-024).
	Protection(ctx context.Context, branch string) (Protection, error)
	// TagProtection lists the tag patterns the platform protects (FR-025).
	TagProtection(ctx context.Context) ([]string, error)
	// Variable reads what the platform reports about a CI variable (FR-026).
	Variable(ctx context.Context, name string, secret bool) (VariableState, error)
}

Reader is the read-only half of the platform abstraction: everything the compliance layer needs to judge a repository, and nothing that could change it.

internal/compliance accepts a Reader and never a Forge. FR-031's "check MUST NOT modify any local or platform state" is therefore enforced by the type system: the evaluation layer is handed no method that writes, so no amount of future editing there can make check mutate anything.

Every method takes a context first, so every call is cancellable and bounded (Constitution VI).

type Target

type Target struct {
	Instance config.Instance
	Owner    string
	Repo     string

	// Credential is the token read from the environment variable the instance
	// names. It is never logged and never rendered (FR-054, CLI-004).
	Credential string
}

Target is the single instance a run operates against, together with the credential that reaches it. One run touches exactly one instance (FR-032).

func Resolve

func Resolve(cfg *config.Config, host, owner, repo string) (Target, error)

Resolve matches a remote host against the configured instances, falling back to the built-in definitions, and reads the credential from the environment variable that instance names.

Both failures happen before any network call: an unmatched host wraps ErrUnknownHost (FR-003) and a missing credential wraps ErrNoCredential (FR-005). Neither message ever carries the credential.

func (Target) Project

func (t Target) Project() string

Project renders the repository as owner/name, the identifier both platforms accept.

type TokenIssuer

type TokenIssuer interface {
	// ProjectTokens lists the active tokens carrying the given name (FR-028).
	ProjectTokens(ctx context.Context, name string) ([]ProjectToken, error)
	// CreateProjectToken creates a token and returns it with its value, which
	// the platform discloses exactly once (R8).
	//
	// The second return is that value. It goes straight into SetVariable and is
	// never stored anywhere else (FR-050).
	CreateProjectToken(ctx context.Context, req ProjectTokenRequest) (ProjectToken, string, error)
	// RevokeProjectToken revokes one token by id (FR-048).
	RevokeProjectToken(ctx context.Context, id int) error
}

TokenIssuer is the part of the platform that issues project-scoped access tokens. Only GitLab implements it; a Forge that does not is what makes a generated variable skip with a warning rather than fail (FR-029).

type Transport

type Transport struct {
	// Base is the round tripper actually performing the request. A nil Base
	// uses http.DefaultTransport.
	Base http.RoundTripper
	// MaxAttempts overrides the default when set, so a test need not wait.
	MaxAttempts int
	// BaseBackoff overrides the default when set.
	BaseBackoff time.Duration
	// Sleep is the pause function, replaced in tests. A nil Sleep uses time.Sleep.
	Sleep func(time.Duration)
}

Transport retries a request that the platform could not serve yet — a rate limit or a server-side failure — a bounded number of times, backing off between attempts, and cancels the moment its context does.

A 4xx other than 429 is the platform's considered answer and is never retried: retrying a 404 or a 403 only wastes the maintainer's time.

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip performs the request, retrying while the platform says "not now".

type VariableState

type VariableState struct {
	Exists bool
	// Masked is GitLab only.
	Masked bool
	// Protected is GitLab only.
	Protected bool
	// Value is GitLab only, and always empty on GitHub, whose Actions
	// credentials are write-only.
	Value string
	// ValueReadable is false wherever the platform cannot disclose a value.
	//
	// It is an explicit field rather than an inference from the platform, so
	// the comparison in internal/compliance reads as a fact about the value
	// rather than a special case about one platform (FR-027).
	ValueReadable bool
}

VariableState is what the platform reports about a CI variable.

type VariableWrite

type VariableWrite struct {
	Name  string
	Value string

	// Secret selects the Actions credential store over the Actions variable
	// store on GitHub. It has no effect on GitLab, where every variable is a
	// CI variable (FR-026).
	Secret bool
	// Masked and Protected are GitLab attributes with no GitHub equivalent.
	Masked    bool
	Protected bool
}

VariableWrite is one variable write, carrying the only value that ever crosses this package boundary.

type Writer

type Writer interface {
	// SetDefaultBranch makes name the platform default (FR-037, FR-038).
	SetDefaultBranch(ctx context.Context, name string) error
	// SetProtection puts the wanted protection in force on a branch (FR-037).
	SetProtection(ctx context.Context, branch string, want Protection) error
	// ProtectTag protects one tag pattern (FR-025).
	ProtectTag(ctx context.Context, pattern string) error
	// SetVariable creates or updates a CI variable (FR-042).
	//
	// This is the only method that receives a value. It writes it to the
	// platform and nowhere else: the value is never logged, never wrapped into
	// an error, and never returned (FR-050, FR-054).
	SetVariable(ctx context.Context, write VariableWrite) error
}

Writer is the half that changes things. Only internal/apply is ever given one.

Directories

Path Synopsis
Package forgetest provides a scriptable fake implementation of forge.Forge for tests, so the evaluation and execution layers are exercised with no network.
Package forgetest provides a scriptable fake implementation of forge.Forge for tests, so the evaluation and execution layers are exercised with no network.
Package github implements forge.Forge against the GitHub REST API through go-github.
Package github implements forge.Forge against the GitHub REST API through go-github.
Package gitlab implements forge.Forge against the GitLab REST API through the official client, and adds the project access token lifecycle, which has no equivalent on the other platform.
Package gitlab implements forge.Forge against the GitLab REST API through the official client, and adds the project access token lifecycle, which has no equivalent on the other platform.

Jump to

Keyboard shortcuts

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