project

package
v1.7.0 Latest Latest
Warning

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

Go to latest
Published: Jun 20, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

alerts.go — Driftfile reconcile of per-function alerts. The alert-spec name on the slice is `<function>-<index>` so multiple alerts on the same function get stable names; reconcile is idempotent (existing names are replaced wholesale; absent names are removed). v1: errors trigger only, webhook notify only — matches the slice's per-user-alerting primitive.

domains.go — Driftfile reconcile of `slice.domains[]`. Adds any host that's declared in the manifest but missing on the slice; removes any host that's live on the slice but absent from the manifest. After add, the user still has to flip DNS and run `drift slice domain verify <host>` — that step requires the CNAME / TXT to be live and is not something we can do automatically from the deploy path.

egress.go — Driftfile reconcile of the per-slice outbound egress allowlist. Compares `slice.atomic.egress` from the Driftfile against the live mode + declared hosts on the slice, and triggers a refresh if anything changed. The operator does the DNS resolution and pushes the rendered IP/port list into the slice's NetworkPolicy via charter — the CLI's only job is to decide whether a refresh is needed.

Reconcile semantics:

  • Driftfile has no egress block (or `mode` is empty / "open"), and live is also open → no-op.
  • Driftfile is open, live is allowlist → POST refresh; operator re-renders the chart with `mode: open` and the slice goes back to "any public host."
  • Driftfile is allowlist, live is open OR list differs → POST refresh; operator re-resolves and pushes the new IP set.
  • Driftfile is allowlist, live matches exactly → no-op.

sql.go — Driftfile reconcile of the SQL primitive. For each declared `sql:` entry the CLI uploads the schema + seed SQL files to the slice's admin endpoints. Idempotent — schemas are expected to be `CREATE … IF NOT EXISTS`, seeds are only applied when the database has no user tables yet (the slice handles this).

Removal: a database that's live on the slice but not in the Driftfile is dropped. The same shape as `applyDomains` / `applyAlerts` so the deploy chain reads consistently.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CountAtomicFunctions

func CountAtomicFunctions(m *Manifest) (int, error)

CountAtomicFunctions returns the total number of `@atomic`-decorated callables the deploy will ship. It MIRRORS the deploy branch: when the Element layout is in play (a Default element or any multi-function element) it counts across discovered elements; otherwise it counts across the folders listed in the Driftfile (the legacy path, which honors custom `dir:` overrides). Keeping it in lockstep with applyAtomic is what stops a flat app from provisioning zero function slots.

An Atomic function IS a decorated callable; un-annotated helpers don't count.

func CountScheduledFunctions added in v1.6.0

func CountScheduledFunctions(m *Manifest) (int, error)

CountScheduledFunctions returns how many `@atomic cron=` (scheduled) callables exist — the authoritative scheduled-job count used to size the slice. Like CountAtomicFunctions, it mirrors the deploy branch.

func CreateSlice

func CreateSlice(name, tier string, cfg SliceConfig, billingMonths int) error

CreateSlice POSTs /ops/slice/create. tier is "hacker" for free slices (server overrides Config with HackerConfig) or "custom" for everything else.

func GetCmd

func GetCmd() *cobra.Command

GetCmd returns the `drift project` command group: deploy the project described by ./Driftfile to a slice, or preview the diff first.

Vocabulary the platform uses:

  • Primitives — Atomic, Backbone, Canvas (the *what*).
  • Slice — the rented infrastructure that holds primitives (the *where*).
  • Project — a Driftfile bundling primitives + slice (the *what + where*, deployed as one unit).

func PriceConfig

func PriceConfig(cfg SliceConfig) (int, error)

PriceConfig POSTs /ops/slice/price with a SliceConfig and returns the monthly cost in cents. Used by both --plan and the cost-confirm prompt; the platform's pricing function is the single source of truth, never the CLI.

func RenderDiff

func RenderDiff(d DiffResult) string

RenderDiff produces the user-facing block for `drift deploy --plan` and the cost-confirm prompt. Wording matches the spec's reconcile- rule examples exactly.

func ResizeSlice

func ResizeSlice(name string, cfg SliceConfig, billingMonths int) error

ResizeSlice POSTs /ops/slice/resize with the new SliceConfig. The platform-side endpoint already enforces "won't shrink below current usage", so even with a destructive flag, populated resources can't disappear silently.

func SlugifyRoute

func SlugifyRoute(route string) string

