declarative

package
v1.6.3 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

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

View Source
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.

View Source
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.

View Source
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

View Source
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

func RandAlphaNum(n int) string

RandAlphaNum returns a cryptographically-random alphanumeric string of length n (used to satisfy secrets with generate: true).

Types

type Action

type Action string

Action is the operation a plan entry performs.

const (
	ActionCreate Action = "create"
	ActionUpdate Action = "update"
	ActionDelete Action = "delete"
	ActionNoop   Action = "noop"
)

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

type ConnView struct {
	Host     string
	Port     string
	User     string
	Password string
	Name     string
	URI      string
}

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

type Edge struct {
	From string   `json:"from"`
	To   string   `json:"to"`
	Type EdgeType `json:"type"`
}

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) Counts

func (p *Plan) Counts() (create, update, del, noop int)

Counts tallies the plan by action.

func (*Plan) HasChanges

func (p *Plan) HasChanges() bool

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) RenderEnv

func (r *Renderer) RenderEnv(scope string, env map[string]string) (map[string]string, error)

RenderEnv interpolates every value of an env map.

func (*Renderer) RenderEnvLenient

func (r *Renderer) RenderEnvLenient(scope string, env map[string]string) map[string]string

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.

func (*Renderer) RenderString

func (r *Renderer) RenderString(name, s string) (string, error)

RenderString interpolates a single value. A reference to a missing key is a hard error (no silent empty env).

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.

func (Resource) Key

func (r Resource) Key() string

Key is the stable identity of a resource within a workspace: "<kind>/<name>". Names are unique per kind, so this keys the desired/actual comparison.

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 NewResourceSet

func NewResourceSet() *ResourceSet

NewResourceSet returns an empty set.

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.

func (*ResourceSet) Has

func (s *ResourceSet) Has(k Kind, name string) bool

Has reports whether a resource of the given kind+name is present.

func (*ResourceSet) Len

func (s *ResourceSet) Len() int

Len reports the number of resources.

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.

Jump to

Keyboard shortcuts

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