mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
chore: refactor Caddyfile generator to accept a validator
This commit is contained in:
@@ -45,7 +45,43 @@ https://{{$hostname}} {
|
||||
}{{end}}
|
||||
`
|
||||
|
||||
func GenerateCaddyfile(containers []api.ServiceContainer, verifyResponse string) (string, error) {
|
||||
// CaddyfileGenerator generates a Caddyfile configuration for the Caddy reverse proxy.
|
||||
type CaddyfileGenerator struct {
|
||||
// MachineID is the unique identifier of the machine where the controller is running.
|
||||
MachineID string
|
||||
Validator CaddyfileValidator
|
||||
}
|
||||
|
||||
// CaddyfileValidator is an interface for validating Caddyfile configurations.
|
||||
type CaddyfileValidator interface {
|
||||
Validate(caddyfile string) error
|
||||
}
|
||||
|
||||
// Generate creates a Caddyfile configuration based on the provided service containers.
|
||||
// If a 'caddy' service container is running on this machine and defines a custom Caddy config (x-caddy) in its service
|
||||
// spec, it will be validated and prepended to the generated Caddyfile. Custom Caddy configs (x-caddy) defined in other
|
||||
// service specs are validated and appended to the generated Caddyfile. Invalid configs are logged and skipped to ensure
|
||||
// the generated Caddyfile remains valid.
|
||||
//
|
||||
// The final Caddyfile structure includes:
|
||||
//
|
||||
// [caddy x-caddy]
|
||||
// [generated Caddyfile from all service ports]
|
||||
// [service-a x-caddy]
|
||||
// ...
|
||||
// [service-z x-caddy]
|
||||
func (g *CaddyfileGenerator) Generate(containers []api.ServiceContainer) (string, error) {
|
||||
baseCaddyfile, err := g.generateBaseFromPorts(containers)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("generate base Caddyfile from service ports: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Implement support for custom Caddy configs (x-caddy) in service specs.
|
||||
|
||||
return baseCaddyfile, nil
|
||||
}
|
||||
|
||||
func (g *CaddyfileGenerator) generateBaseFromPorts(containers []api.ServiceContainer) (string, error) {
|
||||
httpHostUpstreams, httpsHostUpstreams := httpUpstreamsFromContainers(containers)
|
||||
|
||||
funcs := template.FuncMap{"join": strings.Join}
|
||||
@@ -61,7 +97,7 @@ func GenerateCaddyfile(containers []api.ServiceContainer, verifyResponse string)
|
||||
HTTPSHostUpstreams map[string][]string
|
||||
}{
|
||||
VerifyPath: VerifyPath,
|
||||
VerifyResponse: verifyResponse,
|
||||
VerifyResponse: g.MachineID,
|
||||
HTTPHostUpstreams: httpHostUpstreams,
|
||||
HTTPSHostUpstreams: httpsHostUpstreams,
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGenerateCaddyfile(t *testing.T) {
|
||||
func TestCaddyfileGenerator(t *testing.T) {
|
||||
caddyfileHeader := `http:// {
|
||||
handle /.uncloud-verify {
|
||||
respond "verification-response-body" 200
|
||||
respond "test-machine-id" 200
|
||||
}
|
||||
log
|
||||
}
|
||||
@@ -24,6 +24,10 @@ func TestGenerateCaddyfile(t *testing.T) {
|
||||
}
|
||||
`
|
||||
|
||||
generator := &CaddyfileGenerator{
|
||||
MachineID: "test-machine-id",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
containers []api.ServiceContainer
|
||||
@@ -182,7 +186,7 @@ http://app.example.com {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config, err := GenerateCaddyfile(tt.containers, "verification-response-body")
|
||||
config, err := generator.Generate(tt.containers)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
|
||||
@@ -22,13 +22,14 @@ const (
|
||||
// proxy. The generated configuration allows Caddy to route external traffic to service containers across the internal
|
||||
// network.
|
||||
type Controller struct {
|
||||
store *store.Store
|
||||
configDir string
|
||||
verifyResponse string
|
||||
log *slog.Logger
|
||||
machineID string
|
||||
configDir string
|
||||
generator *CaddyfileGenerator
|
||||
store *store.Store
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func NewController(store *store.Store, configDir string, verifyResponse string) (*Controller, error) {
|
||||
func NewController(machineID, configDir, adminSock string, store *store.Store) (*Controller, error) {
|
||||
if err := os.MkdirAll(configDir, 0o750); err != nil {
|
||||
return nil, fmt.Errorf("create directory for Caddy configuration '%s': %w", configDir, err)
|
||||
}
|
||||
@@ -36,11 +37,16 @@ func NewController(store *store.Store, configDir string, verifyResponse string)
|
||||
return nil, fmt.Errorf("change owner of directory for Caddy configuration '%s': %w", configDir, err)
|
||||
}
|
||||
|
||||
generator := &CaddyfileGenerator{
|
||||
MachineID: machineID,
|
||||
}
|
||||
|
||||
return &Controller{
|
||||
store: store,
|
||||
configDir: configDir,
|
||||
verifyResponse: verifyResponse,
|
||||
log: slog.With("component", "caddy-controller"),
|
||||
machineID: machineID,
|
||||
configDir: configDir,
|
||||
generator: generator,
|
||||
store: store,
|
||||
log: slog.With("component", "caddy-controller"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -73,20 +79,20 @@ func (c *Controller) Run(ctx context.Context) error {
|
||||
|
||||
containerRecords, err = c.store.ListContainers(ctx, store.ListOptions{})
|
||||
if err != nil {
|
||||
c.log.Info("Failed to list containers.", "err", err)
|
||||
c.log.Error("Failed to list containers.", "err", err)
|
||||
continue
|
||||
}
|
||||
containers, err = c.filterAvailableContainers(containerRecords)
|
||||
if err != nil {
|
||||
c.log.Info("Failed to filter available containers.", "err", err)
|
||||
c.log.Error("Failed to filter available containers.", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = c.generateCaddyfile(containers); err != nil {
|
||||
c.log.Info("Failed to generate Caddyfile configuration.", "err", err)
|
||||
c.log.Error("Failed to generate Caddyfile configuration.", "err", err)
|
||||
}
|
||||
if err = c.generateJSONConfig(containers); err != nil {
|
||||
c.log.Info("Failed to generate Caddy JSON configuration.", "err", err)
|
||||
c.log.Error("Failed to generate Caddy JSON configuration.", "err", err)
|
||||
}
|
||||
|
||||
c.log.Info("Updated Caddy configuration.", "dir", c.configDir)
|
||||
@@ -110,12 +116,13 @@ func (c *Controller) filterAvailableContainers(
|
||||
}
|
||||
|
||||
func (c *Controller) generateCaddyfile(containers []api.ServiceContainer) error {
|
||||
caddyfile, err := GenerateCaddyfile(containers, c.verifyResponse)
|
||||
caddyfile, err := c.generator.Generate(containers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate Caddyfile: %w", err)
|
||||
}
|
||||
caddyfilePath := filepath.Join(c.configDir, "Caddyfile")
|
||||
|
||||
// TODO: use atomic file write to avoid partial loads on Caddy watch reload.
|
||||
if err = os.WriteFile(caddyfilePath, []byte(caddyfile), 0o640); err != nil {
|
||||
return fmt.Errorf("write Caddyfile to file '%s': %w", caddyfilePath, err)
|
||||
}
|
||||
@@ -127,7 +134,7 @@ func (c *Controller) generateCaddyfile(containers []api.ServiceContainer) error
|
||||
}
|
||||
|
||||
func (c *Controller) generateJSONConfig(containers []api.ServiceContainer) error {
|
||||
config, err := GenerateJSONConfig(containers, c.verifyResponse)
|
||||
config, err := GenerateJSONConfig(containers, c.machineID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user