diff --git a/internal/machine/caddyconfig/caddyfile.go b/internal/machine/caddyconfig/caddyfile.go index c73aa9e8..a2e05ddf 100644 --- a/internal/machine/caddyconfig/caddyfile.go +++ b/internal/machine/caddyconfig/caddyfile.go @@ -114,6 +114,8 @@ func (g *CaddyfileGenerator) Generate(ctx context.Context, records []store.Conta } upstreams := serviceUpstreams(containers) + // Track validation errors for reporting. + var configErrors []string // Find the 'caddy' service container on this machine. Use the most recent one if multiple exist. var caddyCtr *api.ServiceContainer @@ -136,6 +138,8 @@ func (g *CaddyfileGenerator) Generate(ctx context.Context, records []store.Conta if err != nil { g.log.Error("Failed to render template directives in user-defined global Caddy config, skipping it.", "service", caddyCtr.ServiceName(), "container", caddyCtr.ID, "err", err) + configErrors = append(configErrors, + fmt.Sprintf("service '%s': failed to render template: %v", caddyCtr.ServiceName(), err)) } else { caddyfileCandidate := fmt.Sprintf("# User-defined global config from service '%s'.\n%s\n\n%s", caddyCtr.ServiceName(), renderedConfig, caddyfile) @@ -143,6 +147,8 @@ func (g *CaddyfileGenerator) Generate(ctx context.Context, records []store.Conta if err = g.validator.Validate(ctx, caddyfileCandidate); err != nil { g.log.Error("User-defined global Caddy config is invalid, skipping it.", "service", caddyCtr.ServiceName(), "container", caddyCtr.ID, "err", err) + configErrors = append(configErrors, + fmt.Sprintf("service '%s': validation failed: %v", caddyCtr.ServiceName(), err)) } else { caddyfile = caddyfileCandidate } @@ -186,6 +192,8 @@ func (g *CaddyfileGenerator) Generate(ctx context.Context, records []store.Conta if err != nil { g.log.Error("Failed to render template directives in user-defined Caddy config for service, skipping it.", "service", serviceName, "err", err) + configErrors = append(configErrors, + fmt.Sprintf("service '%s': failed to render template: %v", serviceName, err)) continue } @@ -194,15 +202,21 @@ func (g *CaddyfileGenerator) Generate(ctx context.Context, records []store.Conta if err = g.validator.Validate(ctx, caddyfileCandidate); err != nil { g.log.Error("User-defined Caddy config for service is invalid, skipping it.", "service", serviceName, "err", err) + configErrors = append(configErrors, fmt.Sprintf("service '%s': validation failed: %v", serviceName, err)) } else { caddyfile = caddyfileCandidate } } - // TODO: add ac omment with invalid configs and their validation errors: - // # Skipped invalid configs: - // # - Service 'api': Template error in x-caddy - // # - Service 'web': Invalid Caddyfile syntax + // Append error summary as comment if there were any invalid configs. + if len(configErrors) > 0 { + errorsComment := "# Skipped invalid user-defined configs:\n" + for _, e := range configErrors { + errorsComment += fmt.Sprintf("# - %s\n", e) + } + + caddyfile += "\n" + errorsComment + } return caddyfileHeader + "\n" + caddyfile, nil } diff --git a/internal/machine/caddyconfig/caddyfile_test.go b/internal/machine/caddyconfig/caddyfile_test.go index f7cba036..f027fd35 100644 --- a/internal/machine/caddyconfig/caddyfile_test.go +++ b/internal/machine/caddyconfig/caddyfile_test.go @@ -297,7 +297,10 @@ bad.config.com { time.Now(), ), }, - want: caddyfileBase, + want: caddyfileBase + ` +# Skipped invalid user-defined configs: +# - service 'bad-service': validation failed: invalid config detected +`, }, { name: "service with invalid config template is skipped", @@ -313,7 +316,10 @@ bad.template.com { time.Now(), ), }, - want: caddyfileBase, + want: caddyfileBase + ` +# Skipped invalid user-defined configs: +# - service 'bad-template': failed to render template: parse config as Go template: template: Caddyfile:3: unexpected "}" in operand +`, }, { name: "caddy service with invalid global config is skipped", @@ -329,7 +335,10 @@ localhost { time.Now(), ), }, - want: caddyfileBase, + want: caddyfileBase + ` +# Skipped invalid user-defined configs: +# - service 'caddy': validation failed: invalid config detected +`, }, { name: "caddy service on different machine is ignored", @@ -389,6 +398,9 @@ api.example.com { web.example.com { reverse_proxy web:3000 } + +# Skipped invalid user-defined configs: +# - service 'invalid-svc': validation failed: invalid config detected `, }, { @@ -756,6 +768,63 @@ gateway.example.com { web-v2.example.com { reverse_proxy 10.210.1.3:8080 10.210.3.3:8080 10.210.2.3:8080 } + +# Skipped invalid user-defined configs: +# - service 'invalid': validation failed: invalid config detected +`, + }, + { + name: "multiple errors: invalid global, template error, and validation error", + containers: []store.ContainerRecord{ + newContainerRecordWithCaddyConfig( + "caddy", + "10.210.0.1", + `# test:invalid +{ + invalid global +}`, + "test-machine-id", + time.Now(), + ), + newContainerRecordWithCaddyConfig( + "broken-template", + "10.210.0.2", + `broken.example.com { + reverse_proxy {{upstreams "missing +}`, + "test-machine-id", + time.Now(), + ), + newContainerRecordWithCaddyConfig( + "invalid", + "10.210.0.3", + `# test:invalid +invalid.example.com { + respond "Invalid config" +}`, + "test-machine-id", + time.Now(), + ), + newContainerRecordWithCaddyConfig( + "valid", + "10.210.0.4", + `valid.example.com { + respond "Valid config" +}`, + "test-machine-id", + time.Now(), + ), + }, + want: caddyfileBase + ` +# User-defined config for service 'valid'. +valid.example.com { + respond "Valid config" +} + +# Skipped invalid user-defined configs: +# - service 'caddy': validation failed: invalid config detected +# - service 'broken-template': failed to render template: parse config as Go template: template: Caddyfile:2: unterminated quoted string +# - service 'invalid': validation failed: invalid config detected `, }, }