Skip to content
Merged
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
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -170,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 }}`
Expand All @@ -191,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

Expand Down
10 changes: 10 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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
Expand All @@ -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() {
Expand Down
20 changes: 20 additions & 0 deletions readme_flags_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
88 changes: 88 additions & 0 deletions template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,91 @@ 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)
}
}

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)
}
}
Loading