mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-22 05:40:50 +02:00
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.
This commit is contained in:
parent
ef430b791a
commit
1c59200565
4 changed files with 128 additions and 20 deletions
10
README.md
10
README.md
|
@ -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",
|
|
||||||
}
|
|
||||||
encodedAuth, err := command.EncodeAuthToBase64(auth)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
// credentials environment vars not set, trying Docker config instead
|
||||||
|
auth, err = EncodedConfigAuth(imageName)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
93
container/trust.go
Normal file
93
container/trust.go
Normal file
|
@ -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")
|
||||||
|
}
|
18
main.go
18
main.go
|
@ -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…
Add table
Add a link
Reference in a new issue