Help us shape the future of Go! Take the 2026 Go Developer Survey

approval

package
v0.16.0 Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2026 License: AGPL-3.0 Imports: 16 Imported by: 0

Documentation

Overview

Package approval is deploy approval (R-154 – R-159): whether a deploy needs somebody's sign-off, and the request, approval, rejection and expiry of one.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BlocksAutoDeploy

func BlocksAutoDeploy(doc policy.Document, appID string, s *spec.AppSpec) bool

BlocksAutoDeploy reports whether an app's deploys need approval whatever they change, which is what refuses auto-deploy (R-158). An egress loosening is not one of these reasons: it needs approval only for the deploy that introduces it, and an automatic deploy changes the commit and nothing else.

Types

type Approvers

type Approvers interface {
	DeployApprovers(ctx context.Context, appID string) ([]string, error)
}

Approvers names who may approve an app's deploys, for telling them.

type Authorizer

type Authorizer interface {
	CheckControl(ctx context.Context, p authz.Principal, appID string, verb authz.Verb) error
	Allows(ctx context.Context, p authz.Principal, appID string, verb authz.Verb) (bool, error)
	AllowsInstall(ctx context.Context, p authz.Principal, verb authz.Verb) (bool, error)
}

Authorizer is the part of authz.Authorizer approval asks. Allows and AllowsInstall check without auditing a denial; CheckControl audits one.

type Capacity

type Capacity interface {
	Hold(ctx context.Context, runtimeRef string, fn func(context.Context) error) error
}

Capacity holds a runtime's capacity while fn runs (state.Allocations.Hold).

type Notifier

type Notifier interface {
	Notify(ctx context.Context, n api.Notification) error
}

Notifier tells people something through the notification adapters.

type Planner

type Planner interface {
	Check(ctx context.Context, s *spec.AppSpec) (*planner.Plan, error)
}

Planner is the dry run a deploy passes before anything is created.

type PolicyLoader

type PolicyLoader interface {
	Load(ctx context.Context) (policy.Document, error)
}

PolicyLoader reads host policy as it is now. Read per call, never cached (R-274).

type Reason

type Reason string

Reason is why a deploy needs approval. More than one can apply, and the console and the audit log name each.

const (
	// ReasonInstall: host policy requires approval for every app.
	ReasonInstall Reason = "install"
	// ReasonAppPolicy: host policy requires it for this app, and its owner
	// cannot turn that off.
	ReasonAppPolicy Reason = "app_policy"
	// ReasonAppSpec: the app requires it of itself, in the running spec or
	// the one being deployed.
	ReasonAppSpec Reason = "app_spec"
	// ReasonEgressLoosening: the deploy loosens the install's egress rules
	// and policy says loosening needs approval (R-183).
	ReasonEgressLoosening Reason = "egress_loosening"
)

func Reasons

func Reasons(doc policy.Document, appID string, running, next *spec.AppSpec) []Reason

Reasons is why deploying next over running needs approval, or nothing when it does not (R-154). running is the app's pinned spec, nil for an app that has never had one.

The app's own requirement is read from both specs, so that turning it off is itself a deploy that needs approval. An egress loosening counts only when it is new: one the running spec already carries was approved when it was deployed, and a later push under it is not asking for anything more.

func (Reason) Message

func (r Reason) Message() string

Message says what a reason means, for the person being asked to approve.

type Service

type Service struct {
	Deployments *state.Deployments
	Apps        *state.Apps
	Authz       Authorizer
	Policy      PolicyLoader
	Planner     Planner
	Deployer    Starter

	// Capacity serializes a deploy's plan-time capacity check with its
	// creation, per runtime (R-242). Optional: without it the two are not
	// serialized, which is right only where nothing deploys concurrently.
	Capacity Capacity

	// Audit writes an event. Failing to write one is logged by whoever
	// supplies it, never turned into a failure of the action.
	Audit func(ctx context.Context, e audit.Event)

	// Notifier and Approvers are optional: without them nobody is told, and
	// everything else works (R-159's notification is best effort).
	Notifier  Notifier
	Approvers Approvers

	Clock  clock.Clock
	Logger *zap.Logger
}

Service starts deploys, and asks for, records and acts on approval of the ones that need it (R-154 – R-159).

