Files

202 lines
5.8 KiB
Go

package plugin
import (
"flag"
"log"
"net"
"os"
"regexp"
"strings"
"time"
"github.com/caddyserver/caddy/v2"
caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/lucaslorentz/caddy-docker-proxy/plugin/v2/config"
"github.com/lucaslorentz/caddy-docker-proxy/plugin/v2/generator"
)
var isTrue = regexp.MustCompile("(?i)^(true|yes|1)$")
func init() {
caddycmd.RegisterCommand(caddycmd.Command{
Name: "docker-proxy",
Func: cmdFunc,
Usage: "<command>",
Short: "Run caddy as a docker proxy",
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("docker-proxy", flag.ExitOnError)
fs.Bool("mode", false,
"Which mode this instance should run: standalone | controller | server")
fs.String("controller-network", "",
"Network allowed to configure caddy server in CIDR notation. Ex: 10.200.200.0/24")
fs.String("ingress-networks", "",
"Comma separated name of ingress networks connecting caddy servers to containers.\n"+
"When not defined, networks attached to controller container are considered ingress networks")
fs.String("caddyfile-path", "",
"Path to a base Caddyfile that will be extended with docker sites")
fs.String("label-prefix", generator.DefaultLabelPrefix,
"Prefix for Docker labels")
fs.Bool("proxy-service-tasks", true,
"Proxy to service tasks instead of service load balancer")
fs.Bool("process-caddyfile", true,
"Process Caddyfile before loading it, removing invalid servers")
fs.Duration("polling-interval", 30*time.Second,
"Interval caddy should manually check docker for a new caddyfile")
return fs
}(),
})
}
func cmdFunc(flags caddycmd.Flags) (int, error) {
caddy.TrapSignals()
options := createOptions(flags)
if options.Mode&config.Server == config.Server {
log.Printf("[INFO] Running caddy proxy server")
caddy.Run(&caddy.Config{
Admin: &caddy.AdminConfig{
Listen: getAdminListen(options),
},
})
}
if options.Mode&config.Controller == config.Controller {
log.Printf("[INFO] Running caddy proxy controller")
loader := CreateDockerLoader(options)
loader.Start()
}
select {}
}
func getAdminListen(options *config.Options) string {
if options.ControllerNetwork != nil {
ifaces, err := net.Interfaces()
if err != nil {
log.Printf("[ERROR] Failed to get network interfaces: %v", err)
}
for _, i := range ifaces {
addrs, err := i.Addrs()
if err != nil {
log.Printf("[ERROR] Failed to get network interface addresses: %v", err)
continue
}
for _, a := range addrs {
switch v := a.(type) {
case *net.IPAddr:
if options.ControllerNetwork.Contains(v.IP) {
return "tcp/" + v.IP.String() + ":2019"
}
break
case *net.IPNet:
if options.ControllerNetwork.Contains(v.IP) {
return "tcp/" + v.IP.String() + ":2019"
}
break
}
}
}
}
return "tcp/localhost:2019"
}
func createOptions(flags caddycmd.Flags) *config.Options {
caddyfilePath := flags.String("caddyfile-path")
labelPrefixFlag := flags.String("label-prefix")
proxyServiceTasksFlag := flags.Bool("proxy-service-tasks")
processCaddyfileFlag := flags.Bool("process-caddyfile")
pollingIntervalFlag := flags.Duration("polling-interval")
modeFlag := flags.String("mode")
controllerSubnetFlag := flags.String("controller-network")
ingressNetworksFlag := flags.String("ingress-networks")
options := &config.Options{}
var mode string
if modeEnv := os.Getenv("CADDY_DOCKER_MODE"); modeEnv != "" {
mode = modeEnv
} else {
mode = modeFlag
}
switch mode {
case "controller":
options.Mode = config.Controller
break
case "server":
options.Mode = config.Server
default:
options.Mode = config.Standalone
}
if controllerIPRangeEnv := os.Getenv("CADDY_CONTROLLER_NETWORK"); controllerIPRangeEnv != "" {
_, ipNet, err := net.ParseCIDR(controllerIPRangeEnv)
if err != nil {
log.Printf("[ERROR] Failed to parse CADDY_CONTROLLER_NETWORK %v: %v", controllerIPRangeEnv, err)
} else if ipNet != nil {
options.ControllerNetwork = ipNet
}
} else if controllerSubnetFlag != "" {
_, ipNet, err := net.ParseCIDR(controllerSubnetFlag)
if err != nil {
log.Printf("[ERROR] Failed to parse controller-network %v: %v", controllerSubnetFlag, err)
} else if ipNet != nil {
options.ControllerNetwork = ipNet
}
}
if ingressNetworksEnv := os.Getenv("CADDY_INGRESS_NETWORKS"); ingressNetworksEnv != "" {
options.IngressNetworks = strings.Split(ingressNetworksEnv, ",")
} else if ingressNetworksFlag != "" {
options.IngressNetworks = strings.Split(ingressNetworksFlag, ",")
}
if caddyfilePathEnv := os.Getenv("CADDY_DOCKER_CADDYFILE_PATH"); caddyfilePathEnv != "" {
options.CaddyfilePath = caddyfilePathEnv
} else {
options.CaddyfilePath = caddyfilePath
}
if labelPrefixEnv := os.Getenv("CADDY_DOCKER_LABEL_PREFIX"); labelPrefixEnv != "" {
options.LabelPrefix = labelPrefixEnv
} else {
options.LabelPrefix = labelPrefixFlag
}
options.ControlledServersLabel = options.LabelPrefix + "_controlled_server"
if proxyServiceTasksEnv := os.Getenv("CADDY_DOCKER_PROXY_SERVICE_TASKS"); proxyServiceTasksEnv != "" {
options.ProxyServiceTasks = isTrue.MatchString(proxyServiceTasksEnv)
} else {
options.ProxyServiceTasks = proxyServiceTasksFlag
}
if processCaddyfileEnv := os.Getenv("CADDY_DOCKER_PROCESS_CADDYFILE"); processCaddyfileEnv != "" {
options.ProcessCaddyfile = isTrue.MatchString(processCaddyfileEnv)
} else {
options.ProcessCaddyfile = processCaddyfileFlag
}
if pollingIntervalEnv := os.Getenv("CADDY_DOCKER_POLLING_INTERVAL"); pollingIntervalEnv != "" {
if p, err := time.ParseDuration(pollingIntervalEnv); err != nil {
log.Printf("[ERROR] Failed to parse CADDY_DOCKER_POLLING_INTERVAL: %v", err)
options.PollingInterval = pollingIntervalFlag
} else {
options.PollingInterval = p
}
} else {
options.PollingInterval = pollingIntervalFlag
}
return options
}