mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
chore: generate a minimal Caddyfile with verify handler alongside caddy.json
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
package caddyconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateCaddyfile(containers []api.ServiceContainer, verifyResponse string) (string, error) {
|
||||||
|
return fmt.Sprintf(`http:// {
|
||||||
|
handle %s {
|
||||||
|
respond "%s" 200
|
||||||
|
}
|
||||||
|
log
|
||||||
|
}
|
||||||
|
`, VerifyPath, verifyResponse), nil
|
||||||
|
}
|
||||||
@@ -23,23 +23,24 @@ const (
|
|||||||
// network.
|
// network.
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
store *store.Store
|
store *store.Store
|
||||||
path string
|
configDir string
|
||||||
verifyResponse string
|
verifyResponse string
|
||||||
|
log *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewController(store *store.Store, path string, verifyResponse string) (*Controller, error) {
|
func NewController(store *store.Store, configDir string, verifyResponse string) (*Controller, error) {
|
||||||
dir := filepath.Dir(path)
|
if err := os.MkdirAll(configDir, 0o750); err != nil {
|
||||||
if err := os.MkdirAll(dir, 0o750); err != nil {
|
return nil, fmt.Errorf("create directory for Caddy configuration '%s': %w", configDir, err)
|
||||||
return nil, fmt.Errorf("create parent directory for Caddy configuration '%s': %w", dir, err)
|
|
||||||
}
|
}
|
||||||
if err := fs.Chown(dir, "", CaddyGroup); err != nil {
|
if err := fs.Chown(configDir, "", CaddyGroup); err != nil {
|
||||||
return nil, fmt.Errorf("change owner of parent directory for Caddy configuration '%s': %w", dir, err)
|
return nil, fmt.Errorf("change owner of directory for Caddy configuration '%s': %w", configDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Controller{
|
return &Controller{
|
||||||
store: store,
|
store: store,
|
||||||
path: path,
|
configDir: configDir,
|
||||||
verifyResponse: verifyResponse,
|
verifyResponse: verifyResponse,
|
||||||
|
log: slog.With("component", "caddy-controller"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,14 +49,18 @@ func (c *Controller) Run(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("subscribe to container changes: %w", err)
|
return fmt.Errorf("subscribe to container changes: %w", err)
|
||||||
}
|
}
|
||||||
slog.Info("Subscribed to container changes in the cluster to generate Caddy configuration.")
|
c.log.Info("Subscribed to container changes in the cluster to generate Caddy configuration.")
|
||||||
|
|
||||||
containers, err := c.filterAvailableContainers(containerRecords)
|
containers, err := c.filterAvailableContainers(containerRecords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("filter available containers: %w", err)
|
return fmt.Errorf("filter available containers: %w", err)
|
||||||
}
|
}
|
||||||
if err = c.generateConfig(containers); err != nil {
|
|
||||||
return fmt.Errorf("generate Caddy configuration: %w", err)
|
if err = c.generateCaddyfile(containers); err != nil {
|
||||||
|
return fmt.Errorf("generate Caddyfile configuration: %w", err)
|
||||||
|
}
|
||||||
|
if err = c.generateJSONConfig(containers); err != nil {
|
||||||
|
return fmt.Errorf("generate Caddy JSON configuration: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -64,23 +69,27 @@ func (c *Controller) Run(ctx context.Context) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("containers subscription failed")
|
return fmt.Errorf("containers subscription failed")
|
||||||
}
|
}
|
||||||
slog.Debug("Cluster containers changed, updating Caddy configuration.")
|
c.log.Info("Cluster containers changed, updating Caddy configuration.")
|
||||||
|
|
||||||
containerRecords, err = c.store.ListContainers(ctx, store.ListOptions{})
|
containerRecords, err = c.store.ListContainers(ctx, store.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to list containers.", "err", err)
|
c.log.Info("Failed to list containers.", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
containers, err = c.filterAvailableContainers(containerRecords)
|
containers, err = c.filterAvailableContainers(containerRecords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to filter available containers.", "err", err)
|
c.log.Info("Failed to filter available containers.", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err = c.generateConfig(containers); err != nil {
|
|
||||||
slog.Error("Failed to generate Caddy configuration.", "err", err)
|
if err = c.generateCaddyfile(containers); err != nil {
|
||||||
|
c.log.Info("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)
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("Updated Caddy configuration.", "path", c.path)
|
c.log.Info("Updated Caddy configuration.", "dir", c.configDir)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -100,8 +109,25 @@ func (c *Controller) filterAvailableContainers(
|
|||||||
return containers, nil
|
return containers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) generateConfig(containers []api.ServiceContainer) error {
|
func (c *Controller) generateCaddyfile(containers []api.ServiceContainer) error {
|
||||||
config, err := GenerateConfig(containers, c.verifyResponse)
|
caddyfile, err := GenerateCaddyfile(containers, c.verifyResponse)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate Caddyfile: %w", err)
|
||||||
|
}
|
||||||
|
caddyfilePath := filepath.Join(c.configDir, "Caddyfile")
|
||||||
|
|
||||||
|
if err = os.WriteFile(caddyfilePath, []byte(caddyfile), 0o640); err != nil {
|
||||||
|
return fmt.Errorf("write Caddyfile to file '%s': %w", caddyfilePath, err)
|
||||||
|
}
|
||||||
|
if err = fs.Chown(caddyfilePath, "", CaddyGroup); err != nil {
|
||||||
|
return fmt.Errorf("change owner of Caddyfile '%s': %w", caddyfilePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) generateJSONConfig(containers []api.ServiceContainer) error {
|
||||||
|
config, err := GenerateJSONConfig(containers, c.verifyResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -110,12 +136,13 @@ func (c *Controller) generateConfig(containers []api.ServiceContainer) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshal Caddy configuration: %w", err)
|
return fmt.Errorf("marshal Caddy configuration: %w", err)
|
||||||
}
|
}
|
||||||
|
configPath := filepath.Join(c.configDir, "caddy.json")
|
||||||
|
|
||||||
if err = os.WriteFile(c.path, configBytes, 0o640); err != nil {
|
if err = os.WriteFile(configPath, configBytes, 0o640); err != nil {
|
||||||
return fmt.Errorf("write Caddy configuration to file '%s': %w", c.path, err)
|
return fmt.Errorf("write Caddy configuration to file '%s': %w", configPath, err)
|
||||||
}
|
}
|
||||||
if err = fs.Chown(c.path, "", CaddyGroup); err != nil {
|
if err = fs.Chown(configPath, "", CaddyGroup); err != nil {
|
||||||
return fmt.Errorf("change owner of Caddy configuration file '%s': %w", c.path, err)
|
return fmt.Errorf("change owner of Caddy configuration file '%s': %w", configPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateConfig(containers []api.ServiceContainer, verifyResponse string) (*caddy.Config, error) {
|
func GenerateJSONConfig(containers []api.ServiceContainer, verifyResponse string) (*caddy.Config, error) {
|
||||||
// Maps hostnames to lists of upstreams (container IP:port pairs).
|
// Maps hostnames to lists of upstreams (container IP:port pairs).
|
||||||
httpHostUpstreams := make(map[string][]string)
|
httpHostUpstreams := make(map[string][]string)
|
||||||
httpsHostUpstreams := make(map[string][]string)
|
httpsHostUpstreams := make(map[string][]string)
|
||||||
+1
-1
@@ -378,7 +378,7 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
config, err := GenerateConfig(tt.containers, "verification-response-body")
|
config, err := GenerateJSONConfig(tt.containers, "verification-response-body")
|
||||||
|
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
@@ -61,9 +61,9 @@ type Config struct {
|
|||||||
// DockerClient manages system and user containers using the local Docker daemon.
|
// DockerClient manages system and user containers using the local Docker daemon.
|
||||||
DockerClient *client.Client
|
DockerClient *client.Client
|
||||||
|
|
||||||
// CaddyConfigPath specifies where the machine generates the Caddy reverse proxy configuration file for routing
|
// CaddyConfigDir specifies the directory where the machine generates the Caddy reverse proxy configuration file
|
||||||
// external traffic to service containers across the internal network. Default is DataDir/caddy/caddy.json.
|
// for routing external traffic to service containers across the internal network. Default is DataDir/caddy.
|
||||||
CaddyConfigPath string
|
CaddyConfigDir string
|
||||||
// DNSUpstreams specifies the upstream DNS servers for the embedded internal DNS server.
|
// DNSUpstreams specifies the upstream DNS servers for the embedded internal DNS server.
|
||||||
DNSUpstreams []netip.AddrPort
|
DNSUpstreams []netip.AddrPort
|
||||||
}
|
}
|
||||||
@@ -128,8 +128,8 @@ func (c *Config) SetDefaults() (*Config, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.CaddyConfigPath == "" {
|
if cfg.CaddyConfigDir == "" {
|
||||||
cfg.CaddyConfigPath = filepath.Join(cfg.DataDir, "caddy", "caddy.json")
|
cfg.CaddyConfigDir = filepath.Join(cfg.DataDir, "caddy")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
@@ -387,7 +387,7 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
// Create a new caddyconfig controller for managing the Caddy reverse proxy configuration.
|
// Create a new caddyconfig controller for managing the Caddy reverse proxy configuration.
|
||||||
// It will also serve the current machine ID at /.uncloud-verify to verify Caddy reachability.
|
// It will also serve the current machine ID at /.uncloud-verify to verify Caddy reachability.
|
||||||
caddyconfigCtrl, err := caddyconfig.NewController(m.store, m.config.CaddyConfigPath, m.state.ID)
|
caddyconfigCtrl, err := caddyconfig.NewController(m.store, m.config.CaddyConfigDir, m.state.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create caddyconfig controller: %w", err)
|
return fmt.Errorf("create caddyconfig controller: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user