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 ¶
- func CountAtomicFunctions(m *Manifest) (int, error)
- func CountScheduledFunctions(m *Manifest) (int, error)
- func CreateSlice(name, tier string, cfg SliceConfig, billingMonths int) error
- func GetCmd() *cobra.Command
- func PriceConfig(cfg SliceConfig) (int, error)
- func RenderDiff(d DiffResult) string
- func ResizeSlice(name string, cfg SliceConfig, billingMonths int) error
- func SlugifyRoute(route string) string
- type AlertEntry
- type AtomicEntry
- type AtomicLimits
- type AtomicSection
- type BackboneBlobsLimits
- type BackboneLimits
- type BackboneLocksLimits
- type BackboneNoSQLLimits
- type BackboneQueuesLimits
- type BackboneRealtimeLimits
- type BackboneSQLLimits
- type BackboneSecretsLimits
- type BackboneSection
- type CacheEntry
- type CanvasEntry
- type CanvasLimits
- type CanvasSection
- type DiffResult
- type DomainEntry
- type EgressSection
- type FieldDelta
- type LiveSlice
- type Manifest
- type NoSQLEntry
- type ParseErrors
- type SQLEntry
- type Slice
- type SliceConfig
- type Verdict
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CountAtomicFunctions ¶
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
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 ¶
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 ¶
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 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 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 BackboneQueuesLimits ¶
type BackboneRealtimeLimits ¶ added in v1.6.0
type BackboneRealtimeLimits struct {
MaxConcurrentConnections int
}
type BackboneSQLLimits ¶ added in v1.6.0
type BackboneSecretsLimits ¶
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 ¶
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 ¶
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 ¶
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 ¶
ResolvePath is the exported sibling of resolveBaseDir, used by the run driver after parse to find files referenced by the manifest.
type NoSQLEntry ¶
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.
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.