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
- Variables
- type AppSource
- type Config
- type Controller
- func (c *Controller) Pass(ctx context.Context) (otherPolicy bool)
- func (c *Controller) Run(ctx context.Context) error
- func (c *Controller) RunTarget(ctx context.Context, target string) (ran, otherPolicy bool, err error)
- func (c *Controller) Targets() []string
- func (c *Controller) Tick(ctx context.Context, target string) (otherPolicy bool, err error)
- type Deps
- type LinkStore
- type StatusReader
- type StatusWriter
Constants ¶
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 ¶
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 (*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 ¶
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/explanation/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 ¶
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 ¶
StatusWriter replaces the report. It is the generic rails.Store.