// Package provider defines the contract every cloud provider backend // implements: Create/Get/Delete/ListByTag for a single VM, keyed by an // opaque providerID. Concrete implementations live in subpackages (mock, // gcp); this package has no dependency on any of them, so a type→ // constructor registry can be assembled at the composition root (cmd/main.go) // without an import cycle. package provider import ( "context" "time" ) // InstanceState is a provider's lifecycle state for a single VM. type InstanceState string const ( StateProvisioning InstanceState = "Provisioning" StateRunning InstanceState = "Running" StateStopped InstanceState = "Stopped" StateTerminated InstanceState = "Terminated" ) // GC contract: every cloud resource a provider creates must carry these two // labels/tags. Orphan GC (internal/gc) relies on both — Managed to find // resources it owns at all, UID to decide whether a resource is still // claimed by a live Proxy CR. const ( LabelManaged = "proxy-operator-managed" LabelManagedYes = "true" LabelUID = "proxy-operator-uid" ) // Instance is a provider's view of a single VM. type Instance struct { // ID is the opaque providerID, stable for the life of the VM. ID string // IP is the VM's current address, empty until it's assigned one. IP string // State is the VM's current lifecycle state. State InstanceState // UID is the LabelUID value read back off the resource — the owning // Proxy CR's UID, or "" if the resource predates this label (shouldn't // happen for anything this operator created, but Get/ListByTag callers // must tolerate it rather than panic). UID string // CreatedAt is when the provider created the resource. Orphan GC uses // this to skip young instances that may still be mid-create, avoiding a // race with an in-flight Create whose status write hasn't landed yet. CreatedAt time.Time } // Placement is the provider-opaque placement/size configuration a Create // call needs. It deliberately does not import api/v1alpha1 — this package // stays independent of the CRD types; the reconciler maps // v1alpha1.PlacementSpec to this struct when calling Create. Providers may // ignore fields that don't apply to them. type Placement struct { Region string Zone string MachineType string Image string } // CreateRequest carries everything a provider needs to create a VM. type CreateRequest struct { // Name is the deterministic instance name, already derived from the // owning CR's UID via NameFromUID. Create must be idempotent keyed on // this name: a repeat call after a crash must find the existing // instance rather than create a duplicate. Name string // UID is the owning Proxy CR's UID. Create must tag/label the created // resource with LabelUID=UID and LabelManaged=LabelManagedYes. UID string Namespace string ProxyName string Placement Placement // CloudInit is the already-resolved user-data (a Secret reference, if // used, has already been read by the caller). CloudInit string Port int32 } // Provider is the contract every cloud backend implements. Kept // deliberately minimal: this is the same interface five future providers // must satisfy. type Provider interface { // Create starts VM creation and returns as soon as the request is // submitted — it does not block until the VM is running. Must be // idempotent by req.Name, so a repeat call after a crash finds the // existing VM instead of duplicating it. Create(ctx context.Context, req CreateRequest) (providerID string, err error) // Get returns the current state of a previously created instance. // Returning (nil, ErrNotFound) is a normal, expected outcome — it // drives the reconciler's replacement and adoption logic, not an // exceptional condition. Get(ctx context.Context, providerID string) (*Instance, error) // Delete is idempotent: deleting an instance that no longer exists is // not an error. Delete(ctx context.Context, providerID string) error // ListByTag returns every instance this operator has ever tagged with // LabelManaged=LabelManagedYes, for orphan GC. ListByTag(ctx context.Context) ([]Instance, error) }