Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: filter containers seen by docker-gen #623

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,62 +87,70 @@ 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
-include-stopped
include stopped containers.
Bypassed by 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.
-notify-filter key=value
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 by 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 by 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 `<dest>` file is specified, the output is sent to stdout. Mainly useful for debugging.
Expand Down
64 changes: 44 additions & 20 deletions cmd/docker-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var (
onlyExposed bool
onlyPublished bool
includeStopped bool
containerFilter mapstringslice = make(mapstringslice)
configFiles stringslice
configs config.ConfigFile
interval int
Expand Down Expand Up @@ -77,7 +78,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:
Expand All @@ -97,21 +98,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(&notifyOutput, "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 by 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(&notifyCmd, "notify", "", "run command after template is regenerated (e.g `restart xyz`)")
flag.BoolVar(&notifyOutput, "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(&notifyContainerID, "notify-container",
Expand All @@ -120,9 +135,8 @@ 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(&notifyContainerSignal, "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")
Expand Down Expand Up @@ -173,9 +187,7 @@ func main() {
NotifyCmd: notifyCmd,
NotifyOutput: notifyOutput,
NotifyContainers: make(map[string]int),
OnlyExposed: onlyExposed,
OnlyPublished: onlyPublished,
IncludeStopped: includeStopped,
ContainerFilter: containerFilter,
Interval: interval,
KeepBlankLines: keepBlankLines,
}
Expand All @@ -189,25 +201,37 @@ 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["publish"]) == 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,
})

Expand Down
4 changes: 1 addition & 3 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
10 changes: 0 additions & 10 deletions internal/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 0 additions & 31 deletions internal/context/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 12 additions & 12 deletions internal/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ type GeneratorConfig struct {
TLSKey string
TLSCACert string
TLSVerify bool
All bool

ConfigFile config.ConfigFile
}
Expand Down Expand Up @@ -69,7 +68,6 @@ func NewGenerator(gc GeneratorConfig) (*generator, error) {
TLSCert: gc.TLSCert,
TLSCaCert: gc.TLSCACert,
TLSKey: gc.TLSKey,
All: gc.All,
Configs: gc.ConfigFile,
retry: true,
}, nil
Expand Down Expand Up @@ -120,12 +118,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)
Expand Down Expand Up @@ -155,7 +154,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
Expand Down Expand Up @@ -201,7 +200,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
Expand Down Expand Up @@ -382,7 +381,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)
Expand All @@ -391,8 +390,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
Expand Down
2 changes: 1 addition & 1 deletion internal/generator/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func TestGenerateFromEvents(t *testing.T) {
// │ 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())
Expand Down
Loading