Files
egress-proxies-operator/internal/provider/config.go
T
kacerrandClaude Opus 5.5 6e996ae2c7 proxmox: config block, IDs, marks, state mapping, classify, registration
Phase 2 of the proxmox provider plan. Adds the proxmox: config block with
its validation, registers the type in the config allow-list and the
constructor map, and starts internal/provider/proxmox: the api seam and
its data types, providerIDs (vms/<vmid>/<name>) with the UID-derived VMID,
the description mark, state mapping, and error classification.

Tests read the responses captured from pv1-1 in Phase 0 rather than
repeating Proxmox's wording, and every matcher is run against every
captured error so none fires on the wrong message. The contract methods
are Transient stubs until Phases 5 to 7.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 09:42:46 +00:00

146 lines
4.4 KiB
Go

package provider
import (
"fmt"
"os"
"sigs.k8s.io/yaml"
)
// Config is the top-level shape of the --providers-config file.
type Config struct {
Providers []ProviderConfig `json:"providers"`
}
// ProviderConfig is one named, typed provider instance — e.g. "gcp-eu" and
// "gcp-us" can be two ProviderConfigs of Type "gcp" with different GCP
// blocks. Exactly one of the type-specific blocks below should be set,
// matching Type.
type ProviderConfig struct {
Name string `json:"name"`
Type string `json:"type"`
Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"`
GCP *GCPConfig `json:"gcp,omitempty"`
KVM *KVMConfig `json:"kvm,omitempty"`
// KVM2 is the same block shape as KVM, for the kvm2 provider type: one
// or more libvirt/KVM hypervisors reached over SSH.
KVM2 *KVMConfig `json:"kvm2,omitempty"`
// Proxmox is a Proxmox VE cluster reached through its REST API.
Proxmox *ProxmoxConfig `json:"proxmox,omitempty"`
}
// setBlocks names the type-specific blocks that are non-nil, so validate can
// reject a block that doesn't match Type without a pairwise check per type.
func (p ProviderConfig) setBlocks() []string {
var out []string
if p.Kubernetes != nil {
out = append(out, "kubernetes")
}
if p.GCP != nil {
out = append(out, "gcp")
}
if p.KVM != nil {
out = append(out, "kvm")
}
if p.KVM2 != nil {
out = append(out, "kvm2")
}
if p.Proxmox != nil {
out = append(out, "proxmox")
}
return out
}
// KubernetesConfig configures the kubernetes-pod provider, which creates
// proxy Pods in the same cluster the operator itself runs in.
type KubernetesConfig struct {
// Image is the proxy container image. Default "ubuntu/squid:6.6-24.04_edge".
Image string `json:"image,omitempty"`
}
// GCPConfig configures a named GCP provider instance.
type GCPConfig struct {
// Project is the GCP project ID. Required.
Project string `json:"project"`
// Network is the VPC network name. Default "default".
Network string `json:"network,omitempty"`
// NetworkTag is the firewall/network tag applied to created instances.
// Default "proxy-operator".
NetworkTag string `json:"networkTag,omitempty"`
// DiskSizeGB is the boot disk size in GB. Default 10.
DiskSizeGB int64 `json:"diskSizeGb,omitempty"`
}
// LoadConfigFile reads and parses a providers-config file from disk.
func LoadConfigFile(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading providers config %s: %w", path, err)
}
return LoadConfig(data)
}
// LoadConfig parses and validates providers-config YAML. It fails fast:
// unknown provider types, duplicate names, and missing type-specific
// required fields are all load-time errors here, not runtime surprises
// discovered only when a provider is actually used.
func LoadConfig(data []byte) (*Config, error) {
var cfg Config
if err := yaml.UnmarshalStrict(data, &cfg); err != nil {
return nil, fmt.Errorf("parsing providers config: %w", err)
}
if err := cfg.validate(); err != nil {
return nil, fmt.Errorf("validating providers config: %w", err)
}
return &cfg, nil
}
func (c *Config) validate() error {
if len(c.Providers) == 0 {
return fmt.Errorf("providers: at least one provider must be configured")
}
seen := make(map[string]bool, len(c.Providers))
for i, p := range c.Providers {
if p.Name == "" {
return fmt.Errorf("providers[%d]: name is required", i)
}
if seen[p.Name] {
return fmt.Errorf("providers[%d]: duplicate provider name %q", i, p.Name)
}
seen[p.Name] = true
prefix := fmt.Sprintf("providers[%d] %q", i, p.Name)
switch p.Type {
case "kubernetes", "gcp", "kvm", "kvm2", "proxmox":
case "":
return fmt.Errorf("%s: type is required", prefix)
default:
return fmt.Errorf("%s: unknown provider type %q", prefix, p.Type)
}
for _, block := range p.setBlocks() {
if block != p.Type {
return fmt.Errorf("%s: type is %s but a %s block is set", prefix, p.Type, block)
}
}
switch p.Type {
case "gcp":
if p.GCP == nil || p.GCP.Project == "" {
return fmt.Errorf("%s: gcp.project is required", prefix)
}
case "kvm":
if err := validateKVM(prefix, p.KVM); err != nil {
return err
}
case "kvm2":
if err := validateKVMBlock(prefix, "kvm2", p.KVM2); err != nil {
return err
}
case "proxmox":
if err := validateProxmox(prefix, p.Proxmox); err != nil {
return err
}
}
}
return nil
}