Skip to content

Commit

Permalink
Merge pull request #548 from thekondor/423__envfile-cli-support
Browse files Browse the repository at this point in the history
Support `--envfile` CLI argument
  • Loading branch information
lucaslorentz authored Nov 29, 2023
2 parents 0956767 + 767ab0a commit c2e7a64
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 79 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ Run `caddy help docker-proxy` to see all available flags.
Usage of docker-proxy:
--caddyfile-path string
Path to a base Caddyfile that will be extended with Docker sites
--envfile
Path to an environment file with environment variables in the KEY=VALUE format to load into the Caddy process
--controller-network string
Network allowed to configure Caddy server in CIDR notation. Ex: 10.200.200.0/24
--ingress-networks string
Expand Down Expand Up @@ -515,6 +517,7 @@ Those flags can also be set via environment variables:
```
CADDY_DOCKER_CADDYFILE_PATH=<string>
CADDY_DOCKER_ENVFILE=<string>
CADDY_CONTROLLER_NETWORK=<string>
CADDY_INGRESS_NETWORKS=<string>
CADDY_DOCKER_SOCKETS=<string>
Expand Down
10 changes: 10 additions & 0 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ func init() {
fs.String("caddyfile-path", "",
"Path to a base Caddyfile that will be extended with docker sites")

fs.String("envfile", "",
"Environment file with environment variables in the KEY=VALUE format")

fs.String("label-prefix", generator.DefaultLabelPrefix,
"Prefix for Docker labels")

Expand Down Expand Up @@ -141,6 +144,7 @@ func getAdminListen(options *config.Options) string {

func createOptions(flags caddycmd.Flags) *config.Options {
caddyfilePath := flags.String("caddyfile-path")
envFile := flags.String("envfile")
labelPrefixFlag := flags.String("label-prefix")
proxyServiceTasksFlag := flags.Bool("proxy-service-tasks")
processCaddyfileFlag := flags.Bool("process-caddyfile")
Expand Down Expand Up @@ -222,6 +226,12 @@ func createOptions(flags caddycmd.Flags) *config.Options {
options.CaddyfilePath = caddyfilePath
}

if envFileEnv := os.Getenv("CADDY_DOCKER_ENVFILE"); envFileEnv != "" {
options.EnvFile = envFileEnv
} else {
options.EnvFile = envFile
}

if labelPrefixEnv := os.Getenv("CADDY_DOCKER_LABEL_PREFIX"); labelPrefixEnv != "" {
options.LabelPrefix = labelPrefixEnv
} else {
Expand Down
1 change: 1 addition & 0 deletions config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
// Options are the options for generator
type Options struct {
CaddyfilePath string
EnvFile string
DockerSockets []string
DockerCertsPath []string
DockerAPIsVersion []string
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/libdns/libdns v0.2.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@ github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
Expand Down
170 changes: 91 additions & 79 deletions loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/joho/godotenv"
"github.com/lucaslorentz/caddy-docker-proxy/v2/config"
"github.com/lucaslorentz/caddy-docker-proxy/v2/docker"
"github.com/lucaslorentz/caddy-docker-proxy/v2/generator"
Expand Down Expand Up @@ -59,104 +60,115 @@ func logger() *zap.Logger {

// Start docker loader
func (dockerLoader *DockerLoader) Start() error {
if !dockerLoader.initialized {
dockerLoader.initialized = true
log := logger()

dockerClients := []docker.Client{}
for i, dockerSocket := range dockerLoader.options.DockerSockets {
// cf https://github.com/docker/go-docker/blob/master/client.go
// setenv to use NewEnvClient
// or manually
if dockerLoader.initialized {
return nil
}

os.Setenv("DOCKER_HOST", dockerSocket)
dockerLoader.initialized = true
log := logger()

if len(dockerLoader.options.DockerCertsPath) >= i+1 && dockerLoader.options.DockerCertsPath[i] != "" {
os.Setenv("DOCKER_CERT_PATH", dockerLoader.options.DockerCertsPath[i])
} else {
os.Unsetenv("DOCKER_CERT_PATH")
}
if envFile := dockerLoader.options.EnvFile; envFile != "" {
if err := godotenv.Load(dockerLoader.options.EnvFile); err != nil {
log.Error("Load variables from environment file failed", zap.Error(err), zap.String("envFile", dockerLoader.options.EnvFile))
return err
}
log.Info("environment file loaded", zap.String("envFile", dockerLoader.options.EnvFile))
}

if len(dockerLoader.options.DockerAPIsVersion) >= i+1 && dockerLoader.options.DockerAPIsVersion[i] != "" {
os.Setenv("DOCKER_API_VERSION", dockerLoader.options.DockerAPIsVersion[i])
} else {
os.Unsetenv("DOCKER_API_VERSION")
}
dockerClients := []docker.Client{}
for i, dockerSocket := range dockerLoader.options.DockerSockets {
// cf https://github.com/docker/go-docker/blob/master/client.go
// setenv to use NewEnvClient
// or manually

dockerClient, err := client.NewEnvClient()
if err != nil {
log.Error("Docker connection failed to docker specify socket", zap.Error(err), zap.String("DockerSocket", dockerSocket))
return err
}
os.Setenv("DOCKER_HOST", dockerSocket)

dockerPing, err := dockerClient.Ping(context.Background())
if err != nil {
log.Error("Docker ping failed on specify socket", zap.Error(err), zap.String("DockerSocket", dockerSocket))
return err
}
if len(dockerLoader.options.DockerCertsPath) >= i+1 && dockerLoader.options.DockerCertsPath[i] != "" {
os.Setenv("DOCKER_CERT_PATH", dockerLoader.options.DockerCertsPath[i])
} else {
os.Unsetenv("DOCKER_CERT_PATH")
}

dockerClient.NegotiateAPIVersionPing(dockerPing)
if len(dockerLoader.options.DockerAPIsVersion) >= i+1 && dockerLoader.options.DockerAPIsVersion[i] != "" {
os.Setenv("DOCKER_API_VERSION", dockerLoader.options.DockerAPIsVersion[i])
} else {
os.Unsetenv("DOCKER_API_VERSION")
}

wrappedClient := docker.WrapClient(dockerClient)
dockerClient, err := client.NewEnvClient()
if err != nil {
log.Error("Docker connection failed to docker specify socket", zap.Error(err), zap.String("DockerSocket", dockerSocket))
return err
}

dockerClients = append(dockerClients, wrappedClient)
dockerPing, err := dockerClient.Ping(context.Background())
if err != nil {
log.Error("Docker ping failed on specify socket", zap.Error(err), zap.String("DockerSocket", dockerSocket))
return err
}

// by default it will used the env docker
if len(dockerClients) == 0 {
dockerClient, err := client.NewEnvClient()
dockerLoader.options.DockerSockets = append(dockerLoader.options.DockerSockets, os.Getenv("DOCKER_HOST"))
if err != nil {
log.Error("Docker connection failed", zap.Error(err))
return err
}
dockerClient.NegotiateAPIVersionPing(dockerPing)

dockerPing, err := dockerClient.Ping(context.Background())
if err != nil {
log.Error("Docker ping failed", zap.Error(err))
return err
}
wrappedClient := docker.WrapClient(dockerClient)

dockerClient.NegotiateAPIVersionPing(dockerPing)
dockerClients = append(dockerClients, wrappedClient)
}

wrappedClient := docker.WrapClient(dockerClient)
// by default it will used the env docker
if len(dockerClients) == 0 {
dockerClient, err := client.NewEnvClient()
dockerLoader.options.DockerSockets = append(dockerLoader.options.DockerSockets, os.Getenv("DOCKER_HOST"))
if err != nil {
log.Error("Docker connection failed", zap.Error(err))
return err
}

dockerClients = append(dockerClients, wrappedClient)
dockerPing, err := dockerClient.Ping(context.Background())
if err != nil {
log.Error("Docker ping failed", zap.Error(err))
return err
}

dockerLoader.dockerClients = dockerClients
dockerLoader.skipEvents = make([]bool, len(dockerLoader.dockerClients))

dockerLoader.generator = generator.CreateGenerator(
dockerClients,
docker.CreateUtils(),
dockerLoader.options,
)

log.Info(
"Start",
zap.String("CaddyfilePath", dockerLoader.options.CaddyfilePath),
zap.String("LabelPrefix", dockerLoader.options.LabelPrefix),
zap.Duration("PollingInterval", dockerLoader.options.PollingInterval),
zap.Bool("ProxyServiceTasks", dockerLoader.options.ProxyServiceTasks),
zap.Bool("ProcessCaddyfile", dockerLoader.options.ProcessCaddyfile),
zap.Bool("ScanStoppedContainers", dockerLoader.options.ScanStoppedContainers),
zap.String("IngressNetworks", fmt.Sprintf("%v", dockerLoader.options.IngressNetworks)),
zap.Strings("DockerSockets", dockerLoader.options.DockerSockets),
zap.Strings("DockerCertsPath", dockerLoader.options.DockerCertsPath),
zap.Strings("DockerAPIsVersion", dockerLoader.options.DockerAPIsVersion),
)

ready := make(chan struct{})
dockerLoader.timer = time.AfterFunc(0, func() {
<-ready
dockerLoader.update()
})
close(ready)
dockerClient.NegotiateAPIVersionPing(dockerPing)

wrappedClient := docker.WrapClient(dockerClient)

go dockerLoader.monitorEvents()
dockerClients = append(dockerClients, wrappedClient)
}

dockerLoader.dockerClients = dockerClients
dockerLoader.skipEvents = make([]bool, len(dockerLoader.dockerClients))

dockerLoader.generator = generator.CreateGenerator(
dockerClients,
docker.CreateUtils(),
dockerLoader.options,
)

log.Info(
"Start",
zap.String("CaddyfilePath", dockerLoader.options.CaddyfilePath),
zap.String("EnvFile", dockerLoader.options.EnvFile),
zap.String("LabelPrefix", dockerLoader.options.LabelPrefix),
zap.Duration("PollingInterval", dockerLoader.options.PollingInterval),
zap.Bool("ProxyServiceTasks", dockerLoader.options.ProxyServiceTasks),
zap.Bool("ProcessCaddyfile", dockerLoader.options.ProcessCaddyfile),
zap.Bool("ScanStoppedContainers", dockerLoader.options.ScanStoppedContainers),
zap.String("IngressNetworks", fmt.Sprintf("%v", dockerLoader.options.IngressNetworks)),
zap.Strings("DockerSockets", dockerLoader.options.DockerSockets),
zap.Strings("DockerCertsPath", dockerLoader.options.DockerCertsPath),
zap.Strings("DockerAPIsVersion", dockerLoader.options.DockerAPIsVersion),
)

ready := make(chan struct{})
dockerLoader.timer = time.AfterFunc(0, func() {
<-ready
dockerLoader.update()
})
close(ready)

go dockerLoader.monitorEvents()

return nil
}

Expand Down
6 changes: 6 additions & 0 deletions tests/envfile/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
service.local {
handle {$ENV_HANDLE_PATH} {
respond "Hello from TestEnv"
}
tls internal
}
1 change: 1 addition & 0 deletions tests/envfile/Envfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ENV_HANDLE_PATH=/testenv
31 changes: 31 additions & 0 deletions tests/envfile/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
version: '3.7'

services:
caddy:
image: caddy-docker-proxy:local
ports:
- 80:80
- 443:443
networks:
- caddy
environment:
- CADDY_DOCKER_CADDYFILE_PATH=/etc/caddy/Caddyfile
command: ["docker-proxy", "--envfile", "/etc/caddy/env"]
volumes:
- source: "./Caddyfile"
target: '/etc/caddy/Caddyfile'
type: bind
- source: "./Envfile"
target: "/etc/caddy/env"
type: bind
- source: "${DOCKER_SOCKET_PATH}"
target: "${DOCKER_SOCKET_PATH}"
type: ${DOCKER_SOCKET_TYPE}

networks:
caddy:
name: caddy_test
external: true
internal:
name: internal
internal: true
12 changes: 12 additions & 0 deletions tests/envfile/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash

set -e

. ../functions.sh

docker stack deploy -c compose.yaml --prune caddy_test

retry curl --show-error -s -k -f --resolve service.local:443:127.0.0.1 https://service.local/testenv | grep "Hello from TestEnv" || {
docker service logs caddy_test_caddy
exit 1
}

0 comments on commit c2e7a64

Please sign in to comment.