Repository: containrrr/watchtower
Pull request #372 (2019-08-20 09:32:29)

Add docker api version parameter
This PR adds `--api-version` parameter that allows configuring the `DOCKER_API_VERSION` env var used for configuring the Docker Client.

This allows a more flexible solution for #195.

welcome[bot]: Thanks for opening this pull request! Please check out our [contributing guidelines](https://github.com/containrrr/watchtower/blob/master/CONTRIBUTING.md) as well as our [code of conduct](https://github.com/containrrr/watchtower/blob/master/CODE_OF_CONDUCT.md).

kaloyan-raev: @simskij I think I addressed your change requests. Could you have another look?

Patch:
diff --git a/cmd/root.go b/cmd/root.go
index b18ba06..8052e3b 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -18,10 +18,6 @@ import (
 	"github.com/spf13/cobra"
 )
 
-// DockerAPIMinVersion is the minimum version of the docker api required to
-// use watchtower
-const DockerAPIMinVersion string = "1.24"
-
 var (
 	client         container.Client
 	scheduleSpec   string
@@ -90,7 +86,7 @@ func PreRun(cmd *cobra.Command, args []string) {
 	lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
 
 	// configure environment vars for client
-	err := flags.EnvConfig(cmd, DockerAPIMinVersion)
+	err := flags.EnvConfig(cmd)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/docs/arguments.md b/docs/arguments.md
index 77f1d0c..db51c95 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -75,7 +75,17 @@ Docker daemon socket to connect to. Can be pointed at a remote Docker host by sp
 Environment Variable: DOCKER_HOST
                 Type: String
              Default: "unix:///var/run/docker.sock"
-```      
+```
+
+## Docker API version
+The API version to use by the Docker client for connecting to the Docker daemon. The minimum supported version is 1.24.
+
+```
+            Argument: --api-version, -a
+Environment Variable: DOCKER_API_VERSION
+                Type: String
+             Default: "1.24"
+```
 
 ## Include stopped
 Will also include created and exited containers.
diff --git a/docs/credential-helpers.md b/docs/credential-helpers.md
new file mode 100644
index 0000000..1722906
--- /dev/null
+++ b/docs/credential-helpers.md
@@ -0,0 +1,64 @@
+Some private docker registries (the most prominent probably being AWS ECR) use non-standard ways of authentication.
+To be able to use this together with watchtower, we need to use a credential helper.
+
+To keep the image size small we've decided to not include any helpers in the watchtower image, instead we'll put the
+helper in a separate container and mount it using volumes.
+
+### Example
+Example implementation for use with [amazon-ecr-credential-helper](https://github.com/awslabs/amazon-ecr-credential-helper):
+
+```Dockerfile
+FROM golang:latest
+
+ENV CGO_ENABLED 0
+ENV REPO github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cli/docker-credential-ecr-login
+
+RUN go get -u $REPO
+
+RUN rm /go/bin/docker-credential-ecr-login
+
+RUN go build \
+  -o /go/bin/docker-credential-ecr-login \
+  /go/src/$REPO
+
+WORKDIR /go/bin/
+```
+
+and the docker-compose definition:
+```yaml
+version: "3"
+
+services:
+  watchtower:
+    image: index.docker.io/containrrr/watchtower:latest
+    volumes:
+      - /var/run/docker.sock:/var/run/docker.sock
+      - <PATH_TO_HOME_DIR>/.docker/config.json:/config.json
+      - helper:/go/bin
+    environment:
+      - HOME=/
+      - PATH=$PATH:/go/bin
+      - AWS_REGION=<AWS_REGION>
+      - AWS_ACCESS_KEY_ID=<AWS_ACCESS_KEY>
+      - AWS_SECRET_ACCESS_KEY=<AWS_SECRET_ACCESS_KEY>
+volumes:
+  helper: {}
+```
+
+and for `.docker/config.yml`:
+```yaml
+  {
+    "HttpHeaders" : {
+      "User-Agent" : "Docker-Client/19.03.1 (XXXXXX)"
+    },
+    "credsStore" : "osxkeychain", // ...or your prefered helper
+    "auths" : {
+      "xyzxyzxyz.dkr.ecr.eu-north-1.amazonaws.com" : {},
+      "https://index.docker.io/v1/": {}
+    },
+    "credHelpers": {
+      "xyzxyzxyz.dkr.ecr.eu-north-1.amazonaws.com" : "ecr-login",
+      "index.docker.io": "osxkeychain" // ...or your prefered helper 
+    }
+  }
+```
\ No newline at end of file
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index d416243..179bc63 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -9,11 +9,16 @@ import (
 	"github.com/spf13/viper"
 )
 
+// DockerAPIMinVersion is the minimum version of the docker api required to
+// use watchtower
+const DockerAPIMinVersion string = "1.24"
+
 // RegisterDockerFlags that are used directly by the docker api client
 func RegisterDockerFlags(rootCmd *cobra.Command) {
 	flags := rootCmd.PersistentFlags()
 	flags.StringP("host", "H", viper.GetString("DOCKER_HOST"), "daemon socket to connect to")
 	flags.BoolP("tlsverify", "v", viper.GetBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote")
+	flags.StringP("api-version", "a", viper.GetString("DOCKER_API_VERSION"), "api version to use by docker client")
 }
 
 // RegisterSystemFlags that are used by watchtower to modify the program flow
@@ -215,6 +220,7 @@ Should only be used for testing.
 func SetDefaults() {
 	viper.AutomaticEnv()
 	viper.SetDefault("DOCKER_HOST", "unix:///var/run/docker.sock")
+	viper.SetDefault("DOCKER_API_VERSION", DockerAPIMinVersion)
 	viper.SetDefault("WATCHTOWER_POLL_INTERVAL", 300)
 	viper.SetDefault("WATCHTOWER_TIMEOUT", time.Second*10)
 	viper.SetDefault("WATCHTOWER_NOTIFICATIONS", []string{})
@@ -225,10 +231,11 @@ func SetDefaults() {
 
 // EnvConfig translates the command-line options into environment variables
 // that will initialize the api client
-func EnvConfig(cmd *cobra.Command, dockerAPIMinVersion string) error {
+func EnvConfig(cmd *cobra.Command) error {
 	var err error
 	var host string
 	var tls bool
+	var version string
 
 	flags := cmd.PersistentFlags()
 
@@ -238,13 +245,16 @@ func EnvConfig(cmd *cobra.Command, dockerAPIMinVersion string) error {
 	if tls, err = flags.GetBool("tlsverify"); err != nil {
 		return err
 	}
+	if version, err = flags.GetString("api-version"); err != nil {
+		return err
+	}
 	if err = setEnvOptStr("DOCKER_HOST", host); err != nil {
 		return err
 	}
 	if err = setEnvOptBool("DOCKER_TLS_VERIFY", tls); err != nil {
 		return err
 	}
-	if err = setEnvOptStr("DOCKER_API_VERSION", dockerAPIMinVersion); err != nil {
+	if err = setEnvOptStr("DOCKER_API_VERSION", version); err != nil {
 		return err
 	}
 	return nil
diff --git a/mkdocs.yml b/mkdocs.yml
index d64041b..9656c6d 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -14,6 +14,7 @@ nav:
    - 'Arguments': 'arguments.md'
    - 'Notifications': 'notifications.md'
    - 'Container selection': 'container-selection.md'
+   - 'Credential helpers': 'credential-helpers.md'
    - 'Linked containers': 'linked-containers.md'
    - 'Remote hosts': 'remote-hosts.md'
    - 'Secure connections': 'secure-connections.md'


Test patch:
diff --git a/internal/flags/flags_test.go b/internal/flags/flags_test.go
new file mode 100644
index 0000000..ac57b30
--- /dev/null
+++ b/internal/flags/flags_test.go
@@ -0,0 +1,39 @@
+package flags
+
+import (
+	"os"
+	"testing"
+
+	"github.com/spf13/cobra"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestEnvConfig_Defaults(t *testing.T) {
+	cmd := new(cobra.Command)
+	SetDefaults()
+	RegisterDockerFlags(cmd)
+
+	err := EnvConfig(cmd)
+	require.NoError(t, err)
+
+	assert.Equal(t, "unix:///var/run/docker.sock", os.Getenv("DOCKER_HOST"))
+	assert.Equal(t, "", os.Getenv("DOCKER_TLS_VERIFY"))
+	assert.Equal(t, DockerAPIMinVersion, os.Getenv("DOCKER_API_VERSION"))
+}
+
+func TestEnvConfig_Custom(t *testing.T) {
+	cmd := new(cobra.Command)
+	SetDefaults()
+	RegisterDockerFlags(cmd)
+
+	err := cmd.ParseFlags([]string{"--host", "some-custom-docker-host", "--tlsverify", "--api-version", "1.99"})
+	require.NoError(t, err)
+
+	err = EnvConfig(cmd)
+	require.NoError(t, err)
+
+	assert.Equal(t, "some-custom-docker-host", os.Getenv("DOCKER_HOST"))
+	assert.Equal(t, "1", os.Getenv("DOCKER_TLS_VERIFY"))
+	assert.Equal(t, "1.99", os.Getenv("DOCKER_API_VERSION"))
+}