SlugifyRoute derives the per-site directory name from a route. The slug is what the slice uses to lay sites out under /data/canvas/<slug>/.

"/"               -> "default"
"/reviewer"       -> "reviewer"
"/admin/portal"   -> "admin-portal"

Types

type AlertEntry

type AlertEntry struct {
	On        string `yaml:"on"`        // "errors" (v1)
	Threshold int    `yaml:"threshold"` // count of errors in the window
	Window    string `yaml:"window"`    // duration string e.g. "5m"
	Notify    string `yaml:"notify"`    // "webhook=https://..." (v1)
}

AlertEntry declares one alert on a function. `On` is the trigger (`errors` for v1). `Threshold` and `Window` together define when the alert fires (e.g. >=1 error over a 5-minute window). `Notify` is the destination — `webhook=https://hooks.slack.com/...` for v1.

type AtomicEntry

type AtomicEntry struct {
	Name    string `yaml:"name"`
	Dir     string `yaml:"dir"`
	Element string `yaml:"element"`
	Cron    string `yaml:"cron"`

	// Alerts is the per-function alerting list. v1: `errors`
	// trigger only; `webhook` notify only.
	Alerts []AlertEntry `yaml:"alerts,omitempty"`
}

func (*AtomicEntry) UnmarshalYAML

func (a *AtomicEntry) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts either a bare-string (function name) or a map (the long form with name/dir/element/cron).

type AtomicLimits

type AtomicLimits struct {
	MaxNumberOfFunctions            int
	MaxFunctionRuntimeInSeconds     int
	MaxNumberOfDeploymentsInHistory int
	MaxNumberOfHoursForLogRetention int
	MaxNumberOfRequestsPerMinute    int
	MaxNumberOfScheduledJobs        int
	MaxFunctionMemoryBytes          int
}

type AtomicSection

type AtomicSection struct {
	FunctionMemory  string        `yaml:"function_memory"`
	FunctionTimeout string        `yaml:"function_timeout"`
	RateLimit       string        `yaml:"rate_limit"`
	DeployHistory   int           `yaml:"deploy_history"` // past deploys kept per function (rollback)
	Functions       []AtomicEntry `yaml:"functions"`

	// Egress declares the slice's outbound network posture.
	// Schema-only today; richer enforcement modes are planned.
	Egress *EgressSection `yaml:"egress,omitempty"`
}

type BackboneBlobsLimits

type BackboneBlobsLimits struct {
	MaxCount           int
	MaxSizeInBytesEach int
}

type BackboneLimits

type BackboneLimits struct {
	Secrets  BackboneSecretsLimits  `json:"secrets"`
	Blobs    BackboneBlobsLimits    `json:"blobs"`
	NoSQL    BackboneNoSQLLimits    `json:"nosql"`
	SQL      BackboneSQLLimits      `json:"sql"`
	Queues   BackboneQueuesLimits   `json:"queues"`
	Realtime BackboneRealtimeLimits `json:"realtime"`
	Locks    BackboneLocksLimits    `json:"locks"`
	// BackupRetentionDays needs its json tag — the platform model tags this
	// leaf "backup_retention_days" (unlike the other Go-named leaves), so
	// without it the value marshals as "BackupRetentionDays" and the server
	// silently reads 0.
	BackupRetentionDays int `json:"backup_retention_days"`
}

type BackboneLocksLimits

type BackboneLocksLimits struct {
	MaxConcurrent int
}

type BackboneNoSQLLimits

type BackboneNoSQLLimits struct {
	MaxCollections  int
	MaxStorageBytes int
}

type BackboneQueuesLimits

type BackboneQueuesLimits struct {
	MaxQueues    int
	MaxDepthEach int
}

type BackboneRealtimeLimits added in v1.6.0

type BackboneRealtimeLimits struct {
	MaxConcurrentConnections int
}

type BackboneSQLLimits added in v1.6.0

type BackboneSQLLimits struct {
	MaxDatabases    int
	MaxStorageBytes int
}

type BackboneSecretsLimits

type BackboneSecretsLimits struct {
	MaxCount           int
	MaxSizeInBytesEach int
}

type BackboneSection

