Support routes.
This commit is contained in:
@@ -7,9 +7,12 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/wild-cloud/wild-central/internal/services"
|
||||
)
|
||||
|
||||
// InstanceRoute represents a Wild Cloud instance's ingress routing configuration
|
||||
@@ -28,13 +31,23 @@ type CustomRoute struct {
|
||||
Backend string // e.g. "192.168.8.10:22"
|
||||
}
|
||||
|
||||
// HTTPRoute represents an L7 HTTP reverse proxy route (for Wild Works services).
|
||||
// HTTPRouteBackend maps path prefixes to a specific backend with its own L7 options.
|
||||
type HTTPRouteBackend struct {
|
||||
Paths []string // path prefixes (empty = catch-all)
|
||||
Backend string // host:port
|
||||
HealthPath string // optional health check path
|
||||
Headers *services.HeaderConfig // per-route headers
|
||||
IPAllow []string // per-route CIDR whitelist
|
||||
}
|
||||
|
||||
// HTTPRoute represents an L7 HTTP reverse proxy route.
|
||||
// Central terminates TLS using a wildcard certificate and proxies by Host header.
|
||||
// Routes are evaluated in order — path-specific before catch-all.
|
||||
type HTTPRoute struct {
|
||||
Name string // e.g. "my-api"
|
||||
Domain string // e.g. "my-api.payne.io"
|
||||
Backend string // e.g. "192.168.8.60:9001"
|
||||
HealthPath string // e.g. "/health" (optional, for active health checks)
|
||||
Name string // e.g. "my-api"
|
||||
Domain string // e.g. "my-api.payne.io"
|
||||
Subdomains bool // if true, also match *.domain
|
||||
Routes []HTTPRouteBackend // ordered path→backend mappings
|
||||
}
|
||||
|
||||
const defaultConfigPath = "/etc/haproxy/haproxy.cfg"
|
||||
@@ -134,14 +147,28 @@ frontend http_in
|
||||
// 3. L4 wildcard matches (*.cloud.payne.io) → instance backends
|
||||
// 4. Default → L7 termination (if any HTTP routes exist)
|
||||
|
||||
// 1. L7 HTTP services — exact match, route to L7 TLS termination frontend
|
||||
// 1. L7 HTTP services — route to L7 TLS termination frontend
|
||||
if len(opts.HTTPRoutes) > 0 {
|
||||
sb.WriteString(" # L7 HTTP services (exact match → TLS termination)\n")
|
||||
sb.WriteString(" # L7 HTTP services (→ TLS termination)\n")
|
||||
// Exact matches first
|
||||
for _, route := range opts.HTTPRoutes {
|
||||
if route.Subdomains {
|
||||
continue // handled below
|
||||
}
|
||||
aclName := "is_l7_" + sanitizeName(route.Name)
|
||||
fmt.Fprintf(&sb, " acl %s req_ssl_sni -m str %s\n", aclName, route.Domain)
|
||||
fmt.Fprintf(&sb, " use_backend be_l7_termination if %s\n", aclName)
|
||||
}
|
||||
// Wildcard matches after exact
|
||||
for _, route := range opts.HTTPRoutes {
|
||||
if !route.Subdomains {
|
||||
continue
|
||||
}
|
||||
aclName := "is_l7_" + sanitizeName(route.Name)
|
||||
fmt.Fprintf(&sb, " acl %s req_ssl_sni -m end .%s\n", aclName, route.Domain)
|
||||
fmt.Fprintf(&sb, " acl %s req_ssl_sni -m str %s\n", aclName, route.Domain)
|
||||
fmt.Fprintf(&sb, " use_backend be_l7_termination if %s\n", aclName)
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
@@ -209,24 +236,99 @@ frontend http_in
|
||||
sb.WriteString(" mode http\n")
|
||||
sb.WriteString("\n")
|
||||
|
||||
for _, route := range opts.HTTPRoutes {
|
||||
aclName := "host_" + sanitizeName(route.Name)
|
||||
fmt.Fprintf(&sb, " acl %s hdr(host) -i %s\n", aclName, route.Domain)
|
||||
fmt.Fprintf(&sb, " use_backend be_l7_%s if %s\n", sanitizeName(route.Name), aclName)
|
||||
// Host ACLs for all services
|
||||
for _, httpRoute := range opts.HTTPRoutes {
|
||||
hostACL := "host_" + sanitizeName(httpRoute.Name)
|
||||
if httpRoute.Subdomains {
|
||||
fmt.Fprintf(&sb, " acl %s hdr(host) -i -m end .%s\n", hostACL, httpRoute.Domain)
|
||||
fmt.Fprintf(&sb, " acl %s hdr(host) -i -m str %s\n", hostACL, httpRoute.Domain)
|
||||
} else {
|
||||
fmt.Fprintf(&sb, " acl %s hdr(host) -i %s\n", hostACL, httpRoute.Domain)
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
// L7 backends
|
||||
for _, route := range opts.HTTPRoutes {
|
||||
fmt.Fprintf(&sb, "backend be_l7_%s\n", sanitizeName(route.Name))
|
||||
sb.WriteString(" mode http\n")
|
||||
if route.HealthPath != "" {
|
||||
fmt.Fprintf(&sb, " option httpchk GET %s\n", route.HealthPath)
|
||||
fmt.Fprintf(&sb, " server s0 %s check\n", route.Backend)
|
||||
} else {
|
||||
fmt.Fprintf(&sb, " server s0 %s\n", route.Backend)
|
||||
// Per-route ACLs (IP whitelisting, headers) and routing rules
|
||||
for _, httpRoute := range opts.HTTPRoutes {
|
||||
hostACL := "host_" + sanitizeName(httpRoute.Name)
|
||||
routeName := sanitizeName(httpRoute.Name)
|
||||
|
||||
for i, rb := range httpRoute.Routes {
|
||||
aclSuffix := fmt.Sprintf("%s_%d", routeName, i)
|
||||
|
||||
// IP whitelisting for this route
|
||||
if len(rb.IPAllow) > 0 {
|
||||
ipACL := "ip_" + aclSuffix
|
||||
fmt.Fprintf(&sb, " acl %s src %s\n", ipACL, strings.Join(rb.IPAllow, " "))
|
||||
if len(rb.Paths) > 0 {
|
||||
pathACL := "path_" + aclSuffix
|
||||
fmt.Fprintf(&sb, " acl %s path_beg %s\n", pathACL, strings.Join(rb.Paths, " "))
|
||||
fmt.Fprintf(&sb, " http-request deny if %s %s !%s\n", hostACL, pathACL, ipACL)
|
||||
} else {
|
||||
fmt.Fprintf(&sb, " http-request deny if %s !%s\n", hostACL, ipACL)
|
||||
}
|
||||
}
|
||||
|
||||
// Request headers
|
||||
if rb.Headers != nil {
|
||||
for _, k := range sortedKeys(rb.Headers.Request) {
|
||||
fmt.Fprintf(&sb, " http-request set-header %s %q if %s\n", k, rb.Headers.Request[k], hostACL)
|
||||
}
|
||||
}
|
||||
|
||||
// Response headers
|
||||
if rb.Headers != nil {
|
||||
for _, k := range sortedKeys(rb.Headers.Response) {
|
||||
fmt.Fprintf(&sb, " http-response set-header %s %q if %s\n", k, rb.Headers.Response[k], hostACL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString("\n")
|
||||
|
||||
// Routing rules: path-specific first, then catch-all
|
||||
for _, httpRoute := range opts.HTTPRoutes {
|
||||
hostACL := "host_" + sanitizeName(httpRoute.Name)
|
||||
routeName := sanitizeName(httpRoute.Name)
|
||||
|
||||
// Path-specific routes first (most specific wins)
|
||||
for i, rb := range httpRoute.Routes {
|
||||
if len(rb.Paths) == 0 {
|
||||
continue
|
||||
}
|
||||
backendName := fmt.Sprintf("be_l7_%s_%d", routeName, i)
|
||||
pathACL := fmt.Sprintf("path_%s_%d", routeName, i)
|
||||
fmt.Fprintf(&sb, " acl %s path_beg %s\n", pathACL, strings.Join(rb.Paths, " "))
|
||||
fmt.Fprintf(&sb, " use_backend %s if %s %s\n", backendName, hostACL, pathACL)
|
||||
}
|
||||
|
||||
// Catch-all route last
|
||||
for i, rb := range httpRoute.Routes {
|
||||
if len(rb.Paths) > 0 {
|
||||
continue
|
||||
}
|
||||
backendName := fmt.Sprintf("be_l7_%s_%d", routeName, i)
|
||||
fmt.Fprintf(&sb, " use_backend %s if %s\n", backendName, hostACL)
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
// L7 backends — one per route
|
||||
for _, httpRoute := range opts.HTTPRoutes {
|
||||
routeName := sanitizeName(httpRoute.Name)
|
||||
for i, rb := range httpRoute.Routes {
|
||||
backendName := fmt.Sprintf("be_l7_%s_%d", routeName, i)
|
||||
fmt.Fprintf(&sb, "backend %s\n", backendName)
|
||||
sb.WriteString(" mode http\n")
|
||||
if rb.HealthPath != "" {
|
||||
fmt.Fprintf(&sb, " option httpchk GET %s\n", rb.HealthPath)
|
||||
fmt.Fprintf(&sb, " server s0 %s check\n", rb.Backend)
|
||||
} else {
|
||||
fmt.Fprintf(&sb, " server s0 %s\n", rb.Backend)
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,6 +349,16 @@ frontend http_in
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// sortedKeys returns the keys of a map in sorted order for deterministic output.
|
||||
func sortedKeys(m map[string]string) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
// sanitizeName converts a name to a valid HAProxy identifier (alphanumeric + underscore)
|
||||
func sanitizeName(name string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
|
||||
@@ -3,6 +3,8 @@ package haproxy
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/wild-cloud/wild-central/internal/services"
|
||||
)
|
||||
|
||||
func TestNewManager_DefaultPath(t *testing.T) {
|
||||
@@ -174,8 +176,8 @@ func TestGenerate_ACLOrdering(t *testing.T) {
|
||||
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.240", Subdomains: true},
|
||||
}
|
||||
httpRoutes := []HTTPRoute{
|
||||
{Name: "wild-central", Domain: "central.payne.io", Backend: "127.0.0.1:15055"},
|
||||
{Name: "wild-cloud", Domain: "wild-cloud.payne.io", Backend: "127.0.0.1:5055"},
|
||||
{Name: "wild-central", Domain: "central.payne.io", Routes: []HTTPRouteBackend{{Backend: "127.0.0.1:15055"}}},
|
||||
{Name: "wild-cloud", Domain: "wild-cloud.payne.io", Routes: []HTTPRouteBackend{{Backend: "127.0.0.1:5055"}}},
|
||||
}
|
||||
out := m.GenerateWithOpts(instances, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
||||
|
||||
@@ -226,8 +228,8 @@ func TestGenerate_CustomRoute(t *testing.T) {
|
||||
func TestGenerateWithOpts_HTTPRoutes_L7Frontend(t *testing.T) {
|
||||
m := NewManager("")
|
||||
httpRoutes := []HTTPRoute{
|
||||
{Name: "my-api", Domain: "my-api.payne.io", Backend: "192.168.8.60:9001", HealthPath: "/health"},
|
||||
{Name: "dashboard", Domain: "dashboard.payne.io", Backend: "192.168.8.60:8080"},
|
||||
{Name: "my-api", Domain: "my-api.payne.io", Routes: []HTTPRouteBackend{{Backend: "192.168.8.60:9001", HealthPath: "/health"}}},
|
||||
{Name: "dashboard", Domain: "dashboard.payne.io", Routes: []HTTPRouteBackend{{Backend: "192.168.8.60:8080"}}},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(nil, nil, GenerateOpts{
|
||||
@@ -252,7 +254,7 @@ func TestGenerateWithOpts_HTTPRoutes_L7Frontend(t *testing.T) {
|
||||
}
|
||||
|
||||
// L7 backends
|
||||
if !strings.Contains(out, "backend be_l7_my_api") {
|
||||
if !strings.Contains(out, "backend be_l7_my_api_0") {
|
||||
t.Errorf("expected L7 backend for my-api, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "server s0 192.168.8.60:9001 check") {
|
||||
@@ -263,7 +265,7 @@ func TestGenerateWithOpts_HTTPRoutes_L7Frontend(t *testing.T) {
|
||||
}
|
||||
|
||||
// Dashboard backend (no health check)
|
||||
if !strings.Contains(out, "backend be_l7_dashboard") {
|
||||
if !strings.Contains(out, "backend be_l7_dashboard_0") {
|
||||
t.Errorf("expected L7 backend for dashboard, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "server s0 192.168.8.60:8080") {
|
||||
@@ -277,7 +279,7 @@ func TestGenerateWithOpts_MixedL4AndL7(t *testing.T) {
|
||||
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.100"},
|
||||
}
|
||||
httpRoutes := []HTTPRoute{
|
||||
{Name: "my-api", Domain: "my-api.payne.io", Backend: "192.168.8.60:9001"},
|
||||
{Name: "my-api", Domain: "my-api.payne.io", Routes: []HTTPRouteBackend{{Backend: "192.168.8.60:9001"}}},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(instances, nil, GenerateOpts{
|
||||
@@ -291,7 +293,7 @@ func TestGenerateWithOpts_MixedL4AndL7(t *testing.T) {
|
||||
if !strings.Contains(out, "frontend l7_https") {
|
||||
t.Errorf("expected L7 frontend, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "backend be_l7_my_api") {
|
||||
if !strings.Contains(out, "backend be_l7_my_api_0") {
|
||||
t.Errorf("expected L7 backend for my-api, got:\n%s", out)
|
||||
}
|
||||
// L7 fallback should be present
|
||||
@@ -311,8 +313,8 @@ func TestGenerateWithOpts_ACLOrdering_L7BeforeL4Wildcard(t *testing.T) {
|
||||
{Name: "payne-io", Domain: "payne.io", BackendIP: "192.168.8.240", Subdomains: true},
|
||||
}
|
||||
httpRoutes := []HTTPRoute{
|
||||
{Name: "wild-central", Domain: "central.payne.io", Backend: "127.0.0.1:15055"},
|
||||
{Name: "wild-cloud", Domain: "wild-cloud.payne.io", Backend: "127.0.0.1:5055"},
|
||||
{Name: "wild-central", Domain: "central.payne.io", Routes: []HTTPRouteBackend{{Backend: "127.0.0.1:15055"}}},
|
||||
{Name: "wild-cloud", Domain: "wild-cloud.payne.io", Routes: []HTTPRouteBackend{{Backend: "127.0.0.1:5055"}}},
|
||||
}
|
||||
out := m.GenerateWithOpts(instances, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
||||
|
||||
@@ -443,7 +445,7 @@ func TestGenerateWithOpts_OnlyHTTPRoutes(t *testing.T) {
|
||||
m := NewManager("")
|
||||
|
||||
httpRoutes := []HTTPRoute{
|
||||
{Name: "my-api", Domain: "api.example.com", Backend: "192.168.1.10:9001"},
|
||||
{Name: "my-api", Domain: "api.example.com", Routes: []HTTPRouteBackend{{Backend: "192.168.1.10:9001"}}},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(nil, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
||||
@@ -457,7 +459,7 @@ func TestGenerateWithOpts_OnlyHTTPRoutes(t *testing.T) {
|
||||
t.Errorf("expected L7 frontend for HTTP routes, got:\n%s", out)
|
||||
}
|
||||
// L7 backend should be present
|
||||
if !strings.Contains(out, "backend be_l7_my_api") {
|
||||
if !strings.Contains(out, "backend be_l7_my_api_0") {
|
||||
t.Errorf("expected L7 backend for my-api, got:\n%s", out)
|
||||
}
|
||||
// No L4 instance backends
|
||||
@@ -497,7 +499,7 @@ func TestGenerateWithOpts_CertsDir(t *testing.T) {
|
||||
m := NewManager("")
|
||||
|
||||
httpRoutes := []HTTPRoute{
|
||||
{Name: "app", Domain: "app.example.com", Backend: "127.0.0.1:8080"},
|
||||
{Name: "app", Domain: "app.example.com", Routes: []HTTPRouteBackend{{Backend: "127.0.0.1:8080"}}},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(nil, nil, GenerateOpts{
|
||||
@@ -514,7 +516,7 @@ func TestGenerateWithOpts_DefaultCertsDir(t *testing.T) {
|
||||
m := NewManager("")
|
||||
|
||||
httpRoutes := []HTTPRoute{
|
||||
{Name: "app", Domain: "app.example.com", Backend: "127.0.0.1:8080"},
|
||||
{Name: "app", Domain: "app.example.com", Routes: []HTTPRouteBackend{{Backend: "127.0.0.1:8080"}}},
|
||||
}
|
||||
|
||||
// Empty CertsDir should default to /etc/haproxy/certs/
|
||||
@@ -556,7 +558,7 @@ func TestGenerateWithOpts_HealthCheckInL7Backend(t *testing.T) {
|
||||
m := NewManager("")
|
||||
|
||||
httpRoutes := []HTTPRoute{
|
||||
{Name: "api", Domain: "api.example.com", Backend: "10.0.0.5:8080", HealthPath: "/healthz"},
|
||||
{Name: "api", Domain: "api.example.com", Routes: []HTTPRouteBackend{{Backend: "10.0.0.5:8080", HealthPath: "/healthz"}}},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(nil, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
||||
@@ -568,3 +570,159 @@ func TestGenerateWithOpts_HealthCheckInL7Backend(t *testing.T) {
|
||||
t.Errorf("expected server line with 'check' flag for health check, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// --- L7 extended features tests (paths, headers, IP whitelisting) ---
|
||||
|
||||
func TestGenerateWithOpts_PathRestriction(t *testing.T) {
|
||||
m := NewManager("")
|
||||
|
||||
httpRoutes := []HTTPRoute{
|
||||
{
|
||||
Name: "synapse-fed",
|
||||
Domain: "payne.io",
|
||||
Routes: []HTTPRouteBackend{{
|
||||
Backend: "192.168.8.80:80",
|
||||
Paths: []string{"/.well-known/matrix", "/_matrix"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(nil, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
||||
|
||||
if !strings.Contains(out, "acl path_synapse_fed_0 path_beg /.well-known/matrix /_matrix") {
|
||||
t.Errorf("expected path ACL for synapse-fed, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "use_backend be_l7_synapse_fed_0 if host_synapse_fed path_synapse_fed_0") {
|
||||
t.Errorf("expected path-restricted use_backend, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateWithOpts_ResponseHeaders(t *testing.T) {
|
||||
m := NewManager("")
|
||||
|
||||
httpRoutes := []HTTPRoute{
|
||||
{
|
||||
Name: "plausible",
|
||||
Domain: "plausible.cloud.payne.io",
|
||||
Routes: []HTTPRouteBackend{{
|
||||
Backend: "192.168.8.80:80",
|
||||
Headers: &services.HeaderConfig{
|
||||
Response: map[string]string{
|
||||
"Access-Control-Allow-Private-Network": "true",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(nil, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
||||
|
||||
if !strings.Contains(out, `http-response set-header Access-Control-Allow-Private-Network "true" if host_plausible`) {
|
||||
t.Errorf("expected response header rule, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateWithOpts_RequestHeaders(t *testing.T) {
|
||||
m := NewManager("")
|
||||
|
||||
httpRoutes := []HTTPRoute{
|
||||
{
|
||||
Name: "my-api",
|
||||
Domain: "api.payne.io",
|
||||
Routes: []HTTPRouteBackend{{
|
||||
Backend: "192.168.8.80:80",
|
||||
Headers: &services.HeaderConfig{
|
||||
Request: map[string]string{
|
||||
"X-Forwarded-Proto": "https",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(nil, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
||||
|
||||
if !strings.Contains(out, `http-request set-header X-Forwarded-Proto "https" if host_my_api`) {
|
||||
t.Errorf("expected request header rule, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateWithOpts_IPWhitelist(t *testing.T) {
|
||||
m := NewManager("")
|
||||
|
||||
httpRoutes := []HTTPRoute{
|
||||
{
|
||||
Name: "headlamp",
|
||||
Domain: "headlamp.internal.cloud.payne.io",
|
||||
Routes: []HTTPRouteBackend{{
|
||||
Backend: "192.168.8.80:80",
|
||||
IPAllow: []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(nil, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
||||
|
||||
if !strings.Contains(out, "acl ip_headlamp_0 src 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16") {
|
||||
t.Errorf("expected IP ACL for headlamp, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "http-request deny if host_headlamp !ip_headlamp_0") {
|
||||
t.Errorf("expected IP deny rule for headlamp, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateWithOpts_PathRoutingOrder(t *testing.T) {
|
||||
m := NewManager("")
|
||||
|
||||
// Path-restricted routes should appear BEFORE unrestricted routes
|
||||
// In the new model, multiple backends for the same domain go in one HTTPRoute
|
||||
httpRoutes := []HTTPRoute{
|
||||
{Name: "app", Domain: "payne.io", Routes: []HTTPRouteBackend{
|
||||
{Backend: "192.168.8.80:80", Paths: []string{"/_matrix"}},
|
||||
{Backend: "192.168.8.80:80"},
|
||||
}},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(nil, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
||||
|
||||
pathPos := strings.Index(out, "use_backend be_l7_app_0 if")
|
||||
hostPos := strings.Index(out, "use_backend be_l7_app_1 if")
|
||||
|
||||
if pathPos < 0 {
|
||||
t.Fatalf("expected path-restricted use_backend for route 0, got:\n%s", out)
|
||||
}
|
||||
if hostPos < 0 {
|
||||
t.Fatalf("expected unrestricted use_backend for route 1, got:\n%s", out)
|
||||
}
|
||||
if pathPos > hostPos {
|
||||
t.Errorf("path-restricted routes (pos %d) must appear before unrestricted (pos %d):\n%s", pathPos, hostPos, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateWithOpts_NoL7Fields_BackwardCompat(t *testing.T) {
|
||||
m := NewManager("")
|
||||
|
||||
// Route with no paths/headers/ipAllow should work exactly as before
|
||||
httpRoutes := []HTTPRoute{
|
||||
{Name: "simple", Domain: "simple.payne.io", Routes: []HTTPRouteBackend{{Backend: "192.168.8.80:80"}}},
|
||||
}
|
||||
|
||||
out := m.GenerateWithOpts(nil, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
||||
|
||||
if !strings.Contains(out, "acl host_simple hdr(host) -i simple.payne.io") {
|
||||
t.Errorf("expected host ACL, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "use_backend be_l7_simple_0 if host_simple") {
|
||||
t.Errorf("expected simple use_backend, got:\n%s", out)
|
||||
}
|
||||
// Should NOT have path, IP, or header rules
|
||||
if strings.Contains(out, "path_beg") {
|
||||
t.Errorf("unexpected path ACL in simple route, got:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "http-request deny") {
|
||||
t.Errorf("unexpected IP deny in simple route, got:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "http-response set-header") {
|
||||
t.Errorf("unexpected response header in simple route, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user