model

package
v1.10.0 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package model is the vocabulary: what can be stopped, what stopping it means, and — the part that matters most — what must never be touched.

This tool exists to be fired at two in the morning by someone who has just seen a bill. Everything here is shaped by two rules that follow from that:

Stop, never delete. A stopped thing costs nothing and comes back. A deleted
thing may not, and nobody deletes carefully at two in the morning.

The restore is the product. Stopping an account is easy and useless on its
own. What makes this safe to fire is that the exact prior state is recorded
durably first, so putting it back is mechanical rather than archaeological.

Index

Constants

View Source
const (
	ResultChanged   = "changed"
	ResultSkipped   = "skipped"
	ResultFailed    = "failed"
	ResultUnchanged = "unchanged"
)
View Source
const GlobalRegion = "global"

GlobalRegion is the Region recorded for services that have none. CloudFront is the only one so far. It is a real value rather than an empty string so a state file says plainly where the resource lives, and so the executor can route it deliberately instead of by accident.

View Source
const RDSAutoRestart = 7 * 24 * time.Hour

RDSAutoRestart is the trap that makes stopping a database different from stopping anything else: AWS restarts a stopped RDS instance by itself after seven days. A kill switch that silently un-kills a week later is worse than no kill switch, because nobody is watching by then.

Variables

View Source
var NeverTouch = []string{
	"s3", "efs", "fsx", "dynamodb", "glacier", "backup",
	"ebs-volume", "snapshot", "ami", "route53-zone", "iam", "kms",
	"cloudtrail", "config", "secretsmanager", "ssm-parameter",
}

NeverTouch is the set of things this tool will not act on under any flag.

It is a deny list rather than an allow list on purpose. An allow list grows by accident: someone adds a resource kind, forgets the exclusion, and a cost tool deletes a bucket. These are the things whose loss is unrecoverable, so the rule is absolute and there is no option to override it.

Functions

func IsNeverTouch

func IsNeverTouch(s string) bool

IsNeverTouch reports whether a service or resource identifier names something in the protected set. Matched on the ARN service field and on the free-form kind, so it catches both "arn:aws:s3:::bucket" and a hand-built resource.

func PriorBool added in v1.10.0

func PriorBool(prior map[string]any, key string) (bool, bool)

PriorBool reads a boolean out of the recorded prior state.

Only a real bool counts. A CloudFront distribution's prior "enabled" decides whether restore switches it back on, and coercing a string or a number into that answer is how a distribution stays dark after a restore reports success.

func PriorFloat added in v1.10.0

func PriorFloat(prior map[string]any, key string) (float64, bool)

PriorFloat reads a rate out of the recorded prior state.

API Gateway throttles are floats — 10.5 requests/second is legal — so this cannot go through PriorInt without rounding a limit the account was actually set to. A negative rate is refused: the API rejects it, and restoring to a value that fails is not a restore.

func PriorInt

func PriorInt(prior map[string]any, key string) (int, bool)

PriorInt reads an integer out of the recorded prior state.

JSON round-trips numbers as float64, so a desired count of 3 comes back as 3.0 and a naive type assertion to int fails — silently restoring zero, which would leave the account down after a "successful" restore.

func PriorInt32 added in v1.9.1

func PriorInt32(prior map[string]any, key string) (int32, bool)

PriorInt32 reads an integer out of the recorded prior state and narrows it to the int32 the AWS APIs take, refusing rather than wrapping.

The prior comes back through JSON, so nothing constrains its range on the way in: a hand-edited or corrupted state file can hold any number at all. A bare int32(n) turns 4294967296 into 0 and 5000000000 into 705032704, so a restore would scale a service to zero, or to some arbitrary capacity, and report success. Refusing is the only safe answer — the caller cannot guess what was meant, and this tool's whole promise is that the restore path is exact.

Reports false for a missing key, a non-numeric value, a negative count, or anything above math.MaxInt32.

func PriorString

func PriorString(prior map[string]any, key string) (string, bool)

Types

type Action

type Action struct {
	Resource Resource
	Phase    Phase
	// Op describes the change in the words a person would use, because it is
	// printed in the confirmation prompt and read under pressure.
	Op string
	// Warning is printed in red and requires acknowledgement. Empty for the
	// ordinary case.
	Warning string
}

Action is one reversible change.

type BlastRadius

type BlastRadius struct {
	Total     int
	ByKind    map[Kind]int
	TouchesDB bool
	DataLoss  bool // an instance-store instance is in the plan

	// SavingsUSD is the hourly total across resources that have an estimate.
	SavingsUSD float64
	// SavingsByKind breaks that down, so a plan dominated by one kind is
	// visible rather than hidden inside a single number.
	SavingsByKind map[Kind]float64
	// UnpricedByKind counts resources with no defensible estimate. Reported
	// separately and never as zero: a zero saving is a claim that stopping the
	// thing is free, which is a different statement from not knowing.
	UnpricedByKind map[Kind]int
}