Every way a deploy starts goes through here — a person's deploy, a rollback, and the last approval of a request — so that "does this need approval" has one answer, and a deploy that needed it cannot start any other way. The HTTP handlers and, through them, the CLI and MCP tools are clients of it (R-261).

func (*Service) Approve

func (s *Service) Approve(ctx context.Context, p authz.Principal, appID, depID, comment string) (state.Deployment, error)

Approve records p's approval of a deploy waiting for one, and starts it when that is the last approval it needs (R-156).

The plan runs again first, because policy may have changed while the request waited. A plan that now fails ends the request as failed with the plan's own error, rather than leaving it waiting: no number of further approvals can make it pass, and the fix — changing the spec — is a new revision and so a new request. A failure to plan at all (an adapter that cannot be reached, an internal error) is not an answer about the revision, so the request stays waiting and nothing is recorded.

An approval that would start the deploy while another deploy of the app is in flight is refused, and the request keeps waiting: two deploys racing on one bundle is how an app ends up in a state neither intended.

func (*Service) AutoDeployPaused

func (s *Service) AutoDeployPaused(ctx context.Context, appID string, pinned *spec.AppSpec) (bool, error)

AutoDeployPaused reports whether an app's pinned spec asks for auto-deploy and approval now stops it (R-158): what the console says on the app.

func (*Service) Awaiting

func (s *Service) Awaiting(ctx context.Context, p authz.Principal, page state.Page, decidable bool) ([]state.AwaitingApproval, string, error)

Awaiting lists one page of the deploys waiting for approval on every app p may view, oldest first, each saying whether p may decide it, and the cursor for the next page (empty after the last). A page may hold fewer than its limit and still have a cursor, when the requests read were on apps p cannot see.

decidable keeps only the requests p may approve or reject (R-155): the console's Approvals screen and its count, which list what is waiting on the caller. A request waiting on somebody else is still on its app.

func (*Service) CanDecide

func (s *Service) CanDecide(ctx context.Context, p authz.Principal, appID string) (bool, error)

CanDecide reports whether p may approve or reject the app's deploys, without auditing anything: what the console shows, not an attempt.

func (*Service) Deploy

func (s *Service) Deploy(ctx context.Context, p authz.Principal, app state.App, rev state.Revision, trigger string) (state.Deployment, error)

Deploy deploys rev, or, when the deploy needs approval, records a request for it and tells the people who can approve (R-154). The deployment comes back either way: pending and running, or awaiting_approval.

The plan runs first, so a deploy that cannot succeed is refused here rather than part-way through — and is never put in front of an approver.

func (*Service) Describe

func (s *Service) Describe(ctx context.Context, p authz.Principal, deps []state.Deployment) error

Describe fills in what the store leaves to the service: what each approval reason means, and whether p may decide each deploy that is waiting.

func (*Service) ExpireDue

func (s *Service) ExpireDue(ctx context.Context) (int, error)

ExpireDue ends every request whose wait has run out, and records each (R-159). Run on a timer; Approve and Reject also expire the one they are asked about, so a request is never approved after its time.

func (*Service) Needed

func (s *Service) Needed(ctx context.Context, app state.App, rev state.Revision, trigger string) ([]Reason, error)

Needed is whether deploying rev over what the app runs now needs approval, and why: what the plan endpoint shows before anyone deploys.

func (*Service) NeededFor

func (s *Service) NeededFor(ctx context.Context, app state.App, next *spec.AppSpec) ([]Reason, error)

NeededFor is Needed for a spec that has not been saved: what deploying a draft would need, for a dry-run save to show before anything is written.

func (*Service) Reject

func (s *Service) Reject(ctx context.Context, p authz.Principal, appID, depID, comment string) (state.Deployment, error)

Reject ends a deploy waiting for approval (R-156: a rejection by any approver ends the request).

func (*Service) RunExpiry

func (s *Service) RunExpiry(ctx context.Context, every time.Duration)

RunExpiry expires due requests every interval until ctx ends.

type Starter

type Starter interface {
	Start(ctx context.Context, dep state.Deployment, rev state.Revision)
}

Starter runs a deployment that is pending, in the background.

Jump to

Keyboard shortcuts

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