type BackboneSection struct {
	NoSQLStorage  string `yaml:"nosql_storage"`
	SQLStorage    string `yaml:"sql_storage"` // storage per SQL database (e.g. "100MB")
	BlobMaxSize   string `yaml:"blob_max_size"`
	BlobMaxCount  int    `yaml:"blob_max_count"`
	QueueMaxDepth int    `yaml:"queue_max_depth"`
	SecretMaxSize string `yaml:"secret_max_size"` // max size of one secret value (e.g. "4KB")
	Locks         int    `yaml:"locks"`           // max concurrent Backbone locks
	// RealtimeConnections caps simultaneous live realtime WebSocket
	// connections across the slice (the live pub/sub primitive). Billed in
	// 50-connection blocks; 0 (omitted) means realtime is off for this slice.
	RealtimeConnections int                   `yaml:"realtime_connections"`
	NoSQL               []NoSQLEntry          `yaml:"nosql"`
	Queues              []string              `yaml:"queues"`
	Cache               map[string]CacheEntry `yaml:"cache"`
	Secrets             map[string]string     `yaml:"secrets"`

	// SQL declares per-slice SQLite databases. Each entry becomes a
	// `.db` file.
	SQL []SQLEntry `yaml:"sql,omitempty"`
}

type CacheEntry

type CacheEntry struct {
	File  string `yaml:"file"`
	Value string `yaml:"value"`
	TTL   int    `yaml:"ttl"`
}

CacheEntry is the long-form expansion. Short-form `<key>: <path>` expands to {File: <path>}. Short-form `{value: ...}` expands to {Value: <inline-value>}.

func (*CacheEntry) UnmarshalYAML

func (c *CacheEntry) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML for cache map values accepts either a bare-string (file path) or a map (the long form with value/ttl).

type CanvasEntry

type CanvasEntry struct {
	Dir   string `yaml:"dir"`
	Route string `yaml:"route"`
}

func (*CanvasEntry) UnmarshalYAML

func (c *CanvasEntry) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts either a bare-string (canvas directory) or a map (the long form with dir/route).

type CanvasLimits

type CanvasLimits struct {
	TotalMaxSizeInBytes int
}

type CanvasSection

type CanvasSection struct {
	CanvasSize string        `yaml:"canvas_size"`
	Sites      []CanvasEntry `yaml:"sites"`
}

type DiffResult

type DiffResult struct {
	Verdict         Verdict
	SliceName       string
	IsNewSlice      bool
	Grows           []FieldDelta // fields the manifest wants larger than live
	Shrinks         []FieldDelta // fields the manifest wants smaller than live (only set on Abort)
	LiveCostCents   int          // monthly cost of the live slice (0 if Create)
	WantedCostCents int          // monthly cost of the manifest's declared shape
}

DiffResult is the structured output of Diff(). Render it with RenderDiff to get the user-facing prompt block.

func Diff

func Diff(sliceName string, manifest SliceConfig, liveCfg *SliceConfig, liveCostCents, wantedCostCents int) DiffResult

Diff compares the manifest-derived SliceConfig against the live SliceConfig and returns the verdict + per-field deltas. liveCfg must be a pointer; nil means "the slice doesn't exist yet" → Create.

type DomainEntry

type DomainEntry struct {
	Host   string `yaml:"host"`
	Verify string `yaml:"verify"` // "dns-txt" (default for v1)
}

DomainEntry declares one custom hostname for the slice. Verify is the ownership-proof method; "dns-txt" is the only mode for v1.

type EgressSection

type EgressSection struct {
	Mode  string   `yaml:"mode"`            // "open" | "allowlist"
	Hosts []string `yaml:"hosts,omitempty"` // e.g. "api.stripe.com", "*.amazonaws.com", "smtp.sendgrid.net:587"
}

EgressSection — declares whether the slice's outbound traffic to the public internet is open (today's default) or restricted to a curated list of hostnames. Private-CIDR exclusion (RFC-1918, link-local incl. IMDS, CGNAT) is preserved unconditionally regardless of mode.

type FieldDelta

type FieldDelta struct {
	Path    string // human-readable path, e.g. "atomic.functions" or "backbone.nosql_storage"
	Live    int    // current value on the live slice (0 if Create)
	Wanted  int    // value the manifest declares
	IsBytes bool   // render as a size string instead of a bare integer
	IsTime  bool   // render as a duration (seconds) — applies to function_timeout
	IsHours bool   // render as a duration (hours) — applies to log_retention
	IsDays  bool   // render as a duration (days) — applies to backup_retention
}

FieldDelta records one resource/envelope dimension that changed.

func (FieldDelta) Delta

func (f FieldDelta) Delta() int

Delta returns Wanted - Live; positive means grow, negative means shrink.

type LiveSlice

