refactor: extract code from the container package

pull/448/head
Simon Aronsson 5 years ago
parent 4130b110c6
commit d1abce889a

@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"github.com/containrrr/watchtower/pkg/filters"
"os" "os"
"os/signal" "os/signal"
"strconv" "strconv"
@ -108,7 +109,7 @@ func PreRun(cmd *cobra.Command, args []string) {
// Run is the main execution flow of the command // Run is the main execution flow of the command
func Run(c *cobra.Command, names []string) { func Run(c *cobra.Command, names []string) {
filter := container.BuildFilter(names, enableLabel) filter := filters.BuildFilter(names, enableLabel)
runOnce, _ := c.PersistentFlags().GetBool("run-once") runOnce, _ := c.PersistentFlags().GetBool("run-once")
if runOnce { if runOnce {
@ -172,7 +173,7 @@ func runUpgradesOnSchedule(filter t.Filter) error {
func runUpdatesWithNotifications(filter t.Filter) { func runUpdatesWithNotifications(filter t.Filter) {
notifier.StartNotification() notifier.StartNotification()
updateParams := actions.UpdateParams{ updateParams := t.UpdateParams{
Filter: filter, Filter: filter,
Cleanup: cleanup, Cleanup: cleanup,
NoRestart: noRestart, NoRestart: noRestart,

@ -3,6 +3,8 @@ package actions
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/containrrr/watchtower/pkg/filters"
"github.com/containrrr/watchtower/pkg/sorter"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -19,7 +21,7 @@ import (
// will stop and remove all but the most recently started container. // will stop and remove all but the most recently started container.
func CheckForMultipleWatchtowerInstances(client container.Client, cleanup bool) error { func CheckForMultipleWatchtowerInstances(client container.Client, cleanup bool) error {
awaitDockerClient() awaitDockerClient()
containers, err := client.ListContainers(container.WatchtowerContainersFilter) containers, err := client.ListContainers(filters.WatchtowerContainersFilter)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -39,7 +41,7 @@ func cleanupExcessWatchtowers(containers []container.Container, client container
var cleanupErrors int var cleanupErrors int
var stopErrors int var stopErrors int
sort.Sort(container.ByCreated(containers)) sort.Sort(sorter.ByCreated(containers))
allContainersExceptLast := containers[0 : len(containers)-1] allContainersExceptLast := containers[0 : len(containers)-1]
for _, c := range allContainersExceptLast { for _, c := range allContainersExceptLast {

@ -3,6 +3,9 @@ package actions
import ( import (
"github.com/containrrr/watchtower/internal/util" "github.com/containrrr/watchtower/internal/util"
"github.com/containrrr/watchtower/pkg/container" "github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/lifecycle"
"github.com/containrrr/watchtower/pkg/sorter"
"github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -10,10 +13,12 @@ import (
// used to start those containers have been updated. If a change is detected in // used to start those containers have been updated. If a change is detected in
// any of the images, the associated containers are stopped and restarted with // any of the images, the associated containers are stopped and restarted with
// the new image. // the new image.
func Update(client container.Client, params UpdateParams) error { func Update(client container.Client, params types.UpdateParams) error {
log.Debug("Checking containers for updated images") log.Debug("Checking containers for updated images")
executePreCheck(client, params) if params.LifecycleHooks {
lifecycle.ExecutePreChecks(client, params)
}
containers, err := client.ListContainers(params.Filter) containers, err := client.ListContainers(params.Filter)
if err != nil { if err != nil {
@ -30,7 +35,7 @@ func Update(client container.Client, params UpdateParams) error {
containers[i].Stale = stale containers[i].Stale = stale
} }
containers, err = container.SortByDependencies(containers) containers, err = sorter.SortByDependencies(containers)
if err != nil { if err != nil {
return err return err
} }
@ -38,24 +43,28 @@ func Update(client container.Client, params UpdateParams) error {
checkDependencies(containers) checkDependencies(containers)
if params.MonitorOnly { if params.MonitorOnly {
executePostCheck(client, params) if params.LifecycleHooks {
lifecycle.ExecutePostChecks(client, params)
}
return nil return nil
} }
stopContainersInReversedOrder(containers, client, params) stopContainersInReversedOrder(containers, client, params)
restartContainersInSortedOrder(containers, client, params) restartContainersInSortedOrder(containers, client, params)
executePostCheck(client, params) if params.LifecycleHooks {
lifecycle.ExecutePostChecks(client, params)
}
return nil return nil
} }
func stopContainersInReversedOrder(containers []container.Container, client container.Client, params UpdateParams) { func stopContainersInReversedOrder(containers []container.Container, client container.Client, params types.UpdateParams) {
for i := len(containers) - 1; i >= 0; i-- { for i := len(containers) - 1; i >= 0; i-- {
stopStaleContainer(containers[i], client, params) stopStaleContainer(containers[i], client, params)
} }
} }
func stopStaleContainer(container container.Container, client container.Client, params UpdateParams) { func stopStaleContainer(container container.Container, client container.Client, params types.UpdateParams) {
if container.IsWatchtower() { if container.IsWatchtower() {
log.Debugf("This is the watchtower container %s", container.Name()) log.Debugf("This is the watchtower container %s", container.Name())
return return
@ -64,15 +73,17 @@ func stopStaleContainer(container container.Container, client container.Client,
if !container.Stale { if !container.Stale {
return return
} }
if params.LifecycleHooks {
lifecycle.ExecutePreUpdateCommand(client, container)
executePreUpdateCommand(client, container) }
if err := client.StopContainer(container, params.Timeout); err != nil { if err := client.StopContainer(container, params.Timeout); err != nil {
log.Error(err) log.Error(err)
} }
} }
func restartContainersInSortedOrder(containers []container.Container, client container.Client, params UpdateParams) { func restartContainersInSortedOrder(containers []container.Container, client container.Client, params types.UpdateParams) {
imageIDs := make(map[string]bool) imageIDs := make(map[string]bool)
for _, container := range containers { for _, container := range containers {
@ -91,7 +102,7 @@ func restartContainersInSortedOrder(containers []container.Container, client con
} }
} }
func restartStaleContainer(container container.Container, client container.Client, params UpdateParams) { func restartStaleContainer(container container.Container, client container.Client, params types.UpdateParams) {
// Since we can't shutdown a watchtower container immediately, we need to // Since we can't shutdown a watchtower container immediately, we need to
// start the new one while the old one is still running. This prevents us // start the new one while the old one is still running. This prevents us
// from re-using the same container name so we first rename the current // from re-using the same container name so we first rename the current
@ -107,7 +118,7 @@ func restartStaleContainer(container container.Container, client container.Clien
if newContainerID, err := client.StartContainer(container); err != nil { if newContainerID, err := client.StartContainer(container); err != nil {
log.Error(err) log.Error(err)
} else if container.Stale && params.LifecycleHooks { } else if container.Stale && params.LifecycleHooks {
executePostUpdateCommand(client, newContainerID) lifecycle.ExecutePostUpdateCommand(client, newContainerID)
} }
} }
} }
@ -130,82 +141,3 @@ func checkDependencies(containers []container.Container) {
} }
} }
} }
func executePreCheck(client container.Client, params UpdateParams) {
containers, err := client.ListContainers(params.Filter)
if err != nil {
return
}
for _, container := range containers {
executePreCheckCommand(client, container)
}
}
func executePostCheck(client container.Client, params UpdateParams) {
containers, err := client.ListContainers(params.Filter)
if err != nil {
return
}
for _, container := range containers {
executePostCheckCommand(client, container)
}
}
func executePreCheckCommand(client container.Client, container container.Container) {
command := container.GetLifecyclePreCheckCommand()
if len(command) == 0 {
log.Debug("No pre-check command supplied. Skipping")
return
}
log.Info("Executing pre-check command.")
if err := client.ExecuteCommand(container.ID(), command); err != nil {
log.Error(err)
}
}
func executePostCheckCommand(client container.Client, container container.Container) {
command := container.GetLifecyclePostCheckCommand()
if len(command) == 0 {
log.Debug("No post-check command supplied. Skipping")
return
}
log.Info("Executing post-check command.")
if err := client.ExecuteCommand(container.ID(), command); err != nil {
log.Error(err)
}
}
func executePreUpdateCommand(client container.Client, container container.Container) {
command := container.GetLifecyclePreUpdateCommand()
if len(command) == 0 {
log.Debug("No pre-update command supplied. Skipping")
return
}
log.Info("Executing pre-update command.")
if err := client.ExecuteCommand(container.ID(), command); err != nil {
log.Error(err)
}
}
func executePostUpdateCommand(client container.Client, newContainerID string) {
newContainer, err := client.GetContainer(newContainerID)
if err != nil {
log.Error(err)
return
}
command := newContainer.GetLifecyclePostUpdateCommand()
if len(command) == 0 {
log.Debug("No post-update command supplied. Skipping")
return
}
log.Info("Executing post-update command.")
if err := client.ExecuteCommand(newContainerID, command); err != nil {
log.Error(err)
}
}

@ -4,6 +4,7 @@ import (
"github.com/containrrr/watchtower/internal/actions" "github.com/containrrr/watchtower/internal/actions"
"github.com/containrrr/watchtower/pkg/container" "github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/container/mocks" "github.com/containrrr/watchtower/pkg/container/mocks"
"github.com/containrrr/watchtower/pkg/types"
cli "github.com/docker/docker/client" cli "github.com/docker/docker/client"
"time" "time"
@ -59,7 +60,7 @@ var _ = Describe("the update action", func() {
When("there are multiple containers using the same image", func() { When("there are multiple containers using the same image", func() {
It("should only try to remove the image once", func() { It("should only try to remove the image once", func() {
err := actions.Update(client, actions.UpdateParams{ Cleanup: true }) err := actions.Update(client, types.UpdateParams{ Cleanup: true })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1)) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1))
}) })
@ -75,7 +76,7 @@ var _ = Describe("the update action", func() {
time.Now(), time.Now(),
), ),
) )
err := actions.Update(client, actions.UpdateParams{ Cleanup: true }) err := actions.Update(client, types.UpdateParams{ Cleanup: true })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(2)) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(2))
}) })

@ -3,6 +3,7 @@ package container
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/containrrr/watchtower/pkg/registry"
"io/ioutil" "io/ioutil"
"strings" "strings"
"time" "time"
@ -12,7 +13,7 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
dockerclient "github.com/docker/docker/client" sdkClient "github.com/docker/docker/client"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -40,7 +41,7 @@ type Client interface {
// * DOCKER_TLS_VERIFY whether to verify tls certificates // * DOCKER_TLS_VERIFY whether to verify tls certificates
// * DOCKER_API_VERSION the minimum docker api version to work with // * DOCKER_API_VERSION the minimum docker api version to work with
func NewClient(pullImages bool, includeStopped bool, reviveStopped bool, removeVolumes bool) Client { 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 { if err != nil {
log.Fatalf("Error instantiating Docker client: %s", err) log.Fatalf("Error instantiating Docker client: %s", err)
@ -56,7 +57,7 @@ func NewClient(pullImages bool, includeStopped bool, reviveStopped bool, removeV
} }
type dockerClient struct { type dockerClient struct {
api dockerclient.CommonAPIClient api sdkClient.CommonAPIClient
pullImages bool pullImages bool
removeVolumes bool removeVolumes bool
includeStopped bool includeStopped bool
@ -231,53 +232,60 @@ func (client dockerClient) RenameContainer(c Container, newName string) error {
return client.api.ContainerRename(bg, c.ID(), newName) return client.api.ContainerRename(bg, c.ID(), newName)
} }
func (client dockerClient) IsContainerStale(c Container) (bool, error) { func (client dockerClient) IsContainerStale(container Container) (bool, error) {
bg := context.Background() ctx := context.Background()
oldImageInfo := c.imageInfo
imageName := c.ImageName()
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 if !client.pullImages {
auth, err := EncodedAuth(imageName) log.Debugf("Skipping image pull.")
log.Debugf("Got auth value: %s", auth) } else if err := client.PullImage(ctx, container); err != nil {
log.Debugf("Got image name: %s", imageName)
if err != nil {
log.Debugf("Error loading authentication credentials %s", err)
return false, 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) 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 { if err != nil {
log.Debugf("Error pulling image %s, %s", imageName, err)
return false, err return false, err
} }
defer response.Close()
// the pull request will be aborted prematurely unless the response is read if newImageInfo.ID == oldImageID {
if _, err = ioutil.ReadAll(response); err != nil { log.Debugf("No new images found for %s", container.Name())
log.Error(err) return false, nil
}
} }
newImageInfo, _, err := client.api.ImageInspectWithRaw(bg, imageName) 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 { if err != nil {
return false, err log.Debugf("Error loading authentication credentials %s", err)
return err
} }
if newImageInfo.ID != oldImageInfo.ID { response, err := client.api.ImagePull(ctx, imageName, opts)
log.Infof("Found new %s image (%s)", imageName, newImageInfo.ID) if err != nil {
return true, nil log.Debugf("Error pulling image %s, %s", imageName, err)
return err
} }
log.Debugf("No new images found for %s", c.Name()) defer response.Close()
return false, nil // 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 { func (client dockerClient) RemoveImageByID(id string) error {

@ -28,6 +28,11 @@ type Container struct {
imageInfo *types.ImageInspect 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. // ID returns the Docker container ID.
func (c Container) ID() string { func (c Container) ID() string {
return c.containerInfo.ID return c.containerInfo.ID

@ -2,6 +2,7 @@ package container
import ( import (
"github.com/containrrr/watchtower/pkg/container/mocks" "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"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
cli "github.com/docker/docker/client" cli "github.com/docker/docker/client"
@ -34,14 +35,14 @@ var _ = Describe("the container", func() {
}) })
When("listing containers without any filter", func() { When("listing containers without any filter", func() {
It("should return all available containers", func() { It("should return all available containers", func() {
containers, err := client.ListContainers(noFilter) containers, err := client.ListContainers(filters.NoFilter)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 2).To(BeTrue()) Expect(len(containers) == 2).To(BeTrue())
}) })
}) })
When("listing containers with a filter matching nothing", func() { When("listing containers with a filter matching nothing", func() {
It("should return an empty array", 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) containers, err := client.ListContainers(filter)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 0).To(BeTrue()) Expect(len(containers) == 0).To(BeTrue())
@ -49,7 +50,7 @@ var _ = Describe("the container", func() {
}) })
When("listing containers with a watchtower filter", func() { When("listing containers with a watchtower filter", func() {
It("should return only the watchtower container", 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(err).NotTo(HaveOccurred())
Expect(len(containers) == 1).To(BeTrue()) Expect(len(containers) == 1).To(BeTrue())
Expect(containers[0].ImageName()).To(Equal("containrrr/watchtower:latest")) Expect(containers[0].ImageName()).To(Equal("containrrr/watchtower:latest"))
@ -62,7 +63,7 @@ var _ = Describe("the container", func() {
pullImages: false, pullImages: false,
includeStopped: true, includeStopped: true,
} }
containers, err := client.ListContainers(noFilter) containers, err := client.ListContainers(filters.NoFilter)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(len(containers) > 0).To(BeTrue()) Expect(len(containers) > 0).To(BeTrue())
}) })

@ -1,15 +1,15 @@
package container package filters
import t "github.com/containrrr/watchtower/pkg/types" import t "github.com/containrrr/watchtower/pkg/types"
// WatchtowerContainersFilter filters only watchtower containers // WatchtowerContainersFilter filters only watchtower containers
func WatchtowerContainersFilter(c t.FilterableContainer) bool { return c.IsWatchtower() } func WatchtowerContainersFilter(c t.FilterableContainer) bool { return c.IsWatchtower() }
// Filter no containers and returns all // NoFilter will not filter out any containers
func noFilter(t.FilterableContainer) bool { return true } func NoFilter(t.FilterableContainer) bool { return true }
// Filters containers which don't have a specified name // FilterByNames returns all containers that match the specified name
func filterByNames(names []string, baseFilter t.Filter) t.Filter { func FilterByNames(names []string, baseFilter t.Filter) t.Filter {
if len(names) == 0 { if len(names) == 0 {
return baseFilter return baseFilter
} }
@ -24,8 +24,8 @@ func filterByNames(names []string, baseFilter t.Filter) t.Filter {
} }
} }
// Filters out containers that don't have the 'enableLabel' // FilterByEnableLabel returns all containers that have the enabled label set
func filterByEnableLabel(baseFilter t.Filter) t.Filter { func FilterByEnableLabel(baseFilter t.Filter) t.Filter {
return func(c t.FilterableContainer) bool { return func(c t.FilterableContainer) bool {
// If label filtering is enabled, containers should only be considered // If label filtering is enabled, containers should only be considered
// if the label is specifically set. // if the label is specifically set.
@ -38,8 +38,8 @@ func filterByEnableLabel(baseFilter t.Filter) t.Filter {
} }
} }
// Filters out containers that have a 'enableLabel' and is set to disable. // FilterByDisabledLabel returns all containers that have the enabled label set to disable
func filterByDisabledLabel(baseFilter t.Filter) t.Filter { func FilterByDisabledLabel(baseFilter t.Filter) t.Filter {
return func(c t.FilterableContainer) bool { return func(c t.FilterableContainer) bool {
enabledLabel, ok := c.Enabled() enabledLabel, ok := c.Enabled()
if ok && !enabledLabel { if ok && !enabledLabel {
@ -53,13 +53,13 @@ func filterByDisabledLabel(baseFilter t.Filter) t.Filter {
// BuildFilter creates the needed filter of containers // BuildFilter creates the needed filter of containers
func BuildFilter(names []string, enableLabel bool) t.Filter { func BuildFilter(names []string, enableLabel bool) t.Filter {
filter := noFilter filter := NoFilter
filter = filterByNames(names, filter) filter = FilterByNames(names, filter)
if enableLabel { if enableLabel {
// If label filtering is enabled, containers should only be considered // If label filtering is enabled, containers should only be considered
// if the label is specifically set. // if the label is specifically set.
filter = filterByEnableLabel(filter) filter = FilterByEnableLabel(filter)
} }
filter = filterByDisabledLabel(filter) filter = FilterByDisabledLabel(filter)
return filter return filter
} }

@ -1,4 +1,4 @@
package container package filters
import ( import (
"testing" "testing"
@ -20,7 +20,7 @@ func TestWatchtowerContainersFilter(t *testing.T) {
func TestNoFilter(t *testing.T) { func TestNoFilter(t *testing.T) {
container := new(mocks.FilterableContainer) container := new(mocks.FilterableContainer)
assert.True(t, noFilter(container)) assert.True(t, NoFilter(container))
container.AssertExpectations(t) container.AssertExpectations(t)
} }
@ -28,12 +28,12 @@ func TestNoFilter(t *testing.T) {
func TestFilterByNames(t *testing.T) { func TestFilterByNames(t *testing.T) {
var names []string var names []string
filter := filterByNames(names, nil) filter := FilterByNames(names, nil)
assert.Nil(t, filter) assert.Nil(t, filter)
names = append(names, "test") names = append(names, "test")
filter = filterByNames(names, noFilter) filter = FilterByNames(names, NoFilter)
assert.NotNil(t, filter) assert.NotNil(t, filter)
container := new(mocks.FilterableContainer) container := new(mocks.FilterableContainer)
@ -48,7 +48,7 @@ func TestFilterByNames(t *testing.T) {
} }
func TestFilterByEnableLabel(t *testing.T) { func TestFilterByEnableLabel(t *testing.T) {
filter := filterByEnableLabel(noFilter) filter := FilterByEnableLabel(NoFilter)
assert.NotNil(t, filter) assert.NotNil(t, filter)
container := new(mocks.FilterableContainer) container := new(mocks.FilterableContainer)
@ -68,7 +68,7 @@ func TestFilterByEnableLabel(t *testing.T) {
} }
func TestFilterByDisabledLabel(t *testing.T) { func TestFilterByDisabledLabel(t *testing.T) {
filter := filterByDisabledLabel(noFilter) filter := FilterByDisabledLabel(NoFilter)
assert.NotNil(t, filter) assert.NotNil(t, filter)
container := new(mocks.FilterableContainer) container := new(mocks.FilterableContainer)

@ -0,0 +1,93 @@
package lifecycle
import (
"github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
)
// ExecutePreChecks tries to run the pre-check lifecycle hook for all containers included by the current filter.
func ExecutePreChecks(client container.Client, params types.UpdateParams) {
containers, err := client.ListContainers(params.Filter)
if err != nil {
return
}
for _, container := range containers {
ExecutePreCheckCommand(client, container)
}
}
// ExecutePostChecks tries to run the post-check lifecycle hook for all containers included by the current filter.
func ExecutePostChecks(client container.Client, params types.UpdateParams) {
containers, err := client.ListContainers(params.Filter)
if err != nil {
return
}
for _, container := range containers {
ExecutePostCheckCommand(client, container)
}
}
// ExecutePreCheckCommand tries to run the pre-check lifecycle hook for a single container.
func ExecutePreCheckCommand(client container.Client, container container.Container) {
command := container.GetLifecyclePreCheckCommand()
if len(command) == 0 {
log.Debug("No pre-check command supplied. Skipping")
return
}
log.Info("Executing pre-check command.")
if err := client.ExecuteCommand(container.ID(), command); err != nil {
log.Error(err)
}
}
// ExecutePostCheckCommand tries to run the post-check lifecycle hook for a single container.
func ExecutePostCheckCommand(client container.Client, container container.Container) {
command := container.GetLifecyclePostCheckCommand()
if len(command) == 0 {
log.Debug("No post-check command supplied. Skipping")
return
}
log.Info("Executing post-check command.")
if err := client.ExecuteCommand(container.ID(), command); err != nil {
log.Error(err)
}
}
// ExecutePreUpdateCommand tries to run the pre-update lifecycle hook for a single container.
func ExecutePreUpdateCommand(client container.Client, container container.Container) {
command := container.GetLifecyclePreUpdateCommand()
if len(command) == 0 {
log.Debug("No pre-update command supplied. Skipping")
return
}
log.Info("Executing pre-update command.")
if err := client.ExecuteCommand(container.ID(), command); err != nil {
log.Error(err)
}
}
// ExecutePostUpdateCommand tries to run the post-update lifecycle hook for a single container.
func ExecutePostUpdateCommand(client container.Client, newContainerID string) {
newContainer, err := client.GetContainer(newContainerID)
if err != nil {
log.Error(err)
return
}
command := newContainer.GetLifecyclePostUpdateCommand()
if len(command) == 0 {
log.Debug("No post-update command supplied. Skipping")
return
}
log.Info("Executing post-update command.")
if err := client.ExecuteCommand(newContainerID, command); err != nil {
log.Error(err)
}
}

@ -0,0 +1,33 @@
package registry
import (
"github.com/docker/docker/api/types"
log "github.com/sirupsen/logrus"
)
// GetPullOptions creates a struct with all options needed for pulling images from a registry
func GetPullOptions(imageName string) (types.ImagePullOptions, error) {
auth, err := EncodedAuth(imageName)
log.Debugf("Got image name: %s", imageName)
if err != nil {
return types.ImagePullOptions{}, err
}
log.Debugf("Got auth value: %s", auth)
if auth == "" {
return types.ImagePullOptions{}, nil
}
return types.ImagePullOptions{
RegistryAuth: auth,
PrivilegeFunc: DefaultAuthHandler,
}, nil
}
// 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,4 +1,4 @@
package container package registry
import ( import (
"errors" "errors"
@ -97,11 +97,3 @@ func CredentialsStore(configFile configfile.ConfigFile) credentials.Store {
func EncodeAuth(auth types.AuthConfig) (string, error) { func EncodeAuth(auth types.AuthConfig) (string, error) {
return command.EncodeAuthToBase64(auth) 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,4 +1,4 @@
package container package registry
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"

@ -1,13 +1,14 @@
package container package sorter
import ( import (
"fmt" "fmt"
"github.com/containrrr/watchtower/pkg/container"
"time" "time"
) )
// ByCreated allows a list of Container structs to be sorted by the container's // ByCreated allows a list of Container structs to be sorted by the container's
// created date. // created date.
type ByCreated []Container type ByCreated []container.Container
func (c ByCreated) Len() int { return len(c) } func (c ByCreated) Len() int { return len(c) }
func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] } func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
@ -15,12 +16,12 @@ 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 // Less will compare two elements (identified by index) in the Container
// list by created-date. // list by created-date.
func (c ByCreated) Less(i, j int) bool { func (c ByCreated) Less(i, j int) bool {
t1, err := time.Parse(time.RFC3339Nano, c[i].containerInfo.Created) t1, err := time.Parse(time.RFC3339Nano, c[i].ContainerInfo().Created)
if err != nil { if err != nil {
t1 = time.Now() t1 = time.Now()
} }
t2, _ := time.Parse(time.RFC3339Nano, c[j].containerInfo.Created) t2, _ := time.Parse(time.RFC3339Nano, c[j].ContainerInfo().Created)
if err != nil { if err != nil {
t1 = time.Now() t1 = time.Now()
} }
@ -33,18 +34,18 @@ func (c ByCreated) Less(i, j int) bool {
// the front of the list while containers with links will be sorted after all // 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 // of their dependencies. This sort order ensures that linked containers can
// be started in the correct order. // be started in the correct order.
func SortByDependencies(containers []Container) ([]Container, error) { func SortByDependencies(containers []container.Container) ([]container.Container, error) {
sorter := dependencySorter{} sorter := dependencySorter{}
return sorter.Sort(containers) return sorter.Sort(containers)
} }
type dependencySorter struct { type dependencySorter struct {
unvisited []Container unvisited []container.Container
marked map[string]bool marked map[string]bool
sorted []Container sorted []container.Container
} }
func (ds *dependencySorter) Sort(containers []Container) ([]Container, error) { func (ds *dependencySorter) Sort(containers []container.Container) ([]container.Container, error) {
ds.unvisited = containers ds.unvisited = containers
ds.marked = map[string]bool{} ds.marked = map[string]bool{}
@ -57,10 +58,10 @@ func (ds *dependencySorter) Sort(containers []Container) ([]Container, error) {
return ds.sorted, nil return ds.sorted, nil
} }
func (ds *dependencySorter) visit(c Container) error { func (ds *dependencySorter) visit(c container.Container) error {
if _, ok := ds.marked[c.Name()]; ok { if _, ok := ds.marked[c.Name()]; ok {
return fmt.Errorf("Circular reference to %s", c.Name()) return fmt.Errorf("circular reference to %s", c.Name())
} }
// Mark any visited node so that circular references can be detected // Mark any visited node so that circular references can be detected
@ -83,7 +84,7 @@ func (ds *dependencySorter) visit(c Container) error {
return nil return nil
} }
func (ds *dependencySorter) findUnvisited(name string) *Container { func (ds *dependencySorter) findUnvisited(name string) *container.Container {
for _, c := range ds.unvisited { for _, c := range ds.unvisited {
if c.Name() == name { if c.Name() == name {
return &c return &c
@ -93,7 +94,7 @@ func (ds *dependencySorter) findUnvisited(name string) *Container {
return nil return nil
} }
func (ds *dependencySorter) removeUnvisited(c Container) { func (ds *dependencySorter) removeUnvisited(c container.Container) {
var idx int var idx int
for i := range ds.unvisited { for i := range ds.unvisited {
if ds.unvisited[i].Name() == c.Name() { if ds.unvisited[i].Name() == c.Name() {

@ -1,13 +1,12 @@
package actions package types
import ( import (
t "github.com/containrrr/watchtower/pkg/types"
"time" "time"
) )
// UpdateParams contains all different options available to alter the behavior of the Update func // UpdateParams contains all different options available to alter the behavior of the Update func
type UpdateParams struct { type UpdateParams struct {
Filter t.Filter Filter Filter
Cleanup bool Cleanup bool
NoRestart bool NoRestart bool
Timeout time.Duration Timeout time.Duration
Loading…
Cancel
Save