spendguard

package
v0.8.3 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package spendguard refuses new deploys of an app whose spend cap or cost budget is in block_deploys mode and has reached 100% this month. It reads the state the cap controllers already keep, so it is deterministic and needs no usage query on the deploy path.

Index

Constants

This section is empty.

Variables

View Source
var ErrDeploysBlocked = errors.New("deploys are blocked by a spend cap")

ErrDeploysBlocked is wrapped by every refusal.

Functions

This section is empty.

Types

type BlockedError

type BlockedError struct {
	App     string
	Reasons []Reason
}

BlockedError is returned when a deploy is refused.

func (*BlockedError) Error

func (e *BlockedError) Error() string

func (*BlockedError) Unwrap

func (e *BlockedError) Unwrap() error

Unwrap lets callers match ErrDeploysBlocked.

type Guard

type Guard struct {
	Store Store
	// ProjectOf maps an app to its project id; nil or "" means no project.
	ProjectOf func(ctx context.Context, app string) (string, error)
	Logger    *slog.Logger
	Now       func() time.Time
}

Guard decides whether a deploy may proceed.

func (*Guard) BeforeDeploy

func (g *Guard) BeforeDeploy(ctx context.Context, app string) error

BeforeDeploy refuses a new deploy of a blocked app. A failure to read the guard state lets the deploy through, because a cost guard must never stop an app from shipping a fix on a storage hiccup.

func (*Guard) Check

func (g *Guard) Check(ctx context.Context, app string) (Status, error)

Check returns the guard's verdict for app at the current time.

type Reason

type Reason struct {
	Source string  `json:"source"`
	Pct    float64 `json:"pct"`
}

Reason is one cap or budget holding an app at its limit.

type Status

type Status struct {
	Blocked  bool               `json:"blocked"`
	Allowed  bool               `json:"allowed"`
	Reasons  []Reason           `json:"reasons"`
	Override *spendcap.Override `json:"override,omitempty"`
}

Status is the guard's verdict for one app.

type Store

type Store interface {
	GetSpendCap(ctx context.Context, app string) (spendcap.Cap, error)
	GetSpendCapState(ctx context.Context, app string) (spendcap.State, bool, error)
	ListCostBudgets(ctx context.Context) ([]costbudget.Budget, error)
	GetCostBudgetState(ctx context.Context, id string) (costbudget.State, bool, error)
	ActiveSpendOverride(ctx context.Context, app string, now time.Time) (spendcap.Override, bool, error)
}

Store is the persistence the guard reads; *store.DB satisfies it.

Jump to

Keyboard shortcuts

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