controller

package
v1.75.0-rc.12 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package controller is the GitHub controller's loop: every pass, for every organisation the policy binds, read what GitHub holds and who holds the bound groups, decide, act where the organisation is enabled, and report.

It has no listener. It reads the console's API with its own ServiceAccount token, writes to GitHub with each organisation's App, replaces one ConfigMap with its report, and records what it changed to the audit installation as its own workload. It holds nothing of the issuer's: no signing key, no session store, no directory credential.

Index

Constants

View Source
const LinksTarget = "github:links"

LinksTarget is the target of the link check: asking GitHub about every person's linked account and keeping what changed. An organisation's tick reads the links as that check last left them and does not depend on it running first.

Variables

View Source
var ErrUnknownTarget = errors.New("not a target of this controller")

ErrUnknownTarget is a target that is neither an organisation the policy binds nor LinksTarget.

Functions

This section is empty.

Types

type AppSource

type AppSource interface {
	// Credential is one organisation's credential.
	Credential(ctx context.Context, org string) (connection.Credential, bool, error)
	// LinkAppCredential is what a person's authorization is refreshed with.
	LinkAppCredential(ctx context.Context) (link.AppCredential, bool, error)
	// PassRequests are the operators' last requests for a pass now.
	PassRequests(ctx context.Context) (map[string]connection.PassRequest, error)
	// Digest summarises what a change to wakes a pass: the organisations'
	// credentials and records and the link App's.
	Digest(ctx context.Context) ([sha256.Size]byte, error)
}

AppSource is the State-port counterpart of the mounted AppsDir and RecordsDir (internal/portstore.GitHubOrgs implements it).

type Config

type Config struct {
	// Interval is how long between passes.
	Interval time.Duration
	// PolicyRetry is how soon a pass that met a console answering under
	// another policy is tried again; each further retry waits twice as
	// long. Zero is five seconds.
	PolicyRetry time.Duration
	// Enabled are the organisations the controller acts in. Every other
	// bound organisation is derived and reported, and nothing is changed:
	// an organisation is born disabled.
	Enabled map[string]bool
	// AppsDir is where the organisations' credentials are mounted, one
	// file per organisation.
	AppsDir string
	// RecordsDir is where the console's records are mounted: each
	// organisation's record and the operators' pass requests. Empty, or
	// absent, is no records.
	RecordsDir string
	// CredentialPoll is how often the mounted credentials and records are
	// looked at for a change, which runs a pass at once instead of at the
	// next interval: an install lands as a new installation id in the
	// credential, and an operator's Refresh as a marker, and neither should
	// wait for the full interval. Zero is thirty seconds; negative turns it
	// off.
	CredentialPoll time.Duration
}

Config is what a deployment decides.

type Controller

type Controller struct {
	// contains filtered or unexported fields
}

Controller is the loop and what it remembers between passes.

func New

func New(cfg Config, deps Deps) *Controller

New returns a controller.

func (*Controller) Pass

func (c *Controller) Pass(ctx context.Context) (otherPolicy bool)

Pass is a sweep: every target once, links first, each under its own lease and publishing its own report. A target another runner holds is that runner's. It says whether any answer it was given came from a console under another policy — a pass worth trying again soon, because the difference is usually a rollout that has not finished.

func (*Controller) Run

func (c *Controller) Run(ctx context.Context) error

Run passes now and then every interval, until the context ends. A pass that met a console answering under another policy is tried again soon (see rails.Run).

Beside the interval, a notification for a target (the trigger) ticks that target, and the mounted credentials and records are watched (see [Controller.watchCredentials]): a new install or a changed credential runs a pass, and an operator's request ticks its organisation, within the poll period.

func (*Controller) RunTarget

func (c *Controller) RunTarget(ctx context.Context, target string) (ran, otherPolicy bool, err error)

RunTarget ticks one target under its lease and says whether it ran: false, with no error, when another runner holds the lease. The tick's context ends if the lease is lost, so it stops before its next write.

func (*Controller) Targets

func (c *Controller) Targets() []string

Targets are what a pass ticks, in order: the link check, then every bound organisation by login.

func (*Controller) Tick

func (c *Controller) Tick(ctx context.Context, target string) (otherPolicy bool, err error)

Tick is one target's work, with no lease (see Controller.RunTarget): the link check for LinksTarget; for an organisation, its pass, ending in its own report whatever happened. The bool says whether an answer came from a console under another policy.

type Deps

type Deps struct {
	Log    *slog.Logger
	GitHub *http.Client
	Access directoryrosterv1connect.AccessServiceClient
	// Audit is where the controller records what it did: the audit
	// installation, with this workload's own identity, which the writer
	// stamps as the records' observer. Nil records nothing.
	Audit audit.Recorder
	// Console is where an operator's confirmations are read from. Nil
	// confirms nothing, so a tripped breaker stays tripped.
	Console directoryrosterv1connect.GitHubServiceClient
	Status  StatusWriter
	// Links are people's linked accounts. Nil links nobody, and then only
	// an organisation that discloses its members' addresses is matched.
	Links    LinkStore
	Bindings map[string]policy.GitHubOrg
	// Policy is the digest of the policy Bindings came from. The console's
	// answers carry the digest of the policy it computed them under, and a
	// pass acts only on answers from the same one: in a rollout the
	// controller and the console restart at different moments, and a group
	// the new policy binds has, under the old one, nobody in it.
	Policy string
	// Leases takes a lease on each target before its tick, from the State
	// port. Nil runs every tick without one: a single runner, which is what
	// a deployment with no shared State has (see docs/concepts/sluis/ports.md).
	Leases *rails.Leases
	// Trigger says that a target has work: a notification runs that
	// target's tick. Nil is an in-process trigger.
	Trigger port.Trigger
	// Apps is where the organisations' credentials, the link App's and the
	// operators' requests for a pass are read from when they are kept on the
	// State port. Nil reads the mounted directories of [Config].
	Apps AppSource
	Now  func() time.Time
}

Deps are what the controller talks to.

type LinkStore

type LinkStore interface {
	List(ctx context.Context) ([]link.Link, error)
	Update(ctx context.Context, changed []link.Link) ([]link.Link, error)
	Adopt(ctx context.Context, candidates []link.Link) ([]link.Link, map[int64]string, error)
}

LinkStore is where people's links are kept. The controller rewrites a link as it checks it; a person linking again is written by the service.

type StatusReader

type StatusReader = rails.Reader

StatusReader is a status store that can give back what the last pass wrote. With one, a restarted controller knows which held and reported rows it recorded already, and does not record them all again. It is the generic rails.Reader.

type StatusWriter

type StatusWriter = rails.Store

StatusWriter replaces the report. It is the generic rails.Store.

Jump to

Keyboard shortcuts

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