You've already forked watchtower
							
							
				mirror of
				https://github.com/containrrr/watchtower.git
				synced 2025-10-31 00:17:44 +02:00 
			
		
		
		
	refactor: extract code from the container package
This commit is contained in:
		| @@ -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()) | ||||
|  | ||||
| 		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,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) | ||||
							
								
								
									
										93
									
								
								pkg/lifecycle/lifecycle.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								pkg/lifecycle/lifecycle.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										33
									
								
								pkg/registry/registry.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								pkg/registry/registry.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
		Reference in New Issue
	
	Block a user