From 4ba21639a00310a91ac35a1a241353c4187b7070 Mon Sep 17 00:00:00 2001 From: Brian DeHamer Date: Tue, 21 Jul 2015 19:37:18 +0000 Subject: [PATCH] Allow user-configurable DOCKER_HOST --- actions/check.go | 4 +- actions/check_test.go | 81 +++++++++++++++++++++++++++++++ actions/update.go | 3 +- actions/update_test.go | 25 +++++++--- container/client.go | 4 +- container/container.go | 18 +++---- container/mockclient/mock.go | 37 ++++++++++++++ container/mockclient/mock_test.go | 17 +++++++ container/sort_test.go | 30 ++++++++---- main.go | 18 ++++++- 10 files changed, 202 insertions(+), 35 deletions(-) create mode 100644 actions/check_test.go create mode 100644 container/mockclient/mock.go create mode 100644 container/mockclient/mock_test.go diff --git a/actions/check.go b/actions/check.go index d57dfb0..020bc8a 100644 --- a/actions/check.go +++ b/actions/check.go @@ -8,9 +8,7 @@ import ( func watchtowerContainersFilter(c container.Container) bool { return c.IsWatchtower() } -func CheckPrereqs() error { - client := container.NewClient() - +func CheckPrereqs(client container.Client) error { containers, err := client.ListContainers(watchtowerContainersFilter) if err != nil { return err diff --git a/actions/check_test.go b/actions/check_test.go new file mode 100644 index 0000000..1c0fa18 --- /dev/null +++ b/actions/check_test.go @@ -0,0 +1,81 @@ +package actions + +import ( + "errors" + "testing" + "time" + + "github.com/CenturyLinkLabs/watchtower/container" + "github.com/CenturyLinkLabs/watchtower/container/mockclient" + "github.com/samalba/dockerclient" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestCheckPrereqs_Success(t *testing.T) { + cc := &dockerclient.ContainerConfig{ + Labels: map[string]string{"com.centurylinklabs.watchtower": "true"}, + } + c1 := *container.NewContainer( + &dockerclient.ContainerInfo{ + Name: "c1", + Config: cc, + Created: "2015-07-01T12:00:01.000000000Z", + }, + nil, + ) + c2 := *container.NewContainer( + &dockerclient.ContainerInfo{ + Name: "c2", + Config: cc, + Created: "2015-07-01T12:00:00.000000000Z", + }, + nil, + ) + cs := []container.Container{c1, c2} + + client := &mockclient.MockClient{} + client.On("ListContainers", mock.AnythingOfType("container.ContainerFilter")).Return(cs, nil) + client.On("Stop", c2, time.Duration(60)).Return(nil) + + err := CheckPrereqs(client) + + assert.NoError(t, err) + client.AssertExpectations(t) +} + +func TestCheckPrereqs_OnlyOneContainer(t *testing.T) { + cc := &dockerclient.ContainerConfig{ + Labels: map[string]string{"com.centurylinklabs.watchtower": "true"}, + } + c1 := *container.NewContainer( + &dockerclient.ContainerInfo{ + Name: "c1", + Config: cc, + Created: "2015-07-01T12:00:01.000000000Z", + }, + nil, + ) + cs := []container.Container{c1} + + client := &mockclient.MockClient{} + client.On("ListContainers", mock.AnythingOfType("container.ContainerFilter")).Return(cs, nil) + + err := CheckPrereqs(client) + + assert.NoError(t, err) + client.AssertExpectations(t) +} + +func TestCheckPrereqs_ListError(t *testing.T) { + cs := []container.Container{} + + client := &mockclient.MockClient{} + client.On("ListContainers", mock.AnythingOfType("container.ContainerFilter")).Return(cs, errors.New("oops")) + + err := CheckPrereqs(client) + + assert.Error(t, err) + assert.EqualError(t, err, "oops") + client.AssertExpectations(t) +} diff --git a/actions/update.go b/actions/update.go index 1aeb4c8..0beeb5b 100644 --- a/actions/update.go +++ b/actions/update.go @@ -12,8 +12,7 @@ var ( func allContainersFilter(container.Container) bool { return true } -func Update() error { - client := container.NewClient() +func Update(client container.Client) error { containers, err := client.ListContainers(allContainersFilter) if err != nil { return err diff --git a/actions/update_test.go b/actions/update_test.go index 0d104d3..69a88e7 100644 --- a/actions/update_test.go +++ b/actions/update_test.go @@ -5,17 +5,18 @@ import ( "testing" "github.com/CenturyLinkLabs/watchtower/container" + "github.com/samalba/dockerclient" "github.com/stretchr/testify/assert" ) func TestCheckDependencies(t *testing.T) { cs := []container.Container{ - container.NewTestContainer("1", []string{}), - container.NewTestContainer("2", []string{"1:"}), - container.NewTestContainer("3", []string{"2:"}), - container.NewTestContainer("4", []string{"3:"}), - container.NewTestContainer("5", []string{"4:"}), - container.NewTestContainer("6", []string{"5:"}), + newTestContainer("1", []string{}), + newTestContainer("2", []string{"1:"}), + newTestContainer("3", []string{"2:"}), + newTestContainer("4", []string{"3:"}), + newTestContainer("5", []string{"4:"}), + newTestContainer("6", []string{"5:"}), } cs[3].Stale = true @@ -36,3 +37,15 @@ func TestRandName(t *testing.T) { assert.True(t, validPattern.MatchString(name)) } + +func newTestContainer(name string, links []string) container.Container { + return *container.NewContainer( + &dockerclient.ContainerInfo{ + Name: name, + HostConfig: &dockerclient.HostConfig{ + Links: links, + }, + }, + nil, + ) +} diff --git a/container/client.go b/container/client.go index 359537c..44b84fe 100644 --- a/container/client.go +++ b/container/client.go @@ -32,8 +32,8 @@ type Client interface { Rename(Container, string) error } -func NewClient() Client { - docker, err := dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil) +func NewClient(dockerHost string) Client { + docker, err := dockerclient.NewDockerClient(dockerHost, nil) if err != nil { log.Fatalf("Error instantiating Docker client: %s\n", err) diff --git a/container/container.go b/container/container.go index 0bddc86..cbf59f0 100644 --- a/container/container.go +++ b/container/container.go @@ -7,6 +7,13 @@ import ( "github.com/samalba/dockerclient" ) +func NewContainer(containerInfo *dockerclient.ContainerInfo, imageInfo *dockerclient.ImageInfo) *Container { + return &Container{ + containerInfo: containerInfo, + imageInfo: imageInfo, + } +} + type Container struct { Stale bool @@ -95,14 +102,3 @@ func (c Container) hostConfig() *dockerclient.HostConfig { return hostConfig } - -func NewTestContainer(name string, links []string) Container { - return Container{ - containerInfo: &dockerclient.ContainerInfo{ - Name: name, - HostConfig: &dockerclient.HostConfig{ - Links: links, - }, - }, - } -} diff --git a/container/mockclient/mock.go b/container/mockclient/mock.go new file mode 100644 index 0000000..d315962 --- /dev/null +++ b/container/mockclient/mock.go @@ -0,0 +1,37 @@ +package mockclient + +import ( + "time" + + "github.com/CenturyLinkLabs/watchtower/container" + "github.com/stretchr/testify/mock" +) + +type MockClient struct { + mock.Mock +} + +func (m *MockClient) ListContainers(cf container.ContainerFilter) ([]container.Container, error) { + args := m.Called(cf) + return args.Get(0).([]container.Container), args.Error(1) +} + +func (m *MockClient) RefreshImage(c *container.Container) error { + args := m.Called(c) + return args.Error(0) +} + +func (m *MockClient) Stop(c container.Container, timeout time.Duration) error { + args := m.Called(c, timeout) + return args.Error(0) +} + +func (m *MockClient) Start(c container.Container) error { + args := m.Called(c) + return args.Error(0) +} + +func (m *MockClient) Rename(c container.Container, name string) error { + args := m.Called(c, name) + return args.Error(0) +} diff --git a/container/mockclient/mock_test.go b/container/mockclient/mock_test.go new file mode 100644 index 0000000..89984e3 --- /dev/null +++ b/container/mockclient/mock_test.go @@ -0,0 +1,17 @@ +package mockclient + +import ( + "reflect" + "testing" + + "github.com/CenturyLinkLabs/watchtower/container" +) + +func TestMockInterface(t *testing.T) { + iface := reflect.TypeOf((*container.Client)(nil)).Elem() + mock := &MockClient{} + + if !reflect.TypeOf(mock).Implements(iface) { + t.Fatalf("Mock does not implement the Client interface") + } +} diff --git a/container/sort_test.go b/container/sort_test.go index 8bb1ce0..4cbc150 100644 --- a/container/sort_test.go +++ b/container/sort_test.go @@ -32,12 +32,12 @@ func TestByCreated(t *testing.T) { } func TestSortByDependencies_Success(t *testing.T) { - c1 := NewTestContainer("1", []string{}) - c2 := NewTestContainer("2", []string{"1:"}) - c3 := NewTestContainer("3", []string{"2:"}) - c4 := NewTestContainer("4", []string{"3:"}) - c5 := NewTestContainer("5", []string{"4:"}) - c6 := NewTestContainer("6", []string{"5:", "3:"}) + c1 := newTestContainer("1", []string{}) + c2 := newTestContainer("2", []string{"1:"}) + c3 := newTestContainer("3", []string{"2:"}) + c4 := newTestContainer("4", []string{"3:"}) + c5 := newTestContainer("5", []string{"4:"}) + c6 := newTestContainer("6", []string{"5:", "3:"}) containers := []Container{c6, c2, c4, c1, c3, c5} result, err := SortByDependencies(containers) @@ -47,9 +47,9 @@ func TestSortByDependencies_Success(t *testing.T) { } func TestSortByDependencies_Error(t *testing.T) { - c1 := NewTestContainer("1", []string{"3:"}) - c2 := NewTestContainer("2", []string{"1:"}) - c3 := NewTestContainer("3", []string{"2:"}) + c1 := newTestContainer("1", []string{"3:"}) + c2 := newTestContainer("2", []string{"1:"}) + c3 := newTestContainer("3", []string{"2:"}) containers := []Container{c1, c2, c3} _, err := SortByDependencies(containers) @@ -57,3 +57,15 @@ func TestSortByDependencies_Error(t *testing.T) { assert.Error(t, err) assert.EqualError(t, err, "Circular reference to 1") } + +func newTestContainer(name string, links []string) Container { + return *NewContainer( + &dockerclient.ContainerInfo{ + Name: name, + HostConfig: &dockerclient.HostConfig{ + Links: links, + }, + }, + nil, + ) +} diff --git a/main.go b/main.go index e957ced..7b196b7 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "time" "github.com/CenturyLinkLabs/watchtower/actions" + "github.com/CenturyLinkLabs/watchtower/container" "github.com/codegangsta/cli" ) @@ -23,6 +24,12 @@ func main() { app.Before = before app.Action = start app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "host, H", + Value: "unix:///var/run/docker.sock", + Usage: "Docker daemon socket to connect to", + EnvVar: "DOCKER_HOST", + }, cli.IntFlag{ Name: "interval, i", Value: 300, @@ -48,15 +55,17 @@ func handleSignals() { } func before(c *cli.Context) error { - return actions.CheckPrereqs() + client := newContainerClient(c) + return actions.CheckPrereqs(client) } func start(c *cli.Context) { + client := newContainerClient(c) secs := time.Duration(c.Int("interval")) * time.Second for { wg.Add(1) - if err := actions.Update(); err != nil { + if err := actions.Update(client); err != nil { fmt.Println(err) } wg.Done() @@ -64,3 +73,8 @@ func start(c *cli.Context) { time.Sleep(secs) } } + +func newContainerClient(c *cli.Context) container.Client { + dockerHost := c.GlobalString("host") + return container.NewClient(dockerHost) +}