fix: skip validation for user-defined Caddy configs if caddy not running locally

This commit is contained in:
Pasha Sviderski
2025-09-10 15:04:45 +10:00
parent 5baa8087e5
commit 97bdb8eae1
5 changed files with 153 additions and 49 deletions
+109 -40
View File
@@ -18,8 +18,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestCaddyfileGenerator(t *testing.T) {
caddyfileHeader := `# This file is autogenerated by Uncloud based on the configuration of running services.
const testCaddyfileHeader = `# This file is autogenerated by Uncloud based on the configuration of running services.
# Do not edit manually. Any manual changes will be overwritten on the next update.
# Health check endpoint to verify Caddy reachability on this machine.
@@ -38,6 +37,7 @@ http:// {
}
`
func TestCaddyfileGenerator(t *testing.T) {
tests := []struct {
name string
containers []store.ContainerRecord
@@ -47,14 +47,14 @@ http:// {
{
name: "empty containers",
containers: []store.ContainerRecord{},
want: caddyfileHeader,
want: testCaddyfileHeader,
},
{
name: "HTTP container",
containers: []store.ContainerRecord{
newContainerRecord(newContainer("10.210.0.2", "app.example.com:8080/http"), "mach1"),
},
want: caddyfileHeader + `
want: testCaddyfileHeader + `
# Sites generated from service ports.
http://app.example.com {
@@ -71,7 +71,7 @@ http://app.example.com {
newContainerRecord(newContainer("10.210.0.2", "app.example.com:8080/http"), "mach1"),
newContainerRecord(newContainer("10.210.0.3", "app.example.com:8080/http"), "mach1"),
},
want: caddyfileHeader + `
want: testCaddyfileHeader + `
# Sites generated from service ports.
http://app.example.com {
@@ -87,7 +87,7 @@ http://app.example.com {
containers: []store.ContainerRecord{
newContainerRecord(newContainer("10.210.0.2", "secure.example.com:8000/https"), "mach1"),
},
want: caddyfileHeader + `
want: testCaddyfileHeader + `
# Sites generated from service ports.
https://secure.example.com {
@@ -127,7 +127,7 @@ https://secure.example.com {
"mach1",
),
},
want: caddyfileHeader + `
want: testCaddyfileHeader + `
# Sites generated from service ports.
http://app.example.com {
@@ -157,14 +157,14 @@ https://secure.example.com {
containers: []store.ContainerRecord{
newContainerRecord(newContainerWithoutNetwork("ignored.example.com:8080/http"), "mach1"),
},
want: caddyfileHeader,
want: testCaddyfileHeader,
},
{
name: "container with invalid port ignored",
containers: []store.ContainerRecord{
newContainerRecord(newContainer("10.210.0.2", "invalid-port"), "mach1"),
},
want: caddyfileHeader,
want: testCaddyfileHeader,
},
{
name: "containers with unsupported protocols and host mode ignored",
@@ -173,7 +173,7 @@ https://secure.example.com {
newContainerRecord(newContainer("10.210.0.3", "5000/udp"), "mach1"),
newContainerRecord(newContainer("10.210.0.4", "80:8080/tcp@host"), "mach1"),
},
want: caddyfileHeader,
want: testCaddyfileHeader,
},
}
@@ -183,7 +183,7 @@ https://secure.example.com {
// Validator is not expected to be called in these tests.
generator := NewCaddyfileGenerator("test-machine-id", nil, nil)
config, err := generator.Generate(ctx, tt.containers)
config, err := generator.Generate(ctx, tt.containers, true)
if tt.wantErr {
assert.Error(t, err)
@@ -197,25 +197,6 @@ https://secure.example.com {
}
func TestCaddyfileGeneratorWithCustomConfigs(t *testing.T) {
caddyfileBase := `# This file is autogenerated by Uncloud based on the configuration of running services.
# Do not edit manually. Any manual changes will be overwritten on the next update.
# Health check endpoint to verify Caddy reachability on this machine.
http:// {
handle /.uncloud-verify {
respond "test-machine-id" 200
}
log
}
(common_proxy) {
# Retry failed requests up to lb_retries times against other available upstreams.
lb_retries 3
# Upstreams are marked unhealthy for fail_duration after a failed request (passive health checking).
fail_duration 30s
}
`
tests := []struct {
name string
containers []store.ContainerRecord
@@ -275,7 +256,7 @@ web.example.com {
time.Now(),
),
},
want: caddyfileBase + `
want: testCaddyfileHeader + `
# User-defined config for service 'web'.
# Custom config for web service
web.example.com {
@@ -297,7 +278,7 @@ bad.config.com {
time.Now(),
),
},
want: caddyfileBase + `
want: testCaddyfileHeader + `
# Skipped invalid user-defined configs:
# - service 'bad-service': validation failed: invalid config detected
`,
@@ -316,7 +297,7 @@ bad.template.com {
time.Now(),
),
},
want: caddyfileBase + `
want: testCaddyfileHeader + `
# Skipped invalid user-defined configs:
# - service 'bad-template': failed to render template: parse config as Go template: template: Caddyfile:3: unexpected "}" in operand
`,
@@ -335,7 +316,7 @@ localhost {
time.Now(),
),
},
want: caddyfileBase + `
want: testCaddyfileHeader + `
# Skipped invalid user-defined configs:
# - service 'caddy': validation failed: invalid config detected
`,
@@ -354,7 +335,7 @@ localhost {
time.Now(),
),
},
want: caddyfileBase,
want: testCaddyfileHeader,
},
{
name: "multiple services with mixed valid and invalid configs",
@@ -388,7 +369,7 @@ bad.example.com {
time.Now(),
),
},
want: caddyfileBase + `
want: testCaddyfileHeader + `
# User-defined config for service 'api'.
api.example.com {
reverse_proxy api:8080
@@ -490,7 +471,7 @@ api.example.com {
"test-machine-id",
),
},
want: caddyfileBase + `
want: testCaddyfileHeader + `
# Sites generated from service ports.
http://api.example.com {
@@ -530,7 +511,7 @@ new.example.com {
time.Now(),
),
},
want: caddyfileBase + `
want: testCaddyfileHeader + `
# User-defined config for service 'web'.
# New config
new.example.com {
@@ -815,7 +796,7 @@ invalid.example.com {
time.Now(),
),
},
want: caddyfileBase + `
want: testCaddyfileHeader + `
# User-defined config for service 'valid'.
valid.example.com {
respond "Valid config"
@@ -843,7 +824,7 @@ valid.example.com {
t.Run(tt.name, func(t *testing.T) {
generator := NewCaddyfileGenerator("test-machine-id", validator, nil)
config, err := generator.Generate(ctx, tt.containers)
config, err := generator.Generate(ctx, tt.containers, true)
if tt.wantErr {
assert.Error(t, err)
@@ -899,6 +880,94 @@ func newContainerRecordWithCaddyConfig(serviceName, ip, caddyConfig, machineID s
}
}
func TestCaddyfileGeneratorWithoutCustomConfigs(t *testing.T) {
// Test that when includeCustom is false (Caddy not available), x-caddy configs are skipped.
tests := []struct {
name string
containers []store.ContainerRecord
want string
}{
{
name: "x-caddy configs are skipped",
containers: []store.ContainerRecord{
newContainerRecordWithCaddyConfig(
"caddy",
"10.210.0.1",
`# Global config
{
global directive
}`,
"test-machine-id",
time.Now(),
),
newContainerRecordWithCaddyConfig(
"web",
"10.210.0.2",
`web.example.com {
reverse_proxy web:3000
}`,
"test-machine-id",
time.Now(),
),
newContainerRecordWithPorts(
"api",
"10.210.0.3",
[]string{"api.example.com:8080/http"},
"test-machine-id",
),
},
want: testCaddyfileHeader + `
# Sites generated from service ports.
http://api.example.com {
reverse_proxy 10.210.0.3:8080 {
import common_proxy
}
log
}
# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine.
`,
},
{
name: "no containers with x-caddy configs",
containers: []store.ContainerRecord{
newContainerRecordWithPorts(
"api",
"10.210.0.3",
[]string{"api.example.com:8080/http"},
"test-machine-id",
),
},
want: testCaddyfileHeader + `
# Sites generated from service ports.
http://api.example.com {
reverse_proxy 10.210.0.3:8080 {
import common_proxy
}
log
}
# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine.
`,
},
}
ctx := context.Background()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Validator is not expected to be called in these tests.
generator := NewCaddyfileGenerator("test-machine-id", nil, nil)
config, err := generator.Generate(ctx, tt.containers, false)
require.NoError(t, err)
assert.Equal(t, tt.want, config, "Generated Caddyfile doesn't match")
})
}
}
func newContainerRecordWithPorts(serviceName, ip string, ports []string, machineID string) store.ContainerRecord {
portsLabel := strings.Join(ports, ",")
return store.ContainerRecord{