package actions_test import ( "time" "github.com/containrrr/watchtower/internal/actions" "github.com/containrrr/watchtower/pkg/types" dockerTypes "github.com/docker/docker/api/types" dockerContainer "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" . "github.com/containrrr/watchtower/internal/actions/mocks" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) func getCommonTestData(keepContainer string) *TestData { return &TestData{ NameOfContainerToKeep: keepContainer, Containers: []types.Container{ CreateMockContainer( "test-container-01", "test-container-01", "fake-image:latest", time.Now().AddDate(0, 0, -1)), CreateMockContainer( "test-container-02", "test-container-02", "fake-image:latest", time.Now()), CreateMockContainer( "test-container-02", "test-container-02", "fake-image:latest", time.Now()), }, } } func getLinkedTestData(withImageInfo bool) *TestData { staleContainer := CreateMockContainer( "test-container-01", "/test-container-01", "fake-image1:latest", time.Now().AddDate(0, 0, -1)) var imageInfo *dockerTypes.ImageInspect if withImageInfo { imageInfo = CreateMockImageInfo("test-container-02") } linkingContainer := CreateMockContainerWithLinks( "test-container-02", "/test-container-02", "fake-image2:latest", time.Now(), []string{staleContainer.Name()}, imageInfo) return &TestData{ Staleness: map[string]bool{linkingContainer.Name(): false}, Containers: []types.Container{ staleContainer, linkingContainer, }, } } var _ = Describe("the update action", func() { When("watchtower has been instructed to clean up", func() { When("there are multiple containers using the same image", func() { It("should only try to remove the image once", func() { client := CreateMockClient(getCommonTestData(""), false, false) _, err := actions.Update(client, types.UpdateParams{Cleanup: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1)) }) }) When("there are multiple containers using different images", func() { It("should try to remove each of them", func() { testData := getCommonTestData("") testData.Containers = append( testData.Containers, CreateMockContainer( "unique-test-container", "unique-test-container", "unique-fake-image:latest", time.Now(), ), ) client := CreateMockClient(testData, false, false) _, err := actions.Update(client, types.UpdateParams{Cleanup: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(2)) }) }) When("there are linked containers being updated", func() { It("should not try to remove their images", func() { client := CreateMockClient(getLinkedTestData(true), false, false) _, err := actions.Update(client, types.UpdateParams{Cleanup: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1)) }) }) When("performing a rolling restart update", func() { It("should try to remove the image once", func() { client := CreateMockClient(getCommonTestData(""), false, false) _, err := actions.Update(client, types.UpdateParams{Cleanup: true, RollingRestart: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1)) }) }) When("updating a linked container with missing image info", func() { It("should gracefully fail", func() { client := CreateMockClient(getLinkedTestData(false), false, false) report, err := actions.Update(client, types.UpdateParams{}) Expect(err).NotTo(HaveOccurred()) // Note: Linked containers that were skipped for recreation is not counted in Failed // If this happens, an error is emitted to the logs, so a notification should still be sent. Expect(report.Updated()).To(HaveLen(1)) Expect(report.Fresh()).To(HaveLen(1)) }) }) }) When("watchtower has been instructed to monitor only", func() { When("certain containers are set to monitor only", func() { It("should not update those containers", func() { client := CreateMockClient( &TestData{ NameOfContainerToKeep: "test-container-02", Containers: []types.Container{ CreateMockContainer( "test-container-01", "test-container-01", "fake-image1:latest", time.Now()), CreateMockContainerWithConfig( "test-container-02", "test-container-02", "fake-image2:latest", false, false, time.Now(), &dockerContainer.Config{ Labels: map[string]string{ "com.centurylinklabs.watchtower.monitor-only": "true", }, }), }, }, false, false, ) _, err := actions.Update(client, types.UpdateParams{Cleanup: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1)) }) }) When("monitor only is set globally", func() { It("should not update any containers", func() { client := CreateMockClient( &TestData{ Containers: []types.Container{ CreateMockContainer( "test-container-01", "test-container-01", "fake-image:latest", time.Now()), CreateMockContainer( "test-container-02", "test-container-02", "fake-image:latest", time.Now()), }, }, false, false, ) _, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0)) }) When("watchtower has been instructed to have label take precedence", func() { It("it should update containers when monitor only is set to false", func() { client := CreateMockClient( &TestData{ //NameOfContainerToKeep: "test-container-02", Containers: []types.Container{ CreateMockContainerWithConfig( "test-container-02", "test-container-02", "fake-image2:latest", false, false, time.Now(), &dockerContainer.Config{ Labels: map[string]string{ "com.centurylinklabs.watchtower.monitor-only": "false", }, }), }, }, false, false, ) _, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true, LabelPrecedence: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1)) }) It("it should update not containers when monitor only is set to true", func() { client := CreateMockClient( &TestData{ //NameOfContainerToKeep: "test-container-02", Containers: []types.Container{ CreateMockContainerWithConfig( "test-container-02", "test-container-02", "fake-image2:latest", false, false, time.Now(), &dockerContainer.Config{ Labels: map[string]string{ "com.centurylinklabs.watchtower.monitor-only": "true", }, }), }, }, false, false, ) _, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true, LabelPrecedence: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0)) }) It("it should update not containers when monitor only is not set", func() { client := CreateMockClient( &TestData{ Containers: []types.Container{ CreateMockContainer( "test-container-01", "test-container-01", "fake-image:latest", time.Now()), }, }, false, false, ) _, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true, LabelPrecedence: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0)) }) }) }) }) When("watchtower has been instructed to run lifecycle hooks", func() { When("pre-update script returns 1", func() { It("should not update those containers", func() { client := CreateMockClient( &TestData{ //NameOfContainerToKeep: "test-container-02", Containers: []types.Container{ CreateMockContainerWithConfig( "test-container-02", "test-container-02", "fake-image2:latest", true, false, time.Now(), &dockerContainer.Config{ Labels: map[string]string{ "com.centurylinklabs.watchtower.lifecycle.pre-update-timeout": "190", "com.centurylinklabs.watchtower.lifecycle.pre-update": "/PreUpdateReturn1.sh", }, ExposedPorts: map[nat.Port]struct{}{}, }), }, }, false, false, ) _, err := actions.Update(client, types.UpdateParams{Cleanup: true, LifecycleHooks: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0)) }) }) When("prupddate script returns 75", func() { It("should not update those containers", func() { client := CreateMockClient( &TestData{ //NameOfContainerToKeep: "test-container-02", Containers: []types.Container{ CreateMockContainerWithConfig( "test-container-02", "test-container-02", "fake-image2:latest", true, false, time.Now(), &dockerContainer.Config{ Labels: map[string]string{ "com.centurylinklabs.watchtower.lifecycle.pre-update-timeout": "190", "com.centurylinklabs.watchtower.lifecycle.pre-update": "/PreUpdateReturn75.sh", }, ExposedPorts: map[nat.Port]struct{}{}, }), }, }, false, false, ) _, err := actions.Update(client, types.UpdateParams{Cleanup: true, LifecycleHooks: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0)) }) }) When("prupddate script returns 0", func() { It("should update those containers", func() { client := CreateMockClient( &TestData{ //NameOfContainerToKeep: "test-container-02", Containers: []types.Container{ CreateMockContainerWithConfig( "test-container-02", "test-container-02", "fake-image2:latest", true, false, time.Now(), &dockerContainer.Config{ Labels: map[string]string{ "com.centurylinklabs.watchtower.lifecycle.pre-update-timeout": "190", "com.centurylinklabs.watchtower.lifecycle.pre-update": "/PreUpdateReturn0.sh", }, ExposedPorts: map[nat.Port]struct{}{}, }), }, }, false, false, ) _, err := actions.Update(client, types.UpdateParams{Cleanup: true, LifecycleHooks: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1)) }) }) When("container is linked to restarting containers", func() { It("should be marked for restart", func() { provider := CreateMockContainerWithConfig( "test-container-provider", "/test-container-provider", "fake-image2:latest", true, false, time.Now(), &dockerContainer.Config{ Labels: map[string]string{}, ExposedPorts: map[nat.Port]struct{}{}, }) provider.SetStale(true) consumer := CreateMockContainerWithConfig( "test-container-consumer", "/test-container-consumer", "fake-image3:latest", true, false, time.Now(), &dockerContainer.Config{ Labels: map[string]string{ "com.centurylinklabs.watchtower.depends-on": "test-container-provider", }, ExposedPorts: map[nat.Port]struct{}{}, }) containers := []types.Container{ provider, consumer, } Expect(provider.ToRestart()).To(BeTrue()) Expect(consumer.ToRestart()).To(BeFalse()) actions.UpdateImplicitRestart(containers) Expect(containers[0].ToRestart()).To(BeTrue()) Expect(containers[1].ToRestart()).To(BeTrue()) }) }) When("container is not running", func() { It("skip running preupdate", func() { client := CreateMockClient( &TestData{ //NameOfContainerToKeep: "test-container-02", Containers: []types.Container{ CreateMockContainerWithConfig( "test-container-02", "test-container-02", "fake-image2:latest", false, false, time.Now(), &dockerContainer.Config{ Labels: map[string]string{ "com.centurylinklabs.watchtower.lifecycle.pre-update-timeout": "190", "com.centurylinklabs.watchtower.lifecycle.pre-update": "/PreUpdateReturn1.sh", }, ExposedPorts: map[nat.Port]struct{}{}, }), }, }, false, false, ) _, err := actions.Update(client, types.UpdateParams{Cleanup: true, LifecycleHooks: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1)) }) }) When("container is restarting", func() { It("skip running preupdate", func() { client := CreateMockClient( &TestData{ //NameOfContainerToKeep: "test-container-02", Containers: []types.Container{ CreateMockContainerWithConfig( "test-container-02", "test-container-02", "fake-image2:latest", false, true, time.Now(), &dockerContainer.Config{ Labels: map[string]string{ "com.centurylinklabs.watchtower.lifecycle.pre-update-timeout": "190", "com.centurylinklabs.watchtower.lifecycle.pre-update": "/PreUpdateReturn1.sh", }, ExposedPorts: map[nat.Port]struct{}{}, }), }, }, false, false, ) _, err := actions.Update(client, types.UpdateParams{Cleanup: true, LifecycleHooks: true}) Expect(err).NotTo(HaveOccurred()) Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1)) }) }) }) })