fix: write generated Caddyfile to disk only if succesfully loaded into local Caddy (means valid)

This commit is contained in:
Pasha Sviderski
2025-09-10 16:59:31 +10:00
parent 97bdb8eae1
commit 7b88b8b810
4 changed files with 77 additions and 23 deletions
+4 -3
View File
@@ -54,6 +54,9 @@ https://{{$hostname}} {
}
log
}{{end}}
`
caddyfileUnavailabeFooter = `# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine
# or the latest generated config is invalid. Please check the Caddy logs if it's running.
`
)
@@ -118,9 +121,7 @@ func (g *CaddyfileGenerator) Generate(
}
if !includeCustom {
return fmt.Sprintf("%s\n%s\n"+
"# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine.\n",
caddyfileHeader, caddyfile), nil
return fmt.Sprintf("%s\n%s\n%s", caddyfileHeader, caddyfile, caddyfileUnavailabeFooter), nil
}
upstreams := serviceUpstreams(containers)
@@ -926,7 +926,8 @@ http://api.example.com {
log
}
# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine.
# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine
# or the latest generated config is invalid. Please check the Caddy logs if it's running.
`,
},
{
@@ -949,7 +950,8 @@ http://api.example.com {
log
}
# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine.
# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine
# or the latest generated config is invalid. Please check the Caddy logs if it's running.
`,
},
}
+27 -18
View File
@@ -111,42 +111,51 @@ func filterHealthyContainers(containers []store.ContainerRecord) []store.Contain
func (c *Controller) generateAndLoadCaddyfile(ctx context.Context, containers []store.ContainerRecord) {
// Check if Caddy is available before attempting to generate and load config.
caddyAvailable := c.client.IsAvailable(ctx)
caddyfile, err := c.generateCaddyfile(ctx, containers, caddyAvailable)
caddyfile, err := c.generator.Generate(ctx, containers, caddyAvailable)
if err != nil {
c.log.Error("Failed to generate Caddyfile configuration.", "err", err)
return
}
if !caddyAvailable {
// Caddy is not running so the generated Caddyfile should not include user-defined configs thus must be valid.
// It's safe to write the config to disk so that when Caddy is deployed on this machine, it can pick it up.
if err = c.writeCaddyfile(caddyfile); err != nil {
c.log.Error("Failed to write Caddyfile to disk.", "err", err)
return
}
c.log.Debug("Caddy is not running on this machine, skipping configuration load.", "path", c.caddyfilePath)
return
}
// Caddy is available, try to load the config which may fail if the config is invalid. Generally, a config can
// pass the adaptation/validation step but still fail to load, for example, if it references resources that are
// not available.
if err = c.client.Load(ctx, caddyfile); err != nil {
c.log.Error("Failed to load new Caddy configuration into local Caddy instance.",
"err", err, "path", c.caddyfilePath)
} else {
c.log.Info("New Caddy configuration loaded into local Caddy instance.", "path", c.caddyfilePath)
// Don't write invalid config to disk.
return
}
// Config loaded successfully, now write it to disk.
if err = c.writeCaddyfile(caddyfile); err != nil {
c.log.Error("Failed to write Caddyfile to disk after successful load.", "err", err)
// Config is already loaded in Caddy, so this is not critical.
}
c.log.Info("New Caddy configuration loaded into local Caddy instance.", "path", c.caddyfilePath)
}
func (c *Controller) generateCaddyfile(
ctx context.Context, containers []store.ContainerRecord, caddyAvailable bool,
) (string, error) {
caddyfile, err := c.generator.Generate(ctx, containers, caddyAvailable)
if err != nil {
return "", fmt.Errorf("generate Caddyfile: %w", err)
// writeCaddyfile writes the Caddyfile content to disk with proper permissions.
func (c *Controller) writeCaddyfile(caddyfile string) error {
if err := os.WriteFile(c.caddyfilePath, []byte(caddyfile), 0o640); err != nil {
return fmt.Errorf("write Caddyfile to file '%s': %w", c.caddyfilePath, err)
}
if err = os.WriteFile(c.caddyfilePath, []byte(caddyfile), 0o640); err != nil {
return "", fmt.Errorf("write Caddyfile to file '%s': %w", c.caddyfilePath, err)
if err := fs.Chown(c.caddyfilePath, "", CaddyGroup); err != nil {
return fmt.Errorf("change owner of Caddyfile '%s': %w", c.caddyfilePath, err)
}
if err = fs.Chown(c.caddyfilePath, "", CaddyGroup); err != nil {
return "", fmt.Errorf("change owner of Caddyfile '%s': %w", c.caddyfilePath, err)
}
return caddyfile, nil
return nil
}
func (c *Controller) generateJSONConfig(containers []store.ContainerRecord) error {