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>
62 lines
1.5 KiB
Go
62 lines
1.5 KiB
Go
package provider
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"k8s.io/apimachinery/pkg/types"
|
|
)
|
|
|
|
var nameRegexp = regexp.MustCompile(`^proxy-[a-z2-7]{16}$`)
|
|
|
|
func TestNameFromUID_idempotent(t *testing.T) {
|
|
t.Parallel()
|
|
uid := types.UID("f47ac10b-58cc-4372-a567-0e02b2c3d479")
|
|
got1 := NameFromUID(uid)
|
|
got2 := NameFromUID(uid)
|
|
if got1 != got2 {
|
|
t.Errorf("NameFromUID(%q) is not idempotent: %q != %q", uid, got1, got2)
|
|
}
|
|
}
|
|
|
|
func TestNameFromUID_charsetAndLength(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []types.UID{
|
|
"f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
|
"",
|
|
"a",
|
|
"00000000-0000-0000-0000-000000000000",
|
|
"ffffffff-ffff-ffff-ffff-ffffffffffff",
|
|
}
|
|
for _, uid := range tests {
|
|
t.Run(string(uid), func(t *testing.T) {
|
|
t.Parallel()
|
|
name := NameFromUID(uid)
|
|
if !nameRegexp.MatchString(name) {
|
|
t.Errorf("NameFromUID(%q) = %q, does not match %s", uid, name, nameRegexp)
|
|
}
|
|
if len(name) > 63 {
|
|
t.Errorf("NameFromUID(%q) = %q, length %d exceeds GCP's 63-char limit", uid, name, len(name))
|
|
}
|
|
if len(name) != len(namePrefix)+16 {
|
|
t.Errorf("NameFromUID(%q) = %q, length %d, want %d", uid, name, len(name), len(namePrefix)+16)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNameFromUID_distinctAcrossFleet(t *testing.T) {
|
|
t.Parallel()
|
|
const n = 10000
|
|
seen := make(map[string]types.UID, n)
|
|
for i := range n {
|
|
uid := types.UID(fmt.Sprintf("00000000-0000-0000-0000-%012d", i))
|
|
name := NameFromUID(uid)
|
|
if prior, ok := seen[name]; ok {
|
|
t.Fatalf("collision: NameFromUID(%q) == NameFromUID(%q) == %q", uid, prior, name)
|
|
}
|
|
seen[name] = uid
|
|
}
|
|
}
|