service

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 9 Imported by: 0

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

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

View Source
var ErrNotFound = errors.New("service: not found")

ErrNotFound is returned when a service does not exist.

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

func RegisterKind(reg *resource.Registry) error

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

type Observation struct {
	Phase string
	URL   string
}

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 Service

type Service struct {
	Name    string
	Spec    Spec
	Status  Status
	Version int
}

Service is the typed view of a service resource.

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.

func DecodeSpec

func DecodeSpec(r resource.Resource) (Spec, error)

DecodeSpec reads the typed spec from a resource.

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.

func DecodeStatus

func DecodeStatus(r resource.Resource) (Status, error)

DecodeStatus reads the typed status from a resource.

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

func NewStore(rs resource.Store) *Store

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

func (s *Store) Delete(ctx context.Context, name string) error

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.

func (*Store) Get

func (s *Store) Get(ctx context.Context, name string) (Service, error)

Get returns the named service, or ErrNotFound.

func (*Store) List

func (s *Store) List(ctx context.Context) ([]Service, error)

List returns every service, ordered by name.

func (*Store) Put

func (s *Store) Put(ctx context.Context, name string, spec Spec, status Status) (Service, error)

Put creates or updates the named service, writing both its spec and status in one record. The deploy path uses it to register a freshly deployed workload.

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.

func (*Supervisor) Reconcile

func (s *Supervisor) Reconcile(ctx context.Context, key reconcile.Ref) (reconcile.Result, error)

Reconcile drives one service toward its desired state. It is the Reconciler the reconcile manager runs for the Service kind. A vanished service settles silently; a store read error retries; the desired state selects the action.

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

func (Target) Valid

func (t Target) Valid() bool

Valid reports whether t is one of the known targets. The empty target is allowed (a provider that does not classify its output).

Jump to

Keyboard shortcuts

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