The Provider interface (Create/Get/Delete/ListByTag), Instance, and CreateRequest that every cloud backend implements — kept independent of api/v1alpha1 so this package has no CRD-type coupling. Error taxonomy (ErrNotFound/ErrQuotaExceeded/ErrTransient/ErrPermanent) wrapped via a multi-error Unwrap() []error, so errors.Is and errors.As both work off the same value: the reconciler branches on classification, logs keep the underlying SDK error. Unclassified errors default to ErrTransient — retrying is always safer than latching Failed. Deterministic instance naming (SHA-256 -> base32 -> 16 chars, 22 total with the "proxy-" prefix) satisfying GCP's RFC1035 name rules with headroom, and idempotency-tested across 10k UIDs with zero collisions. --providers-config YAML parsing (config.go) with fail-fast validation: unknown type, duplicate name, missing gcp.project, mismatched type/config-block, and strict-mode rejection of unknown keys. internal/provider/registry/registry.go takes its type->constructor map as a parameter rather than hardcoding it, so the package has zero import on internal/provider/mock or internal/provider/gcp (neither exists yet — mock is Step 3, gcp is Step 8) and compiles today. Explicit wiring moves to the composition root in cmd/main.go (Step 10). Deferred internal/provider/metrics.go (the WithMetrics decorator) to Step 9, where the Prometheus vectors it needs actually get built — nothing in this step depends on it. internal/provider at 96.2% coverage, internal/provider/registry at 100%. make test green. Co-Authored-By: Claude <noreply@anthropic.com>
110 lines
4.1 KiB
Go
110 lines
4.1 KiB
Go
// 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)
|
|
}
|