mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
generate caddy config with routes from published http(s) ports
This commit is contained in:
@@ -3,15 +3,20 @@ package caddyfile
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"uncloud/internal/api"
|
||||
"uncloud/internal/fs"
|
||||
"uncloud/internal/machine/docker"
|
||||
"uncloud/internal/machine/store"
|
||||
)
|
||||
|
||||
@@ -96,28 +101,72 @@ func (c *Controller) filterAvailableContainers(containerRecords []*store.Contain
|
||||
}
|
||||
|
||||
func (c *Controller) generateConfig(containers []*api.Container) error {
|
||||
// Maps hostnames to lists of upstreams (container IP:port pairs).
|
||||
httpHostUpstreams := make(map[string][]string)
|
||||
httpsHostUpstreams := make(map[string][]string)
|
||||
for _, ctr := range containers {
|
||||
logger := slog.With("container", ctr.ID)
|
||||
network, ok := ctr.NetworkSettings.Networks[docker.NetworkName]
|
||||
if !ok {
|
||||
// Container is not connected to the uncloud Docker network (could be host network).
|
||||
continue
|
||||
}
|
||||
if network.IPAddress == "" {
|
||||
logger.Error("Container has no IPv4 address.")
|
||||
continue
|
||||
}
|
||||
|
||||
ports, err := ctr.ServicePorts()
|
||||
if err != nil {
|
||||
logger.Error("Failed to parse service ports for container.", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, port := range ports {
|
||||
switch port.Protocol {
|
||||
case api.ProtocolHTTP:
|
||||
upstream := net.JoinHostPort(network.IPAddress, strconv.Itoa(int(port.ContainerPort)))
|
||||
httpHostUpstreams[port.Hostname] = append(httpHostUpstreams[port.Hostname], upstream)
|
||||
case api.ProtocolHTTPS:
|
||||
upstream := net.JoinHostPort(network.IPAddress, strconv.Itoa(int(port.ContainerPort)))
|
||||
httpsHostUpstreams[port.Hostname] = append(httpHostUpstreams[port.Hostname], upstream)
|
||||
default:
|
||||
if port.Mode == api.PortModeIngress {
|
||||
// TODO: implement L4 ingress routing for TCP and UDP.
|
||||
logger.Error("Unsupported protocol for ingress port.", "port", port)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var warnings []caddyconfig.Warning
|
||||
servers := make(map[string]*caddyhttp.Server)
|
||||
servers["http"] = &caddyhttp.Server{
|
||||
Listen: []string{fmt.Sprintf(":%d", caddyhttp.DefaultHTTPPort)},
|
||||
Routes: hostUpstreamsToRoutes(httpHostUpstreams, &warnings),
|
||||
}
|
||||
servers["https"] = &caddyhttp.Server{
|
||||
Listen: []string{fmt.Sprintf(":%d", caddyhttp.DefaultHTTPSPort)},
|
||||
Routes: hostUpstreamsToRoutes(httpsHostUpstreams, &warnings),
|
||||
}
|
||||
|
||||
httpApp := caddyhttp.App{
|
||||
Servers: servers,
|
||||
}
|
||||
|
||||
config := &caddy.Config{
|
||||
AppsRaw: make(caddy.ModuleMap),
|
||||
AppsRaw: caddy.ModuleMap{
|
||||
"http": caddyconfig.JSON(httpApp, &warnings),
|
||||
},
|
||||
}
|
||||
var warnings []caddyconfig.Warning
|
||||
config.AppsRaw["http"] = caddyconfig.JSON(httpApp, &warnings)
|
||||
|
||||
var err error
|
||||
if len(warnings) > 0 {
|
||||
// warnings only contains errors from JSON marshaling, which are highly unlikely with correct code.
|
||||
for _, w := range warnings {
|
||||
slog.Warn("Generate Caddy configuration warning.", "warn", w.String())
|
||||
err = errors.Join(err, errors.New(w.Message))
|
||||
}
|
||||
return fmt.Errorf("marshal Caddy configuration: %w", err)
|
||||
}
|
||||
|
||||
configBytes, err := json.MarshalIndent(config, "", " ")
|
||||
@@ -134,3 +183,31 @@ func (c *Controller) generateConfig(containers []*api.Container) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hostUpstreamsToRoutes converts a map of hostnames to upstreams to a list of Caddy routes.
|
||||
func hostUpstreamsToRoutes(hostUpstreams map[string][]string, warnings *[]caddyconfig.Warning) []caddyhttp.Route {
|
||||
routes := make([]caddyhttp.Route, 0, len(hostUpstreams))
|
||||
for hostname, upstreams := range hostUpstreams {
|
||||
upstreamPool := make([]*reverseproxy.Upstream, len(upstreams))
|
||||
for i, upstream := range upstreams {
|
||||
upstreamPool[i] = &reverseproxy.Upstream{
|
||||
Dial: upstream,
|
||||
}
|
||||
}
|
||||
handler := &reverseproxy.Handler{
|
||||
Upstreams: upstreamPool,
|
||||
}
|
||||
|
||||
routes = append(routes, caddyhttp.Route{
|
||||
MatcherSetsRaw: caddyhttp.RawMatcherSets{
|
||||
{
|
||||
"host": caddyconfig.JSON(caddyhttp.MatchHost{hostname}, warnings),
|
||||
},
|
||||
},
|
||||
HandlersRaw: []json.RawMessage{
|
||||
caddyconfig.JSONModuleObject(handler, "handler", "reverse_proxy", warnings),
|
||||
},
|
||||
})
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user