pool

package
v0.1.64 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const DefaultReconciliationPassTimeout = 30 * time.Second

DefaultReconciliationPassTimeout bounds a single ReconcileAgent pass inside RunAgentReconciliation so a slow or hung List call can't run forever; each pass is best-effort and logs rather than fails the caller either way. Shared by every RunAgentReconciliation caller — currently internal/agentserver (one goroutine per connected remote agent) and internal/cli's `boxy serve` (one goroutine for the embedded agent, so the in-process deployment topology gets the same #174 defense-in-depth sweep a remote agent gets on every connection).

View Source
const ForcedCleanupAge = 30 * time.Minute
View Source
const ResourceProvisionedEventType = "resource.provisioned"

Variables

This section is empty.

Functions

func ReconcileAgent added in v0.1.29

func ReconcileAgent(ctx context.Context, st store.Store, registry *AgentRegistry, agentID string, logger *slog.Logger) error

ReconcileAgent audits one agent's actual resources against the store's belief, closing the leak window described in #133: a dropped Create whose remote side actually succeeded leaves a resource the store never learns about. It uses pkg/policycontroller.Controller, the same Observe->Decide-> Act shape internal/pool/manager.go already uses for pool inventory reconciliation — a second consumer of that package rather than a new abstraction.

Deliberately scoped to two outcomes, not three: this cycle only implements providersdk.ResourceLister for the docker driver (see #133's PR description), and there is no existing convention anywhere in this codebase for mapping a driver-native ResourceStatus.State string (e.g. docker's "running"/"exited") onto model.ResourceState — inventing one here would be unscoped guesswork. So this only adopts orphans and reaps confirmed-gone resources; syncing state on resources both sides already agree exist is left alone.

Runs on every successful registration, not just reconnects — even a brand-new agent identity can have pre-existing boxy-tagged resources from a prior life (e.g. process restarted with a fresh cert).

func RunAgentReconciliation added in v0.1.39

func RunAgentReconciliation(ctx context.Context, st store.Store, registry *AgentRegistry, agentID string, interval, passTimeout time.Duration, logger *slog.Logger)

RunAgentReconciliation runs ReconcileAgent's Observe/Decide/Act cycle immediately, then repeatedly on interval, until ctx is cancelled — defense-in-depth for orphans #174's inline Create-failure handling can't see (e.g. an agent crash between New-VM succeeding and the failure branch running). Each pass is bounded by passTimeout, applied on top of (never beyond) ctx. A failed pass is logged and skipped rather than ending the loop, matching the previous one-shot call's guarantee that reconciliation trouble must never take down agent connectivity. interval is expected to be the connection's own heartbeat interval (see internal/agentserver/server.go) rather than a new standalone constant.

Types

type AdmissionFailureRecorder added in v0.1.42

type AdmissionFailureRecorder interface {
	FailAdmission(context.Context, model.Resource, error) error
}

AdmissionFailureRecorder records a failed preparation as an observable resource error. The pool reconciler then uses its normal quarantine and replacement backoff path.

type AdmissionHandler added in v0.1.42

type AdmissionHandler struct {
	Store        store.Store
	Secrets      boxysecrets.Store
	Personalizer GuestAdmissionPersonalizer
	Failures     AdmissionFailureRecorder
	Packages     ResourcePackageApplier
}

AdmissionHandler applies the policy for resource.provisioned events.

func (*AdmissionHandler) Handle added in v0.1.42

type AdmissionPublisher added in v0.1.42

type AdmissionPublisher interface {
	PublishResourceProvisioned(context.Context, model.Resource) error
}

AdmissionPublisher records the durable event that starts resource preparation. Manager calls it again during reconciliation for pending resources, making the create/store/event sequence recoverable after a crash.

type AgentProvisioner

type AgentProvisioner struct {
	Registry  *AgentRegistry
	Specs     map[model.PoolName]boxyconfig.PoolSpec
	Providers map[string]providersdk.Instance
	Now       func() time.Time
	// GuestSecrets holds the resource-scoped credential produced during pool
	// admission. It is consumed and removed after allocation-time rotation.
	GuestSecrets  boxysecrets.Store
	PackageEngine *resourcepack.Engine
	// ArtifactRegistry resolves declarative source metadata. SourceSigners
	// sign or stage bytes in their owning store so source payloads bypass the
	// control plane and the agent transport.
	ArtifactRegistry artifact.Registry
	SourceSigners    map[string]artifact.SourceSigner
	SourceTTL        time.Duration
}

AgentProvisioner adapts agentsdk.Agent instances into the pool.Provisioner interface. It routes CRUD operations through an agent, which transparently dispatches to the appropriate driver (whether local or remote).

Provision resolves an agent by provider type (optionally pinned via spec.Agent) since it's creating a brand new resource. Destroy and Allocate operate on an *existing* resource and must instead route back to res.Provider.AgentID — the exact agent instance that created it — via Registry.Get. Once more than one agent can advertise the same provider type, re-resolving by type at Destroy/Allocate time could silently route to a different agent than the one that owns the resource; because providersdk.Driver.Delete is contractually idempotent for an already-missing resource, a misrouted Destroy would report success while the resource keeps running, unmanaged, on its real host. See docs/adr/0005-remote-agent-transport-and-registration.md.

func (*AgentProvisioner) Allocate

func (*AgentProvisioner) AllocateWithPackages added in v0.1.54

func (ap *AgentProvisioner) AllocateWithPackages(ctx context.Context, pool model.Pool, res model.Resource, packages []string) (providersdk.AllocationResult, error)

AllocateWithPackages preserves the existing allocation behavior and then applies the explicitly requested allocation-scoped packages. It is an optional sandbox allocator capability so callers without package requests retain the old path.

func (*AgentProvisioner) ApplyResourcePackages added in v0.1.54

func (ap *AgentProvisioner) ApplyResourcePackages(ctx context.Context, pool model.Pool, res model.Resource, event resourcepack.Event) ([]resourcepack.AppliedPackage, error)

ApplyResourcePackages applies resource-scoped packages for an admission or promotion event. The caller persists the returned applied records together with the resource state transition.

func (*AgentProvisioner) CompatibleWithPool added in v0.1.54

func (ap *AgentProvisioner) CompatibleWithPool(pool model.Pool, res model.Resource) bool

func (*AgentProvisioner) Destroy

func (ap *AgentProvisioner) Destroy(ctx context.Context, pool model.Pool, res model.Resource) error

func (*AgentProvisioner) Execute added in v0.1.54

func (ap *AgentProvisioner) Execute(ctx context.Context, target resourcepack.Target, operation resourcepack.Operation) error

Execute implements resourcepack.Executor. Package execution is translated to the existing provider-neutral ExecOperation and sent through the exact owning agent; package policy never crosses this boundary.

func (*AgentProvisioner) ExecuteSandbox added in v0.1.34

func (ap *AgentProvisioner) ExecuteSandbox(ctx context.Context, res model.Resource, operation providersdk.ExecOperation, sink eventstream.Sink) (*providersdk.Result, error)

ExecuteSandbox routes a provider-neutral command to the exact agent that owns a sandbox resource and requires that agent/provider to support live streaming.

func (*AgentProvisioner) ForceOrphan added in v0.1.32

func (ap *AgentProvisioner) ForceOrphan(ctx context.Context, res model.Resource) error

ForceOrphan detaches res from its (verified-gone) agent without any agent call. Refuses if the agent is still registered — see the precondition note on Manager.ForceOrphanResource.

func (*AgentProvisioner) PersonalizeGuestForPool added in v0.1.42

func (ap *AgentProvisioner) PersonalizeGuestForPool(ctx context.Context, pool model.Pool, res model.Resource) (*providersdk.GuestPersonalizationResult, error)

func (*AgentProvisioner) Provision

func (ap *AgentProvisioner) Provision(ctx context.Context, pool model.Pool) (model.Resource, error)

Provision implements pool.Provisioner. It's a thin wrapper around ProvisionLocked with no persist callback — Manager calls ProvisionLocked directly instead (see LockedProvisioner's doc comment) whenever it can, but Provision stays fully functional on its own for any caller (tests, or a future Provisioner-only consumer) that only knows the base interface.

func (*AgentProvisioner) ProvisionLocked added in v0.1.42

func (ap *AgentProvisioner) ProvisionLocked(ctx context.Context, pool model.Pool, persist func(*model.Resource) error) (model.Resource, bool, error)

ProvisionLocked implements pool.LockedProvisioner. See that interface's doc comment for the persist/created contract and why the lock must be acquired here rather than by Manager beforehand.

func (*AgentProvisioner) SupportsGuestPersonalization added in v0.1.42

func (ap *AgentProvisioner) SupportsGuestPersonalization(_ context.Context, pool model.Pool, res model.Resource) (bool, error)

PersonalizeGuestForPool runs only the guest-personalization capability for pool admission. It deliberately does not call generic Allocate, because the returned credential must be retained as the next bootstrap in the selected secret backend until the resource leaves the pool.

SupportsGuestPersonalization must be checked (and, if true, a secret backend confirmed) before calling this — see its doc comment.

type AgentRegistry

type AgentRegistry struct {
	// contains filtered or unexported fields
}

AgentRegistry tracks every agent currently available to the daemon — the embedded agent plus zero or more connected remote agents — and resolves which agent should serve a given provider type / pin.

Resolve is for choosing an agent for NEW provisioning only. Existing resources must route back to the exact agent that created them via Get, not be re-resolved by type — see AgentProvisioner's Destroy/Allocate and docs/adr/0005-remote-agent-transport-and-registration.md.

func NewAgentRegistry

func NewAgentRegistry() *AgentRegistry

NewAgentRegistry creates an empty registry.

func (*AgentRegistry) Availability added in v0.1.40

func (r *AgentRegistry) Availability(agentID string) (agentsdk.AvailabilitySnapshot, bool)

Availability returns agentID's most recently reported per-provider resource snapshot, if any. It delegates to the registered Agent itself via agentsdk.AvailabilityReportingAgent rather than duplicating storage here — a connected RemoteAgent already binds its snapshot to the connection's authenticated identity and stamps its own receipt time (see docs/adr/0005-remote-agent-transport-and-registration.md); the registry adds no state of its own on top of that.

false is returned when agentID isn't registered, when the registered agent doesn't implement AvailabilityReportingAgent (e.g. the embedded agent, which has no heartbeat to carry a snapshot on), or when no heartbeat carrying availability has arrived yet for a connected remote agent.

func (*AgentRegistry) Deregister

func (r *AgentRegistry) Deregister(agentID string)

Deregister removes an agent entirely. Used when an operator explicitly revokes an agent's identity, not on a transient disconnect (see SetAvailable for that case).

func (*AgentRegistry) Get

func (r *AgentRegistry) Get(agentID string) (agentsdk.Agent, bool)

Get looks up an agent by its exact ID, regardless of availability. Used for lifecycle operations (Destroy, Allocate) on a resource that must go back to the specific agent that created it.

func (*AgentRegistry) List

func (r *AgentRegistry) List() []AgentSummary

List returns a snapshot of every registered agent.

func (*AgentRegistry) LockProvisioning added in v0.1.42

func (r *AgentRegistry) LockProvisioning(agentID string) func()

LockProvisioning acquires the per-agent provisioning lock, blocking until it's available, and returns a release func that must be called exactly once. Implements pool.ProvisionLocker.

It closes a race exposed by devfactory implementing providersdk.ResourceLister (see #181's design spec, "Follow-ups"): a provision actuator calls driver.Create() and only writes the resulting resource to the store afterward, while ReconcileAgent's periodic sweep (#133/#174) treats any ID a driver's List() reports that the store doesn't yet know about as an orphan to adopt. Both call sites are independent goroutines (internal/cli/serve.go starts them separately) with no prior synchronization between them. A driver whose Create() writes its own internal state fast enough — devfactory, unlike docker or hyperv, can return in well under a millisecond — makes that window reliably hittable: the sweep can see a resource via List() before the store's own write for the same resource has landed, permanently misclassifying it as an unowned orphan. AgentProvisioner.ProvisionLocked (pool.LockedProvisioner) holds this lock from immediately before its Create call through its store write — not merely from after Create returns, which would leave the exact window above open for a fast driver, since List()-visibility happens inside Create itself, before any caller regains control to acquire anything. (Manager falls back to acquiring this lock only around its own store write, for a Provisioner that doesn't implement LockedProvisioner — a narrower guarantee, since such a provisioner has no per-agent concept for this lock to protect Create with.) ReconcileAgent's observer holds it across its combined List()-and-store-read snapshot for the same agent. Whichever side runs first, the other always sees a fully settled view — no polling, no fixed grace period.

func (*AgentRegistry) Register

func (r *AgentRegistry) Register(a agentsdk.Agent) error

Register adds or replaces an agent (keyed by its AgentInfo.ID) and marks it available. Replacing an existing ID (e.g. a remote agent reconnecting with a new stream) is intentional: it lets Get(agentID) transparently resolve to the new connection.

func (*AgentRegistry) Resolve

func (r *AgentRegistry) Resolve(provider providersdk.Type, pinnedAgentID string) (agentsdk.Agent, error)

Resolve picks an agent to serve a NEW provisioning request for the given provider type. If pinnedAgentID is non-empty, it must name a registered, currently-available agent that supports the provider type — pinning to the wrong, nonexistent, or unavailable agent is a fail-fast config/state error, never a silent fallback to a different agent. Otherwise, an available agent offering the provider type is selected by reported headroom when every eligible agent provides a provider-specific snapshot. If any eligible agent lacks that data, resolution falls back to round-robin rotation so older or partially upgraded agents remain usable.

func (*AgentRegistry) SetAvailable

func (r *AgentRegistry) SetAvailable(agentID string, available bool)

SetAvailable marks an agent as available or unavailable for NEW provisioning, without removing it from the registry. Used by the heartbeat-miss monitor: an unavailable agent's providers are skipped by Resolve, but resources already attributed to it (via Get) are untouched.

type AgentSummary

type AgentSummary struct {
	ID             string                                                `json:"id"`
	Name           string                                                `json:"name"`
	Providers      []providersdk.Type                                    `json:"providers"`
	Available      bool                                                  `json:"available"`
	Connected      bool                                                  `json:"connected"`
	LastSeen       *time.Time                                            `json:"last_seen,omitempty"`
	Availability   map[providersdk.Type]providersdk.ResourceAvailability `json:"availability,omitempty"`
	AvailabilityAt *time.Time                                            `json:"availability_at,omitempty"`
}

AgentSummary is a read-only snapshot of a registered agent, for `GET /api/v1/agents` and `boxy agent list`.

type CleanupError added in v0.1.59

type CleanupError struct {
	ID    model.ResourceID `json:"id"`
	Error string           `json:"error"`
}

type CleanupReport added in v0.1.59

type CleanupReport struct {
	DryRun         bool               `json:"dry_run"`
	Force          bool               `json:"force"`
	CandidateCount int                `json:"candidate_count"`
	CandidateIDs   []model.ResourceID `json:"candidate_ids"`
	CleanedIDs     []model.ResourceID `json:"cleaned_ids"`
	SkippedIDs     []CleanupSkipped   `json:"skipped_ids"`
	Errors         []CleanupError     `json:"errors"`
}

CleanupReport is intentionally composed of safe identifiers and status metadata. It never includes resource Properties or provider credentials.

type CleanupRequest added in v0.1.59

type CleanupRequest struct {
	Actor  string `json:"actor,omitempty"`
	DryRun bool   `json:"dry_run"`
	Force  bool   `json:"force"`
}

CleanupRequest selects a safe resource-maintenance operation.

type CleanupSkipped added in v0.1.59

type CleanupSkipped struct {
	ID     model.ResourceID `json:"id"`
	Reason string           `json:"reason"`
}

type Clock

type Clock interface {
	Now() time.Time
}

type ConfigDeclaredDrainError

type ConfigDeclaredDrainError struct {
	PoolName model.PoolName
}

func (*ConfigDeclaredDrainError) Error

func (e *ConfigDeclaredDrainError) Error() string

type DrainedPoolError

type DrainedPoolError struct {
	PoolName       model.PoolName
	RequestedReady int
}

func (*DrainedPoolError) Error

func (e *DrainedPoolError) Error() string

type DriverProvisioner deprecated

type DriverProvisioner struct {
	Registry  *providersdk.Registry
	Specs     map[model.PoolName]boxyconfig.PoolSpec
	Providers map[string]providersdk.Instance
	Now       func() time.Time
}

Deprecated: use AgentProvisioner. DriverProvisioner is kept for reference and backward compatibility, but all new code should use AgentProvisioner which routes through agentsdk.Agent instead of directly through the registry.

DriverProvisioner adapts providersdk.Driver instances into the pool.Provisioner interface. It dispatches to the correct driver based on each pool's provider configuration.

func (*DriverProvisioner) Allocate

func (*DriverProvisioner) Destroy

func (dp *DriverProvisioner) Destroy(ctx context.Context, pool model.Pool, res model.Resource) error

func (*DriverProvisioner) PersonalizeGuestForPool added in v0.1.42

func (dp *DriverProvisioner) PersonalizeGuestForPool(ctx context.Context, pool model.Pool, res model.Resource) (*providersdk.GuestPersonalizationResult, error)

PersonalizeGuestForPool runs only the guest-personalization capability for pool admission; allocation-time callers continue to use Allocate.

func (*DriverProvisioner) Provision

func (dp *DriverProvisioner) Provision(ctx context.Context, pool model.Pool) (model.Resource, error)

func (*DriverProvisioner) SupportsGuestPersonalization added in v0.1.42

func (dp *DriverProvisioner) SupportsGuestPersonalization(_ context.Context, pool model.Pool, _ model.Resource) (bool, error)

SupportsGuestPersonalization must be checked (and, if true, a secret backend confirmed) before calling PersonalizeGuestForPool — see its doc comment.

type EventPublisher added in v0.1.42

type EventPublisher struct {
	Events lifecycle.EventStore
	Now    func() time.Time
}

func (*EventPublisher) PublishResourceProvisioned added in v0.1.42

func (p *EventPublisher) PublishResourceProvisioned(ctx context.Context, res model.Resource) error

type ForceOrphaner added in v0.1.32

type ForceOrphaner interface {
	ForceOrphan(ctx context.Context, res model.Resource) error
}

ForceOrphaner is implemented by provisioners that support force-orphaning a resource whose owning agent is permanently gone. It never contacts the agent — that's the whole point.

type GuestAdmissionPersonalizer added in v0.1.42

type GuestAdmissionPersonalizer interface {
	// SupportsGuestPersonalization reports whether res's owning agent/driver
	// implements guest personalization at all, without performing any live
	// rotation. Admission must check this — and, if true, that a secret
	// backend is configured — before ever calling PersonalizeGuestForPool:
	// that call's side effect (rotating the guest's real credential) cannot
	// be undone if a secret backend then turns out to be missing to store
	// the result.
	SupportsGuestPersonalization(ctx context.Context, pool model.Pool, res model.Resource) (bool, error)
	PersonalizeGuestForPool(context.Context, model.Pool, model.Resource) (*providersdk.GuestPersonalizationResult, error)
}

GuestAdmissionPersonalizer is implemented by provider adapters that can rotate a newly created guest before it becomes ready inventory.

type InventoryRebuildReport

type InventoryRebuildReport struct {
	Changed bool
	Skipped []InventoryRebuildSkip
}

func RebuildReadyInventory

func RebuildReadyInventory(
	p model.Pool,
	resources []model.Resource,
	fallbackInventory []model.Resource,
) (model.Pool, InventoryRebuildReport, error)

RebuildReadyInventory rebuilds a pool's ready inventory from persisted resources while preserving old embedded inventory as a fallback for older state files.

type InventoryRebuildSkip

type InventoryRebuildSkip struct {
	ResourceID model.ResourceID
	Reason     string
}

type LockedProvisioner added in v0.1.42

type LockedProvisioner interface {
	ProvisionLocked(ctx context.Context, pool model.Pool, persist func(*model.Resource) error) (res model.Resource, created bool, err error)
}

LockedProvisioner is an optional Provisioner capability for implementations that route through something ProvisionLocker can serialize against a concurrent observer (see ProvisionLocker's doc comment on the ghost-orphan race this exists to close). Manager prefers ProvisionLocked over Provision when the configured Provisioner implements it.

Why this exists instead of Manager just locking around Provision itself: Manager doesn't know which agent (or other lock key) a given pool will resolve to until the provisioner resolves it — and for AgentProvisioner, that resolution is itself a stateful, non-repeatable operation (AgentRegistry.Resolve round-robins across every agent advertising a provider type, so resolving twice for one Provision call could select two different agents). Only the provisioner can correctly acquire a lock keyed to the exact same resolution its own Create call will use — so the lock must be acquired *inside* ProvisionLocked, not by Manager beforehand.

persist is called at most once, synchronously, while any internal lock ProvisionLocked acquires is still held: on a successful Create, or on a Create failure that produced a quarantined (OrphanedResourceError) resource. It is never called for a plain Create failure with no resource to persist. persist may mutate *res in place (e.g. Manager sets pool-inventory/admission-specific fields) before writing it — the (possibly mutated) resource is what ProvisionLocked returns. If persist returns an error, ProvisionLocked returns that error instead of any Create error, matching the pre-existing precedence where a failed quarantine write was reported over the Create failure it was recording.

created reports whether Create itself succeeded, independent of whether persist subsequently failed — Manager uses this (not err) to decide between recordProvisionSuccess and recordProvisionFailure, since a persist failure after a successful Create must not count against the pool's provisioning backoff the way a real Create failure does.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager reconciles a pool's inventory against its policies.

func New

func New(s store.Store, p Provisioner) *Manager

func (*Manager) DestroyResource

func (m *Manager) DestroyResource(ctx context.Context, res model.Resource) error

DestroyResource tears down a tracked resource through its origin pool's provider lifecycle and removes Boxy's resource record. Resources are single-use; this never returns resources to ready inventory.

func (*Manager) Drain

func (m *Manager) Drain(ctx context.Context, poolName model.PoolName) (model.Pool, error)

Drain persists an operator drain override and immediately destroys unused ready inventory.

func (*Manager) EnsureReady

func (m *Manager) EnsureReady(ctx context.Context, poolName model.PoolName, minReady int) error

EnsureReady ensures the pool has at least minReady resources available, without mutating the pool's configured preheat policy.

func (*Manager) FailAdmission added in v0.1.42

func (m *Manager) FailAdmission(ctx context.Context, res model.Resource, cause error) error

FailAdmission marks a resource whose pool-admission action cannot safely be retried in place. The next reconciliation pass quarantines and destroys it, while the existing capped provisioning backoff controls replacement.

func (*Manager) Fill

func (m *Manager) Fill(ctx context.Context, poolName model.PoolName) (model.Pool, error)

Fill clears an operator drain override and immediately reconciles configured capacity.

func (*Manager) ForceOrphanAgentResources added in v0.1.32

func (m *Manager) ForceOrphanAgentResources(ctx context.Context, agentID, reason string) (int, error)

ForceOrphanAgentResources force-orphans every resource currently attributed to agentID. Intended to run immediately after the agent has been deregistered (see internal/agentserver.Server.Revoke). Returns the count force-orphaned. A per-resource failure (e.g. a resource adopted by pool.ReconcileAgent with no OriginPool — see #133 — which ForceOrphanResource, like DestroyResource, cannot act on) does not abort the sweep: every other resource still gets a chance, since one problem resource must never block cleanup of every other resource this permanently-gone agent left behind. All per-resource errors are joined and returned alongside the count that did succeed, so the caller can log/report partial progress rather than losing it silently.

func (*Manager) ForceOrphanResource added in v0.1.32

func (m *Manager) ForceOrphanResource(ctx context.Context, res model.Resource, reason string) error

ForceOrphanResource detaches res from Boxy's bookkeeping (pool inventory + store) without ever contacting res's owning agent. Unlike DestroyResource, it never transitions the resource through a Destroying state and never calls m.provisioner.Destroy — the whole point is that the owning agent is permanently gone and cannot be reached. The provisioner must implement ForceOrphaner, which itself refuses unless the agent has already been deregistered (see AgentProvisioner.ForceOrphan) — callers should only reach this after an explicit `boxy agent revoke`.

func (*Manager) Reconcile

func (m *Manager) Reconcile(ctx context.Context, poolName model.PoolName) error

Reconcile performs one reconciliation pass for the pool.

func (*Manager) SetAdmissionPublisher added in v0.1.42

func (m *Manager) SetAdmissionPublisher(publisher AdmissionPublisher)

SetAdmissionPublisher enables asynchronous resource admission. It is optional so existing embedders and unit tests retain the synchronous provisioner behavior until they opt into the durable lifecycle queue.

func (*Manager) SetClock

func (m *Manager) SetClock(c Clock)

func (*Manager) SetGuestSecretStore added in v0.1.42

func (m *Manager) SetGuestSecretStore(secrets boxysecrets.Store)

SetGuestSecretStore enables best-effort cleanup of resource-scoped guest credentials when a resource is destroyed or force-orphaned before it is allocated. Allocation cleanup remains in AgentProvisioner because it owns the successful consumption point.

func (*Manager) SetPromoter added in v0.1.54

func (m *Manager) SetPromoter(promoter ResourcePromoter)

SetPromoter enables template-based resource promotion. A promoter that accepts the manager's pool locker gets the same per-pool serialization as normal reconciliation.

func (*Manager) SetProvisionLocker added in v0.1.42

func (m *Manager) SetProvisionLocker(locker ProvisionLocker)

SetProvisionLocker enables per-agent mutual exclusion between this Manager's provisioning and a concurrent ReconcileAgent sweep for the same agent (see ProvisionLocker). Optional: nil is a safe default for embedders and unit tests that don't run agent-backed reconciliation concurrently with provisioning.

type MaxTotalReachedError

type MaxTotalReachedError struct {
	PoolName       model.PoolName
	MaxTotal       int
	CurrentTotal   int
	ReadyCount     int
	RequestedReady int
}

func (*MaxTotalReachedError) Error

func (e *MaxTotalReachedError) Error() string

type PromotionService added in v0.1.54

type PromotionService struct {
	Store         store.Store
	Provisioner   Provisioner
	Compatibility ResourcePoolCompatibility
	Packages      ResourcePackageApplier
	Personalizer  GuestAdmissionPersonalizer
	Secrets       boxysecrets.Store

	// TemplateParents maps a template name to its single parent template.
	TemplateParents map[string]string
	Clock           Clock
	// contains filtered or unexported fields
}

PromotionService implements the deliberately small promotion seam. It chooses a ready surplus resource from a pool using the nearest template ancestor, applies the destination package delta, and commits ownership only after the destination is usable.

func (*PromotionService) Promote added in v0.1.54

func (p *PromotionService) Promote(ctx context.Context, destinationName model.PoolName, requestedReady int) error

Promote performs at most one promotion per reconciliation pass. Keeping the unit small limits lock duration and lets the normal reconciliation loop continue to enforce destination policy between moves.

func (*PromotionService) SetPoolLocker added in v0.1.54

func (p *PromotionService) SetPoolLocker(lock func(model.PoolName) func())

SetPoolLocker gives promotion the manager's existing per-pool lock. It is intentionally an unexported field so callers use Manager.SetPromoter.

type ProvisionLocker added in v0.1.42

type ProvisionLocker interface {
	LockProvisioning(agentID string) func()
}

ProvisionLocker serializes a driver's Create-then-store-write sequence against a concurrent reconciliation sweep's List()-then-store-read sequence for the same agent, closing a race where a resource visible through a driver's ResourceLister isn't yet visible through the store — see AgentRegistry.LockProvisioning's doc comment for the full mechanism and #181's design spec, "Follow-ups", for how it was found. AgentRegistry implements this; Manager and ReconcileAgent must share the same instance.

type Provisioner

type Provisioner interface {
	Provision(ctx context.Context, pool model.Pool) (model.Resource, error)
	Destroy(ctx context.Context, pool model.Pool, res model.Resource) error
}

Provisioner is the execution seam for the pool subsystem.

What:

It creates and destroys individual resources for a specific pool.

Why:

Pool.Manager should only enforce policy ("keep N Ready resources"), not know
how to talk to Docker/Hyper-V/etc. Provider-specific IO belongs behind this
seam (typically via provider drivers and, later, agents).

When:

Implement PoolProvisioner when wiring Boxy to real providers, or in tests.

How:

  • Provision should return a Resource that matches pool.Inventory.ExpectedType.
  • If the resource is immediately usable, set Resource.State=ResourceStateReady.
  • Destroy should be best-effort; on failure, Pool.Manager will surface the error.

type ResourceCleanupService added in v0.1.59

type ResourceCleanupService struct {
	Store   store.Store
	Manager *Manager
	Audit   diagnostics.AuditSink
	Now     func() time.Time
}

ResourceCleanupService is shared by maintenance callers. The CLI and UI reach it through the REST handler, while tests and daemon wiring can use it directly. Manager is required for forced cleanup because it owns the normal provider destroy/retry lifecycle.

func (*ResourceCleanupService) Purge added in v0.1.59

type ResourcePackageApplier added in v0.1.54

type ResourcePackageApplier interface {
	ApplyResourcePackages(context.Context, model.Pool, model.Resource, resourcepack.Event) ([]resourcepack.AppliedPackage, error)
}

ResourcePackageApplier applies packages at lifecycle boundaries after the resource has been admitted and any guest credential has been rotated.

type ResourcePoolCompatibility added in v0.1.54

type ResourcePoolCompatibility interface {
	CompatibleWithPool(pool model.Pool, resource model.Resource) bool
}

ResourcePoolCompatibility determines whether an existing resource can be operated on by the provider selected for a destination pool.

type ResourcePromoter added in v0.1.54

type ResourcePromoter interface {
	Promote(context.Context, model.PoolName, int) error
}

ResourcePromoter moves an eligible ready resource into a derived pool. The manager invokes it before the normal pool reconciliation pass so promotion can satisfy demand without changing the existing provisioning fallback.

type UnimplementedProvisioner

type UnimplementedProvisioner struct{}

UnimplementedProvisioner is a safe default that fails fast with a clear error. It is useful for early wiring so nil pointers don't masquerade as "not configured".

func (UnimplementedProvisioner) Destroy

func (UnimplementedProvisioner) Provision

Jump to

Keyboard shortcuts

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