Registry authentication was failing silently when pulling images.

Load authentication credentials for available credential stores in order of preference:
 1. Environment variables REPO_USER, REPO_PASS
 2. Docker config files
Request image pull with authentication header.
Wait until pull request is complete before exiting function.
pull/26/head
Ross Cadogan 8 years ago
parent ef430b791a
commit 1c59200565

@ -39,6 +39,16 @@ docker run -d \
centurylink/watchtower
```
If pulling images from a private Docker registry, supply any authentication credentials with the environment variables `REPO_USER` and `REPO_PASS` or omit to leave watchtower load credentials from the default Docker config (`~/.docker/config.json`):
```
docker run -d \
--name watchtower \
-e REPO_USER="<username>" -e REPO_PASS="<password>" \
-v /var/run/docker.sock:/var/run/docker.sock \
drud/watchtower container_to_watch --debug
```
### Arguments
By default, watchtower will monitor all containers running within the Docker daemon to which it is pointed (in most cases this will be the local Docker daemon, but you can override it with the `--host` option described in the next section). However, you can restrict watchtower to monitoring a subset of the running containers by specifying the container names as arguments when launching watchtower.

@ -3,11 +3,11 @@ package container
import (
"fmt"
"time"
"io/ioutil"
log "github.com/Sirupsen/logrus"
dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cli/command"
"golang.org/x/net/context"
)
@ -146,22 +146,28 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
if client.pullImages {
log.Debugf("Pulling %s for %s", imageName, c.Name())
auth := types.AuthConfig {
Username: "testuser",
Password: "testpassword",
var opts types.ImagePullOptions // ImagePullOptions can take a RegistryAuth arg to authenticate against a private registry
auth, err := EncodedEnvAuth(imageName)
if err != nil {
// credentials environment vars not set, trying Docker config instead
auth, err = EncodedConfigAuth(imageName)
}
encodedAuth, err := command.EncodeAuthToBase64(auth)
if err != nil {
return false, err
log.Debug("No authentication credentials found")
opts = types.ImagePullOptions{}
} else {
opts = types.ImagePullOptions{RegistryAuth: auth, PrivilegeFunc: DefaultAuthHandler}
}
// Note: ImagePullOptions below can take a RegistryAuth arg if 401 on private registry
closer, err := client.api.ImagePull(bg, imageName, types.ImagePullOptions{RegistryAuth: encodedAuth})
response, err := client.api.ImagePull(bg, imageName, opts)
if err != nil {
log.Debugf("Error pulling image %s, %s", imageName, err)
return false, err
}
defer closer.Close()
defer response.Close()
// the pull request will be aborted prematurely unless the response is read
_, err = ioutil.ReadAll(response)
}
newImageInfo, _, err := client.api.ImageInspectWithRaw(bg, imageName)
@ -174,7 +180,6 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
return true, nil
} else {
log.Debugf("No new images found for %s", c.Name())
log.Debugf("Old image ID %s is the same as New Image ID %s", oldImageInfo.ID, newImageInfo.ID)
}

@ -0,0 +1,93 @@
package container
import (
"errors"
"os"
"strings"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/reference"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cliconfig/configfile"
"github.com/docker/docker/cliconfig/credentials"
)
/*
* Return an encoded auth config for the given registry
* hardcoded for a test environment
*/
func EncodedTestAuth(ref string) (string, error) {
auth := types.AuthConfig {
Username: "testuser",
Password: "testpassword",
}
return EncodeAuth(auth)
}
/*
* Return an encoded auth config for the given registry
* loaded from environment variables
*/
func EncodedEnvAuth(ref string) (string, error) {
username := os.Getenv("REPO_USER")
password := os.Getenv("REPO_PASS")
if username != "" && password != "" {
auth := types.AuthConfig {
Username: username,
Password: password,
}
log.Debugf("Loaded auth credentials %s from environment for %s", auth, ref)
return EncodeAuth(auth)
} else {
return "", errors.New("Registry auth environment variables (REPO_USER, REPO_PASS) not set")
}
}
/*
* Return an encoded auth config for the given registry
* loaded from the docker config
*/
func EncodedConfigAuth(ref string) (string, error) {
server, err := ParseServerAddress(ref)
configFile := command.LoadDefaultConfigFile(log.StandardLogger().Out)
credStore := CredentialsStore(*configFile)
auth, err := credStore.Get(server)
if err != nil {
return "", err
}
log.Debugf("Loaded auth credentials %s from Docker config for reference %s", auth, ref)
return EncodeAuth(auth)
}
func ParseServerAddress(ref string) (string, error) {
repository, _, err := reference.Parse(ref)
if err != nil {
return ref, err
}
parts := strings.Split(repository, "/")
return parts[0], nil
}
// CredentialsStore returns a new credentials store based
// on the settings provided in the configuration file.
func CredentialsStore(configFile configfile.ConfigFile) credentials.Store {
if configFile.CredentialsStore != "" {
return credentials.NewNativeStore(&configFile)
}
return credentials.NewFileStore(&configFile)
}
/*
* Base64 encode an AuthConfig struct for transmission over HTTP
*/
func EncodeAuth(auth types.AuthConfig) (string, error) {
return command.EncodeAuthToBase64(auth)
}
func DefaultAuthHandler() (string, error) {
log.Error("Authentication requested")
return "", fmt.Errorf("Error requesting privilege")
}

@ -15,10 +15,10 @@ import (
)
var (
wg sync.WaitGroup
client container.Client
pollInterval time.Duration
cleanup bool
wg sync.WaitGroup
client container.Client
pollInterval time.Duration
cleanup bool
)
func init() {
@ -61,8 +61,8 @@ func main() {
Usage: "enable debug mode with verbose logging",
},
cli.StringFlag{
Name: "apiversion",
Usage: "the version of the docker api",
Name: "apiversion",
Usage: "the version of the docker api",
EnvVar: "DOCKER_API_VERSION",
},
}
@ -124,9 +124,9 @@ func handleSignals() {
}
func setEnvOptStr(env string, opt string) error {
if (opt != "" && opt != os.Getenv(env)) {
if opt != "" && opt != os.Getenv(env) {
err := os.Setenv(env, opt)
if (err != nil) {
if err != nil {
return err
}
}
@ -134,7 +134,7 @@ func setEnvOptStr(env string, opt string) error {
}
func setEnvOptBool(env string, opt bool) error {
if (opt == true) {
if opt == true {
return setEnvOptStr(env, "1")
}
return nil

Loading…
Cancel
Save