From cfcd0409805206a4da6937b0e6f505e2b241ddec Mon Sep 17 00:00:00 2001 From: Jason Wilder Date: Fri, 16 Oct 2026 20:48:32 -0600 Subject: [PATCH 1/6] docs(readme): document -version and -wait-retry-interval flags Add -version to the command-line options and a retry interval section under Timeout, with the 1s default the code uses. Add a test that fails if the README stops documenting either flag. --- README.md | 12 ++++++++++++ readme_flags_test.go | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 readme_flags_test.go diff --git a/README.md b/README.md index 54cf2fb..6bf83f6 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,12 @@ Http headers can be specified for http/https protocols. $ dockerize -wait http://web:80 -wait-http-header "Authorization:Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" ``` +Use `-version` to show the version and exit. + +``` +$ dockerize -version +``` + ## Waiting for other dependencies It is common when using tools like [Docker Compose](https://docs.docker.com/compose/) to depend on services in other linked containers, however oftentimes relying on [links](https://docs.docker.com/compose/compose-file/#links) is not enough - whilst the container itself may have _started_, the _service(s)_ within it may not yet be ready - resulting in shell script hacks to work around race conditions. @@ -156,6 +162,12 @@ You can optionally specify how long to wait for the services to become available $ dockerize -wait tcp://db:5432 -wait http://web:80 -timeout 10s ``` +By default dockerize waits 1s between connection attempts. Use `-wait-retry-interval` to change this, for example to retry less often against a slow service. + +``` +$ dockerize -wait tcp://db:5432 -timeout 30s -wait-retry-interval 2s +``` + See [this issue](https://github.com/docker/compose/issues/374#issuecomment-126312313) for a deeper discussion, and why support isn't and won't be available in the Docker ecosystem itself. ## Using Templates diff --git a/readme_flags_test.go b/readme_flags_test.go new file mode 100644 index 0000000..27a74fe --- /dev/null +++ b/readme_flags_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "os" + "strings" + "testing" +) + +func TestReadmeDocumentsFlags(t *testing.T) { + readme, err := os.ReadFile("README.md") + if err != nil { + t.Fatal(err) + } + + for _, flag := range []string{"-version", "-wait-retry-interval"} { + if !strings.Contains(string(readme), "`"+flag+"`") { + t.Errorf("README.md does not document the %s flag", flag) + } + } +} From 6fb070c32ca888981037a41fff57a4f5bdb0a816 Mon Sep 17 00:00:00 2001 From: Jason Wilder Date: Fri, 16 Oct 2026 21:57:56 -0600 Subject: [PATCH 2/6] docs(readme): correct replace, contains and ReadDir template docs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6bf83f6..9639f89 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ $ dockerize -template template1.tmpl ``` Template may also be a directory. In this case all files within this directory are processed as template and stored with the same name in the destination directory. -If the destination directory is omitted, the output is sent to `STDOUT`. The files in the source directory are processed in sorted order (as returned by `ioutil.ReadDir`). +If the destination directory is omitted, the output is sent to `STDOUT`. The files in the source directory are processed in sorted order (as returned by `os.ReadDir`). ``` $ dockerize -template src_dir:dest_dir @@ -182,10 +182,10 @@ variables within a template with `.Env`. There are a few built-in functions as well: * `default $var $default` - Returns a default value for one that does not exist. `{{ default .Env.VERSION "0.1.2" }}` - * `contains $map $key` - Returns true if a string is within another string + * `contains $map $key` - Returns true if the map contains the key. `{{ if contains .Env "HOME" }}` * `exists $path` - Determines if a file path exists or not. `{{ exists "/etc/default/myapp" }}` * `split $string $sep` - Splits a string into an array using a separator string. Alias for [`strings.Split`][go.string.Split]. `{{ split .Env.PATH ":" }}` - * `replace $string $old $new $count` - Replaces all occurrences of a string within another string. Alias for [`strings.Replace`][go.string.Replace]. `{{ replace .Env.PATH ":" }}` + * `replace $string $old $new $count` - Replaces all occurrences of a string within another string. Alias for [`strings.Replace`][go.string.Replace]. Use `-1` for `$count` to replace all occurrences. `{{ replace .Env.PATH ":" ";" -1 }}` * `parseUrl $url` - Parses a URL into it's [protocol, scheme, host, etc. parts][go.url.URL]. Alias for [`url.Parse`][go.url.Parse] * `atoi $value` - Parses a string $value into an int. `{{ if (gt (atoi .Env.NUM_THREADS) 1) }}` * `add $arg1 $arg` - Performs integer addition. `{{ add (atoi .Env.SHARD_NUM) -1 }}` From be70ee7ac8bfe13e139480be71a004d45f7b4680 Mon Sep 17 00:00:00 2001 From: Jason Wilder Date: Fri, 16 Oct 2026 21:57:56 -0600 Subject: [PATCH 3/6] test: render README template examples --- template_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/template_test.go b/template_test.go index 85d788c..6a1ee6a 100644 --- a/template_test.go +++ b/template_test.go @@ -326,3 +326,44 @@ func TestTemplateFuncMapDockerizeOverridesSprig(t *testing.T) { } } } + +func TestGenerateFileRendersReadmeExamples(t *testing.T) { + oldDelims := delims + oldNoOverwrite := noOverwriteFlag + defer func() { + delims = oldDelims + noOverwriteFlag = oldNoOverwrite + }() + + delims = nil + noOverwriteFlag = false + + t.Setenv("PATH", "/usr/bin:/bin:/usr/local/bin") + t.Setenv("DOCKERIZE_README_SET", "1") + if err := os.Unsetenv("DOCKERIZE_README_UNSET"); err != nil { + t.Fatalf("unset env: %v", err) + } + + tempDir := t.TempDir() + templatePath := filepath.Join(tempDir, "readme.tmpl") + destPath := filepath.Join(tempDir, "readme.out") + + templateContent := `{{ replace .Env.PATH ":" ";" -1 }}|{{ if contains .Env "DOCKERIZE_README_SET" }}set{{ else }}unset{{ end }}|{{ if contains .Env "DOCKERIZE_README_UNSET" }}set{{ else }}unset{{ end }}` + if err := os.WriteFile(templatePath, []byte(templateContent), 0o644); err != nil { + t.Fatalf("write template: %v", err) + } + + if ok := generateFile(templatePath, destPath); !ok { + t.Fatalf("generateFile returned false") + } + + got, err := os.ReadFile(destPath) + if err != nil { + t.Fatalf("read rendered file: %v", err) + } + + want := "/usr/bin;/bin;/usr/local/bin|set|unset" + if string(got) != want { + t.Fatalf("rendered output mismatch: got %q want %q", string(got), want) + } +} From c5103ef6d89fa3f55214c77b66bcf0d51549dc4d Mon Sep 17 00:00:00 2001 From: Jason Wilder Date: Fri, 16 Oct 2026 22:07:00 -0600 Subject: [PATCH 4/6] docs(readme): fix jsonQuery example JSON and query path --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9639f89..a881b93 100644 --- a/README.md +++ b/README.md @@ -203,21 +203,21 @@ Objects and fields are accessed by name. Array elements are accessed by index in With the following JSON in `.Env.SERVICES` -``` +```json { "services": [ { "name": "service1", - "port": 8000, + "port": 8000 },{ "name": "service2", - "port": 9000, + "port": 9000 } ] } ``` -the template expression `jsonQuery .Env.SERVICES "services.[1].port"` returns `9000`. +the template expression `jsonQuery .Env.SERVICES "services\\.[1]\\.port"` returns `9000`. The dots between path elements are escaped (`\\.`) because the query parser splits on `\.`; an unescaped `.` is not treated as a separator. ### loop From 2fc48026276d0b50473f0dd4a6e6f95c9906f50f Mon Sep 17 00:00:00 2001 From: Jason Wilder Date: Fri, 16 Oct 2026 22:07:00 -0600 Subject: [PATCH 5/6] test: render the README jsonQuery example --- template_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/template_test.go b/template_test.go index 6a1ee6a..6c7a26a 100644 --- a/template_test.go +++ b/template_test.go @@ -367,3 +367,50 @@ func TestGenerateFileRendersReadmeExamples(t *testing.T) { t.Fatalf("rendered output mismatch: got %q want %q", string(got), want) } } + +func TestGenerateFileRendersJSONQueryReadmeExample(t *testing.T) { + oldDelims := delims + oldNoOverwrite := noOverwriteFlag + defer func() { + delims = oldDelims + noOverwriteFlag = oldNoOverwrite + }() + + delims = nil + noOverwriteFlag = false + + t.Setenv("SERVICES", `{ + "services": [ + { + "name": "service1", + "port": 8000 + },{ + "name": "service2", + "port": 9000 + } + ] +}`) + + tempDir := t.TempDir() + templatePath := filepath.Join(tempDir, "jsonquery.tmpl") + destPath := filepath.Join(tempDir, "jsonquery.out") + + templateContent := `{{ jsonQuery .Env.SERVICES "services\\.[1]\\.port" }}` + if err := os.WriteFile(templatePath, []byte(templateContent), 0o644); err != nil { + t.Fatalf("write template: %v", err) + } + + if ok := generateFile(templatePath, destPath); !ok { + t.Fatalf("generateFile returned false") + } + + got, err := os.ReadFile(destPath) + if err != nil { + t.Fatalf("read rendered file: %v", err) + } + + want := "9000" + if string(got) != want { + t.Fatalf("rendered output mismatch: got %q want %q", string(got), want) + } +} From 20212a63a090d22150b83d2a82d1afe473db0022 Mon Sep 17 00:00:00 2001 From: Jason Wilder Date: Fri, 16 Oct 2026 22:18:29 -0600 Subject: [PATCH 6/6] docs(main): add package comment and doc comments for Context, Env, HttpHeader and Config --- main.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/main.go b/main.go index 9451dc1..1b25806 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,6 @@ +// Dockerize is a utility for rendering templates from environment variables, +// waiting for dependencies to become available, tailing log files, and then +// executing a command. package main import ( @@ -21,14 +24,19 @@ const defaultWaitRetryInterval = time.Second type sliceVar []string type hostFlagsVar []string +// Context is the data passed to templates when they are rendered. Its methods +// are callable from templates, for example {{ .Env.HOME }}. type Context struct { } +// HttpHeader is a name and value pair sent with the HTTP requests made when +// waiting on a URL, set with -wait-http-header. type HttpHeader struct { name string value string } +// Config holds the options parsed from the command-line flags. type Config struct { version bool poll bool @@ -46,6 +54,8 @@ type Config struct { args []string } +// Env returns the current process environment as a map of variable names to +// values. It is used as .Env in templates. func (c *Context) Env() map[string]string { env := make(map[string]string, len(os.Environ())) for _, i := range os.Environ() {