Documentation
¶
Overview ¶
Package declarative is the shared declarative resource model for Miabi. It generalizes the marketplace manifest engine into a family of miabi.io/v1 resource kinds (Application, Stack, Database, Volume, Domain, Secret, Project) and exposes a single parse/validate/render path plus a plan/diff engine.
Four consumers speak the same kinds: the `apply` API, GitOps reconciliation, the Terraform/OpenTofu provider, and marketplace templates. One schema, one validator, one plan engine — author once, consume four ways.
Index ¶
- Constants
- Variables
- func Marshal(set *ResourceSet) ([]byte, error)
- func RandAlphaNum(n int) string
- type Action
- type ApplicationSpec
- type Change
- type ConnView
- type DatabaseSpec
- type DomainSpec
- type Edge
- type EdgeType
- type FieldDiff
- type Kind
- type Meta
- type MountSpec
- type Plan
- type PlanOptions
- type PortSpec
- type ProjectSpec
- type RenderContext
- type Renderer
- type Resource
- type ResourceSet
- type ResourceSpec
- type RouteSpec
- type SecretSpec
- type StackSpec
- type VolumeSpec
Constants ¶
const APIVersion = "miabi.io/v1"
APIVersion is the only accepted apiVersion. It is shared with the marketplace manifest engine so the two families stay in lock-step.
const LabelGitOpsSource = "miabi.io/gitops-source"
LabelGitOpsSource is the Meta.Labels key recording which GitOps project (source id) created an actual resource, so a single project's resources can be listed or torn down without touching another project's.
const LabelManagedBy = "miabi.io/managed-by"
LabelManagedBy is the Meta.Labels key that records which subsystem owns an actual resource. The plan engine uses it to keep prune from ever deleting a resource the apply engine did not create.
Variables ¶
var ErrNoResources = errors.New("no declarative resources found")
ErrNoResources reports that a manifest source parsed with zero resources (the path exists but holds no miabi.io/v1 documents). Callers distinguish it from a missing path (fs.ErrNotExist) to decide whether an empty result is an intentional teardown or an error.
Functions ¶
func Marshal ¶
func Marshal(set *ResourceSet) ([]byte, error)
Marshal serializes a resource set into a single multi-document YAML bundle that Parse round-trips. Project resources are organizational and omitted — their children are already first-class members of the set.
func RandAlphaNum ¶
RandAlphaNum returns a cryptographically-random alphanumeric string of length n (used to satisfy secrets with generate: true).
Types ¶
type ApplicationSpec ¶
type ApplicationSpec struct {
Image string `yaml:"image" json:"image"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Digest string `yaml:"digest,omitempty" json:"digest,omitempty"` // sha256:… (immutable pin)
Command []string `yaml:"command,omitempty" json:"command,omitempty"`
Ports []PortSpec `yaml:"ports,omitempty" json:"ports,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
SecretEnv []string `yaml:"secretEnv,omitempty" json:"secretEnv,omitempty"`
Mounts []MountSpec `yaml:"mounts,omitempty" json:"mounts,omitempty"`
Resources *ResourceSpec `yaml:"resources,omitempty" json:"resources,omitempty"`
// ContainerLabels are user-defined Docker labels stamped on the app's
// container(s) — for label-driven tools like Traefik. Reserved keys
// (io.miabi.*, com.docker.*) are stripped on apply (a manifest is
// machine-authored, so import is fail-soft rather than erroring).
ContainerLabels map[string]string `yaml:"containerLabels,omitempty" json:"containerLabels,omitempty"`
// Stack optionally names the owning Stack resource.
Stack string `yaml:"stack,omitempty" json:"stack,omitempty"`
// ExternalLabel pins the subdomain used for external access
// (`<label>.<base-domain>`). Optional — when unset Miabi generates a
// stable label; pinning it keeps the public URL deterministic across applies.
// The label is platform-wide unique: if it is already claimed by another app,
// it is ignored and a generated label is used instead (the apply still
// succeeds), so a copy-pasted manifest never fails on a label clash.
ExternalLabel string `yaml:"externalLabel,omitempty" json:"externalLabel,omitempty"`
}
ApplicationSpec is a long-running container workload. CI writes the immutable Digest; GitOps converges runtime to it.
type Change ¶
type Change struct {
Action Action `json:"action"`
Kind Kind `json:"kind"`
Name string `json:"name"`
Reason string `json:"reason,omitempty"`
Fields []FieldDiff `json:"fields,omitempty"`
}
Change is one entry in a plan: what happens to one resource.
type ConnView ¶
ConnView is a resolved database's connection detail, exposed to env interpolation as {{ .databases.<name>.* }}.
type DatabaseSpec ¶
type DatabaseSpec struct {
Engine string `yaml:"engine" json:"engine"` // postgres|mysql|mariadb|redis
Version string `yaml:"version,omitempty" json:"version,omitempty"` // e.g. "16-alpine"
Placement string `yaml:"placement,omitempty" json:"placement,omitempty"`
}
DatabaseSpec requests a logical database on a DatabaseInstance.
type DomainSpec ¶
type DomainSpec struct {
TLS string `yaml:"tls,omitempty" json:"tls,omitempty"` // acme|custom (default acme)
Wildcard bool `yaml:"wildcard,omitempty" json:"wildcard,omitempty"`
}
DomainSpec declares an owned hostname/zone. The hostname is the resource's metadata.name (a real FQDN, e.g. shop.example.com). tls is the default certificate policy routes under it inherit; wildcard covers *.name too. Ownership verification (publishing the TXT token) is a runtime action.
type Edge ¶
Edge is a directed dependency from one resource to another, keyed by Resource Key() ("<Kind>/<name>"). From depends on / targets To.
func Edges ¶
func Edges(set *ResourceSet) []Edge
Edges derives the cross-resource dependency edges within a set. It must run on the parsed (un-rendered) set: application env still carries the "{{ .databases.X }}" / "{{ .secrets.X }}" templates that reveal soft references. Only edges whose target is also present in the set are emitted, so a topology never draws a link to a node it is not showing. The result is de-duplicated and stably ordered.
type EdgeType ¶
type EdgeType string
EdgeType classifies a dependency between two resources, so a topology view can label and style the link.
const ( EdgeMount EdgeType = "mount" // Application -> Volume (mounts[].volume) EdgeStack EdgeType = "stack" // Application -> Stack (stack) EdgeRoute EdgeType = "route" // Route -> Application (app) EdgeDomain EdgeType = "domain" // Route -> Domain (host under the domain) EdgeDatabase EdgeType = "database" // Application -> Database (env {{ .databases.X }}) EdgeSecret EdgeType = "secret" // Application -> Secret (env {{ .secrets.X }}) EdgeAppRef EdgeType = "app-ref" // Application -> Application (env {{ .applications.X }}) )
type FieldDiff ¶
type FieldDiff struct {
Field string `json:"field"`
From string `json:"from"`
To string `json:"to"`
}
FieldDiff is a single changed field in an update.
type Kind ¶
type Kind string
Kind enumerates the declarative resource kinds. They map 1:1 to the resources the platform manages and the Terraform provider exposes.
const ( KindApplication Kind = "Application" KindStack Kind = "Stack" KindDatabase Kind = "Database" KindVolume Kind = "Volume" // KindRoute is an HTTP routing rule (host/path → app:port + TLS). It is // distinct from KindDomain: a Route exposes an app on a hostname, while a // Domain is the owned hostname/zone that tracks DNS ownership + the default // TLS policy routes under it inherit. KindRoute Kind = "Route" KindSecret Kind = "Secret" // KindDomain is an owned hostname (or zone) a workspace controls: its default // TLS policy and (optional) wildcard coverage. DNS-ownership verification is a // runtime action, not a declarable field. KindDomain Kind = "Domain" // KindProject bundles a set of resources living in one repo/namespace. KindProject Kind = "Project" )
type Meta ¶
type Meta struct {
Name string `yaml:"name" json:"name"`
// UID is the resource's portable identifier (its Miabi uid). Written on
// export so a restore into another install — or a rename in a manifest — keeps
// identity. Optional in hand-authored manifests; matched ahead of name when set.
UID string `yaml:"uid,omitempty" json:"uid,omitempty"`
Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"`
Annotations map[string]string `yaml:"annotations,omitempty" json:"annotations,omitempty"`
}
Meta is the identity block shared by every kind. Name is the reserved identity; Labels and Annotations are the two free-form key/value maps for user metadata. Labels are short and identifying (intended for selection and grouping); Annotations hold descriptive, non-selectable data the engine stores but never queries. Custom user metadata belongs in one of these maps — never as ad-hoc top-level keys, which would collide with future reserved fields.
type MountSpec ¶
type MountSpec struct {
Volume string `yaml:"volume" json:"volume"`
Path string `yaml:"path" json:"path"`
ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"`
}
MountSpec attaches a managed Volume into the container filesystem.
type Plan ¶
type Plan struct {
Changes []Change `json:"changes"`
}
Plan is an ordered set of changes that converges actual state to desired. Creates/updates run in dependency order (volumes/dbs before apps before domains); deletes run in reverse.
func BuildPlan ¶
func BuildPlan(desired, actual *ResourceSet, opts PlanOptions) *Plan
BuildPlan diffs desired against actual and returns an ordered convergence plan. actual should contain only Miabi-managed resources when Prune is set, so unmanaged resources are never deleted.
func (*Plan) HasChanges ¶
HasChanges reports whether the plan mutates anything.
type PlanOptions ¶
type PlanOptions struct {
// Prune emits deletes for actual resources absent from desired.
Prune bool
// PruneManagedBy, when non-empty, restricts prune to actual resources whose
// LabelManagedBy label equals it — so a GitOps prune never deletes a
// hand-created or otherwise-owned resource. Empty prunes any orphan.
PruneManagedBy string
// PruneGitOpsSource, when non-empty, restricts prune to actual resources whose
// LabelGitOpsSource label equals it — so one GitOps project's reconcile only
// prunes its own resources and never another project's (which live in the same
// workspace snapshot). Empty leaves prune unscoped by source. Resources without
// a source label (e.g. hand-created or pre-labeling) are never matched, so a
// scoped prune never deletes them.
PruneGitOpsSource string
// IncludeNoop keeps in-sync resources in the plan (useful for status views).
IncludeNoop bool
}
PlanOptions tunes plan generation.
type PortSpec ¶
type PortSpec struct {
Container int `yaml:"container" json:"container"`
Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"` // tcp|udp (default tcp)
Scheme string `yaml:"scheme,omitempty" json:"scheme,omitempty"` // http|https (default http)
// ExternalAccess publishes this port on `<externalLabel>.<base-domain>` over
// HTTPS via the reverse proxy (the base domain must be configured platform-wide).
ExternalAccess bool `yaml:"externalAccess,omitempty" json:"externalAccess,omitempty"`
// Publish binds this container port to a host port on the node (raw TCP/UDP).
// HostPort requests a specific host port; 0 auto-allocates from the pool.
Publish bool `yaml:"publish,omitempty" json:"publish,omitempty"`
HostPort int `yaml:"hostPort,omitempty" json:"hostPort,omitempty"`
}
PortSpec is a container port and how it is reached. The two exposure knobs are orthogonal: externalAccess gives it a public HTTPS URL through the reverse proxy, while publish/hostPort binds it to a raw port on the node (like `docker -p`).
type ProjectSpec ¶
type ProjectSpec struct {
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Resources []Resource `yaml:"-" json:"resources,omitempty"`
}
ProjectSpec bundles a set of resources authored in one place. Child resources may be inlined; the parser flattens them into the document set.
type RenderContext ¶
type RenderContext struct {
Inputs map[string]string
Databases map[string]ConnView
Apps map[string]string // app name -> network alias
Secrets map[string]string // secret name -> value
}
RenderContext is the read-only data env interpolation resolves against. It mirrors the marketplace renderer's context but adds secrets, so GitOps and apply share one interpolation surface.
type Renderer ¶
type Renderer struct {
// contains filtered or unexported fields
}
Renderer interpolates manifest strings against a RenderContext.
func NewRenderer ¶
func NewRenderer(ctx RenderContext) *Renderer
NewRenderer builds a renderer (nil maps are tolerated).
func (*Renderer) RenderEnvLenient ¶
RenderEnvLenient interpolates every value best-effort: a value whose references cannot be resolved yet — e.g. a database declared in the same bundle but not provisioned — is left as its original template instead of failing. Used for plan/diff, where a not-yet-created dependency must not abort the whole plan; the apply path re-renders strictly once dependencies exist.
type Resource ¶
type Resource struct {
APIVersion string `yaml:"apiVersion" json:"apiVersion"`
Kind Kind `yaml:"kind" json:"kind"`
Metadata Meta `yaml:"metadata" json:"metadata"`
Application *ApplicationSpec `yaml:"-" json:"application,omitempty"`
Stack *StackSpec `yaml:"-" json:"stack,omitempty"`
Database *DatabaseSpec `yaml:"-" json:"database,omitempty"`
Volume *VolumeSpec `yaml:"-" json:"volume,omitempty"`
Route *RouteSpec `yaml:"-" json:"route,omitempty"`
Secret *SecretSpec `yaml:"-" json:"secret,omitempty"`
Domain *DomainSpec `yaml:"-" json:"domain,omitempty"`
Project *ProjectSpec `yaml:"-" json:"project,omitempty"`
}
Resource is one parsed declarative object. Exactly one of the typed spec pointers is set, determined by Kind.
type ResourceSet ¶
type ResourceSet struct {
// contains filtered or unexported fields
}
ResourceSet is an ordered, key-indexed collection of resources. It is the shape both desired state (parsed manifests) and actual state (a workspace snapshot) take so the plan engine can diff them generically.
func Parse ¶
func Parse(data []byte) (*ResourceSet, error)
Parse decodes a single- or multi-document YAML stream into a normalized, validated set of resources. Unknown spec fields are rejected so typos surface rather than being silently dropped. A Project's inline resources are flattened into the set.
func ParseFS ¶
func ParseFS(fsys fs.FS, dir string) (*ResourceSet, error)
ParseFS walks an fs.FS rooted at dir, parsing every .yaml/.yml file in lexical order into one combined set. It is the entry point GitOps uses over a cloned repo's manifest path.
func (*ResourceSet) Add ¶
func (s *ResourceSet) Add(r Resource)
Add appends r. A later Add with the same Key replaces the earlier entry.
func (*ResourceSet) All ¶
func (s *ResourceSet) All() []Resource
All returns the resources in insertion order.
func (*ResourceSet) ByKind ¶
func (s *ResourceSet) ByKind(k Kind) []Resource
ByKind returns the resources of a given kind, in insertion order.
func (*ResourceSet) Get ¶
func (s *ResourceSet) Get(key string) (Resource, bool)
Get returns the resource for key and whether it exists.
type ResourceSpec ¶
type ResourceSpec struct {
Memory string `yaml:"memory,omitempty" json:"memory,omitempty"` // e.g. "512Mi"
CPU string `yaml:"cpu,omitempty" json:"cpu,omitempty"` // e.g. "0.5"
// GPU is the number of whole GPU units the app requests (0 = none). GPUKind
// narrows the request to a vendor/model ("nvidia", "NVIDIA A100-…").
GPU int `yaml:"gpu,omitempty" json:"gpu,omitempty"`
GPUKind string `yaml:"gpuKind,omitempty" json:"gpuKind,omitempty"`
}
ResourceSpec caps memory/CPU and requests GPUs. Empty/zero means unlimited/none.
func (*ResourceSpec) MemoryBytes ¶
func (rs *ResourceSpec) MemoryBytes() (int64, error)
MemoryBytes parses the memory cap (e.g. "512Mi", "1Gi") into bytes. Empty or "0" means unlimited (0).
func (*ResourceSpec) NanoCPUs ¶
func (rs *ResourceSpec) NanoCPUs() (int64, error)
NanoCPUs parses the CPU cap (a core fraction, e.g. "0.5", "2") into nano-CPUs (1 core = 1e9). Empty or "0" means unlimited (0).
type RouteSpec ¶
type RouteSpec struct {
Hosts []string `yaml:"hosts" json:"hosts"`
App string `yaml:"app" json:"app"` // target application name
Port int `yaml:"port,omitempty" json:"port,omitempty"`
Path string `yaml:"path,omitempty" json:"path,omitempty"`
TLS string `yaml:"tls,omitempty" json:"tls,omitempty"` // acme|custom|off (default acme)
}
RouteSpec binds one or more hostnames (and an optional path) to an application's port with a TLS mode — an HTTP routing rule. List every hostname the route should answer on, e.g. both example.com and www.example.com.
type SecretSpec ¶
type SecretSpec struct {
Value string `yaml:"value,omitempty" json:"value,omitempty"`
Generate bool `yaml:"generate,omitempty" json:"generate,omitempty"`
Length int `yaml:"length,omitempty" json:"length,omitempty"`
}
SecretSpec is a write-only secret value. Value is never read back; the diff engine treats secrets as opaque (presence/absence only).
type StackSpec ¶
type StackSpec struct {
Description string `yaml:"description,omitempty" json:"description,omitempty"`
}
StackSpec groups applications into one logical unit / network.
type VolumeSpec ¶
type VolumeSpec struct {
Size string `yaml:"size,omitempty" json:"size,omitempty"` // e.g. "5Gi" (0/empty = unbounded)
}
VolumeSpec declares persistent storage.