Documentation
¶
Overview ¶
Package provider is a production cloud-infrastructure client: it spins, inspects, and destroys VMs on a real provider (Hetzner today; others later).
It has two consumers by design. Today the real-cluster test harness (test/cloud) imports it. In Phase 8 the node autoscaler (roadmap T-82/T-84, internal/daemon/provision) imports the SAME package — the Driver interface below is the frozen, provider-agnostic lifecycle that autoscaling consumes (Create/Destroy/Get/List/PriceEURPerHour, normalized statuses, idempotent Destroy). Living under internal/cloud rather than test/ is deliberate: it is reusable production code, not a test-only prototype, so there is no future move and no risk of a divergent second copy.
The concrete *Hetzner type also carries provider-client operations the autoscaler may use but the Driver interface deliberately omits (SSH keys, firewalls, private networks) — keeping the interface minimal and provider-agnostic. Genuinely test-only orchestration (NAT simulation via a jump host, fault injection, debug bundles, the keep-on-fail reaper schedule) stays in test/cloud and never leaks here.
Index ¶
- Constants
- Variables
- func ReapOlderThan(ctx context.Context, d Driver, selector map[string]string, createdLabel string, ...) (destroyed []string, err error)
- type Driver
- type FirewallRule
- type Hetzner
- func (h *Hetzner) AddRouteToNetwork(ctx context.Context, networkID int64, destination, gatewayPrivateIP string) error
- func (h *Hetzner) ApplyFirewallToServers(ctx context.Context, firewallID int64, serverIDs []int64) error
- func (h *Hetzner) AttachServerToNetwork(ctx context.Context, providerID string, networkID int64) error
- func (h *Hetzner) AvailableServerTypes(ctx context.Context, region string) ([]ServerTypeInfo, error)
- func (h *Hetzner) Create(ctx context.Context, spec MachineSpec) (Machine, error)
- func (h *Hetzner) CreateFirewall(ctx context.Context, name string, rules []FirewallRule, ...) (int64, error)
- func (h *Hetzner) CreateNetwork(ctx context.Context, name, ipRange, zone string, labels map[string]string) (int64, error)
- func (h *Hetzner) DeleteFirewall(ctx context.Context, id int64) error
- func (h *Hetzner) DeleteNetwork(ctx context.Context, id int64) error
- func (h *Hetzner) DeleteSSHKey(ctx context.Context, id int64) error
- func (h *Hetzner) Destroy(ctx context.Context, providerID string) error
- func (h *Hetzner) EnsureSSHKey(ctx context.Context, name, publicKey string, labels map[string]string) (int64, error)
- func (h *Hetzner) Get(ctx context.Context, providerID string) (Machine, error)
- func (h *Hetzner) List(ctx context.Context, labelSelector map[string]string) ([]Machine, error)
- func (h *Hetzner) ListFirewalls(ctx context.Context, labelSelector map[string]string) ([]int64, error)
- func (h *Hetzner) ListNetworks(ctx context.Context, labelSelector map[string]string) ([]int64, error)
- func (h *Hetzner) ListSSHKeys(ctx context.Context, labelSelector map[string]string) ([]int64, error)
- func (h *Hetzner) PriceEURPerHour(ctx context.Context, region, serverType string) (float64, error)
- func (h *Hetzner) RemoveFirewallFromServers(ctx context.Context, firewallID int64, serverIDs []int64) error
- type Machine
- type MachineSpec
- type ServerTypeInfo
Constants ¶
const ( StatusCreating = "creating" StatusRunning = "running" StatusDeleting = "deleting" StatusUnknown = "unknown" )
Machine statuses, normalized across providers by the driver (never by callers). Mirrors the Phase 8 contract.
Variables ¶
var ErrMachineNotFound = errors.New("provider: machine not found")
ErrMachineNotFound is returned by Get for an absent machine and lets Destroy treat "already gone" as success. Matches the planned provision.ErrMachineNotFound.
Functions ¶
func ReapOlderThan ¶
func ReapOlderThan(ctx context.Context, d Driver, selector map[string]string, createdLabel string, maxAge time.Duration, now time.Time) (destroyed []string, err error)
ReapOlderThan destroys every machine matching selector whose CreatedAt (or, missing that, the createdLabel value parsed as a unix timestamp) is older than maxAge. It is the cost safety net for keep-on-failure runs: a forgotten cluster self-destructs on the next harness start or CI cleanup. Returns the provider IDs it destroyed. Best-effort: a single Destroy failure is collected and reported but does not stop the sweep.
Types ¶
type Driver ¶
type Driver interface {
Create(ctx context.Context, spec MachineSpec) (Machine, error)
// Destroy is idempotent: an absent machine is success (nil error).
Destroy(ctx context.Context, providerID string) error
// Get returns ErrMachineNotFound when the machine is gone.
Get(ctx context.Context, providerID string) (Machine, error)
// List returns machines matching every label in the selector (AND).
List(ctx context.Context, labelSelector map[string]string) ([]Machine, error)
// PriceEURPerHour is the budget rail; 0 with nil error = unknown.
PriceEURPerHour(ctx context.Context, region, serverType string) (float64, error)
}
Driver is the minimal, provider-agnostic machine lifecycle. It is the exact shape Phase 8's autoscaler will consume; keep it that way.
type FirewallRule ¶
type FirewallRule struct {
Direction string // "in" | "out"
Protocol string // "tcp" | "udp" | "icmp"
Port string // "8443" or "80-443"; empty for icmp
SourceIPs []string // CIDRs; default ["0.0.0.0/0","::/0"] for "in"
DestIPs []string // CIDRs; default ["0.0.0.0/0","::/0"] for "out"
}
FirewallRule is one inbound/outbound rule.
type Hetzner ¶
type Hetzner struct {
// contains filtered or unexported fields
}
Hetzner is a raw-REST Hetzner Cloud driver (no SDK — the surface is a handful of endpoints, matching roadmap T-83's mandate). It implements Driver plus the test-only extras the harness needs (SSH keys, firewalls, private networks).
func NewHetzner ¶
NewHetzner builds a driver from an API token. baseURL defaults to the real API; pass a non-empty override (e.g. an httptest server) in tests.
func (*Hetzner) AddRouteToNetwork ¶
func (h *Hetzner) AddRouteToNetwork(ctx context.Context, networkID int64, destination, gatewayPrivateIP string) error
AddRouteToNetwork installs a route in a private network (e.g. default route 0.0.0.0/0 via the NAT-gateway node's private IP), so no-public-IP nodes can egress through the gateway.
func (*Hetzner) ApplyFirewallToServers ¶
func (h *Hetzner) ApplyFirewallToServers(ctx context.Context, firewallID int64, serverIDs []int64) error
ApplyFirewallToServers attaches a firewall to servers (idempotent).
func (*Hetzner) AttachServerToNetwork ¶
func (h *Hetzner) AttachServerToNetwork(ctx context.Context, providerID string, networkID int64) error
AttachServerToNetwork attaches an existing server to a private network (idempotent: an already-attached server returns a conflict we swallow).
func (*Hetzner) AvailableServerTypes ¶
func (h *Hetzner) AvailableServerTypes(ctx context.Context, region string) ([]ServerTypeInfo, error)
AvailableServerTypes returns the NON-deprecated server types actually ORDERABLE in region, normalized. Orderability is the datacenters endpoint's server_types.available set — NOT the price list (a type can carry a price for a location it is not currently orderable in). The harness uses this to auto-pick a valid type per arch instead of hardcoding names that get deprecated or run out of capacity in a location.
func (*Hetzner) CreateFirewall ¶
func (h *Hetzner) CreateFirewall(ctx context.Context, name string, rules []FirewallRule, labels map[string]string, applyToServerIDs []int64) (int64, error)
CreateFirewall creates a firewall with the given rules, optionally applied to server IDs at creation. Returns the firewall ID.
func (*Hetzner) CreateNetwork ¶
func (h *Hetzner) CreateNetwork(ctx context.Context, name, ipRange, zone string, labels map[string]string) (int64, error)
CreateNetwork creates a private network with one cloud subnet (for NAT-node egress). Returns the network ID.
func (*Hetzner) DeleteFirewall ¶
DeleteFirewall removes a firewall (idempotent). A firewall still applied to servers cannot be deleted; detach first with RemoveFirewallFromServers.
func (*Hetzner) DeleteNetwork ¶
DeleteNetwork removes a private network (idempotent).
func (*Hetzner) DeleteSSHKey ¶
DeleteSSHKey removes an uploaded key (idempotent).
func (*Hetzner) EnsureSSHKey ¶
func (h *Hetzner) EnsureSSHKey(ctx context.Context, name, publicKey string, labels map[string]string) (int64, error)
EnsureSSHKey uploads a public key under name (idempotent by name) and returns its provider ID. Hetzner rejects a duplicate name/fingerprint with 409/422; on conflict we look the existing key up and reuse it.
func (*Hetzner) ListFirewalls ¶
func (h *Hetzner) ListFirewalls(ctx context.Context, labelSelector map[string]string) ([]int64, error)
ListFirewalls returns firewall IDs matching the label selector.
func (*Hetzner) ListNetworks ¶
func (h *Hetzner) ListNetworks(ctx context.Context, labelSelector map[string]string) ([]int64, error)
ListNetworks returns private-network IDs matching the label selector.
func (*Hetzner) ListSSHKeys ¶
func (h *Hetzner) ListSSHKeys(ctx context.Context, labelSelector map[string]string) ([]int64, error)
ListSSHKeys returns uploaded SSH key IDs matching the label selector.
func (*Hetzner) PriceEURPerHour ¶
type Machine ¶
type Machine struct {
// --- frozen T-82 core ---
ProviderID string
Name string
Status string // one of the Status* constants
PublicIPv4 string
HourlyPriceEUR float64
Labels map[string]string
// --- harness extensions ---
Arch string // normalized GOARCH: "amd64" | "arm64"
PublicIPv6 string // Hetzner hands out a /64; this is its base address
PrivateIPv4 string // first private-network IP, when attached
CreatedAt time.Time // provider-reported creation time
}
Machine is the provider-agnostic view of a created VM. Core fields match the planned production Machine; PublicIPv6/PrivateIPv4/CreatedAt are harness extras.
type MachineSpec ¶
type MachineSpec struct {
// --- frozen T-82 core ---
Name string // RFC-1123: lowercase, digits, '-', ≤63
Region string // provider location, e.g. "nbg1"
ServerType string // provider size, e.g. "cx22" (amd64) / "cax11" (arm64)
CloudInit string // user-data (cloud-init); ≤32KiB on Hetzner
Labels map[string]string // provider-side labels for List()
// --- harness extensions (production driver may drop) ---
Image string // OS image, e.g. "debian-12", "ubuntu-24.04"; default "debian-12"
SSHKeyIDs []int64
EnableIPv4 *bool // nil = true; false = no public IPv4 (NAT-node simulation)
EnableIPv6 *bool // nil = true
NetworkIDs []int64 // private networks to attach at create (NAT egress)
}
MachineSpec describes a VM to create.
The first five fields are the FROZEN T-82 core (do not rename — they are the promotion contract). The remaining fields are harness extensions the production driver may ignore or drop: T-83 hardcodes image=debian-12 and IPv4-only, but the harness needs mixed OS/arch and no-public-IP nodes to simulate NAT.