The old console kept the zone files and the live address-overrides.rpz in the same fancy-dns-zones ConfigMap; fancy-dns-rpz was only the static seed the overrides started from. Reading the live cluster showed the two had diverged (6 entries against 5, the missing one being stackship.k6n.net), so the default would have silently dropped a redirect. The default is now fancy-dns-zones, and the test puts a different stale seed in fancy-dns-rpz so reading the wrong one fails. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
150 lines
5.5 KiB
Go
150 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/dynamic"
|
|
dynfake "k8s.io/client-go/dynamic/fake"
|
|
|
|
"git.k8s-git-k6n.net/mats/fancy-dns/internal/kubesource"
|
|
)
|
|
|
|
const rpz = "$TTL 60\ntornberg.me A 10.10.3.10\nk6n.net A 10.10.3.10\n"
|
|
|
|
const zone = `$TTL 300
|
|
@ IN SOA ns1.home.example.com. hostmaster.home.example.com. 1 3600 600 604800 300
|
|
@ IN NS ns1.home.example.com.
|
|
ns1 IN A 10.0.0.53
|
|
nas IN A 10.0.0.20
|
|
`
|
|
|
|
func configMap(ns, name string, data map[string]any) *unstructured.Unstructured {
|
|
return &unstructured.Unstructured{Object: map[string]any{
|
|
"apiVersion": "v1", "kind": "ConfigMap",
|
|
"metadata": map[string]any{"name": name, "namespace": ns},
|
|
"data": data,
|
|
}}
|
|
}
|
|
|
|
func fakeCluster(objs ...runtime.Object) *dynfake.FakeDynamicClient {
|
|
return dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(),
|
|
map[schema.GroupVersionResource]string{
|
|
configMaps: "ConfigMapList",
|
|
kubesource.ZoneGVR: "DNSZoneList",
|
|
kubesource.GroupGVR: "ClientGroupList",
|
|
kubesource.ListGVR: "BlockListList",
|
|
kubesource.OverrideGVR: "AddressOverrideList",
|
|
kubesource.RuleGVR: "DomainRuleList",
|
|
}, objs...)
|
|
}
|
|
|
|
func useClient(t *testing.T, c dynamic.Interface) {
|
|
t.Helper()
|
|
old := newClient
|
|
newClient = func(kubesource.ClientOptions) (dynamic.Interface, error) { return c, nil }
|
|
t.Cleanup(func() { newClient = old })
|
|
}
|
|
|
|
// TestMigrateFromTheClusterThenApply reads the two ConfigMaps the old console
|
|
// wrote, prints manifests without touching anything, and then applies them.
|
|
func TestMigrateFromTheClusterThenApply(t *testing.T) {
|
|
c := fakeCluster(
|
|
// The old console kept the zone files and the live overrides in one
|
|
// ConfigMap; fancy-dns-rpz was only the seed it started from, and is
|
|
// deliberately different here so reading the wrong one fails.
|
|
configMap("dns", "fancy-dns-zones", map[string]any{"zone--home.example.com.zone": zone, "address-overrides.rpz": rpz, "unrelated": "x"}),
|
|
configMap("dns", "fancy-dns-rpz", map[string]any{"address-overrides.rpz": "$TTL 60\nstale-seed.example A 10.0.0.1\n"}),
|
|
)
|
|
useClient(t, c)
|
|
|
|
var out, errb bytes.Buffer
|
|
if err := migrateMain([]string{"-kube-namespace", "dns"}, &out, &errb); err != nil {
|
|
t.Fatalf("dry run: %v\n%s", err, errb.String())
|
|
}
|
|
for _, want := range []string{"kind: DNSZone", "kind: AddressOverride", "domain: tornberg.me", "zone: home.example.com"} {
|
|
if !strings.Contains(out.String(), want) {
|
|
t.Errorf("manifest lacks %q:\n%s", want, out.String())
|
|
}
|
|
}
|
|
if l, _ := c.Resource(kubesource.OverrideGVR).Namespace("dns").List(context.Background(), metav1.ListOptions{}); len(l.Items) != 0 {
|
|
t.Fatal("the dry run wrote objects")
|
|
}
|
|
|
|
out.Reset()
|
|
errb.Reset()
|
|
if err := migrateMain([]string{"-kube-namespace", "dns", "-apply"}, &out, &errb); err != nil {
|
|
t.Fatalf("apply: %v\n%s", err, errb.String())
|
|
}
|
|
l, _ := c.Resource(kubesource.OverrideGVR).Namespace("dns").List(context.Background(), metav1.ListOptions{})
|
|
if len(l.Items) != 2 {
|
|
t.Fatalf("%d overrides applied, want 2", len(l.Items))
|
|
}
|
|
for _, it := range l.Items {
|
|
if strings.Contains(it.GetName(), "stale-seed") {
|
|
t.Fatal("the stale seed ConfigMap was read instead of the live one")
|
|
}
|
|
}
|
|
if l, _ := c.Resource(kubesource.ZoneGVR).Namespace("dns").List(context.Background(), metav1.ListOptions{}); len(l.Items) != 1 {
|
|
t.Fatalf("%d zones applied, want 1", len(l.Items))
|
|
}
|
|
if !strings.Contains(errb.String(), "old ConfigMaps were not touched") {
|
|
t.Errorf("no reassurance in the report:\n%s", errb.String())
|
|
}
|
|
// The ConfigMaps are exactly as they were.
|
|
cm, err := c.Resource(configMaps).Namespace("dns").Get(context.Background(), "fancy-dns-zones", metav1.GetOptions{})
|
|
if err != nil || cm.Object["data"].(map[string]any)["address-overrides.rpz"] != rpz {
|
|
t.Fatalf("the source ConfigMap changed: %v %v", cm, err)
|
|
}
|
|
// Running it again changes nothing (create-or-update).
|
|
if err := migrateMain([]string{"-kube-namespace", "dns", "-apply"}, &out, &errb); err != nil {
|
|
t.Fatalf("second apply: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMigrateOfflineFromFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dir, "home.example.com.zone"), []byte(zone), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rpzPath := filepath.Join(t.TempDir(), "a.rpz")
|
|
if err := os.WriteFile(rpzPath, []byte(rpz), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// No cluster client is built when nothing is read from one.
|
|
old := newClient
|
|
newClient = func(kubesource.ClientOptions) (dynamic.Interface, error) {
|
|
t.Fatal("a cluster client was built for an offline run")
|
|
return nil, nil
|
|
}
|
|
defer func() { newClient = old }()
|
|
|
|
var out, errb bytes.Buffer
|
|
err := migrateMain([]string{"-kube-namespace", "dns", "-zones-dir", dir, "-rpz-file", rpzPath, "-zones-configmap", "", "-rpz-configmap", ""}, &out, &errb)
|
|
if err != nil {
|
|
t.Fatalf("%v\n%s", err, errb.String())
|
|
}
|
|
if !strings.Contains(out.String(), "kind: DNSZone") || !strings.Contains(out.String(), "kind: AddressOverride") {
|
|
t.Fatalf("manifest:\n%s", out.String())
|
|
}
|
|
}
|
|
|
|
func TestMigrateMissingConfigMapsIsNotAnError(t *testing.T) {
|
|
useClient(t, fakeCluster())
|
|
var out, errb bytes.Buffer
|
|
if err := migrateMain([]string{"-kube-namespace", "dns"}, &out, &errb); err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
if !strings.Contains(errb.String(), "does not exist") || !strings.Contains(errb.String(), "nothing to migrate") {
|
|
t.Fatalf("report:\n%s", errb.String())
|
|
}
|
|
}
|