mirror of
https://github.com/containrrr/watchtower.git
synced 2025-12-13 21:56:38 +01:00
refactor: extract code from the container package
This commit is contained in:
parent
4130b110c6
commit
d1abce889a
15 changed files with 253 additions and 185 deletions
|
|
@ -3,6 +3,7 @@ package container
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/containrrr/watchtower/pkg/registry"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -12,7 +13,7 @@ import (
|
|||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
sdkClient "github.com/docker/docker/client"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
|
@ -40,7 +41,7 @@ type Client interface {
|
|||
// * DOCKER_TLS_VERIFY whether to verify tls certificates
|
||||
// * DOCKER_API_VERSION the minimum docker api version to work with
|
||||
func NewClient(pullImages bool, includeStopped bool, reviveStopped bool, removeVolumes bool) Client {
|
||||
cli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv)
|
||||
cli, err := sdkClient.NewClientWithOpts(sdkClient.FromEnv)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Error instantiating Docker client: %s", err)
|
||||
|
|
@ -56,7 +57,7 @@ func NewClient(pullImages bool, includeStopped bool, reviveStopped bool, removeV
|
|||
}
|
||||
|
||||
type dockerClient struct {
|
||||
api dockerclient.CommonAPIClient
|
||||
api sdkClient.CommonAPIClient
|
||||
pullImages bool
|
||||
removeVolumes bool
|
||||
includeStopped bool
|
||||
|
|
@ -231,53 +232,60 @@ func (client dockerClient) RenameContainer(c Container, newName string) error {
|
|||
return client.api.ContainerRename(bg, c.ID(), newName)
|
||||
}
|
||||
|
||||
func (client dockerClient) IsContainerStale(c Container) (bool, error) {
|
||||
bg := context.Background()
|
||||
oldImageInfo := c.imageInfo
|
||||
imageName := c.ImageName()
|
||||
func (client dockerClient) IsContainerStale(container Container) (bool, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
if client.pullImages {
|
||||
log.Debugf("Pulling %s for %s", imageName, c.Name())
|
||||
|
||||
var opts types.ImagePullOptions // ImagePullOptions can take a RegistryAuth arg to authenticate against a private registry
|
||||
auth, err := EncodedAuth(imageName)
|
||||
log.Debugf("Got auth value: %s", auth)
|
||||
log.Debugf("Got image name: %s", imageName)
|
||||
if err != nil {
|
||||
log.Debugf("Error loading authentication credentials %s", err)
|
||||
return false, err
|
||||
} else if auth == "" {
|
||||
log.Debugf("No authentication credentials found for %s", imageName)
|
||||
opts = types.ImagePullOptions{} // empty/no auth credentials
|
||||
} else {
|
||||
opts = types.ImagePullOptions{RegistryAuth: auth, PrivilegeFunc: DefaultAuthHandler}
|
||||
}
|
||||
|
||||
response, err := client.api.ImagePull(bg, imageName, opts)
|
||||
if err != nil {
|
||||
log.Debugf("Error pulling image %s, %s", imageName, err)
|
||||
return false, err
|
||||
}
|
||||
defer response.Close()
|
||||
|
||||
// the pull request will be aborted prematurely unless the response is read
|
||||
if _, err = ioutil.ReadAll(response); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if !client.pullImages {
|
||||
log.Debugf("Skipping image pull.")
|
||||
} else if err := client.PullImage(ctx, container); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
newImageInfo, _, err := client.api.ImageInspectWithRaw(bg, imageName)
|
||||
return client.HasNewImage(ctx, container)
|
||||
}
|
||||
|
||||
func (client dockerClient) HasNewImage(ctx context.Context, container Container) (bool, error) {
|
||||
oldImageID := container.imageInfo.ID
|
||||
imageName := container.ImageName()
|
||||
|
||||
newImageInfo, _, err := client.api.ImageInspectWithRaw(ctx, imageName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if newImageInfo.ID != oldImageInfo.ID {
|
||||
log.Infof("Found new %s image (%s)", imageName, newImageInfo.ID)
|
||||
return true, nil
|
||||
if newImageInfo.ID == oldImageID {
|
||||
log.Debugf("No new images found for %s", container.Name())
|
||||
return false, nil
|
||||
}
|
||||
|
||||
log.Debugf("No new images found for %s", c.Name())
|
||||
return false, nil
|
||||
log.Infof("Found new %s image (%s)", imageName, newImageInfo.ID)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (client dockerClient) PullImage(ctx context.Context, container Container) error {
|
||||
containerName := container.Name()
|
||||
imageName := container.ImageName()
|
||||
log.Debugf("Pulling %s for %s", imageName, containerName)
|
||||
|
||||
opts, err := registry.GetPullOptions(imageName)
|
||||
if err != nil {
|
||||
log.Debugf("Error loading authentication credentials %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := client.api.ImagePull(ctx, imageName, opts)
|
||||
if err != nil {
|
||||
log.Debugf("Error pulling image %s, %s", imageName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
defer response.Close()
|
||||
// the pull request will be aborted prematurely unless the response is read
|
||||
if _, err = ioutil.ReadAll(response); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client dockerClient) RemoveImageByID(id string) error {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ type Container struct {
|
|||
imageInfo *types.ImageInspect
|
||||
}
|
||||
|
||||
// ContainerInfo fetches JSON info for the container
|
||||
func (c Container) ContainerInfo() *types.ContainerJSON {
|
||||
return c.containerInfo
|
||||
}
|
||||
|
||||
// ID returns the Docker container ID.
|
||||
func (c Container) ID() string {
|
||||
return c.containerInfo.ID
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package container
|
|||
|
||||
import (
|
||||
"github.com/containrrr/watchtower/pkg/container/mocks"
|
||||
"github.com/containrrr/watchtower/pkg/filters"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
cli "github.com/docker/docker/client"
|
||||
|
|
@ -34,14 +35,14 @@ var _ = Describe("the container", func() {
|
|||
})
|
||||
When("listing containers without any filter", func() {
|
||||
It("should return all available containers", func() {
|
||||
containers, err := client.ListContainers(noFilter)
|
||||
containers, err := client.ListContainers(filters.NoFilter)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(containers) == 2).To(BeTrue())
|
||||
})
|
||||
})
|
||||
When("listing containers with a filter matching nothing", func() {
|
||||
It("should return an empty array", func() {
|
||||
filter := filterByNames([]string{"lollercoaster"}, noFilter)
|
||||
filter := filters.FilterByNames([]string{"lollercoaster"}, filters.NoFilter)
|
||||
containers, err := client.ListContainers(filter)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(containers) == 0).To(BeTrue())
|
||||
|
|
@ -49,7 +50,7 @@ var _ = Describe("the container", func() {
|
|||
})
|
||||
When("listing containers with a watchtower filter", func() {
|
||||
It("should return only the watchtower container", func() {
|
||||
containers, err := client.ListContainers(WatchtowerContainersFilter)
|
||||
containers, err := client.ListContainers(filters.WatchtowerContainersFilter)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(containers) == 1).To(BeTrue())
|
||||
Expect(containers[0].ImageName()).To(Equal("containrrr/watchtower:latest"))
|
||||
|
|
@ -62,7 +63,7 @@ var _ = Describe("the container", func() {
|
|||
pullImages: false,
|
||||
includeStopped: true,
|
||||
}
|
||||
containers, err := client.ListContainers(noFilter)
|
||||
containers, err := client.ListContainers(filters.NoFilter)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(containers) > 0).To(BeTrue())
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
package container
|
||||
|
||||
import t "github.com/containrrr/watchtower/pkg/types"
|
||||
|
||||
// WatchtowerContainersFilter filters only watchtower containers
|
||||
func WatchtowerContainersFilter(c t.FilterableContainer) bool { return c.IsWatchtower() }
|
||||
|
||||
// Filter no containers and returns all
|
||||
func noFilter(t.FilterableContainer) bool { return true }
|
||||
|
||||
// Filters containers which don't have a specified name
|
||||
func filterByNames(names []string, baseFilter t.Filter) t.Filter {
|
||||
if len(names) == 0 {
|
||||
return baseFilter
|
||||
}
|
||||
|
||||
return func(c t.FilterableContainer) bool {
|
||||
for _, name := range names {
|
||||
if (name == c.Name()) || (name == c.Name()[1:]) {
|
||||
return baseFilter(c)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Filters out containers that don't have the 'enableLabel'
|
||||
func filterByEnableLabel(baseFilter t.Filter) t.Filter {
|
||||
return func(c t.FilterableContainer) bool {
|
||||
// If label filtering is enabled, containers should only be considered
|
||||
// if the label is specifically set.
|
||||
_, ok := c.Enabled()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return baseFilter(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Filters out containers that have a 'enableLabel' and is set to disable.
|
||||
func filterByDisabledLabel(baseFilter t.Filter) t.Filter {
|
||||
return func(c t.FilterableContainer) bool {
|
||||
enabledLabel, ok := c.Enabled()
|
||||
if ok && !enabledLabel {
|
||||
// If the label has been set and it demands a disable
|
||||
return false
|
||||
}
|
||||
|
||||
return baseFilter(c)
|
||||
}
|
||||
}
|
||||
|
||||
// BuildFilter creates the needed filter of containers
|
||||
func BuildFilter(names []string, enableLabel bool) t.Filter {
|
||||
filter := noFilter
|
||||
filter = filterByNames(names, filter)
|
||||
if enableLabel {
|
||||
// If label filtering is enabled, containers should only be considered
|
||||
// if the label is specifically set.
|
||||
filter = filterByEnableLabel(filter)
|
||||
}
|
||||
filter = filterByDisabledLabel(filter)
|
||||
return filter
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containrrr/watchtower/pkg/container/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWatchtowerContainersFilter(t *testing.T) {
|
||||
container := new(mocks.FilterableContainer)
|
||||
|
||||
container.On("IsWatchtower").Return(true)
|
||||
|
||||
assert.True(t, WatchtowerContainersFilter(container))
|
||||
|
||||
container.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestNoFilter(t *testing.T) {
|
||||
container := new(mocks.FilterableContainer)
|
||||
|
||||
assert.True(t, noFilter(container))
|
||||
|
||||
container.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestFilterByNames(t *testing.T) {
|
||||
var names []string
|
||||
|
||||
filter := filterByNames(names, nil)
|
||||
assert.Nil(t, filter)
|
||||
|
||||
names = append(names, "test")
|
||||
|
||||
filter = filterByNames(names, noFilter)
|
||||
assert.NotNil(t, filter)
|
||||
|
||||
container := new(mocks.FilterableContainer)
|
||||
container.On("Name").Return("test")
|
||||
assert.True(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Name").Return("NoTest")
|
||||
assert.False(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestFilterByEnableLabel(t *testing.T) {
|
||||
filter := filterByEnableLabel(noFilter)
|
||||
assert.NotNil(t, filter)
|
||||
|
||||
container := new(mocks.FilterableContainer)
|
||||
container.On("Enabled").Return(true, true)
|
||||
assert.True(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Enabled").Return(false, true)
|
||||
assert.True(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Enabled").Return(false, false)
|
||||
assert.False(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestFilterByDisabledLabel(t *testing.T) {
|
||||
filter := filterByDisabledLabel(noFilter)
|
||||
assert.NotNil(t, filter)
|
||||
|
||||
container := new(mocks.FilterableContainer)
|
||||
container.On("Enabled").Return(true, true)
|
||||
assert.True(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Enabled").Return(false, true)
|
||||
assert.False(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Enabled").Return(false, false)
|
||||
assert.True(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestBuildFilter(t *testing.T) {
|
||||
var names []string
|
||||
names = append(names, "test")
|
||||
|
||||
filter := BuildFilter(names, false)
|
||||
|
||||
container := new(mocks.FilterableContainer)
|
||||
container.On("Name").Return("Invalid")
|
||||
container.On("Enabled").Return(false, false)
|
||||
assert.False(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Name").Return("test")
|
||||
container.On("Enabled").Return(false, false)
|
||||
assert.True(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Name").Return("Invalid")
|
||||
container.On("Enabled").Return(true, true)
|
||||
assert.False(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Name").Return("test")
|
||||
container.On("Enabled").Return(true, true)
|
||||
assert.True(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Enabled").Return(false, true)
|
||||
assert.False(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestBuildFilterEnableLabel(t *testing.T) {
|
||||
var names []string
|
||||
names = append(names, "test")
|
||||
|
||||
filter := BuildFilter(names, true)
|
||||
|
||||
container := new(mocks.FilterableContainer)
|
||||
container.On("Enabled").Return(false, false)
|
||||
assert.False(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Name").Return("Invalid")
|
||||
container.On("Enabled").Twice().Return(true, true)
|
||||
assert.False(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Name").Return("test")
|
||||
container.On("Enabled").Twice().Return(true, true)
|
||||
assert.True(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
|
||||
container = new(mocks.FilterableContainer)
|
||||
container.On("Enabled").Return(false, true)
|
||||
assert.False(t, filter(container))
|
||||
container.AssertExpectations(t)
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ByCreated allows a list of Container structs to be sorted by the container's
|
||||
// created date.
|
||||
type ByCreated []Container
|
||||
|
||||
func (c ByCreated) Len() int { return len(c) }
|
||||
func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
|
||||
// Less will compare two elements (identified by index) in the Container
|
||||
// list by created-date.
|
||||
func (c ByCreated) Less(i, j int) bool {
|
||||
t1, err := time.Parse(time.RFC3339Nano, c[i].containerInfo.Created)
|
||||
if err != nil {
|
||||
t1 = time.Now()
|
||||
}
|
||||
|
||||
t2, _ := time.Parse(time.RFC3339Nano, c[j].containerInfo.Created)
|
||||
if err != nil {
|
||||
t1 = time.Now()
|
||||
}
|
||||
|
||||
return t1.Before(t2)
|
||||
}
|
||||
|
||||
// SortByDependencies will sort the list of containers taking into account any
|
||||
// links between containers. Container with no outgoing links will be sorted to
|
||||
// the front of the list while containers with links will be sorted after all
|
||||
// of their dependencies. This sort order ensures that linked containers can
|
||||
// be started in the correct order.
|
||||
func SortByDependencies(containers []Container) ([]Container, error) {
|
||||
sorter := dependencySorter{}
|
||||
return sorter.Sort(containers)
|
||||
}
|
||||
|
||||
type dependencySorter struct {
|
||||
unvisited []Container
|
||||
marked map[string]bool
|
||||
sorted []Container
|
||||
}
|
||||
|
||||
func (ds *dependencySorter) Sort(containers []Container) ([]Container, error) {
|
||||
ds.unvisited = containers
|
||||
ds.marked = map[string]bool{}
|
||||
|
||||
for len(ds.unvisited) > 0 {
|
||||
if err := ds.visit(ds.unvisited[0]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ds.sorted, nil
|
||||
}
|
||||
|
||||
func (ds *dependencySorter) visit(c Container) error {
|
||||
|
||||
if _, ok := ds.marked[c.Name()]; ok {
|
||||
return fmt.Errorf("Circular reference to %s", c.Name())
|
||||
}
|
||||
|
||||
// Mark any visited node so that circular references can be detected
|
||||
ds.marked[c.Name()] = true
|
||||
defer delete(ds.marked, c.Name())
|
||||
|
||||
// Recursively visit links
|
||||
for _, linkName := range c.Links() {
|
||||
if linkedContainer := ds.findUnvisited(linkName); linkedContainer != nil {
|
||||
if err := ds.visit(*linkedContainer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move container from unvisited to sorted
|
||||
ds.removeUnvisited(c)
|
||||
ds.sorted = append(ds.sorted, c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *dependencySorter) findUnvisited(name string) *Container {
|
||||
for _, c := range ds.unvisited {
|
||||
if c.Name() == name {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *dependencySorter) removeUnvisited(c Container) {
|
||||
var idx int
|
||||
for i := range ds.unvisited {
|
||||
if ds.unvisited[i].Name() == c.Name() {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ds.unvisited = append(ds.unvisited[0:idx], ds.unvisited[idx+1:]...)
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliconfig "github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// EncodedAuth returns an encoded auth config for the given registry
|
||||
// loaded from environment variables or docker config
|
||||
// as available in that order
|
||||
func EncodedAuth(ref string) (string, error) {
|
||||
auth, err := EncodedEnvAuth(ref)
|
||||
if err != nil {
|
||||
auth, err = EncodedConfigAuth(ref)
|
||||
}
|
||||
return auth, err
|
||||
}
|
||||
|
||||
// EncodedEnvAuth returns an encoded auth config for the given registry
|
||||
// loaded from environment variables
|
||||
// Returns an error if authentication environment variables have not been set
|
||||
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 for %s", auth, ref)
|
||||
return EncodeAuth(auth)
|
||||
}
|
||||
return "", errors.New("Registry auth environment variables (REPO_USER, REPO_PASS) not set")
|
||||
}
|
||||
|
||||
// EncodedConfigAuth returns an encoded auth config for the given registry
|
||||
// loaded from the docker config
|
||||
// Returns an empty string if credentials cannot be found for the referenced server
|
||||
// The docker config must be mounted on the container
|
||||
func EncodedConfigAuth(ref string) (string, error) {
|
||||
server, err := ParseServerAddress(ref)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to parse the image ref %s", err)
|
||||
return "", err
|
||||
}
|
||||
configDir := os.Getenv("DOCKER_CONFIG")
|
||||
if configDir == "" {
|
||||
configDir = "/"
|
||||
}
|
||||
configFile, err := cliconfig.Load(configDir)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to find default config file %s", err)
|
||||
return "", err
|
||||
}
|
||||
credStore := CredentialsStore(*configFile)
|
||||
auth, _ := credStore.Get(server) // returns (types.AuthConfig{}) if server not in credStore
|
||||
|
||||
if auth == (types.AuthConfig{}) {
|
||||
log.Debugf("No credentials for %s in %s", server, configFile.Filename)
|
||||
return "", nil
|
||||
}
|
||||
log.Debugf("Loaded auth credentials %s from %s", auth, configFile.Filename)
|
||||
return EncodeAuth(auth)
|
||||
}
|
||||
|
||||
// ParseServerAddress extracts the server part from a container image ref
|
||||
func ParseServerAddress(ref string) (string, error) {
|
||||
|
||||
parsedRef, err := reference.Parse(ref)
|
||||
if err != nil {
|
||||
return ref, err
|
||||
}
|
||||
|
||||
parts := strings.Split(parsedRef.String(), "/")
|
||||
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, configFile.CredentialsStore)
|
||||
}
|
||||
return credentials.NewFileStore(&configFile)
|
||||
}
|
||||
|
||||
// EncodeAuth Base64 encode an AuthConfig struct for transmission over HTTP
|
||||
func EncodeAuth(auth types.AuthConfig) (string, error) {
|
||||
return command.EncodeAuthToBase64(auth)
|
||||
}
|
||||
|
||||
// DefaultAuthHandler will be invoked if an AuthConfig is rejected
|
||||
// It could be used to return a new value for the "X-Registry-Auth" authentication header,
|
||||
// but there's no point trying again with the same value as used in AuthConfig
|
||||
func DefaultAuthHandler() (string, error) {
|
||||
log.Debug("Authentication request was rejected. Trying again without authentication")
|
||||
return "", nil
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncodedEnvAuth_ShouldReturnAnErrorIfRepoEnvsAreUnset(t *testing.T) {
|
||||
os.Unsetenv("REPO_USER")
|
||||
os.Unsetenv("REPO_PASS")
|
||||
_, err := EncodedEnvAuth("")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
func TestEncodedEnvAuth_ShouldReturnAuthHashIfRepoEnvsAreSet(t *testing.T) {
|
||||
expectedHash := "eyJ1c2VybmFtZSI6ImNvbnRhaW5ycnItdXNlciIsInBhc3N3b3JkIjoiY29udGFpbnJyci1wYXNzIn0="
|
||||
|
||||
os.Setenv("REPO_USER", "containrrr-user")
|
||||
os.Setenv("REPO_PASS", "containrrr-pass")
|
||||
config, _ := EncodedEnvAuth("")
|
||||
|
||||
assert.Equal(t, config, expectedHash)
|
||||
}
|
||||
|
||||
func TestEncodedConfigAuth_ShouldReturnAnErrorIfFileIsNotPresent(t *testing.T) {
|
||||
os.Setenv("DOCKER_CONFIG", "/dev/null/should-fail")
|
||||
_, err := EncodedConfigAuth("")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* This part only confirms that it still works in the same way as it did
|
||||
* with the old version of the docker api client sdk. I'd say that
|
||||
* ParseServerAddress likely needs to be elaborated a bit to default to
|
||||
* dockerhub in case no server address was provided.
|
||||
*
|
||||
* ++ @simskij, 2019-04-04
|
||||
*/
|
||||
|
||||
func TestParseServerAddress_ShouldReturnErrorIfPassedEmptyString(t *testing.T) {
|
||||
_, err := ParseServerAddress("")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestParseServerAddress_ShouldReturnTheRepoNameIfPassedAFullyQualifiedImageName(t *testing.T) {
|
||||
val, _ := ParseServerAddress("github.com/containrrrr/config")
|
||||
assert.Equal(t, val, "github.com")
|
||||
}
|
||||
|
||||
func TestParseServerAddress_ShouldReturnTheOrganizationPartIfPassedAnImageNameMissingServerName(t *testing.T) {
|
||||
val, _ := ParseServerAddress("containrrr/config")
|
||||
assert.Equal(t, val, "containrrr")
|
||||
}
|
||||
|
||||
func TestParseServerAddress_ShouldReturnTheServerNameIfPassedAFullyQualifiedImageName(t *testing.T) {
|
||||
val, _ := ParseServerAddress("github.com/containrrrr/config")
|
||||
assert.Equal(t, val, "github.com")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue