Skip to content

Commit 93fef88

Browse files
committed
chore: refactor Caddyfile generator to accept a validator
1 parent 455174c commit 93fef88

File tree

4 files changed

+76
-21
lines changed

4 files changed

+76
-21
lines changed

internal/machine/caddyconfig/caddyfile.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,43 @@ https://{{$hostname}} {
4545
}{{end}}
4646
`
4747

48-
func GenerateCaddyfile(containers []api.ServiceContainer, verifyResponse string) (string, error) {
48+
// CaddyfileGenerator generates a Caddyfile configuration for the Caddy reverse proxy.
49+
type CaddyfileGenerator struct {
50+
// MachineID is the unique identifier of the machine where the controller is running.
51+
MachineID string
52+
Validator CaddyfileValidator
53+
}
54+
55+
// CaddyfileValidator is an interface for validating Caddyfile configurations.
56+
type CaddyfileValidator interface {
57+
Validate(caddyfile string) error
58+
}
59+
60+
// Generate creates a Caddyfile configuration based on the provided service containers.
61+
// If a 'caddy' service container is running on this machine and defines a custom Caddy config (x-caddy) in its service
62+
// spec, it will be validated and prepended to the generated Caddyfile. Custom Caddy configs (x-caddy) defined in other
63+
// service specs are validated and appended to the generated Caddyfile. Invalid configs are logged and skipped to ensure
64+
// the generated Caddyfile remains valid.
65+
//
66+
// The final Caddyfile structure includes:
67+
//
68+
// [caddy x-caddy]
69+
// [generated Caddyfile from all service ports]
70+
// [service-a x-caddy]
71+
// ...
72+
// [service-z x-caddy]
73+
func (g *CaddyfileGenerator) Generate(containers []api.ServiceContainer) (string, error) {
74+
baseCaddyfile, err := g.generateBaseFromPorts(containers)
75+
if err != nil {
76+
return "", fmt.Errorf("generate base Caddyfile from service ports: %w", err)
77+
}
78+
79+
// TODO: Implement support for custom Caddy configs (x-caddy) in service specs.
80+
81+
return baseCaddyfile, nil
82+
}
83+
84+
func (g *CaddyfileGenerator) generateBaseFromPorts(containers []api.ServiceContainer) (string, error) {
4985
httpHostUpstreams, httpsHostUpstreams := httpUpstreamsFromContainers(containers)
5086

5187
funcs := template.FuncMap{"join": strings.Join}
@@ -61,7 +97,7 @@ func GenerateCaddyfile(containers []api.ServiceContainer, verifyResponse string)
6197
HTTPSHostUpstreams map[string][]string
6298
}{
6399
VerifyPath: VerifyPath,
64-
VerifyResponse: verifyResponse,
100+
VerifyResponse: g.MachineID,
65101
HTTPHostUpstreams: httpHostUpstreams,
66102
HTTPSHostUpstreams: httpsHostUpstreams,
67103
}

internal/machine/caddyconfig/caddyfile_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88
"github.com/stretchr/testify/require"
99
)
1010

11-
func TestGenerateCaddyfile(t *testing.T) {
11+
func TestCaddyfileGenerator(t *testing.T) {
1212
caddyfileHeader := `http:// {
1313
handle /.uncloud-verify {
14-
respond "verification-response-body" 200
14+
respond "test-machine-id" 200
1515
}
1616
log
1717
}
@@ -24,6 +24,10 @@ func TestGenerateCaddyfile(t *testing.T) {
2424
}
2525
`
2626

27+
generator := &CaddyfileGenerator{
28+
MachineID: "test-machine-id",
29+
}
30+
2731
tests := []struct {
2832
name string
2933
containers []api.ServiceContainer
@@ -182,7 +186,7 @@ http://app.example.com {
182186

183187
for _, tt := range tests {
184188
t.Run(tt.name, func(t *testing.T) {
185-
config, err := GenerateCaddyfile(tt.containers, "verification-response-body")
189+
config, err := generator.Generate(tt.containers)
186190

187191
if tt.wantErr {
188192
assert.Error(t, err)

internal/machine/caddyconfig/controller.go

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,31 @@ const (
2222
// proxy. The generated configuration allows Caddy to route external traffic to service containers across the internal
2323
// network.
2424
type Controller struct {
25-
store *store.Store
26-
configDir string
27-
verifyResponse string
28-
log *slog.Logger
25+
machineID string
26+
configDir string
27+
generator *CaddyfileGenerator
28+
store *store.Store
29+
log *slog.Logger
2930
}
3031

31-
func NewController(store *store.Store, configDir string, verifyResponse string) (*Controller, error) {
32+
func NewController(machineID, configDir, adminSock string, store *store.Store) (*Controller, error) {
3233
if err := os.MkdirAll(configDir, 0o750); err != nil {
3334
return nil, fmt.Errorf("create directory for Caddy configuration '%s': %w", configDir, err)
3435
}
3536
if err := fs.Chown(configDir, "", CaddyGroup); err != nil {
3637
return nil, fmt.Errorf("change owner of directory for Caddy configuration '%s': %w", configDir, err)
3738
}
3839

40+
generator := &CaddyfileGenerator{
41+
MachineID: machineID,
42+
}
43+
3944
return &Controller{
40-
store: store,
41-
configDir: configDir,
42-
verifyResponse: verifyResponse,
43-
log: slog.With("component", "caddy-controller"),
45+
machineID: machineID,
46+
configDir: configDir,
47+
generator: generator,
48+
store: store,
49+
log: slog.With("component", "caddy-controller"),
4450
}, nil
4551
}
4652

@@ -73,20 +79,20 @@ func (c *Controller) Run(ctx context.Context) error {
7379

7480
containerRecords, err = c.store.ListContainers(ctx, store.ListOptions{})
7581
if err != nil {
76-
c.log.Info("Failed to list containers.", "err", err)
82+
c.log.Error("Failed to list containers.", "err", err)
7783
continue
7884
}
7985
containers, err = c.filterAvailableContainers(containerRecords)
8086
if err != nil {
81-
c.log.Info("Failed to filter available containers.", "err", err)
87+
c.log.Error("Failed to filter available containers.", "err", err)
8288
continue
8389
}
8490

8591
if err = c.generateCaddyfile(containers); err != nil {
86-
c.log.Info("Failed to generate Caddyfile configuration.", "err", err)
92+
c.log.Error("Failed to generate Caddyfile configuration.", "err", err)
8793
}
8894
if err = c.generateJSONConfig(containers); err != nil {
89-
c.log.Info("Failed to generate Caddy JSON configuration.", "err", err)
95+
c.log.Error("Failed to generate Caddy JSON configuration.", "err", err)
9096
}
9197

9298
c.log.Info("Updated Caddy configuration.", "dir", c.configDir)
@@ -110,12 +116,13 @@ func (c *Controller) filterAvailableContainers(
110116
}
111117

112118
func (c *Controller) generateCaddyfile(containers []api.ServiceContainer) error {
113-
caddyfile, err := GenerateCaddyfile(containers, c.verifyResponse)
119+
caddyfile, err := c.generator.Generate(containers)
114120
if err != nil {
115121
return fmt.Errorf("generate Caddyfile: %w", err)
116122
}
117123
caddyfilePath := filepath.Join(c.configDir, "Caddyfile")
118124

125+
// TODO: use atomic file write to avoid partial loads on Caddy watch reload.
119126
if err = os.WriteFile(caddyfilePath, []byte(caddyfile), 0o640); err != nil {
120127
return fmt.Errorf("write Caddyfile to file '%s': %w", caddyfilePath, err)
121128
}
@@ -127,7 +134,7 @@ func (c *Controller) generateCaddyfile(containers []api.ServiceContainer) error
127134
}
128135

129136
func (c *Controller) generateJSONConfig(containers []api.ServiceContainer) error {
130-
config, err := GenerateJSONConfig(containers, c.verifyResponse)
137+
config, err := GenerateJSONConfig(containers, c.machineID)
131138
if err != nil {
132139
return err
133140
}

internal/machine/machine.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ const (
4242
DefaultMachineSockPath = "/run/uncloud/machine.sock"
4343
DefaultUncloudSockPath = "/run/uncloud/uncloud.sock"
4444
DefaultSockGroup = "uncloud"
45+
// DefaultCaddyAdminSockPath is the default path to the Caddy admin socket for validating the generated Caddy
46+
// reverse proxy configuration.
47+
DefaultCaddyAdminSockPath = "/run/uncloud/caddy/admin.sock"
4548
)
4649

4750
type Config struct {
@@ -387,7 +390,12 @@ func (m *Machine) Run(ctx context.Context) error {
387390

388391
// Create a new caddyconfig controller for managing the Caddy reverse proxy configuration.
389392
// It will also serve the current machine ID at /.uncloud-verify to verify Caddy reachability.
390-
caddyconfigCtrl, err := caddyconfig.NewController(m.store, m.config.CaddyConfigDir, m.state.ID)
393+
caddyconfigCtrl, err := caddyconfig.NewController(
394+
m.state.ID,
395+
m.config.CaddyConfigDir,
396+
DefaultCaddyAdminSockPath,
397+
m.store,
398+
)
391399
if err != nil {
392400
return fmt.Errorf("create caddyconfig controller: %w", err)
393401
}

0 commit comments

Comments
 (0)