refactor: extract code from the container package

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

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

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

@ -3,6 +3,9 @@ package actions
import (
"github.com/containrrr/watchtower/internal/util"
"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"
)
@ -10,10 +13,12 @@ import (
// 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
// 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")
executePreCheck(client, params)
if params.LifecycleHooks {
lifecycle.ExecutePreChecks(client, params)
}
containers, err := client.ListContainers(params.Filter)
if err != nil {
@ -30,7 +35,7 @@ func Update(client container.Client, params UpdateParams) error {
containers[i].Stale = stale
}
containers, err = container.SortByDependencies(containers)
containers, err = sorter.SortByDependencies(containers)
if err != nil {
return err
}
@ -38,24 +43,28 @@ func Update(client container.Client, params UpdateParams) error {
checkDependencies(containers)
if params.MonitorOnly {
executePostCheck(client, params)
if params.LifecycleHooks {
lifecycle.ExecutePostChecks(client, params)
}
return nil
}
stopContainersInReversedOrder(containers, client, params)
restartContainersInSortedOrder(containers, client, params)
executePostCheck(client, params)
if params.LifecycleHooks {
lifecycle.ExecutePostChecks(client, params)
}
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-- {
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() {
log.Debugf("This is the watchtower container %s", container.Name())
return
@ -64,15 +73,17 @@ func stopStaleContainer(container container.Container, client container.Client,
if !container.Stale {
return
}
if params.LifecycleHooks {
lifecycle.ExecutePreUpdateCommand(client, container)
executePreUpdateCommand(client, container)
}
if err := client.StopContainer(container, params.Timeout); err != nil {
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)
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
// 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
@ -107,7 +118,7 @@ func restartStaleContainer(container container.Container, client container.Clien
if newContainerID, err := client.StartContainer(container); err != nil {
log.Error(err)
} 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/pkg/container"
"github.com/containrrr/watchtower/pkg/container/mocks"
"github.com/containrrr/watchtower/pkg/types"
cli "github.com/docker/docker/client"
"time"
@ -59,7 +60,7 @@ var _ = Describe("the update action", func() {
When("there are multiple containers using the same image", 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(client.TestData.TriedToRemoveImageCount).To(Equal(1))
})
@ -75,7 +76,7 @@ var _ = Describe("the update action", func() {
time.Now(),
),
)
err := actions.Update(client, actions.UpdateParams{ Cleanup: true })
err := actions.Update(client, types.UpdateParams{ Cleanup: true })
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(2))
})

@ -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())
if !client.pullImages {
log.Debugf("Skipping image pull.")
} else if err := client.PullImage(ctx, container); err != nil {
return false, err
}
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}
}
return client.HasNewImage(ctx, container)
}
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()
func (client dockerClient) HasNewImage(ctx context.Context, container Container) (bool, error) {
oldImageID := container.imageInfo.ID
imageName := container.ImageName()
// the pull request will be aborted prematurely unless the response is read
if _, err = ioutil.ReadAll(response); err != nil {
log.Error(err)
}
newImageInfo, _, err := client.api.ImageInspectWithRaw(ctx, imageName)
if err != nil {
return false, err
}
newImageInfo, _, err := client.api.ImageInspectWithRaw(bg, imageName)
if newImageInfo.ID == oldImageID {
log.Debugf("No new images found for %s", container.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 {
return false, err
log.Debugf("Error loading authentication credentials %s", err)
return err
}
if newImageInfo.ID != oldImageInfo.ID {
log.Infof("Found new %s image (%s)", imageName, newImageInfo.ID)
return true, nil
response, err := client.api.ImagePull(ctx, imageName, opts)
if err != nil {
log.Debugf("Error pulling image %s, %s", imageName, err)
return err
}
log.Debugf("No new images found for %s", c.Name())
return false, nil
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,15 +1,15 @@
package container
package filters
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 }
// NoFilter will not filter out any containers
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 {
// FilterByNames returns all containers that match the specified name
func FilterByNames(names []string, baseFilter t.Filter) t.Filter {
if len(names) == 0 {
return baseFilter
}
@ -24,8 +24,8 @@ func filterByNames(names []string, baseFilter t.Filter) t.Filter {
}
}
// Filters out containers that don't have the 'enableLabel'
func filterByEnableLabel(baseFilter t.Filter) t.Filter {
// FilterByEnableLabel returns all containers that have the enabled label set
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.
@ -38,8 +38,8 @@ func filterByEnableLabel(baseFilter t.Filter) t.Filter {
}
}
// Filters out containers that have a 'enableLabel' and is set to disable.
func filterByDisabledLabel(baseFilter t.Filter) t.Filter {
// FilterByDisabledLabel returns all containers that have the enabled label set to disable
func FilterByDisabledLabel(baseFilter t.Filter) t.Filter {
return func(c t.FilterableContainer) bool {
enabledLabel, ok := c.Enabled()
if ok && !enabledLabel {
@ -53,13 +53,13 @@ func filterByDisabledLabel(baseFilter t.Filter) t.Filter {
// BuildFilter creates the needed filter of containers
func BuildFilter(names []string, enableLabel bool) t.Filter {
filter := noFilter
filter = filterByNames(names, 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 = FilterByEnableLabel(filter)
}
filter = filterByDisabledLabel(filter)
filter = FilterByDisabledLabel(filter)
return filter
}

@ -1,4 +1,4 @@
package container
package filters
import (
"testing"
@ -20,7 +20,7 @@ func TestWatchtowerContainersFilter(t *testing.T) {
func TestNoFilter(t *testing.T) {
container := new(mocks.FilterableContainer)
assert.True(t, noFilter(container))
assert.True(t, NoFilter(container))
container.AssertExpectations(t)
}
@ -28,12 +28,12 @@ func TestNoFilter(t *testing.T) {
func TestFilterByNames(t *testing.T) {
var names []string
filter := filterByNames(names, nil)
filter := FilterByNames(names, nil)
assert.Nil(t, filter)
names = append(names, "test")
filter = filterByNames(names, noFilter)
filter = FilterByNames(names, NoFilter)
assert.NotNil(t, filter)
container := new(mocks.FilterableContainer)
@ -48,7 +48,7 @@ func TestFilterByNames(t *testing.T) {
}
func TestFilterByEnableLabel(t *testing.T) {
filter := filterByEnableLabel(noFilter)
filter := FilterByEnableLabel(NoFilter)
assert.NotNil(t, filter)
container := new(mocks.FilterableContainer)
@ -68,7 +68,7 @@ func TestFilterByEnableLabel(t *testing.T) {
}
func TestFilterByDisabledLabel(t *testing.T) {
filter := filterByDisabledLabel(noFilter)
filter := FilterByDisabledLabel(NoFilter)
assert.NotNil(t, filter)
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 (
"errors"
@ -97,11 +97,3 @@ func CredentialsStore(configFile configfile.ConfigFile) credentials.Store {
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,4 +1,4 @@
package container
package registry
import (
"github.com/stretchr/testify/assert"

@ -1,13 +1,14 @@
package container
package sorter
import (
"fmt"
"github.com/containrrr/watchtower/pkg/container"
"time"
)
// ByCreated allows a list of Container structs to be sorted by the container's
// created date.
type ByCreated []Container
type ByCreated []container.Container
func (c ByCreated) Len() int { return len(c) }
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
// list by created-date.
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 {
t1 = time.Now()
}
t2, _ := time.Parse(time.RFC3339Nano, c[j].containerInfo.Created)
t2, _ := time.Parse(time.RFC3339Nano, c[j].ContainerInfo().Created)
if err != nil {
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
// of their dependencies. This sort order ensures that linked containers can
// be started in the correct order.
func SortByDependencies(containers []Container) ([]Container, error) {
func SortByDependencies(containers []container.Container) ([]container.Container, error) {
sorter := dependencySorter{}
return sorter.Sort(containers)
}
type dependencySorter struct {
unvisited []Container
unvisited []container.Container
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.marked = map[string]bool{}
@ -57,10 +58,10 @@ func (ds *dependencySorter) Sort(containers []Container) ([]Container, error) {
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 {
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
@ -83,7 +84,7 @@ func (ds *dependencySorter) visit(c Container) error {
return nil
}
func (ds *dependencySorter) findUnvisited(name string) *Container {
func (ds *dependencySorter) findUnvisited(name string) *container.Container {
for _, c := range ds.unvisited {
if c.Name() == name {
return &c
@ -93,7 +94,7 @@ func (ds *dependencySorter) findUnvisited(name string) *Container {
return nil
}
func (ds *dependencySorter) removeUnvisited(c Container) {
func (ds *dependencySorter) removeUnvisited(c container.Container) {
var idx int
for i := range ds.unvisited {
if ds.unvisited[i].Name() == c.Name() {

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