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"` } // 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 switch p.Type { case "kubernetes": if p.GCP != nil { return fmt.Errorf("providers[%d] %q: type is kubernetes but a gcp block is set", i, p.Name) } case "gcp": if p.Kubernetes != nil { return fmt.Errorf("providers[%d] %q: type is gcp but a kubernetes block is set", i, p.Name) } if p.GCP == nil || p.GCP.Project == "" { return fmt.Errorf("providers[%d] %q: gcp.project is required", i, p.Name) } case "": return fmt.Errorf("providers[%d] %q: type is required", i, p.Name) default: return fmt.Errorf("providers[%d] %q: unknown provider type %q", i, p.Name, p.Type) } } return nil }