mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-22 05:40:50 +02:00
fix: always use container interface (#1516)
This commit is contained in:
parent
25fdb40312
commit
dd1ec09668
12 changed files with 147 additions and 111 deletions
|
@ -1,12 +1,13 @@
|
||||||
package actions_test
|
package actions_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/internal/actions"
|
"github.com/containrrr/watchtower/internal/actions"
|
||||||
"github.com/containrrr/watchtower/pkg/container"
|
"github.com/containrrr/watchtower/pkg/types"
|
||||||
|
|
||||||
. "github.com/containrrr/watchtower/internal/actions/mocks"
|
. "github.com/containrrr/watchtower/internal/actions/mocks"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
|
@ -37,7 +38,7 @@ var _ = Describe("the actions package", func() {
|
||||||
It("should not do anything", func() {
|
It("should not do anything", func() {
|
||||||
client := CreateMockClient(
|
client := CreateMockClient(
|
||||||
&TestData{
|
&TestData{
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainer(
|
CreateMockContainer(
|
||||||
"test-container",
|
"test-container",
|
||||||
"test-container",
|
"test-container",
|
||||||
|
@ -59,7 +60,7 @@ var _ = Describe("the actions package", func() {
|
||||||
client = CreateMockClient(
|
client = CreateMockClient(
|
||||||
&TestData{
|
&TestData{
|
||||||
NameOfContainerToKeep: "test-container-02",
|
NameOfContainerToKeep: "test-container-02",
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainer(
|
CreateMockContainer(
|
||||||
"test-container-01",
|
"test-container-01",
|
||||||
"test-container-01",
|
"test-container-01",
|
||||||
|
@ -89,7 +90,7 @@ var _ = Describe("the actions package", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
client = CreateMockClient(
|
client = CreateMockClient(
|
||||||
&TestData{
|
&TestData{
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainer(
|
CreateMockContainer(
|
||||||
"test-container-01",
|
"test-container-01",
|
||||||
"test-container-01",
|
"test-container-01",
|
||||||
|
|
|
@ -2,16 +2,15 @@ package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/containrrr/watchtower/pkg/types"
|
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containrrr/watchtower/pkg/container"
|
||||||
"github.com/containrrr/watchtower/pkg/filters"
|
"github.com/containrrr/watchtower/pkg/filters"
|
||||||
"github.com/containrrr/watchtower/pkg/sorter"
|
"github.com/containrrr/watchtower/pkg/sorter"
|
||||||
|
"github.com/containrrr/watchtower/pkg/types"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/pkg/container"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckForSanity makes sure everything is sane before starting
|
// CheckForSanity makes sure everything is sane before starting
|
||||||
|
@ -55,7 +54,7 @@ func CheckForMultipleWatchtowerInstances(client container.Client, cleanup bool,
|
||||||
return cleanupExcessWatchtowers(containers, client, cleanup)
|
return cleanupExcessWatchtowers(containers, client, cleanup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupExcessWatchtowers(containers []container.Container, client container.Client, cleanup bool) error {
|
func cleanupExcessWatchtowers(containers []types.Container, client container.Client, cleanup bool) error {
|
||||||
var stopErrors int
|
var stopErrors int
|
||||||
|
|
||||||
sort.Sort(sorter.ByCreated(containers))
|
sort.Sort(sorter.ByCreated(containers))
|
||||||
|
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/pkg/container"
|
|
||||||
|
|
||||||
t "github.com/containrrr/watchtower/pkg/types"
|
t "github.com/containrrr/watchtower/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,7 +19,7 @@ type MockClient struct {
|
||||||
type TestData struct {
|
type TestData struct {
|
||||||
TriedToRemoveImageCount int
|
TriedToRemoveImageCount int
|
||||||
NameOfContainerToKeep string
|
NameOfContainerToKeep string
|
||||||
Containers []container.Container
|
Containers []t.Container
|
||||||
Staleness map[string]bool
|
Staleness map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,12 +38,12 @@ func CreateMockClient(data *TestData, pullImages bool, removeVolumes bool) MockC
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListContainers is a mock method returning the provided container testdata
|
// ListContainers is a mock method returning the provided container testdata
|
||||||
func (client MockClient) ListContainers(_ t.Filter) ([]container.Container, error) {
|
func (client MockClient) ListContainers(_ t.Filter) ([]t.Container, error) {
|
||||||
return client.TestData.Containers, nil
|
return client.TestData.Containers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopContainer is a mock method
|
// StopContainer is a mock method
|
||||||
func (client MockClient) StopContainer(c container.Container, _ time.Duration) error {
|
func (client MockClient) StopContainer(c t.Container, _ time.Duration) error {
|
||||||
if c.Name() == client.TestData.NameOfContainerToKeep {
|
if c.Name() == client.TestData.NameOfContainerToKeep {
|
||||||
return errors.New("tried to stop the instance we want to keep")
|
return errors.New("tried to stop the instance we want to keep")
|
||||||
}
|
}
|
||||||
|
@ -53,12 +51,12 @@ func (client MockClient) StopContainer(c container.Container, _ time.Duration) e
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartContainer is a mock method
|
// StartContainer is a mock method
|
||||||
func (client MockClient) StartContainer(_ container.Container) (t.ContainerID, error) {
|
func (client MockClient) StartContainer(_ t.Container) (t.ContainerID, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameContainer is a mock method
|
// RenameContainer is a mock method
|
||||||
func (client MockClient) RenameContainer(_ container.Container, _ string) error {
|
func (client MockClient) RenameContainer(_ t.Container, _ string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +67,7 @@ func (client MockClient) RemoveImageByID(_ t.ImageID) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContainer is a mock method
|
// GetContainer is a mock method
|
||||||
func (client MockClient) GetContainer(_ t.ContainerID) (container.Container, error) {
|
func (client MockClient) GetContainer(_ t.ContainerID) (t.Container, error) {
|
||||||
return client.TestData.Containers[0], nil
|
return client.TestData.Containers[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +86,7 @@ func (client MockClient) ExecuteCommand(_ t.ContainerID, command string, _ int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsContainerStale is true if not explicitly stated in TestData for the mock client
|
// IsContainerStale is true if not explicitly stated in TestData for the mock client
|
||||||
func (client MockClient) IsContainerStale(cont container.Container) (bool, t.ImageID, error) {
|
func (client MockClient) IsContainerStale(cont t.Container) (bool, t.ImageID, error) {
|
||||||
stale, found := client.TestData.Staleness[cont.Name()]
|
stale, found := client.TestData.Staleness[cont.Name()]
|
||||||
if !found {
|
if !found {
|
||||||
stale = true
|
stale = true
|
||||||
|
@ -97,6 +95,6 @@ func (client MockClient) IsContainerStale(cont container.Container) (bool, t.Ima
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarnOnHeadPullFailed is always true for the mock client
|
// WarnOnHeadPullFailed is always true for the mock client
|
||||||
func (client MockClient) WarnOnHeadPullFailed(_ container.Container) bool {
|
func (client MockClient) WarnOnHeadPullFailed(_ t.Container) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateMockContainer creates a container substitute valid for testing
|
// CreateMockContainer creates a container substitute valid for testing
|
||||||
func CreateMockContainer(id string, name string, image string, created time.Time) container.Container {
|
func CreateMockContainer(id string, name string, image string, created time.Time) wt.Container {
|
||||||
content := types.ContainerJSON{
|
content := types.ContainerJSON{
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{
|
ContainerJSONBase: &types.ContainerJSONBase{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
@ -31,7 +31,7 @@ func CreateMockContainer(id string, name string, image string, created time.Time
|
||||||
ExposedPorts: map[nat.Port]struct{}{},
|
ExposedPorts: map[nat.Port]struct{}{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return *container.NewContainer(
|
return container.NewContainer(
|
||||||
&content,
|
&content,
|
||||||
CreateMockImageInfo(image),
|
CreateMockImageInfo(image),
|
||||||
)
|
)
|
||||||
|
@ -48,12 +48,12 @@ func CreateMockImageInfo(image string) *types.ImageInspect {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMockContainerWithImageInfo should only be used for testing
|
// CreateMockContainerWithImageInfo should only be used for testing
|
||||||
func CreateMockContainerWithImageInfo(id string, name string, image string, created time.Time, imageInfo types.ImageInspect) container.Container {
|
func CreateMockContainerWithImageInfo(id string, name string, image string, created time.Time, imageInfo types.ImageInspect) wt.Container {
|
||||||
return CreateMockContainerWithImageInfoP(id, name, image, created, &imageInfo)
|
return CreateMockContainerWithImageInfoP(id, name, image, created, &imageInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMockContainerWithImageInfoP should only be used for testing
|
// CreateMockContainerWithImageInfoP should only be used for testing
|
||||||
func CreateMockContainerWithImageInfoP(id string, name string, image string, created time.Time, imageInfo *types.ImageInspect) container.Container {
|
func CreateMockContainerWithImageInfoP(id string, name string, image string, created time.Time, imageInfo *types.ImageInspect) wt.Container {
|
||||||
content := types.ContainerJSON{
|
content := types.ContainerJSON{
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{
|
ContainerJSONBase: &types.ContainerJSONBase{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
@ -66,21 +66,21 @@ func CreateMockContainerWithImageInfoP(id string, name string, image string, cre
|
||||||
Labels: make(map[string]string),
|
Labels: make(map[string]string),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return *container.NewContainer(
|
return container.NewContainer(
|
||||||
&content,
|
&content,
|
||||||
imageInfo,
|
imageInfo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMockContainerWithDigest should only be used for testing
|
// CreateMockContainerWithDigest should only be used for testing
|
||||||
func CreateMockContainerWithDigest(id string, name string, image string, created time.Time, digest string) container.Container {
|
func CreateMockContainerWithDigest(id string, name string, image string, created time.Time, digest string) wt.Container {
|
||||||
c := CreateMockContainer(id, name, image, created)
|
c := CreateMockContainer(id, name, image, created)
|
||||||
c.ImageInfo().RepoDigests = []string{digest}
|
c.ImageInfo().RepoDigests = []string{digest}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMockContainerWithConfig creates a container substitute valid for testing
|
// CreateMockContainerWithConfig creates a container substitute valid for testing
|
||||||
func CreateMockContainerWithConfig(id string, name string, image string, running bool, restarting bool, created time.Time, config *dockerContainer.Config) container.Container {
|
func CreateMockContainerWithConfig(id string, name string, image string, running bool, restarting bool, created time.Time, config *dockerContainer.Config) wt.Container {
|
||||||
content := types.ContainerJSON{
|
content := types.ContainerJSON{
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{
|
ContainerJSONBase: &types.ContainerJSONBase{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
@ -97,14 +97,14 @@ func CreateMockContainerWithConfig(id string, name string, image string, running
|
||||||
},
|
},
|
||||||
Config: config,
|
Config: config,
|
||||||
}
|
}
|
||||||
return *container.NewContainer(
|
return container.NewContainer(
|
||||||
&content,
|
&content,
|
||||||
CreateMockImageInfo(image),
|
CreateMockImageInfo(image),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateContainerForProgress creates a container substitute for tracking session/update progress
|
// CreateContainerForProgress creates a container substitute for tracking session/update progress
|
||||||
func CreateContainerForProgress(index int, idPrefix int, nameFormat string) (container.Container, wt.ImageID) {
|
func CreateContainerForProgress(index int, idPrefix int, nameFormat string) (wt.Container, wt.ImageID) {
|
||||||
indexStr := strconv.Itoa(idPrefix + index)
|
indexStr := strconv.Itoa(idPrefix + index)
|
||||||
mockID := indexStr + strings.Repeat("0", 61-len(indexStr))
|
mockID := indexStr + strings.Repeat("0", 61-len(indexStr))
|
||||||
contID := "c79" + mockID
|
contID := "c79" + mockID
|
||||||
|
@ -120,7 +120,7 @@ func CreateContainerForProgress(index int, idPrefix int, nameFormat string) (con
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMockContainerWithLinks should only be used for testing
|
// CreateMockContainerWithLinks should only be used for testing
|
||||||
func CreateMockContainerWithLinks(id string, name string, image string, created time.Time, links []string, imageInfo *types.ImageInspect) container.Container {
|
func CreateMockContainerWithLinks(id string, name string, image string, created time.Time, links []string, imageInfo *types.ImageInspect) wt.Container {
|
||||||
content := types.ContainerJSON{
|
content := types.ContainerJSON{
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{
|
ContainerJSONBase: &types.ContainerJSONBase{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
@ -136,7 +136,7 @@ func CreateMockContainerWithLinks(id string, name string, image string, created
|
||||||
Labels: make(map[string]string),
|
Labels: make(map[string]string),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return *container.NewContainer(
|
return container.NewContainer(
|
||||||
&content,
|
&content,
|
||||||
imageInfo,
|
imageInfo,
|
||||||
)
|
)
|
||||||
|
|
|
@ -57,7 +57,7 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
|
||||||
} else {
|
} else {
|
||||||
progress.AddScanned(targetContainer, newestImage)
|
progress.AddScanned(targetContainer, newestImage)
|
||||||
}
|
}
|
||||||
containers[i].Stale = stale
|
containers[i].SetStale(stale)
|
||||||
|
|
||||||
if stale {
|
if stale {
|
||||||
staleCount++
|
staleCount++
|
||||||
|
@ -71,7 +71,7 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
|
||||||
|
|
||||||
UpdateImplicitRestart(containers)
|
UpdateImplicitRestart(containers)
|
||||||
|
|
||||||
var containersToUpdate []container.Container
|
var containersToUpdate []types.Container
|
||||||
if !params.MonitorOnly {
|
if !params.MonitorOnly {
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
if !c.IsMonitorOnly() {
|
if !c.IsMonitorOnly() {
|
||||||
|
@ -96,7 +96,7 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
|
||||||
return progress.Report(), nil
|
return progress.Report(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func performRollingRestart(containers []container.Container, client container.Client, params types.UpdateParams) map[types.ContainerID]error {
|
func performRollingRestart(containers []types.Container, client container.Client, params types.UpdateParams) map[types.ContainerID]error {
|
||||||
cleanupImageIDs := make(map[types.ImageID]bool, len(containers))
|
cleanupImageIDs := make(map[types.ImageID]bool, len(containers))
|
||||||
failed := make(map[types.ContainerID]error, len(containers))
|
failed := make(map[types.ContainerID]error, len(containers))
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ func performRollingRestart(containers []container.Container, client container.Cl
|
||||||
} else {
|
} else {
|
||||||
if err := restartStaleContainer(containers[i], client, params); err != nil {
|
if err := restartStaleContainer(containers[i], client, params); err != nil {
|
||||||
failed[containers[i].ID()] = err
|
failed[containers[i].ID()] = err
|
||||||
} else if containers[i].Stale {
|
} else if containers[i].IsStale() {
|
||||||
// Only add (previously) stale containers' images to cleanup
|
// Only add (previously) stale containers' images to cleanup
|
||||||
cleanupImageIDs[containers[i].ImageID()] = true
|
cleanupImageIDs[containers[i].ImageID()] = true
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ func performRollingRestart(containers []container.Container, client container.Cl
|
||||||
return failed
|
return failed
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopContainersInReversedOrder(containers []container.Container, client container.Client, params types.UpdateParams) (failed map[types.ContainerID]error, stopped map[types.ImageID]bool) {
|
func stopContainersInReversedOrder(containers []types.Container, client container.Client, params types.UpdateParams) (failed map[types.ContainerID]error, stopped map[types.ImageID]bool) {
|
||||||
failed = make(map[types.ContainerID]error, len(containers))
|
failed = make(map[types.ContainerID]error, len(containers))
|
||||||
stopped = make(map[types.ImageID]bool, len(containers))
|
stopped = make(map[types.ImageID]bool, len(containers))
|
||||||
for i := len(containers) - 1; i >= 0; i-- {
|
for i := len(containers) - 1; i >= 0; i-- {
|
||||||
|
@ -137,7 +137,7 @@ func stopContainersInReversedOrder(containers []container.Container, client cont
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopStaleContainer(container container.Container, client container.Client, params types.UpdateParams) error {
|
func stopStaleContainer(container types.Container, client container.Client, params types.UpdateParams) error {
|
||||||
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 nil
|
return nil
|
||||||
|
@ -148,7 +148,7 @@ func stopStaleContainer(container container.Container, client container.Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform an additional check here to prevent us from stopping a linked container we cannot restart
|
// Perform an additional check here to prevent us from stopping a linked container we cannot restart
|
||||||
if container.LinkedToRestarting {
|
if container.IsLinkedToRestarting() {
|
||||||
if err := container.VerifyConfiguration(); err != nil {
|
if err := container.VerifyConfiguration(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -174,7 +174,7 @@ func stopStaleContainer(container container.Container, client container.Client,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func restartContainersInSortedOrder(containers []container.Container, client container.Client, params types.UpdateParams, stoppedImages map[types.ImageID]bool) map[types.ContainerID]error {
|
func restartContainersInSortedOrder(containers []types.Container, client container.Client, params types.UpdateParams, stoppedImages map[types.ImageID]bool) map[types.ContainerID]error {
|
||||||
cleanupImageIDs := make(map[types.ImageID]bool, len(containers))
|
cleanupImageIDs := make(map[types.ImageID]bool, len(containers))
|
||||||
failed := make(map[types.ContainerID]error, len(containers))
|
failed := make(map[types.ContainerID]error, len(containers))
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ func restartContainersInSortedOrder(containers []container.Container, client con
|
||||||
if stoppedImages[c.SafeImageID()] {
|
if stoppedImages[c.SafeImageID()] {
|
||||||
if err := restartStaleContainer(c, client, params); err != nil {
|
if err := restartStaleContainer(c, client, params); err != nil {
|
||||||
failed[c.ID()] = err
|
failed[c.ID()] = err
|
||||||
} else if c.Stale {
|
} else if c.IsStale() {
|
||||||
// Only add (previously) stale containers' images to cleanup
|
// Only add (previously) stale containers' images to cleanup
|
||||||
cleanupImageIDs[c.ImageID()] = true
|
cleanupImageIDs[c.ImageID()] = true
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ func cleanupImages(client container.Client, imageIDs map[types.ImageID]bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func restartStaleContainer(container container.Container, client container.Client, params types.UpdateParams) error {
|
func restartStaleContainer(container types.Container, client container.Client, params types.UpdateParams) error {
|
||||||
// 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
|
||||||
|
@ -235,7 +235,7 @@ func restartStaleContainer(container container.Container, client container.Clien
|
||||||
|
|
||||||
// UpdateImplicitRestart iterates through the passed containers, setting the
|
// UpdateImplicitRestart iterates through the passed containers, setting the
|
||||||
// `LinkedToRestarting` flag if any of it's linked containers are marked for restart
|
// `LinkedToRestarting` flag if any of it's linked containers are marked for restart
|
||||||
func UpdateImplicitRestart(containers []container.Container) {
|
func UpdateImplicitRestart(containers []types.Container) {
|
||||||
|
|
||||||
for ci, c := range containers {
|
for ci, c := range containers {
|
||||||
if c.ToRestart() {
|
if c.ToRestart() {
|
||||||
|
@ -249,7 +249,7 @@ func UpdateImplicitRestart(containers []container.Container) {
|
||||||
"linked": c.Name(),
|
"linked": c.Name(),
|
||||||
}).Debug("container is linked to restarting")
|
}).Debug("container is linked to restarting")
|
||||||
// NOTE: To mutate the array, the `c` variable cannot be used as it's a copy
|
// NOTE: To mutate the array, the `c` variable cannot be used as it's a copy
|
||||||
containers[ci].LinkedToRestarting = true
|
containers[ci].SetLinkedToRestarting(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ func UpdateImplicitRestart(containers []container.Container) {
|
||||||
|
|
||||||
// linkedContainerMarkedForRestart returns the name of the first link that matches a
|
// linkedContainerMarkedForRestart returns the name of the first link that matches a
|
||||||
// container marked for restart
|
// container marked for restart
|
||||||
func linkedContainerMarkedForRestart(links []string, containers []container.Container) string {
|
func linkedContainerMarkedForRestart(links []string, containers []types.Container) string {
|
||||||
for _, linkName := range links {
|
for _, linkName := range links {
|
||||||
for _, candidate := range containers {
|
for _, candidate := range containers {
|
||||||
if candidate.Name() == linkName && candidate.ToRestart() {
|
if candidate.Name() == linkName && candidate.ToRestart() {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/internal/actions"
|
"github.com/containrrr/watchtower/internal/actions"
|
||||||
"github.com/containrrr/watchtower/pkg/container"
|
|
||||||
"github.com/containrrr/watchtower/pkg/types"
|
"github.com/containrrr/watchtower/pkg/types"
|
||||||
dockerTypes "github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
dockerContainer "github.com/docker/docker/api/types/container"
|
dockerContainer "github.com/docker/docker/api/types/container"
|
||||||
|
@ -18,7 +17,7 @@ import (
|
||||||
func getCommonTestData(keepContainer string) *TestData {
|
func getCommonTestData(keepContainer string) *TestData {
|
||||||
return &TestData{
|
return &TestData{
|
||||||
NameOfContainerToKeep: keepContainer,
|
NameOfContainerToKeep: keepContainer,
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainer(
|
CreateMockContainer(
|
||||||
"test-container-01",
|
"test-container-01",
|
||||||
"test-container-01",
|
"test-container-01",
|
||||||
|
@ -59,7 +58,7 @@ func getLinkedTestData(withImageInfo bool) *TestData {
|
||||||
|
|
||||||
return &TestData{
|
return &TestData{
|
||||||
Staleness: map[string]bool{linkingContainer.Name(): false},
|
Staleness: map[string]bool{linkingContainer.Name(): false},
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
staleContainer,
|
staleContainer,
|
||||||
linkingContainer,
|
linkingContainer,
|
||||||
},
|
},
|
||||||
|
@ -130,7 +129,7 @@ var _ = Describe("the update action", func() {
|
||||||
client := CreateMockClient(
|
client := CreateMockClient(
|
||||||
&TestData{
|
&TestData{
|
||||||
NameOfContainerToKeep: "test-container-02",
|
NameOfContainerToKeep: "test-container-02",
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainer(
|
CreateMockContainer(
|
||||||
"test-container-01",
|
"test-container-01",
|
||||||
"test-container-01",
|
"test-container-01",
|
||||||
|
@ -163,7 +162,7 @@ var _ = Describe("the update action", func() {
|
||||||
It("should not update any containers", func() {
|
It("should not update any containers", func() {
|
||||||
client := CreateMockClient(
|
client := CreateMockClient(
|
||||||
&TestData{
|
&TestData{
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainer(
|
CreateMockContainer(
|
||||||
"test-container-01",
|
"test-container-01",
|
||||||
"test-container-01",
|
"test-container-01",
|
||||||
|
@ -194,7 +193,7 @@ var _ = Describe("the update action", func() {
|
||||||
client := CreateMockClient(
|
client := CreateMockClient(
|
||||||
&TestData{
|
&TestData{
|
||||||
//NameOfContainerToKeep: "test-container-02",
|
//NameOfContainerToKeep: "test-container-02",
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainerWithConfig(
|
CreateMockContainerWithConfig(
|
||||||
"test-container-02",
|
"test-container-02",
|
||||||
"test-container-02",
|
"test-container-02",
|
||||||
|
@ -227,7 +226,7 @@ var _ = Describe("the update action", func() {
|
||||||
client := CreateMockClient(
|
client := CreateMockClient(
|
||||||
&TestData{
|
&TestData{
|
||||||
//NameOfContainerToKeep: "test-container-02",
|
//NameOfContainerToKeep: "test-container-02",
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainerWithConfig(
|
CreateMockContainerWithConfig(
|
||||||
"test-container-02",
|
"test-container-02",
|
||||||
"test-container-02",
|
"test-container-02",
|
||||||
|
@ -259,7 +258,7 @@ var _ = Describe("the update action", func() {
|
||||||
client := CreateMockClient(
|
client := CreateMockClient(
|
||||||
&TestData{
|
&TestData{
|
||||||
//NameOfContainerToKeep: "test-container-02",
|
//NameOfContainerToKeep: "test-container-02",
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainerWithConfig(
|
CreateMockContainerWithConfig(
|
||||||
"test-container-02",
|
"test-container-02",
|
||||||
"test-container-02",
|
"test-container-02",
|
||||||
|
@ -300,7 +299,7 @@ var _ = Describe("the update action", func() {
|
||||||
ExposedPorts: map[nat.Port]struct{}{},
|
ExposedPorts: map[nat.Port]struct{}{},
|
||||||
})
|
})
|
||||||
|
|
||||||
provider.Stale = true
|
provider.SetStale(true)
|
||||||
|
|
||||||
consumer := CreateMockContainerWithConfig(
|
consumer := CreateMockContainerWithConfig(
|
||||||
"test-container-consumer",
|
"test-container-consumer",
|
||||||
|
@ -316,7 +315,7 @@ var _ = Describe("the update action", func() {
|
||||||
ExposedPorts: map[nat.Port]struct{}{},
|
ExposedPorts: map[nat.Port]struct{}{},
|
||||||
})
|
})
|
||||||
|
|
||||||
containers := []container.Container{
|
containers := []types.Container{
|
||||||
provider,
|
provider,
|
||||||
consumer,
|
consumer,
|
||||||
}
|
}
|
||||||
|
@ -338,7 +337,7 @@ var _ = Describe("the update action", func() {
|
||||||
client := CreateMockClient(
|
client := CreateMockClient(
|
||||||
&TestData{
|
&TestData{
|
||||||
//NameOfContainerToKeep: "test-container-02",
|
//NameOfContainerToKeep: "test-container-02",
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainerWithConfig(
|
CreateMockContainerWithConfig(
|
||||||
"test-container-02",
|
"test-container-02",
|
||||||
"test-container-02",
|
"test-container-02",
|
||||||
|
@ -370,7 +369,7 @@ var _ = Describe("the update action", func() {
|
||||||
client := CreateMockClient(
|
client := CreateMockClient(
|
||||||
&TestData{
|
&TestData{
|
||||||
//NameOfContainerToKeep: "test-container-02",
|
//NameOfContainerToKeep: "test-container-02",
|
||||||
Containers: []container.Container{
|
Containers: []types.Container{
|
||||||
CreateMockContainerWithConfig(
|
CreateMockContainerWithConfig(
|
||||||
"test-container-02",
|
"test-container-02",
|
||||||
"test-container-02",
|
"test-container-02",
|
||||||
|
|
|
@ -25,15 +25,15 @@ const defaultStopSignal = "SIGTERM"
|
||||||
// A Client is the interface through which watchtower interacts with the
|
// A Client is the interface through which watchtower interacts with the
|
||||||
// Docker API.
|
// Docker API.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
ListContainers(t.Filter) ([]Container, error)
|
ListContainers(t.Filter) ([]t.Container, error)
|
||||||
GetContainer(containerID t.ContainerID) (Container, error)
|
GetContainer(containerID t.ContainerID) (t.Container, error)
|
||||||
StopContainer(Container, time.Duration) error
|
StopContainer(t.Container, time.Duration) error
|
||||||
StartContainer(Container) (t.ContainerID, error)
|
StartContainer(t.Container) (t.ContainerID, error)
|
||||||
RenameContainer(Container, string) error
|
RenameContainer(t.Container, string) error
|
||||||
IsContainerStale(Container) (stale bool, latestImage t.ImageID, err error)
|
IsContainerStale(t.Container) (stale bool, latestImage t.ImageID, err error)
|
||||||
ExecuteCommand(containerID t.ContainerID, command string, timeout int) (SkipUpdate bool, err error)
|
ExecuteCommand(containerID t.ContainerID, command string, timeout int) (SkipUpdate bool, err error)
|
||||||
RemoveImageByID(t.ImageID) error
|
RemoveImageByID(t.ImageID) error
|
||||||
WarnOnHeadPullFailed(container Container) bool
|
WarnOnHeadPullFailed(container t.Container) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient returns a new Client instance which can be used to interact with
|
// NewClient returns a new Client instance which can be used to interact with
|
||||||
|
@ -82,7 +82,7 @@ type dockerClient struct {
|
||||||
ClientOptions
|
ClientOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client dockerClient) WarnOnHeadPullFailed(container Container) bool {
|
func (client dockerClient) WarnOnHeadPullFailed(container t.Container) bool {
|
||||||
if client.WarnOnHeadFailed == WarnAlways {
|
if client.WarnOnHeadFailed == WarnAlways {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -93,8 +93,8 @@ func (client dockerClient) WarnOnHeadPullFailed(container Container) bool {
|
||||||
return registry.WarnOnAPIConsumption(container)
|
return registry.WarnOnAPIConsumption(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client dockerClient) ListContainers(fn t.Filter) ([]Container, error) {
|
func (client dockerClient) ListContainers(fn t.Filter) ([]t.Container, error) {
|
||||||
cs := []Container{}
|
cs := []t.Container{}
|
||||||
bg := context.Background()
|
bg := context.Background()
|
||||||
|
|
||||||
if client.IncludeStopped && client.IncludeRestarting {
|
if client.IncludeStopped && client.IncludeRestarting {
|
||||||
|
@ -149,24 +149,24 @@ func (client dockerClient) createListFilter() filters.Args {
|
||||||
return filterArgs
|
return filterArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client dockerClient) GetContainer(containerID t.ContainerID) (Container, error) {
|
func (client dockerClient) GetContainer(containerID t.ContainerID) (t.Container, error) {
|
||||||
bg := context.Background()
|
bg := context.Background()
|
||||||
|
|
||||||
containerInfo, err := client.api.ContainerInspect(bg, string(containerID))
|
containerInfo, err := client.api.ContainerInspect(bg, string(containerID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Container{}, err
|
return &Container{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
imageInfo, _, err := client.api.ImageInspectWithRaw(bg, containerInfo.Image)
|
imageInfo, _, err := client.api.ImageInspectWithRaw(bg, containerInfo.Image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to retrieve container image info: %v", err)
|
log.Warnf("Failed to retrieve container image info: %v", err)
|
||||||
return Container{containerInfo: &containerInfo, imageInfo: nil}, nil
|
return &Container{containerInfo: &containerInfo, imageInfo: nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container{containerInfo: &containerInfo, imageInfo: &imageInfo}, nil
|
return &Container{containerInfo: &containerInfo, imageInfo: &imageInfo}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client dockerClient) StopContainer(c Container, timeout time.Duration) error {
|
func (client dockerClient) StopContainer(c t.Container, timeout time.Duration) error {
|
||||||
bg := context.Background()
|
bg := context.Background()
|
||||||
signal := c.StopSignal()
|
signal := c.StopSignal()
|
||||||
if signal == "" {
|
if signal == "" {
|
||||||
|
@ -186,7 +186,7 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
|
||||||
// TODO: This should probably be checked.
|
// TODO: This should probably be checked.
|
||||||
_ = client.waitForStopOrTimeout(c, timeout)
|
_ = client.waitForStopOrTimeout(c, timeout)
|
||||||
|
|
||||||
if c.containerInfo.HostConfig.AutoRemove {
|
if c.ContainerInfo().HostConfig.AutoRemove {
|
||||||
log.Debugf("AutoRemove container %s, skipping ContainerRemove call.", shortID)
|
log.Debugf("AutoRemove container %s, skipping ContainerRemove call.", shortID)
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("Removing container %s", shortID)
|
log.Debugf("Removing container %s", shortID)
|
||||||
|
@ -208,11 +208,11 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client dockerClient) StartContainer(c Container) (t.ContainerID, error) {
|
func (client dockerClient) StartContainer(c t.Container) (t.ContainerID, error) {
|
||||||
bg := context.Background()
|
bg := context.Background()
|
||||||
config := c.runtimeConfig()
|
config := c.GetCreateConfig()
|
||||||
hostConfig := c.hostConfig()
|
hostConfig := c.GetCreateHostConfig()
|
||||||
networkConfig := &network.NetworkingConfig{EndpointsConfig: c.containerInfo.NetworkSettings.Networks}
|
networkConfig := &network.NetworkingConfig{EndpointsConfig: c.ContainerInfo().NetworkSettings.Networks}
|
||||||
// simpleNetworkConfig is a networkConfig with only 1 network.
|
// simpleNetworkConfig is a networkConfig with only 1 network.
|
||||||
// see: https://github.com/docker/docker/issues/29265
|
// see: https://github.com/docker/docker/issues/29265
|
||||||
simpleNetworkConfig := func() *network.NetworkingConfig {
|
simpleNetworkConfig := func() *network.NetworkingConfig {
|
||||||
|
@ -260,7 +260,7 @@ func (client dockerClient) StartContainer(c Container) (t.ContainerID, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client dockerClient) doStartContainer(bg context.Context, c Container, creation container.CreateResponse) error {
|
func (client dockerClient) doStartContainer(bg context.Context, c t.Container, creation container.CreateResponse) error {
|
||||||
name := c.Name()
|
name := c.Name()
|
||||||
|
|
||||||
log.Debugf("Starting container %s (%s)", name, t.ContainerID(creation.ID).ShortID())
|
log.Debugf("Starting container %s (%s)", name, t.ContainerID(creation.ID).ShortID())
|
||||||
|
@ -271,13 +271,13 @@ func (client dockerClient) doStartContainer(bg context.Context, c Container, cre
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client dockerClient) RenameContainer(c Container, newName string) error {
|
func (client dockerClient) RenameContainer(c t.Container, newName string) error {
|
||||||
bg := context.Background()
|
bg := context.Background()
|
||||||
log.Debugf("Renaming container %s (%s) to %s", c.Name(), c.ID().ShortID(), newName)
|
log.Debugf("Renaming container %s (%s) to %s", c.Name(), c.ID().ShortID(), newName)
|
||||||
return client.api.ContainerRename(bg, string(c.ID()), newName)
|
return client.api.ContainerRename(bg, string(c.ID()), newName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client dockerClient) IsContainerStale(container Container) (stale bool, latestImage t.ImageID, err error) {
|
func (client dockerClient) IsContainerStale(container t.Container) (stale bool, latestImage t.ImageID, err error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
if !client.PullImages || container.IsNoPull() {
|
if !client.PullImages || container.IsNoPull() {
|
||||||
|
@ -289,8 +289,8 @@ func (client dockerClient) IsContainerStale(container Container) (stale bool, la
|
||||||
return client.HasNewImage(ctx, container)
|
return client.HasNewImage(ctx, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client dockerClient) HasNewImage(ctx context.Context, container Container) (hasNew bool, latestImage t.ImageID, err error) {
|
func (client dockerClient) HasNewImage(ctx context.Context, container t.Container) (hasNew bool, latestImage t.ImageID, err error) {
|
||||||
currentImageID := t.ImageID(container.containerInfo.ContainerJSONBase.Image)
|
currentImageID := t.ImageID(container.ContainerInfo().ContainerJSONBase.Image)
|
||||||
imageName := container.ImageName()
|
imageName := container.ImageName()
|
||||||
|
|
||||||
newImageInfo, _, err := client.api.ImageInspectWithRaw(ctx, imageName)
|
newImageInfo, _, err := client.api.ImageInspectWithRaw(ctx, imageName)
|
||||||
|
@ -310,7 +310,7 @@ func (client dockerClient) HasNewImage(ctx context.Context, container Container)
|
||||||
|
|
||||||
// PullImage pulls the latest image for the supplied container, optionally skipping if it's digest can be confirmed
|
// PullImage pulls the latest image for the supplied container, optionally skipping if it's digest can be confirmed
|
||||||
// to match the one that the registry reports via a HEAD request
|
// to match the one that the registry reports via a HEAD request
|
||||||
func (client dockerClient) PullImage(ctx context.Context, container Container) error {
|
func (client dockerClient) PullImage(ctx context.Context, container t.Container) error {
|
||||||
containerName := container.Name()
|
containerName := container.Name()
|
||||||
imageName := container.ImageName()
|
imageName := container.ImageName()
|
||||||
|
|
||||||
|
@ -478,7 +478,7 @@ func (client dockerClient) waitForExecOrTimeout(bg context.Context, ID string, e
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client dockerClient) waitForStopOrTimeout(c Container, waitTime time.Duration) error {
|
func (client dockerClient) waitForStopOrTimeout(c t.Container, waitTime time.Duration) error {
|
||||||
bg := context.Background()
|
bg := context.Background()
|
||||||
timeout := time.After(waitTime)
|
timeout := time.After(waitTime)
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,8 @@ var _ = Describe("the client", func() {
|
||||||
mockServer.Close()
|
mockServer.Close()
|
||||||
})
|
})
|
||||||
Describe("WarnOnHeadPullFailed", func() {
|
Describe("WarnOnHeadPullFailed", func() {
|
||||||
containerUnknown := *MockContainer(WithImageName("unknown.repo/prefix/imagename:latest"))
|
containerUnknown := MockContainer(WithImageName("unknown.repo/prefix/imagename:latest"))
|
||||||
containerKnown := *MockContainer(WithImageName("docker.io/prefix/imagename:latest"))
|
containerKnown := MockContainer(WithImageName("docker.io/prefix/imagename:latest"))
|
||||||
|
|
||||||
When(`warn on head failure is set to "always"`, func() {
|
When(`warn on head failure is set to "always"`, func() {
|
||||||
c := dockerClient{ClientOptions: ClientOptions{WarnOnHeadFailed: WarnAlways}}
|
c := dockerClient{ClientOptions: ClientOptions{WarnOnHeadFailed: WarnAlways}}
|
||||||
|
@ -66,7 +66,7 @@ var _ = Describe("the client", func() {
|
||||||
When("the image consist of a pinned hash", func() {
|
When("the image consist of a pinned hash", func() {
|
||||||
It("should gracefully fail with a useful message", func() {
|
It("should gracefully fail with a useful message", func() {
|
||||||
c := dockerClient{}
|
c := dockerClient{}
|
||||||
pinnedContainer := *MockContainer(WithImageName("sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230b"))
|
pinnedContainer := MockContainer(WithImageName("sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230b"))
|
||||||
c.PullImage(context.Background(), pinnedContainer)
|
c.PullImage(context.Background(), pinnedContainer)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -74,8 +74,8 @@ var _ = Describe("the client", func() {
|
||||||
When("removing a running container", func() {
|
When("removing a running container", func() {
|
||||||
When("the container still exist after stopping", func() {
|
When("the container still exist after stopping", func() {
|
||||||
It("should attempt to remove the container", func() {
|
It("should attempt to remove the container", func() {
|
||||||
container := *MockContainer(WithContainerState(types.ContainerState{Running: true}))
|
container := MockContainer(WithContainerState(types.ContainerState{Running: true}))
|
||||||
containerStopped := *MockContainer(WithContainerState(types.ContainerState{Running: false}))
|
containerStopped := MockContainer(WithContainerState(types.ContainerState{Running: false}))
|
||||||
|
|
||||||
cid := container.ContainerInfo().ID
|
cid := container.ContainerInfo().ID
|
||||||
mockServer.AppendHandlers(
|
mockServer.AppendHandlers(
|
||||||
|
@ -90,7 +90,7 @@ var _ = Describe("the client", func() {
|
||||||
})
|
})
|
||||||
When("the container does not exist after stopping", func() {
|
When("the container does not exist after stopping", func() {
|
||||||
It("should not cause an error", func() {
|
It("should not cause an error", func() {
|
||||||
container := *MockContainer(WithContainerState(types.ContainerState{Running: true}))
|
container := MockContainer(WithContainerState(types.ContainerState{Running: true}))
|
||||||
|
|
||||||
cid := container.ContainerInfo().ID
|
cid := container.ContainerInfo().ID
|
||||||
mockServer.AppendHandlers(
|
mockServer.AppendHandlers(
|
||||||
|
@ -261,18 +261,18 @@ func withContainerImageName(matcher gt.GomegaMatcher) gt.GomegaMatcher {
|
||||||
return WithTransform(containerImageName, matcher)
|
return WithTransform(containerImageName, matcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerImageName(container Container) string {
|
func containerImageName(container t.Container) string {
|
||||||
return container.ImageName()
|
return container.ImageName()
|
||||||
}
|
}
|
||||||
|
|
||||||
func havingRestartingState(expected bool) gt.GomegaMatcher {
|
func havingRestartingState(expected bool) gt.GomegaMatcher {
|
||||||
return WithTransform(func(container Container) bool {
|
return WithTransform(func(container t.Container) bool {
|
||||||
return container.containerInfo.State.Restarting
|
return container.ContainerInfo().State.Restarting
|
||||||
}, Equal(expected))
|
}, Equal(expected))
|
||||||
}
|
}
|
||||||
|
|
||||||
func havingRunningState(expected bool) gt.GomegaMatcher {
|
func havingRunningState(expected bool) gt.GomegaMatcher {
|
||||||
return WithTransform(func(container Container) bool {
|
return WithTransform(func(container t.Container) bool {
|
||||||
return container.containerInfo.State.Running
|
return container.ContainerInfo().State.Running
|
||||||
}, Equal(expected))
|
}, Equal(expected))
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,26 @@ type Container struct {
|
||||||
imageInfo *types.ImageInspect
|
imageInfo *types.ImageInspect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsLinkedToRestarting returns the current value of the LinkedToRestarting field for the container
|
||||||
|
func (c *Container) IsLinkedToRestarting() bool {
|
||||||
|
return c.LinkedToRestarting
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStale returns the current value of the Stale field for the container
|
||||||
|
func (c *Container) IsStale() bool {
|
||||||
|
return c.Stale
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkedToRestarting sets the LinkedToRestarting field for the container
|
||||||
|
func (c *Container) SetLinkedToRestarting(value bool) {
|
||||||
|
c.LinkedToRestarting = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStale implements sets the Stale field for the container
|
||||||
|
func (c *Container) SetStale(value bool) {
|
||||||
|
c.Stale = value
|
||||||
|
}
|
||||||
|
|
||||||
// ContainerInfo fetches JSON info for the container
|
// ContainerInfo fetches JSON info for the container
|
||||||
func (c Container) ContainerInfo() *types.ContainerJSON {
|
func (c Container) ContainerInfo() *types.ContainerJSON {
|
||||||
return c.containerInfo
|
return c.containerInfo
|
||||||
|
@ -240,18 +260,23 @@ func (c Container) StopSignal() string {
|
||||||
return c.getLabelValueOrEmpty(signalLabel)
|
return c.getLabelValueOrEmpty(signalLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCreateConfig returns the container's current Config converted into a format
|
||||||
|
// that can be re-submitted to the Docker create API.
|
||||||
|
//
|
||||||
// Ideally, we'd just be able to take the ContainerConfig from the old container
|
// Ideally, we'd just be able to take the ContainerConfig from the old container
|
||||||
// and use it as the starting point for creating the new container; however,
|
// and use it as the starting point for creating the new container; however,
|
||||||
// the ContainerConfig that comes back from the Inspect call merges the default
|
// the ContainerConfig that comes back from the Inspect call merges the default
|
||||||
// configuration (the stuff specified in the metadata for the image itself)
|
// configuration (the stuff specified in the metadata for the image itself)
|
||||||
// with the overridden configuration (the stuff that you might specify as part
|
// with the overridden configuration (the stuff that you might specify as part
|
||||||
// of the "docker run"). In order to avoid unintentionally overriding the
|
// of the "docker run").
|
||||||
|
//
|
||||||
|
// In order to avoid unintentionally overriding the
|
||||||
// defaults in the new image we need to separate the override options from the
|
// defaults in the new image we need to separate the override options from the
|
||||||
// default options. To do this we have to compare the ContainerConfig for the
|
// default options. To do this we have to compare the ContainerConfig for the
|
||||||
// running container with the ContainerConfig from the image that container was
|
// running container with the ContainerConfig from the image that container was
|
||||||
// started from. This function returns a ContainerConfig which contains just
|
// started from. This function returns a ContainerConfig which contains just
|
||||||
// the options overridden at runtime.
|
// the options overridden at runtime.
|
||||||
func (c Container) runtimeConfig() *dockercontainer.Config {
|
func (c Container) GetCreateConfig() *dockercontainer.Config {
|
||||||
config := c.containerInfo.Config
|
config := c.containerInfo.Config
|
||||||
hostConfig := c.containerInfo.HostConfig
|
hostConfig := c.containerInfo.HostConfig
|
||||||
imageConfig := c.imageInfo.Config
|
imageConfig := c.imageInfo.Config
|
||||||
|
@ -295,9 +320,9 @@ func (c Container) runtimeConfig() *dockercontainer.Config {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any links in the HostConfig need to be re-written before they can be
|
// GetCreateHostConfig returns the container's current HostConfig with any links
|
||||||
// re-submitted to the Docker create API.
|
// re-written so that they can be re-submitted to the Docker create API.
|
||||||
func (c Container) hostConfig() *dockercontainer.HostConfig {
|
func (c Container) GetCreateHostConfig() *dockercontainer.HostConfig {
|
||||||
hostConfig := c.containerInfo.HostConfig
|
hostConfig := c.containerInfo.HostConfig
|
||||||
|
|
||||||
for i, link := range hostConfig.Links {
|
for i, link := range hostConfig.Links {
|
||||||
|
|
|
@ -29,7 +29,7 @@ func ExecutePostChecks(client container.Client, params types.UpdateParams) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutePreCheckCommand tries to run the pre-check lifecycle hook for a single container.
|
// ExecutePreCheckCommand tries to run the pre-check lifecycle hook for a single container.
|
||||||
func ExecutePreCheckCommand(client container.Client, container container.Container) {
|
func ExecutePreCheckCommand(client container.Client, container types.Container) {
|
||||||
clog := log.WithField("container", container.Name())
|
clog := log.WithField("container", container.Name())
|
||||||
command := container.GetLifecyclePreCheckCommand()
|
command := container.GetLifecyclePreCheckCommand()
|
||||||
if len(command) == 0 {
|
if len(command) == 0 {
|
||||||
|
@ -45,7 +45,7 @@ func ExecutePreCheckCommand(client container.Client, container container.Contain
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutePostCheckCommand tries to run the post-check lifecycle hook for a single container.
|
// ExecutePostCheckCommand tries to run the post-check lifecycle hook for a single container.
|
||||||
func ExecutePostCheckCommand(client container.Client, container container.Container) {
|
func ExecutePostCheckCommand(client container.Client, container types.Container) {
|
||||||
clog := log.WithField("container", container.Name())
|
clog := log.WithField("container", container.Name())
|
||||||
command := container.GetLifecyclePostCheckCommand()
|
command := container.GetLifecyclePostCheckCommand()
|
||||||
if len(command) == 0 {
|
if len(command) == 0 {
|
||||||
|
@ -61,7 +61,7 @@ func ExecutePostCheckCommand(client container.Client, container container.Contai
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutePreUpdateCommand tries to run the pre-update lifecycle hook for a single container.
|
// ExecutePreUpdateCommand tries to run the pre-update lifecycle hook for a single container.
|
||||||
func ExecutePreUpdateCommand(client container.Client, container container.Container) (SkipUpdate bool, err error) {
|
func ExecutePreUpdateCommand(client container.Client, container types.Container) (SkipUpdate bool, err error) {
|
||||||
timeout := container.PreUpdateTimeout()
|
timeout := container.PreUpdateTimeout()
|
||||||
command := container.GetLifecyclePreUpdateCommand()
|
command := container.GetLifecyclePreUpdateCommand()
|
||||||
clog := log.WithField("container", container.Name())
|
clog := log.WithField("container", container.Name())
|
||||||
|
|
|
@ -2,13 +2,14 @@ package sorter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/containrrr/watchtower/pkg/container"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containrrr/watchtower/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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.Container
|
type ByCreated []types.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] }
|
||||||
|
@ -34,18 +35,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) ([]container.Container, error) {
|
func SortByDependencies(containers []types.Container) ([]types.Container, error) {
|
||||||
sorter := dependencySorter{}
|
sorter := dependencySorter{}
|
||||||
return sorter.Sort(containers)
|
return sorter.Sort(containers)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dependencySorter struct {
|
type dependencySorter struct {
|
||||||
unvisited []container.Container
|
unvisited []types.Container
|
||||||
marked map[string]bool
|
marked map[string]bool
|
||||||
sorted []container.Container
|
sorted []types.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *dependencySorter) Sort(containers []container.Container) ([]container.Container, error) {
|
func (ds *dependencySorter) Sort(containers []types.Container) ([]types.Container, error) {
|
||||||
ds.unvisited = containers
|
ds.unvisited = containers
|
||||||
ds.marked = map[string]bool{}
|
ds.marked = map[string]bool{}
|
||||||
|
|
||||||
|
@ -58,7 +59,7 @@ func (ds *dependencySorter) Sort(containers []container.Container) ([]container.
|
||||||
return ds.sorted, nil
|
return ds.sorted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *dependencySorter) visit(c container.Container) error {
|
func (ds *dependencySorter) visit(c types.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())
|
||||||
|
@ -84,7 +85,7 @@ func (ds *dependencySorter) visit(c container.Container) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *dependencySorter) findUnvisited(name string) *container.Container {
|
func (ds *dependencySorter) findUnvisited(name string) *types.Container {
|
||||||
for _, c := range ds.unvisited {
|
for _, c := range ds.unvisited {
|
||||||
if c.Name() == name {
|
if c.Name() == name {
|
||||||
return &c
|
return &c
|
||||||
|
@ -94,7 +95,7 @@ func (ds *dependencySorter) findUnvisited(name string) *container.Container {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *dependencySorter) removeUnvisited(c container.Container) {
|
func (ds *dependencySorter) removeUnvisited(c types.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,8 +1,10 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
dc "github.com/docker/docker/api/types/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImageID is a hash string representing a container image
|
// ImageID is a hash string representing a container image
|
||||||
|
@ -62,4 +64,15 @@ type Container interface {
|
||||||
GetLifecyclePostCheckCommand() string
|
GetLifecyclePostCheckCommand() string
|
||||||
GetLifecyclePreUpdateCommand() string
|
GetLifecyclePreUpdateCommand() string
|
||||||
GetLifecyclePostUpdateCommand() string
|
GetLifecyclePostUpdateCommand() string
|
||||||
|
VerifyConfiguration() error
|
||||||
|
SetStale(bool)
|
||||||
|
IsStale() bool
|
||||||
|
IsNoPull() bool
|
||||||
|
SetLinkedToRestarting(bool)
|
||||||
|
IsLinkedToRestarting() bool
|
||||||
|
PreUpdateTimeout() int
|
||||||
|
PostUpdateTimeout() int
|
||||||
|
IsRestarting() bool
|
||||||
|
GetCreateConfig() *dc.Config
|
||||||
|
GetCreateHostConfig() *dc.HostConfig
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue