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 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 ### 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. 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 ( import (
"fmt" "fmt"
"time" "time"
"io/ioutil"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/cli/command"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -146,22 +146,28 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
if client.pullImages { if client.pullImages {
log.Debugf("Pulling %s for %s", imageName, c.Name()) log.Debugf("Pulling %s for %s", imageName, c.Name())
auth := types.AuthConfig { var opts types.ImagePullOptions // ImagePullOptions can take a RegistryAuth arg to authenticate against a private registry
Username: "testuser", auth, err := EncodedEnvAuth(imageName)
Password: "testpassword", if err != nil {
// credentials environment vars not set, trying Docker config instead
auth, err = EncodedConfigAuth(imageName)
} }
encodedAuth, err := command.EncodeAuthToBase64(auth)
if err != nil { 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 response, err := client.api.ImagePull(bg, imageName, opts)
closer, err := client.api.ImagePull(bg, imageName, types.ImagePullOptions{RegistryAuth: encodedAuth})
if err != nil { if err != nil {
log.Debugf("Error pulling image %s, %s", imageName, err) log.Debugf("Error pulling image %s, %s", imageName, err)
return false, 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) newImageInfo, _, err := client.api.ImageInspectWithRaw(bg, imageName)
@ -174,7 +180,6 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
return true, nil return true, nil
} else { } else {
log.Debugf("No new images found for %s", c.Name()) 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 ( var (
wg sync.WaitGroup wg sync.WaitGroup
client container.Client client container.Client
pollInterval time.Duration pollInterval time.Duration
cleanup bool cleanup bool
) )
func init() { func init() {
@ -61,8 +61,8 @@ func main() {
Usage: "enable debug mode with verbose logging", Usage: "enable debug mode with verbose logging",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "apiversion", Name: "apiversion",
Usage: "the version of the docker api", Usage: "the version of the docker api",
EnvVar: "DOCKER_API_VERSION", EnvVar: "DOCKER_API_VERSION",
}, },
} }
@ -124,9 +124,9 @@ func handleSignals() {
} }
func setEnvOptStr(env string, opt string) error { func setEnvOptStr(env string, opt string) error {
if (opt != "" && opt != os.Getenv(env)) { if opt != "" && opt != os.Getenv(env) {
err := os.Setenv(env, opt) err := os.Setenv(env, opt)
if (err != nil) { if err != nil {
return err return err
} }
} }
@ -134,7 +134,7 @@ func setEnvOptStr(env string, opt string) error {
} }
func setEnvOptBool(env string, opt bool) error { func setEnvOptBool(env string, opt bool) error {
if (opt == true) { if opt == true {
return setEnvOptStr(env, "1") return setEnvOptStr(env, "1")
} }
return nil return nil

Loading…
Cancel
Save