feat: Extract Wild Central as standalone Go service
Extract the Central networking functionality from wild-cloud/api into a standalone service. Wild Central manages DNS (dnsmasq), gateway (HAProxy), firewall (nftables), VPN (WireGuard), TLS (certbot), security (CrowdSec), and DDNS — all the network appliance concerns. All Cloud-specific code (instances, clusters, nodes, apps, backups, operations, kubectl/talosctl tooling) has been removed. The API struct and route registration contain only Central endpoints. Tests updated to match the new API signature. Builds and all tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
196
internal/haproxy/config_test.go
Normal file
196
internal/haproxy/config_test.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package haproxy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewManager_DefaultPath(t *testing.T) {
|
||||
m := NewManager("")
|
||||
if m.GetConfigPath() != "/etc/haproxy/haproxy.cfg" {
|
||||
t.Errorf("got %q, want default path", m.GetConfigPath())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewManager_CustomPath(t *testing.T) {
|
||||
m := NewManager("/tmp/haproxy.cfg")
|
||||
if m.GetConfigPath() != "/tmp/haproxy.cfg" {
|
||||
t.Errorf("got %q, want /tmp/haproxy.cfg", m.GetConfigPath())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeName(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"payne-cloud", "payne_cloud"},
|
||||
{"cloud.payne.io", "cloud_payne_io"},
|
||||
{"civil_ssh", "civil_ssh"},
|
||||
{"test cloud 1", "test_cloud_1"},
|
||||
{"abc123", "abc123"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := sanitizeName(c.in); got != c.want {
|
||||
t.Errorf("sanitizeName(%q) = %q, want %q", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetListenPorts_BasePorts(t *testing.T) {
|
||||
m := NewManager("")
|
||||
ports := m.GetListenPorts(nil, nil)
|
||||
want := map[int]bool{80: true, 443: true, 8404: true}
|
||||
for _, p := range ports {
|
||||
delete(want, p)
|
||||
}
|
||||
if len(want) > 0 {
|
||||
t.Errorf("missing base ports: %v", want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetListenPorts_IncludesCustom(t *testing.T) {
|
||||
m := NewManager("")
|
||||
custom := []CustomRoute{{Name: "ssh", Port: 2222, Backend: "192.168.1.10:22"}}
|
||||
ports := m.GetListenPorts(nil, custom)
|
||||
found := false
|
||||
for _, p := range ports {
|
||||
if p == 2222 {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("custom port 2222 not in listen ports: %v", ports)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_AlwaysIncludesBoilerplate(t *testing.T) {
|
||||
m := NewManager("")
|
||||
out := m.Generate(nil, nil)
|
||||
|
||||
for _, want := range []string{
|
||||
"global\n",
|
||||
"defaults\n",
|
||||
"frontend stats\n",
|
||||
"frontend http_in\n",
|
||||
"redirect scheme https code 301",
|
||||
"bind *:8404",
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("expected %q in output, got:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_NoInstances_NoHTTPSFrontend(t *testing.T) {
|
||||
m := NewManager("")
|
||||
out := m.Generate(nil, nil)
|
||||
if strings.Contains(out, "frontend https_in") {
|
||||
t.Errorf("https_in frontend should be absent when no instances configured")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_WithInstance_SNIACLs(t *testing.T) {
|
||||
m := NewManager("")
|
||||
instances := []InstanceRoute{
|
||||
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20"},
|
||||
}
|
||||
out := m.Generate(instances, nil)
|
||||
|
||||
// Both ACL lines for OR-matching (subdomain and exact)
|
||||
if !strings.Contains(out, "req_ssl_sni -m end .cloud.payne.io") {
|
||||
t.Errorf("expected subdomain SNI ACL, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "req_ssl_sni -m str cloud.payne.io") {
|
||||
t.Errorf("expected exact SNI ACL, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "use_backend be_https_payne_cloud if is_payne_cloud") {
|
||||
t.Errorf("expected use_backend directive, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_WithInstance_Backend(t *testing.T) {
|
||||
m := NewManager("")
|
||||
instances := []InstanceRoute{
|
||||
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20"},
|
||||
}
|
||||
out := m.Generate(instances, nil)
|
||||
|
||||
if !strings.Contains(out, "backend be_https_payne_cloud") {
|
||||
t.Errorf("expected backend block, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "server s0 192.168.8.20:443 check") {
|
||||
t.Errorf("expected server line with LB IP, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_MultipleInstances(t *testing.T) {
|
||||
m := NewManager("")
|
||||
instances := []InstanceRoute{
|
||||
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20"},
|
||||
{Name: "test-cloud", Domain: "cloud2.payne.io", BackendIP: "192.168.8.30"},
|
||||
}
|
||||
out := m.Generate(instances, nil)
|
||||
|
||||
if !strings.Contains(out, "be_https_payne_cloud") {
|
||||
t.Errorf("expected payne-cloud backend, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "be_https_test_cloud") {
|
||||
t.Errorf("expected test-cloud backend, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "192.168.8.20:443") {
|
||||
t.Errorf("expected payne-cloud IP, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "192.168.8.30:443") {
|
||||
t.Errorf("expected test-cloud IP, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_WithExtraDomains(t *testing.T) {
|
||||
m := NewManager("")
|
||||
instances := []InstanceRoute{
|
||||
{
|
||||
Name: "payne-cloud",
|
||||
Domain: "cloud.payne.io",
|
||||
BackendIP: "192.168.8.20",
|
||||
ExtraDomains: []string{"payne.io", "mywildcloud.org"},
|
||||
},
|
||||
{Name: "test-cloud", Domain: "cloud2.payne.io", BackendIP: "192.168.8.30"},
|
||||
}
|
||||
out := m.Generate(instances, nil)
|
||||
|
||||
// Extra domains should add ACL entries under the same payne-cloud ACL name
|
||||
for _, domain := range []string{"payne.io", "mywildcloud.org"} {
|
||||
if !strings.Contains(out, "req_ssl_sni -m end ."+domain) {
|
||||
t.Errorf("expected subdomain ACL for %s, got:\n%s", domain, out)
|
||||
}
|
||||
if !strings.Contains(out, "req_ssl_sni -m str "+domain) {
|
||||
t.Errorf("expected exact ACL for %s, got:\n%s", domain, out)
|
||||
}
|
||||
}
|
||||
// Extra domains should route to payne-cloud, not test-cloud
|
||||
if !strings.Contains(out, "use_backend be_https_payne_cloud if is_payne_cloud") {
|
||||
t.Errorf("expected payne-cloud use_backend, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_CustomRoute(t *testing.T) {
|
||||
m := NewManager("")
|
||||
custom := []CustomRoute{
|
||||
{Name: "civil_ssh", Port: 2222, Backend: "192.168.8.10:22"},
|
||||
}
|
||||
out := m.Generate(nil, custom)
|
||||
|
||||
if !strings.Contains(out, "frontend fe_civil_ssh") {
|
||||
t.Errorf("expected custom frontend, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "bind *:2222") {
|
||||
t.Errorf("expected bind on port 2222, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "backend be_civil_ssh") {
|
||||
t.Errorf("expected custom backend, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "server s0 192.168.8.10:22") {
|
||||
t.Errorf("expected server line with backend address, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user