Replace InstanceConfig with DNSEntry for dnsmasq config generation
InstanceConfig was a ~50-field struct designed for Wild Cloud k8s instances,
but only 3 fields were ever read by dnsmasq (the sole consumer). The
reconciliation bridge constructed fake InstanceConfig objects from registered
domains just to satisfy this interface.
Replace with DNSEntry{Domain, IP} — a 2-field struct purpose-built for
dnsmasq. All domains get local=/ directives to prevent AAAA queries from
leaking to upstream DNS (Happy Eyeballs / RFC 8305 latency on LAN).
Removed dead code: InstanceConfig, NodeConfig, LoadCloudConfig,
SaveCloudConfig, DeepMerge, LoadMergedInstanceConfig, EnsureInstanceConfig,
instance path helpers, instanceLoadBalancerIP, GenerateInstanceConfig,
and all modular per-instance dnsmasq methods.
This commit is contained in:
@@ -8,141 +8,42 @@ import (
|
||||
"github.com/wild-cloud/wild-central/internal/config"
|
||||
)
|
||||
|
||||
func instanceWith(domain, internalDomain, lbIP string) config.InstanceConfig {
|
||||
var inst config.InstanceConfig
|
||||
inst.Cluster.Name = "test"
|
||||
inst.Cloud.Domain = domain
|
||||
inst.Cloud.InternalDomain = internalDomain
|
||||
inst.Cluster.LoadBalancerIp = lbIP
|
||||
return inst
|
||||
func entry(domain, ip string) DNSEntry {
|
||||
return DNSEntry{Domain: domain, IP: ip}
|
||||
}
|
||||
|
||||
// Test: instanceLoadBalancerIP prefers cluster.loadBalancerIp
|
||||
func TestInstanceLoadBalancerIP_ClusterField(t *testing.T) {
|
||||
inst := instanceWith("cloud.example.com", "internal.example.com", "10.0.0.5")
|
||||
if got := instanceLoadBalancerIP(inst); got != "10.0.0.5" {
|
||||
t.Errorf("got %q, want %q", got, "10.0.0.5")
|
||||
}
|
||||
}
|
||||
|
||||
// Test: instanceLoadBalancerIP falls back to apps.metallb.loadBalancerIp
|
||||
func TestInstanceLoadBalancerIP_MetalLBFallback(t *testing.T) {
|
||||
var inst config.InstanceConfig
|
||||
inst.Apps = map[string]any{
|
||||
"metallb": map[string]any{
|
||||
"loadBalancerIp": "10.0.0.9",
|
||||
},
|
||||
}
|
||||
if got := instanceLoadBalancerIP(inst); got != "10.0.0.9" {
|
||||
t.Errorf("got %q, want %q", got, "10.0.0.9")
|
||||
}
|
||||
}
|
||||
|
||||
// Test: instanceLoadBalancerIP returns empty string when nothing is set
|
||||
func TestInstanceLoadBalancerIP_Empty(t *testing.T) {
|
||||
var inst config.InstanceConfig
|
||||
if got := instanceLoadBalancerIP(inst); got != "" {
|
||||
t.Errorf("got %q, want empty string", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: cluster.loadBalancerIp takes precedence over metallb
|
||||
func TestInstanceLoadBalancerIP_ClusterTakesPrecedence(t *testing.T) {
|
||||
inst := instanceWith("cloud.example.com", "internal.example.com", "10.0.0.5")
|
||||
inst.Apps = map[string]any{
|
||||
"metallb": map[string]any{
|
||||
"loadBalancerIp": "10.0.0.9",
|
||||
},
|
||||
}
|
||||
if got := instanceLoadBalancerIP(inst); got != "10.0.0.5" {
|
||||
t.Errorf("got %q, want cluster IP %q", got, "10.0.0.5")
|
||||
}
|
||||
}
|
||||
|
||||
// Test: GenerateInstanceConfig with load balancer IP produces active DNS entries
|
||||
func TestGenerateInstanceConfig_WithLBIP(t *testing.T) {
|
||||
// Test: Generate with entries produces expected DNS entries
|
||||
func TestGenerate_WithEntries(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
inst := instanceWith("cloud.example.com", "internal.example.com", "192.168.1.10")
|
||||
|
||||
out := g.GenerateInstanceConfig(inst)
|
||||
|
||||
if !strings.Contains(out, "local=/internal.example.com/") {
|
||||
t.Errorf("expected local=/ directive for internal domain, got:\n%s", out)
|
||||
entries := []DNSEntry{
|
||||
entry("cloud.example.com", "192.168.1.10"),
|
||||
entry("internal.example.com", "192.168.1.10"),
|
||||
}
|
||||
if !strings.Contains(out, "address=/internal.example.com/192.168.1.10") {
|
||||
t.Errorf("expected address entry for internal domain, got:\n%s", out)
|
||||
|
||||
out := g.Generate(nil, entries)
|
||||
|
||||
if !strings.Contains(out, "local=/cloud.example.com/") {
|
||||
t.Errorf("expected local=/ for domain, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "address=/cloud.example.com/192.168.1.10") {
|
||||
t.Errorf("expected address entry for external domain, got:\n%s", out)
|
||||
t.Errorf("expected address entry for domain, got:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "WARNING") {
|
||||
t.Errorf("unexpected WARNING in output:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: GenerateInstanceConfig without load balancer IP produces commented entries
|
||||
func TestGenerateInstanceConfig_NoLBIP(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
inst := instanceWith("cloud.example.com", "internal.example.com", "")
|
||||
|
||||
out := g.GenerateInstanceConfig(inst)
|
||||
|
||||
if !strings.Contains(out, "WARNING") {
|
||||
t.Errorf("expected WARNING in output when no LB IP, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "# local=/internal.example.com/") {
|
||||
t.Errorf("expected commented local=/ directive, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "# address=/internal.example.com/<load-balancer-ip>") {
|
||||
t.Errorf("expected commented address entry for internal domain, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "# address=/cloud.example.com/<load-balancer-ip>") {
|
||||
t.Errorf("expected commented address entry for external domain, got:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "\nlocal=/") {
|
||||
t.Errorf("unexpected active local=/ directive when no LB IP, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: GenerateInstanceConfig includes instance name in header comment
|
||||
func TestGenerateInstanceConfig_IncludesInstanceName(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
inst := instanceWith("cloud.example.com", "internal.example.com", "192.168.1.10")
|
||||
inst.Cluster.Name = "my-instance"
|
||||
|
||||
out := g.GenerateInstanceConfig(inst)
|
||||
|
||||
if !strings.Contains(out, "my-instance") {
|
||||
t.Errorf("expected instance name in config header, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Generate (monolithic) with instances produces expected DNS entries
|
||||
func TestGenerate_WithInstances(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
inst := instanceWith("cloud.example.com", "internal.example.com", "192.168.1.10")
|
||||
|
||||
out := g.Generate(nil, []config.InstanceConfig{inst})
|
||||
|
||||
if !strings.Contains(out, "local=/internal.example.com/") {
|
||||
t.Errorf("expected local=/ for internal domain, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "address=/internal.example.com/192.168.1.10") {
|
||||
t.Errorf("expected address entry for internal domain, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "address=/cloud.example.com/192.168.1.10") {
|
||||
t.Errorf("expected address entry for external domain, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "server=1.1.1.1") {
|
||||
t.Errorf("expected upstream DNS server, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Generate with no instances still produces valid config skeleton
|
||||
func TestGenerate_NoInstances(t *testing.T) {
|
||||
// Test: Generate with no entries still produces valid config skeleton
|
||||
func TestGenerate_NoEntries(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
|
||||
out := g.Generate(nil, []config.InstanceConfig{})
|
||||
out := g.Generate(nil, []DNSEntry{})
|
||||
|
||||
if !strings.Contains(out, "bind-interfaces") {
|
||||
t.Errorf("expected bind-interfaces in config, got:\n%s", out)
|
||||
@@ -155,31 +56,23 @@ func TestGenerate_NoInstances(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Generate with instance missing LB IP produces commented entries
|
||||
func TestGenerate_InstanceWithoutLBIP(t *testing.T) {
|
||||
// Test: Generate skips entries with empty IP
|
||||
func TestGenerate_EntryWithoutIP(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
inst := instanceWith("cloud.example.com", "internal.example.com", "")
|
||||
inst.Cluster.Name = "no-lb-instance"
|
||||
|
||||
out := g.Generate(nil, []config.InstanceConfig{inst})
|
||||
out := g.Generate(nil, []DNSEntry{entry("cloud.example.com", "")})
|
||||
|
||||
if !strings.Contains(out, "# No load balancer IP configured for instance no-lb-instance") {
|
||||
t.Errorf("expected comment about missing LB IP, got:\n%s", out)
|
||||
if strings.Contains(out, "address=/") {
|
||||
t.Errorf("expected no address= entries for empty IP, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: GenerateMainConfig produces conf-dir directive
|
||||
// Test: GenerateMainConfig produces valid base config
|
||||
func TestGenerateMainConfig(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
|
||||
out := g.GenerateMainConfig(nil)
|
||||
|
||||
if !strings.Contains(out, "conf-dir=") {
|
||||
t.Errorf("expected conf-dir directive in main config, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, instanceConfigDir) {
|
||||
t.Errorf("expected instance config dir %q in conf-dir, got:\n%s", instanceConfigDir, out)
|
||||
}
|
||||
if !strings.Contains(out, "bind-interfaces") {
|
||||
t.Errorf("expected bind-interfaces in main config, got:\n%s", out)
|
||||
}
|
||||
@@ -228,7 +121,6 @@ func TestGenerateMainConfig_DHCPNoGateway(t *testing.T) {
|
||||
cfg.Cloud.Dnsmasq.DHCP.Enabled = true
|
||||
cfg.Cloud.Dnsmasq.DHCP.RangeStart = "192.168.8.100"
|
||||
cfg.Cloud.Dnsmasq.DHCP.RangeEnd = "192.168.8.200"
|
||||
// No gateway set
|
||||
|
||||
out := g.GenerateMainConfig(cfg)
|
||||
|
||||
@@ -282,7 +174,6 @@ func TestGenerateMainConfig_DHCPLeaseTimeDefault(t *testing.T) {
|
||||
cfg.Cloud.Dnsmasq.DHCP.Enabled = true
|
||||
cfg.Cloud.Dnsmasq.DHCP.RangeStart = "192.168.8.100"
|
||||
cfg.Cloud.Dnsmasq.DHCP.RangeEnd = "192.168.8.200"
|
||||
// LeaseTime not set
|
||||
|
||||
out := g.GenerateMainConfig(cfg)
|
||||
|
||||
@@ -307,181 +198,117 @@ func TestNewConfigGenerator_DefaultPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Service registration — domain set but no internal domain
|
||||
func TestGenerate_ServiceWithoutInternalDomain(t *testing.T) {
|
||||
// Test: Domain-only entry (no internal domain pair)
|
||||
func TestGenerate_DomainOnly(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
globalCfg := &config.State{}
|
||||
|
||||
// Service registration: only domain + backend IP, no internal domain
|
||||
inst := instanceWith("my-api.payne.io", "", "192.168.8.151")
|
||||
out := g.Generate(globalCfg, []config.InstanceConfig{inst})
|
||||
out := g.Generate(globalCfg, []DNSEntry{entry("my-api.payne.io", "192.168.8.151")})
|
||||
|
||||
// Should have address entry for the domain
|
||||
if !strings.Contains(out, "address=/my-api.payne.io/192.168.8.151") {
|
||||
t.Errorf("expected address entry for service domain, got:\n%s", out)
|
||||
t.Errorf("expected address entry for domain, got:\n%s", out)
|
||||
}
|
||||
// Must NOT have empty local=// entry
|
||||
if strings.Contains(out, "local=//") {
|
||||
t.Errorf("unexpected empty local=// in output:\n%s", out)
|
||||
}
|
||||
// Must NOT have empty address=// entry
|
||||
if strings.Contains(out, "address=//") {
|
||||
t.Errorf("unexpected empty address=// in output:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: GenerateInstanceConfig — service without internal domain
|
||||
func TestGenerateInstanceConfig_ServiceWithoutInternalDomain(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
inst := instanceWith("central.payne.io", "", "192.168.8.151")
|
||||
|
||||
out := g.GenerateInstanceConfig(inst)
|
||||
|
||||
if !strings.Contains(out, "address=/central.payne.io/192.168.8.151") {
|
||||
t.Errorf("expected address entry, got:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "local=//") {
|
||||
t.Errorf("unexpected empty local=// in output:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "address=//") {
|
||||
t.Errorf("unexpected empty address=// in output:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: reach:internal → local=/ directive present (prevents upstream DNS forwarding)
|
||||
func TestGenerate_ReachInternal_HasLocal(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
globalCfg := &config.State{}
|
||||
|
||||
// Simulate reconciliation for an internal-reach service:
|
||||
// InternalDomain is set (reach:internal), Domain is empty (reach:public)
|
||||
inst := instanceWith("", "my-api.payne.io", "192.168.8.151")
|
||||
|
||||
out := g.Generate(globalCfg, []config.InstanceConfig{inst})
|
||||
|
||||
if !strings.Contains(out, "local=/my-api.payne.io/") {
|
||||
t.Errorf("reach:internal must produce local=/ directive, got:\n%s", out)
|
||||
t.Errorf("expected local=/ entry for domain, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "address=/my-api.payne.io/192.168.8.151") {
|
||||
t.Errorf("reach:internal must produce address entry, got:\n%s", out)
|
||||
// Must NOT have empty entries
|
||||
if strings.Contains(out, "local=//") {
|
||||
t.Errorf("unexpected empty local=// in output:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "address=//") {
|
||||
t.Errorf("unexpected empty address=// in output:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: reach:public → no local=/ directive (allows upstream DNS forwarding)
|
||||
func TestGenerate_ReachPublic_NoLocal(t *testing.T) {
|
||||
// Test: All domains get local=/ to prevent AAAA leaking upstream
|
||||
func TestGenerate_AllDomainsGetLocal(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
globalCfg := &config.State{}
|
||||
|
||||
// Simulate reconciliation for a public-reach service:
|
||||
// Domain is set (reach:public), InternalDomain is empty
|
||||
inst := instanceWith("cloud.payne.io", "", "192.168.8.240")
|
||||
// Both "internal" and "public" domains should get local=/ —
|
||||
// prevents AAAA queries from leaking to upstream DNS (Happy Eyeballs).
|
||||
entries := []DNSEntry{
|
||||
entry("internal-api.payne.io", "192.168.8.151"),
|
||||
entry("cloud.payne.io", "192.168.8.240"),
|
||||
}
|
||||
|
||||
out := g.Generate(globalCfg, []config.InstanceConfig{inst})
|
||||
out := g.Generate(globalCfg, entries)
|
||||
|
||||
if !strings.Contains(out, "local=/internal-api.payne.io/") {
|
||||
t.Errorf("expected local=/ for internal domain, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "local=/cloud.payne.io/") {
|
||||
t.Errorf("expected local=/ for public domain (prevents AAAA leaking), got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Mix of domains each get their own entries
|
||||
func TestGenerate_MixedDomains(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
globalCfg := &config.State{}
|
||||
|
||||
entries := []DNSEntry{
|
||||
entry("cloud.payne.io", "192.168.8.240"), // k8s passthrough
|
||||
entry("central.payne.io", "192.168.8.151"), // Central HTTP
|
||||
entry("wild-cloud.payne.io", "192.168.8.151"), // service HTTP
|
||||
}
|
||||
|
||||
out := g.Generate(globalCfg, entries)
|
||||
|
||||
if !strings.Contains(out, "address=/cloud.payne.io/192.168.8.240") {
|
||||
t.Errorf("reach:public must produce address entry, got:\n%s", out)
|
||||
t.Errorf("expected address for cloud domain, got:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "local=/cloud.payne.io/") {
|
||||
t.Errorf("reach:public must NOT produce local=/ directive, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: InternalDomain field → local=/ entry for LAN-only resolution
|
||||
func TestGenerate_InternalDomainOnly_HasLocal(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
globalCfg := &config.State{}
|
||||
|
||||
// Instance with only an internal domain (no external domain)
|
||||
inst := instanceWith("", "internal.cloud.payne.io", "192.168.8.240")
|
||||
|
||||
out := g.Generate(globalCfg, []config.InstanceConfig{inst})
|
||||
|
||||
if !strings.Contains(out, "local=/internal.cloud.payne.io/") {
|
||||
t.Errorf("InternalDomain must produce local=/ entry, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "address=/internal.cloud.payne.io/192.168.8.240") {
|
||||
t.Errorf("InternalDomain must produce address entry, got:\n%s", out)
|
||||
}
|
||||
// No external domain → no address for empty domain
|
||||
if strings.Contains(out, "address=//") {
|
||||
t.Errorf("no external domain must not produce empty address=// entry, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Mix of instances with and without internal domains
|
||||
func TestGenerate_MixedServicesAndInstances(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
globalCfg := &config.State{}
|
||||
|
||||
instances := []config.InstanceConfig{
|
||||
instanceWith("cloud.payne.io", "internal.cloud.payne.io", "192.168.8.240"), // k8s instance
|
||||
instanceWith("central.payne.io", "", "192.168.8.151"), // service (no internal domain)
|
||||
instanceWith("wild-cloud.payne.io", "", "192.168.8.151"), // service (no internal domain)
|
||||
}
|
||||
|
||||
out := g.Generate(globalCfg, instances)
|
||||
|
||||
// k8s instance should have both local= and address= for internal domain
|
||||
if !strings.Contains(out, "local=/internal.cloud.payne.io/") {
|
||||
t.Errorf("expected local=/ for k8s internal domain, got:\n%s", out)
|
||||
}
|
||||
// Services should have address= entries
|
||||
if !strings.Contains(out, "address=/central.payne.io/192.168.8.151") {
|
||||
t.Errorf("expected address for central, got:\n%s", out)
|
||||
}
|
||||
// No broken empty entries
|
||||
if !strings.Contains(out, "address=/wild-cloud.payne.io/192.168.8.151") {
|
||||
t.Errorf("expected address for wild-cloud, got:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "local=//") {
|
||||
t.Errorf("unexpected empty local=// in output:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: empty instances list produces base config with no address= entries
|
||||
func TestGenerate_EmptyInstances(t *testing.T) {
|
||||
// Test: empty entries list produces base config with no address= entries
|
||||
func TestGenerate_EmptyEntries(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
globalCfg := &config.State{}
|
||||
|
||||
out := g.Generate(globalCfg, []config.InstanceConfig{})
|
||||
out := g.Generate(globalCfg, []DNSEntry{})
|
||||
|
||||
// Should have base config skeleton
|
||||
if !strings.Contains(out, "bind-interfaces") {
|
||||
t.Errorf("expected bind-interfaces in config, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "server=1.1.1.1") {
|
||||
t.Errorf("expected upstream DNS server, got:\n%s", out)
|
||||
}
|
||||
// Should not have any address= entries
|
||||
if strings.Contains(out, "address=/") {
|
||||
t.Errorf("expected no address= entries with empty instances, got:\n%s", out)
|
||||
t.Errorf("expected no address= entries with empty entries, got:\n%s", out)
|
||||
}
|
||||
// Should not have any local=/ entries
|
||||
if strings.Contains(out, "local=/") {
|
||||
t.Errorf("expected no local=/ entries with empty instances, got:\n%s", out)
|
||||
t.Errorf("expected no local=/ entries with empty entries, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: multiple instances each get their own entries
|
||||
func TestGenerate_MultipleInstances(t *testing.T) {
|
||||
// Test: multiple entries each get their own DNS records
|
||||
func TestGenerate_MultipleEntries(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
globalCfg := &config.State{}
|
||||
|
||||
instances := []config.InstanceConfig{
|
||||
instanceWith("cloud1.example.com", "internal1.example.com", "10.0.0.1"),
|
||||
instanceWith("cloud2.example.com", "internal2.example.com", "10.0.0.2"),
|
||||
instanceWith("cloud3.example.com", "internal3.example.com", "10.0.0.3"),
|
||||
entries := []DNSEntry{
|
||||
entry("cloud1.example.com", "10.0.0.1"),
|
||||
entry("cloud2.example.com", "10.0.0.2"),
|
||||
entry("cloud3.example.com", "10.0.0.3"),
|
||||
}
|
||||
|
||||
out := g.Generate(globalCfg, instances)
|
||||
out := g.Generate(globalCfg, entries)
|
||||
|
||||
for i, inst := range instances {
|
||||
ip := []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"}[i]
|
||||
if !strings.Contains(out, fmt.Sprintf("address=/%s/%s", inst.Cloud.Domain, ip)) {
|
||||
t.Errorf("expected address entry for %s, got:\n%s", inst.Cloud.Domain, out)
|
||||
for _, e := range entries {
|
||||
if !strings.Contains(out, fmt.Sprintf("address=/%s/%s", e.Domain, e.IP)) {
|
||||
t.Errorf("expected address entry for %s, got:\n%s", e.Domain, out)
|
||||
}
|
||||
if !strings.Contains(out, fmt.Sprintf("address=/%s/%s", inst.Cloud.InternalDomain, ip)) {
|
||||
t.Errorf("expected address entry for %s, got:\n%s", inst.Cloud.InternalDomain, out)
|
||||
}
|
||||
if !strings.Contains(out, fmt.Sprintf("local=/%s/", inst.Cloud.InternalDomain)) {
|
||||
t.Errorf("expected local=/ entry for %s, got:\n%s", inst.Cloud.InternalDomain, out)
|
||||
if !strings.Contains(out, fmt.Sprintf("local=/%s/", e.Domain)) {
|
||||
t.Errorf("expected local=/ entry for %s, got:\n%s", e.Domain, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -490,8 +317,7 @@ func TestGenerate_MultipleInstances(t *testing.T) {
|
||||
func TestGenerate_NilConfig(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
|
||||
// Should not panic with nil config
|
||||
out := g.Generate(nil, []config.InstanceConfig{})
|
||||
out := g.Generate(nil, []DNSEntry{})
|
||||
|
||||
if !strings.Contains(out, "bind-interfaces") {
|
||||
t.Errorf("expected valid config with nil cfg, got:\n%s", out)
|
||||
@@ -507,32 +333,31 @@ func TestGenerate_ConfiguredDnsmasqIP(t *testing.T) {
|
||||
cfg := &config.State{}
|
||||
cfg.Cloud.Dnsmasq.IP = "192.168.8.100"
|
||||
|
||||
out := g.Generate(cfg, []config.InstanceConfig{})
|
||||
out := g.Generate(cfg, []DNSEntry{})
|
||||
|
||||
if !strings.Contains(out, "listen-address=192.168.8.100") {
|
||||
t.Errorf("expected configured dnsmasq IP in listen-address, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: verify no address=// or local=// entries appear in any test output
|
||||
// Test: verify no address=// or local=// entries appear
|
||||
func TestGenerate_NoLeakedEmptyEntries(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
cfg := &config.State{}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
instances []config.InstanceConfig
|
||||
name string
|
||||
entries []DNSEntry
|
||||
}{
|
||||
{"empty", nil},
|
||||
{"domain_only", []config.InstanceConfig{instanceWith("example.com", "", "10.0.0.1")}},
|
||||
{"internal_only", []config.InstanceConfig{instanceWith("", "internal.example.com", "10.0.0.1")}},
|
||||
{"both", []config.InstanceConfig{instanceWith("example.com", "internal.example.com", "10.0.0.1")}},
|
||||
{"no_lb_ip", []config.InstanceConfig{instanceWith("example.com", "internal.example.com", "")}},
|
||||
{"domain_only", []DNSEntry{entry("example.com", "10.0.0.1")}},
|
||||
{"no_ip", []DNSEntry{entry("example.com", "")}},
|
||||
{"no_domain", []DNSEntry{entry("", "10.0.0.1")}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
out := g.Generate(cfg, tc.instances)
|
||||
out := g.Generate(cfg, tc.entries)
|
||||
if strings.Contains(out, "address=//") {
|
||||
t.Errorf("leaked empty address=// in output:\n%s", out)
|
||||
}
|
||||
@@ -542,23 +367,3 @@ func TestGenerate_NoLeakedEmptyEntries(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test: GenerateInstanceConfig with all fields empty doesn't produce broken entries
|
||||
func TestGenerateInstanceConfig_EmptyFields(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
var inst config.InstanceConfig
|
||||
// All fields empty: no domain, no internal domain, no LB IP
|
||||
|
||||
out := g.GenerateInstanceConfig(inst)
|
||||
|
||||
if strings.Contains(out, "address=//") {
|
||||
t.Errorf("empty fields produced broken address=// entry:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "local=//") {
|
||||
t.Errorf("empty fields produced broken local=// entry:\n%s", out)
|
||||
}
|
||||
// Should still contain the header comment
|
||||
if !strings.Contains(out, "DNS configuration for instance") {
|
||||
t.Errorf("expected header comment in output, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user