BlastRadius is how much of the account this touches — the number a person checks before typing yes.

type Entry

type Entry struct {
	Kind   Kind           `json:"kind"`
	ID     string         `json:"id"`
	ARN    string         `json:"arn,omitempty"`
	Name   string         `json:"name,omitempty"`
	Region string         `json:"region"`
	Phase  Phase          `json:"phase"`
	Prior  map[string]any `json:"prior"`
	// Result records what actually happened, so a partial fire can be restored
	// without re-running against resources that were never changed.
	Result string `json:"result,omitempty"`
	Error  string `json:"error,omitempty"`
}

func (Entry) RestoreDeadline

func (e Entry) RestoreDeadline(firedAt time.Time) (time.Time, bool)

RestoreDeadline is when a stopped database will come back on its own, or the zero time when the entry is not a database.

type Kind

type Kind string
const (
	KindALBListener Kind = "alb-listener"
	KindLambda      Kind = "lambda"
	KindECSService  Kind = "ecs-service"
	KindASG         Kind = "asg"
	KindEC2Instance Kind = "ec2-instance"
	KindNATGateway  Kind = "nat-gateway"
	KindRDSInstance Kind = "rds-instance"
	KindRDSCluster  Kind = "rds-cluster"

	// Three services discovery could not see, so a trip left them running.
	// Each is stopped by writing a number the service already has a field for,
	// which is why all three restore exactly: there is nothing to recreate.
	KindEKSNodegroup    Kind = "eks-nodegroup"
	KindCloudFront      Kind = "cloudfront-distribution"
	KindAPIGatewayStage Kind = "apigateway-stage"
)

type Phase

type Phase int

Phase is the order things happen in, and the order is not cosmetic.

Ingress goes first. Drain the compute while traffic is still arriving and a target-tracking scaling policy will fight the teardown, health checks will alarm, and whatever is driving the spend keeps driving it against a shrinking fleet. Cut the traffic and the rest goes quietly.

const (
	PhaseIngress Phase = iota
	PhaseCompute
	PhaseNetwork
	PhaseData
)

func (Phase) String

func (p Phase) String() string

func (Phase) Why

func (p Phase) Why() string

type Plan

type Plan struct {
	ID        string
	CreatedAt time.Time
	Account   string
	Regions   []string
	Actions   []Action
	Refusals  []Refusal
	// AckRequired lists warnings that must be acknowledged before firing.
	AckRequired []string
}

Plan is what will happen, in order, with everything that will not.

func (Plan) BlastRadius

func (p Plan) BlastRadius() BlastRadius

func (Plan) ByPhase

func (p Plan) ByPhase() [][]Action

ByPhase returns the actions in execution order, grouped.

func (Plan) IsEmpty

func (p Plan) IsEmpty() bool

type Refusal

type Refusal struct {
	Resource Resource
	Reason   string
}

Refusal is a resource the planner deliberately left alone. These are as important as the actions: a plan that silently omits things is a plan nobody can check, and "why is that still running" is the first question asked.

type Resource

type Resource struct {
	ID     string
	ARN    string
	Kind   Kind
	Name   string
	Region string
	Tags   map[string]string

	// Prior is the state that must be restored, captured at discovery. Kept as
	// a map so a new resource kind does not need a new struct threaded through
	// the state file, and so an unrecognised field survives a version change
	// rather than being silently dropped on restore.
	Prior map[string]any

	// HasInstanceStore marks an instance whose local NVMe is erased by a stop.
	// EBS survives; instance store does not, and the API gives no warning.
	HasInstanceStore bool

	// EstimatedHourlyUSD is what stopping it saves, where it can be known
	// cheaply. Zero means unknown, not free.
	EstimatedHourlyUSD float64
}

Resource is one thing found in the account.

func (Resource) Ref

func (r Resource) Ref() string

type Snapshot

type Snapshot struct {
	PlanID    string     `json:"plan_id"`
	CreatedAt time.Time  `json:"created_at"`
	Account   string     `json:"account"`
	Regions   []string   `json:"regions"`
	Entries   []Entry    `json:"entries"`
	Fired     *time.Time `json:"fired_at,omitempty"`
	Restored  *time.Time `json:"restored_at,omitempty"`
}

Snapshot is what makes the whole thing safe: the exact prior state of every resource the plan will touch, written durably *before* anything is changed.

Without it, restore is someone guessing what the desired count used to be.

func (Snapshot) Changed

func (s Snapshot) Changed() []Entry

Changed returns the entries a restore has to act on.

Jump to

Keyboard shortcuts

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