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>
45 lines
1.8 KiB
Go
45 lines
1.8 KiB
Go
// Package registry assembles a set of named providers from a
|
|
// provider.Config by dispatching each entry's Type through a
|
|
// caller-supplied map of constructors.
|
|
//
|
|
// This package deliberately never imports internal/provider/mock or
|
|
// internal/provider/gcp. If it did, and internal/provider ever needed to
|
|
// import this package (e.g. to expose a default registry), that would be an
|
|
// import cycle: internal/provider/mock already imports internal/provider
|
|
// for the Provider interface. Keeping the type→constructor map external —
|
|
// supplied by the composition root in cmd/main.go — avoids the cycle
|
|
// entirely and keeps this package trivially testable with fake
|
|
// constructors.
|
|
package registry
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
|
|
)
|
|
|
|
// Constructor builds a provider.Provider from its config block.
|
|
type Constructor func(ctx context.Context, cfg provider.ProviderConfig) (provider.Provider, error)
|
|
|
|
// Build constructs every provider listed in cfg, dispatching on
|
|
// ProviderConfig.Type through builtins. Fails fast: an unknown type or a
|
|
// constructor error aborts the whole build rather than returning a
|
|
// partially-usable provider set that would misbehave once a caller reaches
|
|
// for the missing entry.
|
|
func Build(ctx context.Context, cfg *provider.Config, builtins map[string]Constructor) (map[string]provider.Provider, error) {
|
|
providers := make(map[string]provider.Provider, len(cfg.Providers))
|
|
for _, pc := range cfg.Providers {
|
|
ctor, ok := builtins[pc.Type]
|
|
if !ok {
|
|
return nil, fmt.Errorf("provider %q: unknown type %q", pc.Name, pc.Type)
|
|
}
|
|
p, err := ctor(ctx, pc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("provider %q: %w", pc.Name, err)
|
|
}
|
|
providers[pc.Name] = p
|
|
}
|
|
return providers, nil
|
|
}
|