type LiveSlice struct {
	Name             string      `json:"name"`
	Tier             string      `json:"tier"`
	Config           SliceConfig `json:"config"`
	MonthlyCostCents int         `json:"monthly_cost_cents"`
}

LiveSlice is a CLI-local mirror of the fields we actually use from the platform's models.Slice. The wire endpoint returns more fields (createdAt, billing, provisioning) but we only need name + config.

func FetchLiveSlice

func FetchLiveSlice(name string) (*LiveSlice, error)

FetchLiveSlice GETs /ops/slice/get?name=<name>. Returns nil if the slice doesn't exist (404), error for any other failure.

type Manifest

type Manifest struct {
	Slice Slice `yaml:"slice"`
	// contains filtered or unexported fields
}

Manifest is the parsed Driftfile, after shorthand expansion. The shape mirrors the spec's nested model exactly; downstream code reads off this struct without needing to think about short forms.

func ParseDriftfile

func ParseDriftfile(path string) (*Manifest, error)

ParseDriftfile reads a Driftfile from disk, expands shorthands, resolves $ENVREF secrets, and validates everything against the spec. baseDir is the directory containing the Driftfile and is used as the resolution root for relative paths.

func (*Manifest) ResolvePath

func (m *Manifest) ResolvePath(rel string) string

ResolvePath is the exported sibling of resolveBaseDir, used by the run driver after parse to find files referenced by the manifest.

type NoSQLEntry

type NoSQLEntry struct {
	Name string `yaml:"name"`
	Seed string `yaml:"seed"` // path to JSONL
}

func (*NoSQLEntry) UnmarshalYAML

func (n *NoSQLEntry) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts either a bare-string (collection name) or a map (the long form with name/seed).

type ParseErrors

type ParseErrors []string

ParseErrors aggregates every validation failure in one error so the user sees them all at once. Implements `error` so it can flow through the cobra RunE return.

func (ParseErrors) Error

func (p ParseErrors) Error() string

type SQLEntry

type SQLEntry struct {
	Name   string `yaml:"name"`
	Schema string `yaml:"schema,omitempty"`
	Seed   string `yaml:"seed,omitempty"`
}

SQLEntry declares one SQL database. `Schema` is a path to a SQL file with idempotent DDL (`CREATE TABLE IF NOT EXISTS`); it runs on every deploy. `Seed` is a path to a SQL file that runs only when the database has no user tables yet.

func (*SQLEntry) UnmarshalYAML added in v1.6.0

func (s *SQLEntry) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts either a bare-string (database name → empty database) or a map (the long form with name/schema/seed). This mirrors nosql so `sql: [ledger]` and the long form both work.

type Slice

type Slice struct {
	Name            string `yaml:"name"`
	LogRetention    string `yaml:"log_retention"`
	BackupRetention string `yaml:"backup_retention"`

	Atomic   AtomicSection   `yaml:"atomic"`
	Backbone BackboneSection `yaml:"backbone"`
	Canvas   CanvasSection   `yaml:"canvas"`

	// Domains lists per-slice custom hostnames the slice should answer
	// on (e.g. forms.gemeente.example). Schema-only today; the
	// reconcile path is planned.
	Domains []DomainEntry `yaml:"domains"`
}

type SliceConfig

type SliceConfig struct {
	Canvas   CanvasLimits   `json:"canvas"`
	Atomic   AtomicLimits   `json:"atomic"`
	Backbone BackboneLimits `json:"backbone"`
}

SliceConfig mirrors the JSON wire shape of drift-common/models.SliceConfig. Field names match the platform's encoded shape exactly: parent fields live under lowercase JSON keys (canvas, atomic, backbone, secrets, blobs, nosql, queues, locks); leaf fields use the Go field names because the platform's models package omits json tags on those.

func ManifestToSliceConfig

func ManifestToSliceConfig(m *Manifest) (SliceConfig, error)

ManifestToSliceConfig builds a SliceConfig from a parsed Driftfile. Unset envelope knobs leave the corresponding field at zero, which the platform reads as "use the slice envelope's default for this field" (the same convention the configurator uses today).

Returns a translation-error block if any envelope knob fails to parse despite passing the upstream Driftfile validation. That would be an internal bug, not user error, but we surface it rather than swallowing it.

type Verdict

type Verdict int

Verdict is the four-way classifier described above.

const (
	VerdictCreate Verdict = iota
	VerdictMatch
	VerdictGrow
	VerdictAbort
)

func (Verdict) String

func (v Verdict) String() string

Jump to

Keyboard shortcuts

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