provider

package
v0.1.0-alpha.8 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

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

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

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

func NewHetzner(token, baseURL string) *Hetzner

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

func (h *Hetzner) Create(ctx context.Context, spec MachineSpec) (Machine, error)

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

func (h *Hetzner) DeleteFirewall(ctx context.Context, id int64) error

DeleteFirewall removes a firewall (idempotent). A firewall still applied to servers cannot be deleted; detach first with RemoveFirewallFromServers.

func (*Hetzner) DeleteNetwork

func (h *Hetzner) DeleteNetwork(ctx context.Context, id int64) error

DeleteNetwork removes a private network (idempotent).

func (*Hetzner) DeleteSSHKey

func (h *Hetzner) DeleteSSHKey(ctx context.Context, id int64) error

DeleteSSHKey removes an uploaded key (idempotent).

func (*Hetzner) Destroy

func (h *Hetzner) Destroy(ctx context.Context, providerID string) error

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

func (h *Hetzner) Get(ctx context.Context, providerID string) (Machine, error)

func (*Hetzner) List

func (h *Hetzner) List(ctx context.Context, labelSelector map[string]string) ([]Machine, error)

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

func (h *Hetzner) PriceEURPerHour(ctx context.Context, region, serverType string) (float64, error)

func (*Hetzner) RemoveFirewallFromServers

func (h *Hetzner) RemoveFirewallFromServers(ctx context.Context, firewallID int64, serverIDs []int64) error

RemoveFirewallFromServers detaches a firewall from servers (idempotent).

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.

type ServerTypeInfo

type ServerTypeInfo struct {
	Name           string
	Arch           string // normalized: "amd64" | "arm64"
	HourlyPriceEUR float64
	Cores          int
	MemoryGB       float64
}

ServerTypeInfo is a server type orderable in a region, normalized.

Jump to

Keyboard shortcuts

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