Documentation
¶
Overview ¶
Package service models a deployed workload as a typed resource on the event-sourced foundation. When a hosting extension's deploy operation succeeds, the operator surface materializes a Service here: which provider deployed it, what it deploys (a static site, a container, a VPS), the external id and URL the provider returned, and the state the workload should be driven toward. A deployed app is therefore not fire-and-forget. The record is admitted, versioned, and provenance-stamped like every other kind, so a reconcile loop can later track its health, restart it, or tear it down, and the control plane can read it back.
The secret a deploy used never enters a Service: only the credential's name is recorded; the value stays in the vault. The Service is safe to list and sync.
Index ¶
Constants ¶
const ( // GroupVersion is the Service kind's API group and version. GroupVersion = "service.ionagent.io/v1alpha1" // Kind is the resource kind name services are stored under. Kind = "Service" )
const DefaultPoll = 60 * time.Second
DefaultPoll is how often a running service is re-observed when the supervisor settles a reconcile without an error. It is a backstop on top of the manager's resync: a healthy workload is re-checked on this cadence so a drift the store never hears about is still caught.
Variables ¶
var ErrNotFound = errors.New("service: not found")
ErrNotFound is returned when a service does not exist.
var KindDef = resource.Kind{ APIVersion: GroupVersion, Name: Kind, Schema: specSchema, Singular: "service", Plural: "services", }
KindDef is the Service kind definition registered with a resource registry.
Functions ¶
func RegisterKind ¶
RegisterKind registers the Service kind so a store admits services. It is idempotent.
Types ¶
type DesiredState ¶
type DesiredState string
DesiredState is the state a reconcile loop should drive a service toward.
const ( // StateRunning means the workload should be up. StateRunning DesiredState = "running" // StateStopped means the workload should be retired (a teardown target). StateStopped DesiredState = "stopped" )
type Driver ¶
type Driver interface {
Observe(ctx context.Context, svc Service) (Observation, error)
Teardown(ctx context.Context, svc Service) error
}
Driver is the provider-agnostic boundary the supervisor drives a workload through. The supervisor decides WHEN to act and toward WHICH desired state; the driver runs the one remote call for the workload's provider. A driver resolves the credential the service recorded from the vault itself, so no secret ever reaches the supervisor or the service record.
Observe reads a workload's current health without changing it. Teardown removes the remote workload; it must be idempotent, because the supervisor may call it again if the record outlives a first attempt (a crash between teardown and record deletion).
type Observation ¶
Observation is what a Driver reports about a live workload. Phase is the provider's own health word ("running", "failed"), or empty when the provider cannot say (it declares no status operation, or returned nothing recognizable). URL is the live address the workload currently serves at, empty when unchanged or unknown. The supervisor records these onto the service's status; it never interprets them beyond "did anything change".
type Spec ¶
type Spec struct {
// Provider is the extension/provider that deployed this workload (e.g. "cloudflare").
Provider string `json:"provider"`
// Target is what was deployed (static site, container, VPS). Optional.
Target Target `json:"target,omitempty"`
// ExternalID is the provider's own id for the workload (a deployment id, a server
// id), used to address it on status and teardown. Empty until the provider returns one.
ExternalID string `json:"externalID,omitempty"`
// URL is the live address the workload serves at, when the provider returns one.
URL string `json:"url,omitempty"`
// DesiredState is the state the reconcile loop should hold the workload in.
DesiredState DesiredState `json:"desiredState,omitempty"`
// Credential names the credential used to deploy and supervise this workload, so a
// later status/teardown resolves the same one. The value stays in the vault.
Credential string `json:"credential,omitempty"`
// Address is provider-opaque addressing the deploy recorded so a later status or
// teardown can re-find the same workload (e.g. a Pages account id and project name).
// The supervisor never interprets these: it replays them verbatim into the
// provider's status/teardown operation, so the provider alone decides what it needs
// to address its own workload. Never a secret; the credential stays in the vault.
Address map[string]string `json:"address,omitempty"`
}
Spec is the desired shape of a deployed workload: pure metadata, no secret.
type Status ¶
type Status struct {
// Phase is a short lifecycle word: "deployed", "stopped", "failed".
Phase string `json:"phase,omitempty"`
// ObservedURL is the last URL a status check saw the workload at.
ObservedURL string `json:"observedURL,omitempty"`
// LastDeploy is the RFC3339 time of the last deploy, supplied by the caller's clock
// (never read from the wall clock here, so the record stays replay-equivalent).
LastDeploy string `json:"lastDeploy,omitempty"`
}
Status is a service's observed state, set by the deploy/teardown path and (later) a reconcile loop rather than admitted against the spec schema.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the typed service facade over a resource.Store. Services live in the instance-global scope, addressed by their name.
func NewStore ¶
NewStore returns a service facade over rs. The caller must have registered the Service kind with the registry rs admits against (see RegisterKind).
func (*Store) Delete ¶
Delete removes the named service record. It is the bookkeeping half of a teardown: the remote workload is removed by the provider's teardown operation, this retires the record. A missing service is not an error.
type Supervisor ¶
type Supervisor struct {
// contains filtered or unexported fields
}
Supervisor is the reconciler that holds a deployed Service in its desired state. It is level-triggered: handed only a key, it re-reads the live service every time and drives it toward Spec.DesiredState, so it is self-healing and crash-resumable. A service wanting to run is re-observed and its status refreshed; a service wanting to stop is torn down through its provider and its record retired. The supervisor owns no provider knowledge: every remote effect goes through the Driver.
func NewSupervisor ¶
func NewSupervisor(store *Store, drv Driver, opts ...SupervisorOption) *Supervisor
NewSupervisor builds a supervisor over store that effects remote changes through drv.
type SupervisorOption ¶
type SupervisorOption func(*Supervisor)
SupervisorOption configures a Supervisor.
func WithClock ¶
func WithClock(c clock.Timing) SupervisorOption
WithClock sets the time source used to stamp observed status (default clock.System).
func WithPoll ¶
func WithPoll(d time.Duration) SupervisorOption
WithPoll sets the re-observe interval for a running service (default DefaultPoll). A value <= 0 disables the periodic re-observe, leaving only the manager's resync to re-drive the service.
type Target ¶
type Target string
Target classifies what a hosting provider deploys, so the operator (and, later, the agent) can pick a provider that can satisfy a goal. The set is small on purpose: a provider declares the targets it supports, and a deploy records the one it used.
const ( // TargetStaticSite is a static website (e.g. Cloudflare Pages, Vercel static). TargetStaticSite Target = "static-site" // TargetContainer is a container/PaaS workload (e.g. Render, Cloud Run). TargetContainer Target = "container" // TargetVPS is a raw virtual machine (e.g. Hetzner, DigitalOcean droplet). TargetVPS Target = "vps" )