diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml index 6d60f2d5..41bed37f 100644 --- a/.github/workflows/assets.yml +++ b/.github/workflows/assets.yml @@ -1,5 +1,8 @@ name: Release assets +permissions: + contents: write + on: push: tags: @@ -11,11 +14,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 1 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod diff --git a/.github/workflows/build-publish-dispatch.yml b/.github/workflows/build-publish-dispatch.yml new file mode 100644 index 00000000..60de02c6 --- /dev/null +++ b/.github/workflows/build-publish-dispatch.yml @@ -0,0 +1,83 @@ +name: Build and publish Docker images on demand + +permissions: + contents: read + packages: write + +on: + workflow_dispatch: + inputs: + image_tag: + description: "Image tag" + type: string + required: true + +jobs: + multiarch-build: + name: Build and publish ${{ matrix.base }} image with tag ${{ inputs.image_tag }} + strategy: + matrix: + base: [alpine, debian] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Retrieve version + id: docker-gen_version + run: echo "VERSION=$(git describe --tags)" >> "$GITHUB_OUTPUT" + + - name: Get Docker tags + id: docker_meta + uses: docker/metadata-action@v5 + with: + images: | + nginxproxy/docker-gen + tags: | + type=raw,value=${{ inputs.image_tag }},enable=${{ matrix.base == 'alpine' }} + type=raw,value=${{ inputs.image_tag }},suffix=-debian,enable=${{ matrix.base == 'debian' }} + labels: | + org.opencontainers.image.authors=Nicolas Duchon (@buchdag), Jason Wilder + org.opencontainers.image.version=${{ steps.docker-gen_version.outputs.VERSION }} + flavor: | + latest=false + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push the image + id: docker_build + uses: docker/build-push-action@v6 + with: + context: . + build-args: DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }} + platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x + file: Dockerfile.${{ matrix.base }} + sbom: true + push: true + provenance: mode=max + tags: ${{ steps.docker_meta.outputs.tags }} + labels: ${{ steps.docker_meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Docker image digest + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 90cad8d6..cde01a55 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -1,5 +1,9 @@ name: Build and publish Docker images +permissions: + contents: read + packages: write + on: workflow_dispatch: schedule: @@ -28,7 +32,7 @@ jobs: if: (github.event_name == 'schedule' && github.repository == 'nginx-proxy/docker-gen') || (github.event_name != 'schedule') steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml index a983869d..03324bcf 100644 --- a/.github/workflows/dockerhub-description.yml +++ b/.github/workflows/dockerhub-description.yml @@ -1,5 +1,8 @@ name: Update Docker Hub Description +permissions: + contents: read + on: push: branches: @@ -15,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Docker Hub Description uses: peter-evans/dockerhub-description@v4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b9ca177e..c6302880 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,5 +1,8 @@ name: Tests +permissions: + contents: read + on: push: branches: @@ -16,8 +19,8 @@ jobs: strategy: matrix: go-version: - - "1.23" - "1.24" + - "1.25" os: - macos - ubuntu @@ -28,11 +31,11 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} diff --git a/Dockerfile.alpine b/Dockerfile.alpine index ecf20a2d..1aa0d46b 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,7 +1,7 @@ ARG DOCKER_GEN_VERSION=main # Build docker-gen from scratch -FROM --platform=$BUILDPLATFORM golang:1.24.3-alpine AS go-builder +FROM --platform=$BUILDPLATFORM golang:1.25.1-alpine AS go-builder ENV CGO_ENABLED=0 @@ -24,7 +24,7 @@ RUN set -eux; \ go env | grep -E 'OS=|ARCH=|ARM=|AMD64='; \ go build -ldflags "-X main.buildVersion=${DOCKER_GEN_VERSION}" -o docker-gen ./cmd/docker-gen -FROM alpine:3.21.3 +FROM alpine:3.22.1 ARG DOCKER_GEN_VERSION ENV DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \ diff --git a/Dockerfile.debian b/Dockerfile.debian index c83d202f..a23319e1 100644 --- a/Dockerfile.debian +++ b/Dockerfile.debian @@ -1,7 +1,7 @@ ARG DOCKER_GEN_VERSION=main # Build docker-gen from scratch -FROM --platform=$BUILDPLATFORM golang:1.24.3 AS go-builder +FROM --platform=$BUILDPLATFORM golang:1.25.1 AS go-builder ENV CGO_ENABLED=0 @@ -24,7 +24,7 @@ RUN set -eux; \ go env | grep -E 'OS=|ARCH=|ARM=|AMD64='; \ go build -ldflags "-X main.buildVersion=${DOCKER_GEN_VERSION}" -o docker-gen ./cmd/docker-gen -FROM debian:12.10-slim +FROM debian:13.1-slim ARG DOCKER_GEN_VERSION ENV DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \ diff --git a/README.md b/README.md index 74077ac2..6f6b6826 100644 --- a/README.md +++ b/README.md @@ -87,22 +87,29 @@ Usage: docker-gen [options] template [dest] Generate files from docker container meta-data Options: - -config value - config files with template directives. Config files will be merged if this option is specified multiple times. (default []) + -config path + config files with template directives. + Config files will be merged if this option is specified multiple times. (default []) + -container-filter key=value + container filter for inclusion by docker-gen. + You can pass this option multiple times to combine filters with AND. + https://docs.docker.com/engine/reference/commandline/ps/#filter -endpoint string docker api endpoint (tcp|unix://..). Default unix:///var/run/docker.sock + -event-filter key=value + additional filter for event watched by docker-gen (e.g -event-filter event=connect -event-filter event=disconnect). + You can pass this option multiple times to combine filters. + By default docker-gen listen for container events start, stop, die and health_status. + https://docs.docker.com/engine/reference/commandline/events/#filtering-events + -include-stopped + include stopped containers. + Bypassed when providing a container status filter (-container-filter status=foo). -interval int notify command interval (secs) -keep-blank-lines keep blank lines in the output file -notify restart xyz run command after template is regenerated (e.g restart xyz) - -notify-output - log the output(stdout/stderr) of notify command - -notify-sighup container-ID - send HUP signal to container. - Equivalent to 'docker kill -s HUP container-ID', or `-notify-container container-ID -notify-signal 1`. - You can pass this option multiple times to send HUP to multiple containers. -notify-container container-ID send -notify-signal signal (defaults to 1 / HUP) to container. You can pass this option multiple times to notify multiple containers. @@ -110,39 +117,45 @@ Options: container filter for notification (e.g -notify-filter name=foo). You can pass this option multiple times to combine filters with AND. https://docs.docker.com/engine/reference/commandline/ps/#filter + -notify-output + log the output(stdout/stderr) of notify command + -notify-sighup container-ID + send HUP signal to container. + Equivalent to 'docker kill -s HUP container-ID', or `-notify-container container-ID -notify-signal 1`. + You can pass this option multiple times to send HUP to multiple containers. -notify-signal signal signal to send to the -notify-container and -notify-filter. -1 to call docker restart. Defaults to 1 aka. HUP. All available signals available on the dockerclient https://github.com/fsouza/go-dockerclient/blob/main/signal.go -only-exposed - only include containers with exposed ports + only include containers with exposed ports. + Bypassed when using the exposed filter with (-container-filter exposed=foo). -only-published - only include containers with published ports (implies -only-exposed) - -include-stopped - include stopped containers + only include containers with published ports (implies -only-exposed). + Bypassed when providing a container published filter (-container-filter published=foo). -tlscacert string - path to TLS CA certificate file (default "~/.docker/machine/machines/default/ca.pem") + path to TLS CA certificate file (default "~/.docker/ca.pem") -tlscert string - path to TLS client certificate file (default "~/.docker/machine/machines/default/cert.pem") + path to TLS client certificate file (default "~/.docker/cert.pem") -tlskey string - path to TLS client key file (default "~/.docker/machine/machines/default/key.pem") + path to TLS client key file (default "~/.docker/key.pem") -tlsverify - verify docker daemon's TLS certicate (default true) + verify docker daemon's TLS certicate -version show version + -wait string + minimum and maximum durations to wait (e.g. "500ms:2s") before triggering generate -watch watch for container changes - -wait - minimum (and/or maximum) duration to wait after each container change before triggering Arguments: template - path to a template to generate - dest - path to write the template. If not specfied, STDOUT is used + dest - path to write the template to. If not specfied, STDOUT is used Environment Variables: DOCKER_HOST - default value for -endpoint - DOCKER_CERT_PATH - directory path containing key.pem, cert.pm and ca.pem - DOCKER_TLS_VERIFY - enable client TLS verification] + DOCKER_CERT_PATH - directory path containing key.pem, cert.pem and ca.pem + DOCKER_TLS_VERIFY - enable client TLS verification ``` If no `` file is specified, the output is sent to stdout. Mainly useful for debugging. diff --git a/cmd/docker-gen/main.go b/cmd/docker-gen/main.go index 8ee86364..a6372ef4 100644 --- a/cmd/docker-gen/main.go +++ b/cmd/docker-gen/main.go @@ -34,8 +34,10 @@ var ( onlyExposed bool onlyPublished bool includeStopped bool + containerFilter mapstringslice = make(mapstringslice) configFiles stringslice configs config.ConfigFile + eventFilter mapstringslice = mapstringslice{"event": {"start", "stop", "die", "health_status"}} interval int keepBlankLines bool endpoint string @@ -77,7 +79,7 @@ Options:`) println(` Arguments: template - path to a template to generate - dest - path to a write the template. If not specfied, STDOUT is used`) + dest - path to write the template to. If not specfied, STDOUT is used`) println(` Environment Variables: @@ -97,21 +99,35 @@ func loadConfig(file string) error { } func initFlags() { - certPath := filepath.Join(os.Getenv("DOCKER_CERT_PATH")) if certPath == "" { certPath = filepath.Join(os.Getenv("HOME"), ".docker") } + flag.BoolVar(&version, "version", false, "show version") + + // General configuration options flag.BoolVar(&watch, "watch", false, "watch for container changes") flag.StringVar(&wait, "wait", "", "minimum and maximum durations to wait (e.g. \"500ms:2s\") before triggering generate") - flag.BoolVar(&onlyExposed, "only-exposed", false, "only include containers with exposed ports") + flag.Var(&configFiles, "config", "config files with template directives. Config files will be merged if this option is specified multiple times.") + flag.BoolVar(&keepBlankLines, "keep-blank-lines", false, "keep blank lines in the output file") + // Containers filtering options + flag.BoolVar(&onlyExposed, "only-exposed", false, + "only include containers with exposed ports. Bypassed when providing a container exposed filter (-container-filter exposed=foo).") flag.BoolVar(&onlyPublished, "only-published", false, - "only include containers with published ports (implies -only-exposed)") - flag.BoolVar(&includeStopped, "include-stopped", false, "include stopped containers") - flag.BoolVar(¬ifyOutput, "notify-output", false, "log the output(stdout/stderr) of notify command") + "only include containers with published ports (implies -only-exposed). Bypassed when providing a container published filter (-container-filter published=foo).") + flag.BoolVar(&includeStopped, "include-stopped", false, + "include stopped containers. Bypassed when providing a container status filter (-container-filter status=foo).") + flag.Var(&containerFilter, "container-filter", + "container filter for inclusion by docker-gen. You can pass this option multiple times to combine filters with AND. https://docs.docker.com/engine/reference/commandline/ps/#filter") + + // Command notification options flag.StringVar(¬ifyCmd, "notify", "", "run command after template is regenerated (e.g `restart xyz`)") + flag.BoolVar(¬ifyOutput, "notify-output", false, "log the output(stdout/stderr) of notify command") + flag.IntVar(&interval, "interval", 0, "notify command interval (secs)") + + // Containers notification options flag.Var(&sighupContainerID, "notify-sighup", "send HUP signal to container. Equivalent to docker kill -s HUP `container-ID`. You can pass this option multiple times to send HUP to multiple containers.") flag.Var(¬ifyContainerID, "notify-container", @@ -120,15 +136,17 @@ func initFlags() { "container filter for notification (e.g -notify-filter name=foo). You can pass this option multiple times to combine filters with AND. https://docs.docker.com/engine/reference/commandline/ps/#filter") flag.IntVar(¬ifyContainerSignal, "notify-signal", int(docker.SIGHUP), "signal to send to the notify-container and notify-filter. Defaults to SIGHUP") - flag.Var(&configFiles, "config", "config files with template directives. Config files will be merged if this option is specified multiple times.") - flag.IntVar(&interval, "interval", 0, "notify command interval (secs)") - flag.BoolVar(&keepBlankLines, "keep-blank-lines", false, "keep blank lines in the output file") + + // Docker API endpoint configuration options flag.StringVar(&endpoint, "endpoint", "", "docker api endpoint (tcp|unix://..). Default unix:///var/run/docker.sock") flag.StringVar(&tlsCert, "tlscert", filepath.Join(certPath, "cert.pem"), "path to TLS client certificate file") flag.StringVar(&tlsKey, "tlskey", filepath.Join(certPath, "key.pem"), "path to TLS client key file") flag.StringVar(&tlsCaCert, "tlscacert", filepath.Join(certPath, "ca.pem"), "path to TLS CA certificate file") flag.BoolVar(&tlsVerify, "tlsverify", os.Getenv("DOCKER_TLS_VERIFY") != "", "verify docker daemon's TLS certicate") + flag.Var(&eventFilter, "event-filter", + "additional filter for event watched by docker-gen (e.g -event-filter event=connect -event-filter event=disconnect). You can pass this option multiple times to combine filters. By default docker-gen listen for container events start, stop, die and health_status. https://docs.docker.com/engine/reference/commandline/events/#filtering-events") + flag.Usage = usage flag.Parse() } @@ -173,9 +191,7 @@ func main() { NotifyCmd: notifyCmd, NotifyOutput: notifyOutput, NotifyContainers: make(map[string]int), - OnlyExposed: onlyExposed, - OnlyPublished: onlyPublished, - IncludeStopped: includeStopped, + ContainerFilter: containerFilter, Interval: interval, KeepBlankLines: keepBlankLines, } @@ -189,26 +205,39 @@ func main() { cfg.NotifyContainersFilter = notifyContainerFilter cfg.NotifyContainersSignal = notifyContainerSignal } + if len(cfg.ContainerFilter["status"]) == 0 { + if includeStopped { + cfg.ContainerFilter["status"] = []string{ + "created", + "restarting", + "running", + "removing", + "paused", + "exited", + "dead", + } + } else { + cfg.ContainerFilter["status"] = []string{"running"} + } + } + if onlyPublished && len(cfg.ContainerFilter["publish"]) == 0 { + cfg.ContainerFilter["publish"] = []string{"1-65535"} + } else if onlyExposed && len(cfg.ContainerFilter["expose"]) == 0 { + cfg.ContainerFilter["expose"] = []string{"1-65535"} + } configs = config.ConfigFile{ Config: []config.Config{cfg}, } } - all := false - for _, config := range configs.Config { - if config.IncludeStopped { - all = true - } - } - generator, err := generator.NewGenerator(generator.GeneratorConfig{ - Endpoint: endpoint, - TLSKey: tlsKey, - TLSCert: tlsCert, - TLSCACert: tlsCaCert, - TLSVerify: tlsVerify, - All: all, - ConfigFile: configs, + Endpoint: endpoint, + TLSKey: tlsKey, + TLSCert: tlsCert, + TLSCACert: tlsCaCert, + TLSVerify: tlsVerify, + EventFilter: eventFilter, + ConfigFile: configs, }) if err != nil { diff --git a/go.mod b/go.mod index 4fbad338..76e56872 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/nginx-proxy/docker-gen -go 1.23.0 +go 1.24.0 require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/sprig/v3 v3.3.0 - github.com/fsouza/go-dockerclient v1.12.1 - github.com/stretchr/testify v1.10.0 + github.com/fsouza/go-dockerclient v1.12.2 + github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -18,32 +18,32 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/docker v27.5.1+incompatible // indirect + github.com/docker/docker v28.3.3+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/klauspost/compress v1.15.9 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/sys/sequential v0.5.0 // indirect - github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.7.0 // indirect golang.org/x/crypto v0.35.0 // indirect - golang.org/x/sys v0.30.0 // indirect + golang.org/x/sys v0.35.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/go.sum b/go.sum index 4a70e652..b4a22a80 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8 h1:V8krnnfGj4pV65YLUm3C0/8bl7V5Nry2Pwvy3ru/wLc= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= @@ -21,22 +21,22 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= -github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsouza/go-dockerclient v1.12.1 h1:FMoLq+Zhv9Oz/rFmu6JWkImfr6CBgZOPcL+bHW4gS0o= -github.com/fsouza/go-dockerclient v1.12.1/go.mod h1:OqsgJJcpCwqyM3JED7TdfM9QVWS5O7jSYwXxYKmOooY= +github.com/fsouza/go-dockerclient v1.12.2 h1:+pbP/SacoHfqaVZuiudvcdYGd9jzU7y9EcgoBOHivEI= +github.com/fsouza/go-dockerclient v1.12.2/go.mod h1:ZGCkAsnBGjnTRG9wV6QaICPJ5ig2KlaxTccDQy5WQ38= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -45,8 +45,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -59,12 +59,14 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= -github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= -github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= @@ -78,7 +80,6 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -93,8 +94,8 @@ github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cA github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -117,10 +118,10 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -139,5 +140,5 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= diff --git a/internal/config/config.go b/internal/config/config.go index 38ac223c..bb490e39 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -16,9 +16,7 @@ type Config struct { NotifyContainers map[string]int NotifyContainersFilter map[string][]string NotifyContainersSignal int - OnlyExposed bool - OnlyPublished bool - IncludeStopped bool + ContainerFilter map[string][]string Interval int KeepBlankLines bool } diff --git a/internal/context/context.go b/internal/context/context.go index 2bb49275..de283f94 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -105,16 +105,6 @@ func (r *RuntimeContainer) Equals(o RuntimeContainer) bool { return r.ID == o.ID && r.Image == o.Image } -func (r *RuntimeContainer) PublishedAddresses() []Address { - mapped := []Address{} - for _, address := range r.Addresses { - if address.HostPort != "" { - mapped = append(mapped, address) - } - } - return mapped -} - type DockerImage struct { Registry string Repository string diff --git a/internal/context/context_test.go b/internal/context/context_test.go index f571f665..4e8f8557 100644 --- a/internal/context/context_test.go +++ b/internal/context/context_test.go @@ -132,37 +132,6 @@ func TestGetCurrentContainerEmpty(t *testing.T) { assert.Equal(t, "", GetCurrentContainerID()) } -func TestPublishedAddresses(t *testing.T) { - container := &RuntimeContainer{ - Addresses: []Address{ - { - IP: "172.19.0.1", - HostPort: "80", - }, - { - IP: "172.19.0.2", - }, - { - IP: "172.19.0.3", - HostPort: "8080", - }, - }, - } - - expected := []Address{ - { - IP: "172.19.0.1", - HostPort: "80", - }, - { - IP: "172.19.0.3", - HostPort: "8080", - }, - } - - assert.ElementsMatch(t, expected, container.PublishedAddresses()) -} - func TestRuntimeContainerEquals(t *testing.T) { rc1 := &RuntimeContainer{ ID: "baz", diff --git a/internal/generator/generator.go b/internal/generator/generator.go index 327c99a9..613d7924 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -26,6 +26,7 @@ type generator struct { TLSVerify bool TLSCert, TLSCaCert, TLSKey string All bool + EventFilter map[string][]string wg sync.WaitGroup retry bool @@ -38,7 +39,8 @@ type GeneratorConfig struct { TLSKey string TLSCACert string TLSVerify bool - All bool + + EventFilter map[string][]string ConfigFile config.ConfigFile } @@ -63,15 +65,15 @@ func NewGenerator(gc GeneratorConfig) (*generator, error) { context.SetDockerEnv(apiVersion) return &generator{ - Client: client, - Endpoint: gc.Endpoint, - TLSVerify: gc.TLSVerify, - TLSCert: gc.TLSCert, - TLSCaCert: gc.TLSCACert, - TLSKey: gc.TLSKey, - All: gc.All, - Configs: gc.ConfigFile, - retry: true, + Client: client, + Endpoint: gc.Endpoint, + TLSVerify: gc.TLSVerify, + TLSCert: gc.TLSCert, + TLSCaCert: gc.TLSCACert, + TLSKey: gc.TLSKey, + EventFilter: gc.EventFilter, + Configs: gc.ConfigFile, + retry: true, }, nil } @@ -120,12 +122,13 @@ func (g *generator) generateFromSignals() { } func (g *generator) generateFromContainers() { - containers, err := g.getContainers() - if err != nil { - log.Printf("Error listing containers: %s\n", err) - return - } for _, config := range g.Configs.Config { + containers, err := g.getContainers(config) + if err != nil { + log.Printf("Error listing containers: %s\n", err) + return + } + changed := template.GenerateFile(config, containers) if !changed { log.Printf("Contents of %s did not change. Skipping notification '%s'", config.Dest, config.NotifyCmd) @@ -155,7 +158,7 @@ func (g *generator) generateAtInterval() { for { select { case <-ticker.C: - containers, err := g.getContainers() + containers, err := g.getContainers(cfg) if err != nil { log.Printf("Error listing containers: %s\n", err) continue @@ -201,7 +204,7 @@ func (g *generator) generateFromEvents() { defer g.wg.Done() debouncedChan := newDebounceChannel(watcher, cfg.Wait) for range debouncedChan { - containers, err := g.getContainers() + containers, err := g.getContainers(cfg) if err != nil { log.Printf("Error listing containers: %s\n", err) continue @@ -249,7 +252,11 @@ func (g *generator) generateFromEvents() { break } if !watching { - err := client.AddEventListener(eventChan) + options := docker.EventsOptions{ + Filters: g.EventFilter, + } + + err := client.AddEventListenerWithOptions(options, eventChan) if err != nil && err != docker.ErrListenerAlreadyExists { log.Printf("Error registering docker event listener: %s", err) time.Sleep(10 * time.Second) @@ -281,12 +288,11 @@ func (g *generator) generateFromEvents() { time.Sleep(10 * time.Second) break } - if event.Status == "start" || event.Status == "stop" || event.Status == "die" || strings.Contains(event.Status, "health_status:") { - log.Printf("Received event %s for container %s", event.Status, event.ID[:12]) - // fanout event to all watchers - for _, watcher := range watchers { - watcher <- event - } + + log.Printf("Received event %s for %s %s", event.Action, event.Type, event.Actor.ID[:12]) + // fanout event to all watchers + for _, watcher := range watchers { + watcher <- event } case <-time.After(10 * time.Second): // check for docker liveness @@ -382,7 +388,7 @@ func (g *generator) sendSignalToFilteredContainers(config config.Config) { } } -func (g *generator) getContainers() ([]*context.RuntimeContainer, error) { +func (g *generator) getContainers(config config.Config) ([]*context.RuntimeContainer, error) { apiInfo, err := g.Client.Info() if err != nil { log.Printf("Error retrieving docker server info: %s\n", err) @@ -391,8 +397,9 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) { } apiContainers, err := g.Client.ListContainers(docker.ListContainersOptions{ - All: g.All, - Size: false, + All: true, + Size: false, + Filters: config.ContainerFilter, }) if err != nil { return nil, err diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go index d8f69fec..a2879b78 100644 --- a/internal/generator/generator_test.go +++ b/internal/generator/generator_test.go @@ -26,12 +26,11 @@ func TestGenerateFromEvents(t *testing.T) { var counter atomic.Int32 eventsResponse := ` -{"status":"start","id":"8dfafdbc3a40","from":"base:latest","time":1374067924} -{"status":"stop","id":"8dfafdbc3a40","from":"base:latest","time":1374067966} -{"status":"start","id":"8dfafdbc3a40","from":"base:latest","time":1374067970} -{"status":"destroy","id":"8dfafdbc3a40","from":"base:latest","time":1374067990}` +{"Type":"container","Action":"start","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067924} +{"Type":"container","Action":"stop","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067966} +{"Type":"container","Action":"start","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067970}` infoResponse := `{"Containers":1,"Images":1,"Debug":false,"NFd":11,"NGoroutines":21,"MemoryLimit":true,"SwapLimit":false}` - versionResponse := `{"Version":"1.8.0","Os":"Linux","KernelVersion":"3.18.5-tinycore64","GoVersion":"go1.4.1","GitCommit":"a8a31ef","Arch":"amd64","ApiVersion":"1.19"}` + versionResponse := `{"Version":"19.03.12","Os":"Linux","KernelVersion":"4.19.76-linuxkit","GoVersion":"go1.13.14","GitCommit":"48a66213fe","Arch":"amd64","ApiVersion":"1.40"}` server, _ := dockertest.NewServer("127.0.0.1:0", nil, nil) server.CustomHandler("/events", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -200,14 +199,14 @@ func TestGenerateFromEvents(t *testing.T) { // init 150ms 200ms 250ms 300ms 350ms 400ms 450ms 500ms 550ms 600ms 650ms 700ms // ├──────╫──────┼──────┼──────╫──────┼──────┼──────╫──────┼──────┼──────┼──────┼──────┤ // File0 ├─ 1 ║ ║ ║ - // File1 ├─ 1 ╟─ 2 ╟─ 3 ╟─ 5 - // File2 ├─ 1 ╟───── max (250ms) ──║───────────> 4 ╟─────── min (200ms) ─────> 6 - // File3 └─ 1 ╟──────────────────> ╟──────────────────> ╟─────────── min (250ms) ────────> 7 + // File1 ├─ 2 ╟─ 5 ╟─ 6 ╟─ 8 + // File2 ├─ 3 ╟───── max (250ms) ──║───────────> 7 ╟─────── min (200ms) ─────> 9 + // File3 └─ 4 ╟──────────────────> ╟──────────────────> ╟─────────── min (250ms) ────────> 10 // ┌───╨───┐ ┌───╨──┐ ┌───╨───┐ // │ start │ │ stop │ │ start │ // └───────┘ └──────┘ └───────┘ - expectedCounters := []int{1, 5, 6, 7} + expectedCounters := []int{1, 8, 9, 10} for i, counter := range expectedCounters { value, _ = os.ReadFile(destFiles[i].Name()) diff --git a/internal/template/groupby_test.go b/internal/template/groupby_test.go index e34f9ca2..f591e0e4 100644 --- a/internal/template/groupby_test.go +++ b/internal/template/groupby_test.go @@ -29,7 +29,8 @@ var groupByContainers = []*context.RuntimeContainer{ ID: "3", }, { - ID: "4", + Env: map[string]string{}, + ID: "4", }, } @@ -170,6 +171,10 @@ func TestGroupByMulti(t *testing.T) { }, ID: "3", }, + { + Env: map[string]string{}, + ID: "4", + }, } groups, _ := groupByMulti(containers, "Env.VIRTUAL_HOST", ",") diff --git a/internal/template/reflect.go b/internal/template/reflect.go index f329f28d..47e76eed 100644 --- a/internal/template/reflect.go +++ b/internal/template/reflect.go @@ -41,7 +41,29 @@ func deepGetImpl(v reflect.Value, path []string) interface{} { case reflect.Struct: return deepGetImpl(v.FieldByName(path[0]), path[1:]) case reflect.Map: - return deepGetImpl(v.MapIndex(reflect.ValueOf(path[0])), path[1:]) + // If the first part of the path is a key in the map, we use it directly + if mapValue := v.MapIndex(reflect.ValueOf(path[0])); mapValue.IsValid() { + return deepGetImpl(mapValue, path[1:]) + } + + // If the first part of the path is not a key in the map, we try to find a valid key by joining the path parts + var builder strings.Builder + for i, pathPart := range path { + if i > 0 { + builder.WriteString(".") + } + builder.WriteString(pathPart) + joinedPath := builder.String() + + if mapValue := v.MapIndex(reflect.ValueOf(joinedPath)); mapValue.IsValid() { + if i == len(path) { + return mapValue.Interface() + } + return deepGetImpl(mapValue, path[i+1:]) + } + } + + return nil case reflect.Slice, reflect.Array: i, err := parseAllocateInt(path[0]) if err != nil { diff --git a/internal/template/reflect_test.go b/internal/template/reflect_test.go index 2c43eb3b..dac9c4ee 100644 --- a/internal/template/reflect_test.go +++ b/internal/template/reflect_test.go @@ -62,6 +62,14 @@ func TestDeepGet(t *testing.T) { path string want interface{} }{ + { + "map of string", + map[string]string{ + "Env": "quux", + }, + "Env", + "quux", + }, { "map key empty string", map[string]map[string]map[string]string{ @@ -74,6 +82,28 @@ func TestDeepGet(t *testing.T) { "...", "foo", }, + { + "map with dot in key", + map[string]map[string]string{ + "Env": { + "foo.bar.baz.qux": "quux", + }, + }, + "Env.foo.bar.baz.qux", + "quux", + }, + { + "nested maps with dot in keys", + map[string]map[string]map[string]string{ + "Env": { + "foo.bar": { + "baz.qux": "quux", + }, + }, + }, + "Env.foo.bar.baz.qux", + "quux", + }, {"struct", s, "X", "foo"}, {"pointer to struct", sp, "X", "foo"}, {"double pointer to struct", &sp, ".X", nil}, diff --git a/internal/template/sort.go b/internal/template/sort.go index ef71258f..b4efd4c9 100644 --- a/internal/template/sort.go +++ b/internal/template/sort.go @@ -90,6 +90,14 @@ func getFieldAsString(item interface{}, path string) string { func (s sortableByKey) Less(i, j int) bool { dataI := getFieldAsString(s.data[i], s.key) dataJ := getFieldAsString(s.data[j], s.key) + + if intI, err := strconv.ParseInt(dataI, 10, 64); err == nil { + if intJ, err := strconv.ParseInt(dataJ, 10, 64); err == nil { + // If both are integers, compare as integers + return intI < intJ + } + } + return dataI < dataJ } diff --git a/internal/template/sort_test.go b/internal/template/sort_test.go index e5c5ff44..b541fcdf 100644 --- a/internal/template/sort_test.go +++ b/internal/template/sort_test.go @@ -55,13 +55,19 @@ func TestSortObjectsByKeys(t *testing.T) { Env: map[string]string{ "VIRTUAL_HOST": "bar.localhost", }, - ID: "9", + Labels: map[string]string{ + "com.docker.compose.container_number": "1", + }, + ID: "11", } o1 := &context.RuntimeContainer{ Created: time.Date(2021, 1, 2, 0, 0, 10, 0, time.UTC), Env: map[string]string{ "VIRTUAL_HOST": "foo.localhost", }, + Labels: map[string]string{ + "com.docker.compose.container_number": "11", + }, ID: "1", } o2 := &context.RuntimeContainer{ @@ -69,12 +75,16 @@ func TestSortObjectsByKeys(t *testing.T) { Env: map[string]string{ "VIRTUAL_HOST": "baz.localhost", }, - ID: "3", + Labels: map[string]string{}, + ID: "3", } o3 := &context.RuntimeContainer{ Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Env: map[string]string{}, - ID: "8", + Labels: map[string]string{ + "com.docker.compose.container_number": "2", + }, + ID: "8", } containers := []*context.RuntimeContainer{o0, o1, o2, o3} @@ -85,9 +95,11 @@ func TestSortObjectsByKeys(t *testing.T) { want []interface{} }{ {"Asc simple", sortObjectsByKeysAsc, "ID", []interface{}{o1, o2, o3, o0}}, - {"Asc complex", sortObjectsByKeysAsc, "Env.VIRTUAL_HOST", []interface{}{o3, o0, o2, o1}}, {"Desc simple", sortObjectsByKeysDesc, "ID", []interface{}{o0, o3, o2, o1}}, + {"Asc complex", sortObjectsByKeysAsc, "Env.VIRTUAL_HOST", []interface{}{o3, o0, o2, o1}}, {"Desc complex", sortObjectsByKeysDesc, "Env.VIRTUAL_HOST", []interface{}{o1, o2, o0, o3}}, + {"Asc complex w/ dots in key name", sortObjectsByKeysAsc, "Labels.com.docker.compose.container_number", []interface{}{o2, o0, o3, o1}}, + {"Desc complex w/ dots in key name", sortObjectsByKeysDesc, "Labels.com.docker.compose.container_number", []interface{}{o1, o3, o0, o2}}, {"Asc time", sortObjectsByKeysAsc, "Created", []interface{}{o3, o0, o2, o1}}, {"Desc time", sortObjectsByKeysDesc, "Created", []interface{}{o1, o2, o0, o3}}, } { diff --git a/internal/template/template.go b/internal/template/template.go index b6ab00b3..1cfb3039 100644 --- a/internal/template/template.go +++ b/internal/template/template.go @@ -149,40 +149,8 @@ func removeBlankLines(reader io.Reader, writer io.Writer) { bwriter.Flush() } -func filterRunning(config config.Config, containers context.Context) context.Context { - if config.IncludeStopped { - return containers - } else { - filteredContainers := context.Context{} - for _, container := range containers { - if container.State.Running { - filteredContainers = append(filteredContainers, container) - } - } - return filteredContainers - } -} - func GenerateFile(config config.Config, containers context.Context) bool { - filteredRunningContainers := filterRunning(config, containers) - filteredContainers := context.Context{} - if config.OnlyPublished { - for _, container := range filteredRunningContainers { - if len(container.PublishedAddresses()) > 0 { - filteredContainers = append(filteredContainers, container) - } - } - } else if config.OnlyExposed { - for _, container := range filteredRunningContainers { - if len(container.Addresses) > 0 { - filteredContainers = append(filteredContainers, container) - } - } - } else { - filteredContainers = filteredRunningContainers - } - - contents := executeTemplate(config.Template, filteredContainers) + contents := executeTemplate(config.Template, containers) if !config.KeepBlankLines { buf := new(bytes.Buffer) @@ -201,8 +169,7 @@ func GenerateFile(config config.Config, containers context.Context) bool { if err != nil { log.Fatalf("Unable to write to dest file %s: %s\n", config.Dest, err) } - - log.Printf("Generated '%s' from %d containers", config.Dest, len(filteredContainers)) + log.Printf("Generated '%s' from %d containers", config.Dest, len(containers)) return true } return false