From f3adefa4549c28b6f8d6d47e671e36447c9633be Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 6 Apr 2016 14:52:06 +0100 Subject: [PATCH] Update docker client, to get better state strings in the UI --- probe/docker/container.go | 45 +- probe/docker/container_test.go | 19 +- probe/docker/registry_test.go | 4 + probe/docker/tagger.go | 2 +- render/detailed/labels.go | 2 +- render/detailed/metadata.go | 2 +- render/detailed/metadata_test.go | 4 +- render/detailed/node_test.go | 2 +- render/filters.go | 12 +- test/fixture/report_fixture.go | 2 + .../github.com/fsouza/go-dockerclient/AUTHORS | 7 + .../github.com/fsouza/go-dockerclient/LICENSE | 2 +- .../fsouza/go-dockerclient/Makefile | 28 +- .../fsouza/go-dockerclient/README.markdown | 2 +- .../github.com/fsouza/go-dockerclient/auth.go | 8 +- .../fsouza/go-dockerclient/auth_test.go | 28 + .../fsouza/go-dockerclient/client.go | 2 + .../fsouza/go-dockerclient/container.go | 212 +++- .../fsouza/go-dockerclient/container_test.go | 84 +- .../fsouza/go-dockerclient/event.go | 76 +- .../fsouza/go-dockerclient/event_test.go | 200 +++- .../docker/pkg/archive/copy_unix_test.go | 978 ++++++++++++++++++ .../docker/docker/pkg/system/chtimes_unix.go | 14 + .../docker/pkg/system/chtimes_windows.go | 27 + .../docker/docker/pkg/system/stat_openbsd.go | 15 + .../docker/pkg/system/stat_unsupported.go | 2 +- .../golang.org/x/net/context/context.go | 12 +- .../fsouza/go-dockerclient/image.go | 24 + .../fsouza/go-dockerclient/image_test.go | 4 +- .../go-dockerclient/integration_test.go | 2 +- .../github.com/fsouza/go-dockerclient/misc.go | 75 +- .../fsouza/go-dockerclient/misc_test.go | 32 +- .../fsouza/go-dockerclient/network.go | 65 +- .../fsouza/go-dockerclient/network_test.go | 79 +- .../fsouza/go-dockerclient/testing/server.go | 90 +- .../go-dockerclient/testing/server_test.go | 29 + .../travis-scripts/install.bash | 17 + .../travis-scripts/run-tests.bash | 15 + vendor/manifest | 2 +- 39 files changed, 2019 insertions(+), 206 deletions(-) create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy_unix_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_unix.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_windows.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_openbsd.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/travis-scripts/install.bash create mode 100644 vendor/github.com/fsouza/go-dockerclient/travis-scripts/run-tests.bash diff --git a/probe/docker/container.go b/probe/docker/container.go index e575cec20..b0b020800 100644 --- a/probe/docker/container.go +++ b/probe/docker/container.go @@ -31,6 +31,7 @@ const ( ContainerHostname = "docker_container_hostname" ContainerIPsWithScopes = "docker_container_ips_with_scopes" ContainerState = "docker_container_state" + ContainerStateHuman = "docker_container_state_human" ContainerUptime = "docker_container_uptime" ContainerRestartCount = "docker_container_restart_count" ContainerNetworkMode = "docker_container_network_mode" @@ -55,9 +56,12 @@ const ( CPUUsageInKernelmode = "docker_cpu_usage_in_kernelmode" CPUSystemCPUUsage = "docker_cpu_system_cpu_usage" - StateRunning = "running" - StateStopped = "stopped" - StatePaused = "paused" + StateCreated = "created" + StateDead = "dead" + StateExited = "exited" + StatePaused = "paused" + StateRestarting = "restarting" + StateRunning = "running" NetworkModeHost = "host" @@ -90,6 +94,7 @@ type Container interface { Hostname() string GetNode(string, []net.IP) report.Node State() string + StateString() string HasTTY() bool Container() *docker.Container StartGatheringStats() error @@ -144,12 +149,11 @@ func (c *container) HasTTY() bool { } func (c *container) State() string { - if c.container.State.Paused { - return StatePaused - } else if c.container.State.Running { - return StateRunning - } - return StateStopped + return c.container.State.String() +} + +func (c *container) StateString() string { + return c.container.State.StateString() } func (c *container) Container() *docker.Container { @@ -333,16 +337,15 @@ func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node { ipsWithScopes = append(ipsWithScopes, report.MakeScopedAddressNodeID(hostID, ip)) } - state := c.State() - result := report.MakeNodeWith(map[string]string{ - ContainerID: c.ID(), - ContainerName: strings.TrimPrefix(c.container.Name, "/"), - ContainerCreated: c.container.Created.Format(time.RFC822), - ContainerCommand: c.container.Path + " " + strings.Join(c.container.Args, " "), - ImageID: c.Image(), - ContainerHostname: c.Hostname(), - ContainerState: state, + ContainerID: c.ID(), + ContainerName: strings.TrimPrefix(c.container.Name, "/"), + ContainerCreated: c.container.Created.Format(time.RFC822), + ContainerCommand: c.container.Path + " " + strings.Join(c.container.Args, " "), + ImageID: c.Image(), + ContainerHostname: c.Hostname(), + ContainerState: c.StateString(), + ContainerStateHuman: c.State(), }).WithSets(report.EmptySets. Add(ContainerPorts, c.ports(localAddrs)). Add(ContainerIPs, report.MakeStringSet(ips...)). @@ -390,3 +393,9 @@ func ExtractContainerIPsWithScopes(nmd report.Node) []string { v, _ := nmd.Sets.Lookup(ContainerIPsWithScopes) return []string(v) } + +// ContainerIsStopped checks if the docker container is in one of our "stopped" states +func ContainerIsStopped(c Container) bool { + state := c.StateString() + return (state != StateRunning && state != StateRestarting && state != StatePaused) +} diff --git a/probe/docker/container_test.go b/probe/docker/container_test.go index 361d9f3a7..49baeb2e2 100644 --- a/probe/docker/container_test.go +++ b/probe/docker/container_test.go @@ -74,15 +74,16 @@ func TestContainer(t *testing.T) { // Now see if we go them uptime := (now.Sub(startTime) / time.Second) * time.Second want := report.MakeNode().WithLatests(map[string]string{ - "docker_container_command": " ", - "docker_container_created": "01 Jan 01 00:00 UTC", - "docker_container_id": "ping", - "docker_container_name": "pong", - "docker_image_id": "baz", - "docker_label_foo1": "bar1", - "docker_label_foo2": "bar2", - "docker_container_state": "running", - "docker_container_uptime": uptime.String(), + "docker_container_command": " ", + "docker_container_created": "01 Jan 01 00:00 UTC", + "docker_container_id": "ping", + "docker_container_name": "pong", + "docker_image_id": "baz", + "docker_label_foo1": "bar1", + "docker_label_foo2": "bar2", + "docker_container_state": "running", + "docker_container_state_human": "Up 6 years", + "docker_container_uptime": uptime.String(), }).WithSets(report.EmptySets. Add("docker_container_ports", report.MakeStringSet("1.2.3.4:80->80/tcp", "81/tcp")). Add("docker_container_ips", report.MakeStringSet("1.2.3.4")). diff --git a/probe/docker/registry_test.go b/probe/docker/registry_test.go index 75d048c21..fefb412d8 100644 --- a/probe/docker/registry_test.go +++ b/probe/docker/registry_test.go @@ -39,6 +39,10 @@ func (c *mockContainer) Hostname() string { } func (c *mockContainer) State() string { + return "Up 3 minutes" +} + +func (c *mockContainer) StateString() string { return docker.StateRunning } diff --git a/probe/docker/tagger.go b/probe/docker/tagger.go index ddbe12b39..d238a0195 100644 --- a/probe/docker/tagger.go +++ b/probe/docker/tagger.go @@ -78,7 +78,7 @@ func (t *Tagger) tag(tree process.Tree, topology *report.Topology) { } }) - if c == nil || c.State() == StateStopped || c.PID() == 1 { + if c == nil || ContainerIsStopped(c) || c.PID() == 1 { continue } diff --git a/render/detailed/labels.go b/render/detailed/labels.go index 556e459a0..421d13bd7 100644 --- a/render/detailed/labels.go +++ b/render/detailed/labels.go @@ -20,7 +20,7 @@ var labels = map[string]string{ docker.ContainerIPs: "IPs", docker.ContainerPorts: "Ports", docker.ContainerRestartCount: "Restart #", - docker.ContainerState: "State", + docker.ContainerStateHuman: "State", docker.ContainerUptime: "Uptime", docker.ImageID: "Image ID", docker.MemoryUsage: "Memory", diff --git a/render/detailed/metadata.go b/render/detailed/metadata.go index cce29789b..f032f02c3 100644 --- a/render/detailed/metadata.go +++ b/render/detailed/metadata.go @@ -23,7 +23,7 @@ var ( } containerNodeMetadata = []MetadataRowTemplate{ Latest{ID: docker.ContainerID, Truncate: 12, Prime: true}, - Latest{ID: docker.ContainerState, Prime: true}, + Latest{ID: docker.ContainerStateHuman, Prime: true}, Latest{ID: docker.ContainerCommand, Prime: true}, Latest{ID: docker.ImageID, Truncate: 12}, Latest{ID: docker.ContainerUptime}, diff --git a/render/detailed/metadata_test.go b/render/detailed/metadata_test.go index 39ccc2658..66511f269 100644 --- a/render/detailed/metadata_test.go +++ b/render/detailed/metadata_test.go @@ -22,13 +22,13 @@ func TestNodeMetadata(t *testing.T) { node: report.MakeNodeWith(map[string]string{ docker.ContainerID: fixture.ClientContainerID, docker.LabelPrefix + "label1": "label1value", - docker.ContainerState: docker.StateRunning, + docker.ContainerStateHuman: docker.StateRunning, }).WithTopology(report.Container).WithSets(report.EmptySets. Add(docker.ContainerIPs, report.MakeStringSet("10.10.10.0/24", "10.10.10.1/24")), ), want: []detailed.MetadataRow{ {ID: docker.ContainerID, Value: fixture.ClientContainerID, Prime: true}, - {ID: docker.ContainerState, Value: "running", Prime: true}, + {ID: docker.ContainerStateHuman, Value: "running", Prime: true}, {ID: docker.ContainerIPs, Value: "10.10.10.0/24, 10.10.10.1/24"}, }, }, diff --git a/render/detailed/node_test.go b/render/detailed/node_test.go index 91d5adc0c..87fe2685b 100644 --- a/render/detailed/node_test.go +++ b/render/detailed/node_test.go @@ -178,7 +178,7 @@ func TestMakeDetailedContainerNode(t *testing.T) { Pseudo: false, Metadata: []detailed.MetadataRow{ {ID: "docker_container_id", Value: fixture.ServerContainerID, Prime: true}, - {ID: "docker_container_state", Value: "running", Prime: true}, + {ID: "docker_container_state_human", Value: "running", Prime: true}, {ID: "docker_image_id", Value: fixture.ServerContainerImageID}, }, DockerLabels: []detailed.MetadataRow{ diff --git a/render/filters.go b/render/filters.go index 27ed285a4..07745abe4 100644 --- a/render/filters.go +++ b/render/filters.go @@ -161,18 +161,18 @@ func FilterNoop(in Renderer) Renderer { // FilterStopped filters out stopped containers. func FilterStopped(r Renderer) Renderer { - return MakeFilter(IsStopped, r) + return MakeFilter(IsRunning, r) } -// IsStopped checks if the node is a stopped docker container -func IsStopped(n report.Node) bool { - containerState, ok := n.Latest.Lookup(docker.ContainerState) - return !ok || containerState != docker.StateStopped +// IsRunning checks if the node is a running docker container +func IsRunning(n report.Node) bool { + state, ok := n.Latest.Lookup(docker.ContainerState) + return !ok || (state == docker.StateRunning || state == docker.StateRestarting || state == docker.StatePaused) } // FilterRunning filters out running containers. func FilterRunning(r Renderer) Renderer { - return MakeFilter(Complement(IsStopped), r) + return MakeFilter(Complement(IsRunning), r) } // FilterNonProcspied removes endpoints which were not found in procspy. diff --git a/test/fixture/report_fixture.go b/test/fixture/report_fixture.go index 4eacbc67a..ebd498c17 100644 --- a/test/fixture/report_fixture.go +++ b/test/fixture/report_fixture.go @@ -258,6 +258,7 @@ var ( kubernetes.PodID: ClientPodID, kubernetes.Namespace: KubernetesNamespace, docker.ContainerState: docker.StateRunning, + docker.ContainerStateHuman: docker.StateRunning, }).WithID(ClientContainerNodeID).WithTopology(report.Container).WithParents(report.EmptySets. Add("host", report.MakeStringSet(ClientHostNodeID)). Add("container_image", report.MakeStringSet(ClientContainerImageNodeID)). @@ -270,6 +271,7 @@ var ( docker.ContainerID: ServerContainerID, docker.ContainerName: "task-name-5-server-aceb93e2f2b797caba01", docker.ContainerState: docker.StateRunning, + docker.ContainerStateHuman: docker.StateRunning, docker.ImageID: ServerContainerImageID, report.HostNodeID: ServerHostNodeID, docker.LabelPrefix + detailed.AmazonECSContainerNameLabel: "server", diff --git a/vendor/github.com/fsouza/go-dockerclient/AUTHORS b/vendor/github.com/fsouza/go-dockerclient/AUTHORS index 0c42ae344..bb71cc345 100644 --- a/vendor/github.com/fsouza/go-dockerclient/AUTHORS +++ b/vendor/github.com/fsouza/go-dockerclient/AUTHORS @@ -14,6 +14,7 @@ Ben Marini Ben McCann Ben Parees Benno van den Berg +Bradley Cicenas Brendan Fosberry Brian Lalor Brian P. Hamachek @@ -48,6 +49,8 @@ Fabio Rehm Fatih Arslan Flavia Missi Francisco Souza +Frank Groeneveld +George Moura Grégoire Delattre Guillermo Álvarez Fernández Harry Zhang @@ -84,7 +87,9 @@ Michael Schmatz Michal Fojtik Mike Dillon Mrunal Patel +Nate Jones Nguyen Sy Thanh Son +Nicholas Van Wiggeren Nick Ethier Omeid Matten Orivej Desh @@ -98,9 +103,11 @@ Philippe Lafoucrière Rafe Colton Rob Miller Robert Williamson +Roman Khlystik Salvador Gironès Sam Rijs Sami Wagiaalla +Samuel Archambault Samuel Karp Silas Sewell Simon Eskildsen diff --git a/vendor/github.com/fsouza/go-dockerclient/LICENSE b/vendor/github.com/fsouza/go-dockerclient/LICENSE index 4e11de100..b1cdd4cd2 100644 --- a/vendor/github.com/fsouza/go-dockerclient/LICENSE +++ b/vendor/github.com/fsouza/go-dockerclient/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015, go-dockerclient authors +Copyright (c) 2016, go-dockerclient authors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/vendor/github.com/fsouza/go-dockerclient/Makefile b/vendor/github.com/fsouza/go-dockerclient/Makefile index 7a94eaa1e..64af37890 100644 --- a/vendor/github.com/fsouza/go-dockerclient/Makefile +++ b/vendor/github.com/fsouza/go-dockerclient/Makefile @@ -11,8 +11,7 @@ cov \ clean -SRCS = $(shell git ls-files '*.go' | grep -v '^external/') -PKGS = ./. ./testing +PKGS = . ./testing all: test @@ -22,32 +21,31 @@ vendor: lint: @ go get -v github.com/golang/lint/golint - $(foreach file,$(SRCS),golint $(file) || exit;) + @for file in $$(git ls-files '*.go' | grep -v 'external/'); do \ + export output="$$(golint $${file} | grep -v 'type name will be used as docker.DockerInfo')"; \ + [ -n "$${output}" ] && echo "$${output}" && export status=1; \ + done; \ + exit $${status:-0} vet: @-go get -v golang.org/x/tools/cmd/vet $(foreach pkg,$(PKGS),go vet $(pkg);) fmt: - gofmt -w $(SRCS) + gofmt -s -w $(PKGS) fmtcheck: - $(foreach file,$(SRCS),gofmt -d $(file);) - -prepare_docker: - sudo stop docker - sudo rm -rf /var/lib/docker - sudo rm -f `which docker` - sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D - echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" | sudo tee /etc/apt/sources.list.d/docker.list - sudo apt-get update - sudo apt-get install docker-engine=$(DOCKER_VERSION)-0~$(shell lsb_release -cs) -y --force-yes -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" + @ export output=$$(gofmt -s -d $(PKGS)); \ + [ -n "$${output}" ] && echo "$${output}" && export status=1; \ + exit $${status:-0} pretest: lint vet fmtcheck -test: pretest +gotest: $(foreach pkg,$(PKGS),go test $(pkg) || exit;) +test: pretest gotest + integration: go test -tags docker_integration -run TestIntegration -v diff --git a/vendor/github.com/fsouza/go-dockerclient/README.markdown b/vendor/github.com/fsouza/go-dockerclient/README.markdown index b75a7e920..b915039f1 100644 --- a/vendor/github.com/fsouza/go-dockerclient/README.markdown +++ b/vendor/github.com/fsouza/go-dockerclient/README.markdown @@ -4,7 +4,7 @@ [![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fsouza/go-dockerclient) This package presents a client for the Docker remote API. It also provides -support for the extensions in the [Swarm API](https://docs.docker.com/swarm/api/swarm-api/). +support for the extensions in the [Swarm API](https://docs.docker.com/swarm/swarm-api/). This package also provides support for docker's network API, which is a simple passthrough to the libnetwork remote API. Note that docker's network API is diff --git a/vendor/github.com/fsouza/go-dockerclient/auth.go b/vendor/github.com/fsouza/go-dockerclient/auth.go index 775c70c0b..1be277c96 100644 --- a/vendor/github.com/fsouza/go-dockerclient/auth.go +++ b/vendor/github.com/fsouza/go-dockerclient/auth.go @@ -82,10 +82,12 @@ func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) { buf.ReadFrom(r) byteData := buf.Bytes() - var confsWrapper map[string]map[string]dockerConfig + confsWrapper := struct { + Auths map[string]dockerConfig `json:"auths"` + }{} if err := json.Unmarshal(byteData, &confsWrapper); err == nil { - if confs, ok := confsWrapper["auths"]; ok { - return confs, nil + if len(confsWrapper.Auths) > 0 { + return confsWrapper.Auths, nil } } diff --git a/vendor/github.com/fsouza/go-dockerclient/auth_test.go b/vendor/github.com/fsouza/go-dockerclient/auth_test.go index e53b17601..dc94c3430 100644 --- a/vendor/github.com/fsouza/go-dockerclient/auth_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/auth_test.go @@ -49,6 +49,34 @@ func TestAuthBadConfig(t *testing.T) { } } +func TestAuthAndOtherFields(t *testing.T) { + auth := base64.StdEncoding.EncodeToString([]byte("user:pass")) + read := strings.NewReader(fmt.Sprintf(`{ + "auths":{"docker.io":{"auth":"%s","email":"user@example.com"}}, + "detachKeys": "ctrl-e,e", + "HttpHeaders": { "MyHeader": "MyValue" }}`, auth)) + + ac, err := NewAuthConfigurations(read) + if err != nil { + t.Error(err) + } + c, ok := ac.Configs["docker.io"] + if !ok { + t.Error("NewAuthConfigurations: Expected Configs to contain docker.io") + } + if got, want := c.Email, "user@example.com"; got != want { + t.Errorf(`AuthConfigurations.Configs["docker.io"].Email: wrong result. Want %q. Got %q`, want, got) + } + if got, want := c.Username, "user"; got != want { + t.Errorf(`AuthConfigurations.Configs["docker.io"].Username: wrong result. Want %q. Got %q`, want, got) + } + if got, want := c.Password, "pass"; got != want { + t.Errorf(`AuthConfigurations.Configs["docker.io"].Password: wrong result. Want %q. Got %q`, want, got) + } + if got, want := c.ServerAddress, "docker.io"; got != want { + t.Errorf(`AuthConfigurations.Configs["docker.io"].ServerAddress: wrong result. Want %q. Got %q`, want, got) + } +} func TestAuthConfig(t *testing.T) { auth := base64.StdEncoding.EncodeToString([]byte("user:pass")) read := strings.NewReader(fmt.Sprintf(`{"auths":{"docker.io":{"auth":"%s","email":"user@example.com"}}}`, auth)) diff --git a/vendor/github.com/fsouza/go-dockerclient/client.go b/vendor/github.com/fsouza/go-dockerclient/client.go index 114fb87b7..d893ba684 100644 --- a/vendor/github.com/fsouza/go-dockerclient/client.go +++ b/vendor/github.com/fsouza/go-dockerclient/client.go @@ -555,6 +555,8 @@ type hijackOptions struct { data interface{} } +// CloseWaiter is an interface with methods for closing the underlying resource +// and then waiting for it to finish processing. type CloseWaiter interface { io.Closer Wait() error diff --git a/vendor/github.com/fsouza/go-dockerclient/container.go b/vendor/github.com/fsouza/go-dockerclient/container.go index 317814b90..fcf115351 100644 --- a/vendor/github.com/fsouza/go-dockerclient/container.go +++ b/vendor/github.com/fsouza/go-dockerclient/container.go @@ -14,6 +14,8 @@ import ( "strconv" "strings" "time" + + "github.com/fsouza/go-dockerclient/external/github.com/docker/go-units" ) // ErrContainerAlreadyExists is the error returned by CreateContainer when the @@ -52,7 +54,14 @@ type APIContainers struct { SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty"` SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty"` Names []string `json:"Names,omitempty" yaml:"Names,omitempty"` - Labels map[string]string `json:"Labels,omitempty" yaml:"Labels, omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` + Networks NetworkList `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"` +} + +// NetworkList encapsulates a map of networks, as returned by the Docker API in +// ListContainers. +type NetworkList struct { + Networks map[string]ContainerNetwork `json:"Networks" yaml:"Networks,omitempty"` } // ListContainers returns a slice of containers matching the given criteria. @@ -92,26 +101,73 @@ func (p Port) Proto() string { // State represents the state of a container. type State struct { - Running bool `json:"Running,omitempty" yaml:"Running,omitempty"` - Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty"` - Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty"` - OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty"` - Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty"` - ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"` - Error string `json:"Error,omitempty" yaml:"Error,omitempty"` - StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty"` - FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty"` + Status string `json:"Status,omitempty" yaml:"Status,omitempty"` + Running bool `json:"Running,omitempty" yaml:"Running,omitempty"` + Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty"` + Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty"` + OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty"` + RemovalInProgress bool `json:"RemovalInProgress,omitempty" yaml:"RemovalInProgress,omitempty"` + Dead bool `json:"Dead,omitempty" yaml:"Dead,omitempty"` + Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty"` + ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"` + Error string `json:"Error,omitempty" yaml:"Error,omitempty"` + StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty"` + FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty"` } -// String returns the string representation of a state. +// String returns a human-readable description of the state func (s *State) String() string { + if s.Running { + if s.Paused { + return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) + } + if s.Restarting { + return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) + } + + return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) + } + + if s.RemovalInProgress { + return "Removal In Progress" + } + + if s.Dead { + return "Dead" + } + + if s.StartedAt.IsZero() { + return "Created" + } + + if s.FinishedAt.IsZero() { + return "" + } + + return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) +} + +// StateString returns a single string to describe state +func (s *State) StateString() string { if s.Running { if s.Paused { return "paused" } - return fmt.Sprintf("Up %s", time.Now().UTC().Sub(s.StartedAt)) + if s.Restarting { + return "restarting" + } + return "running" } - return fmt.Sprintf("Exit %d", s.ExitCode) + + if s.Dead { + return "dead" + } + + if s.StartedAt.IsZero() { + return "created" + } + + return "exited" } // PortBinding represents the host/container port mapping as returned in the @@ -135,6 +191,7 @@ type ContainerNetwork struct { IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"` Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"` EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"` + NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"` } // NetworkSettings contains network-related information about a container @@ -308,6 +365,34 @@ type Container struct { AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty"` } +// UpdateContainerOptions specify parameters to the UpdateContainer function. +// +// See https://goo.gl/Y6fXUy for more details. +type UpdateContainerOptions struct { + BlkioWeight int `json:"BlkioWeight"` + CPUShares int `json:"CpuShares"` + CPUPeriod int `json:"CpuPeriod"` + CPUQuota int `json:"CpuQuota"` + CpusetCpus string `json:"CpusetCpus"` + CpusetMems string `json:"CpusetMems"` + Memory int `json:"Memory"` + MemorySwap int `json:"MemorySwap"` + MemoryReservation int `json:"MemoryReservation"` + KernelMemory int `json:"KernelMemory"` +} + +// UpdateContainer updates the container at ID with the options +// +// See https://goo.gl/Y6fXUy for more details. +func (c *Client) UpdateContainer(id string, opts UpdateContainerOptions) error { + resp, err := c.do("POST", fmt.Sprintf("/containers/"+id+"/update"), doOptions{data: opts, forceJSON: true}) + if err != nil { + return err + } + defer resp.Body.Close() + return nil +} + // RenameContainerOptions specify parameters to the RenameContainer function. // // See https://goo.gl/laSOIy for more details. @@ -469,48 +554,71 @@ type Device struct { CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty"` } +// BlockWeight represents a relative device weight for an individual device inside +// of a container +// +// See https://goo.gl/FSdP0H for more details. +type BlockWeight struct { + Path string `json:"Path,omitempty"` + Weight string `json:"Weight,omitempty"` +} + +// BlockLimit represents a read/write limit in IOPS or Bandwidth for a device +// inside of a container +// +// See https://goo.gl/FSdP0H for more details. +type BlockLimit struct { + Path string `json:"Path,omitempty"` + Rate string `json:"Rate,omitempty"` +} + // HostConfig contains the container options related to starting a container on // a given host type HostConfig struct { - Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"` - CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"` - CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"` - GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty"` - ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"` - LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"` - Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"` - PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"` - Links []string `json:"Links,omitempty" yaml:"Links,omitempty"` - PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"` - DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only - DNSOptions []string `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty"` - DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"` - ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"` - VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` - NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"` - IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"` - PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"` - UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"` - RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"` - Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"` - LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"` - ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"` - SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"` - CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"` - Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` - MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` - MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty"` - OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable"` - CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` - CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` - CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty"` - CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty"` - CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"` - CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"` - BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight"` - Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"` - VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` - OomScoreAdj int `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty"` + Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"` + CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"` + CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"` + GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty"` + ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"` + LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"` + Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"` + PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"` + Links []string `json:"Links,omitempty" yaml:"Links,omitempty"` + PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"` + DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only + DNSOptions []string `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty"` + DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"` + ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"` + VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` + NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"` + IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"` + PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"` + UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"` + RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"` + Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"` + LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"` + ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"` + SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"` + CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"` + Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` + MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` + MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty"` + OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable"` + CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` + CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` + CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty"` + CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty"` + CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"` + CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"` + BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight"` + BlkioWeightDevice []BlockWeight `json:"BlkioWeightDevice,omitempty" yaml:"BlkioWeightDevice"` + BlkioDeviceReadBps []BlockLimit `json:"BlkioDeviceReadBps,omitempty" yaml:"BlkioDeviceReadBps"` + BlkioDeviceReadIOps []BlockLimit `json:"BlkioDeviceReadIOps,omitempty" yaml:"BlkioDeviceReadIOps"` + BlkioDeviceWriteBps []BlockLimit `json:"BlkioDeviceWriteBps,omitempty" yaml:"BlkioDeviceWriteBps"` + BlkioDeviceWriteIOps []BlockLimit `json:"BlkioDeviceWriteIOps,omitempty" yaml:"BlkioDeviceWriteIOps"` + Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"` + VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` + OomScoreAdj int `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty"` } // StartContainer starts a container, returning an error in case of failure. diff --git a/vendor/github.com/fsouza/go-dockerclient/container_test.go b/vendor/github.com/fsouza/go-dockerclient/container_test.go index 1b05b27bb..bbd66bae7 100644 --- a/vendor/github.com/fsouza/go-dockerclient/container_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/container_test.go @@ -16,7 +16,6 @@ import ( "net/url" "os" "reflect" - "regexp" "runtime" "strconv" "strings" @@ -32,13 +31,37 @@ func TestStateString(t *testing.T) { input State expected string }{ - {State{Running: true, Paused: true}, "^paused$"}, - {State{Running: true, StartedAt: started}, "^Up 3h.*$"}, - {State{Running: false, ExitCode: 7}, "^Exit 7$"}, + {State{Running: true, Paused: true, StartedAt: started}, "Up 3 hours (Paused)"}, + {State{Running: true, Restarting: true, ExitCode: 7, FinishedAt: started}, "Restarting (7) 3 hours ago"}, + {State{Running: true, StartedAt: started}, "Up 3 hours"}, + {State{RemovalInProgress: true}, "Removal In Progress"}, + {State{Dead: true}, "Dead"}, + {State{}, "Created"}, + {State{StartedAt: started}, ""}, + {State{ExitCode: 7, StartedAt: started, FinishedAt: started}, "Exited (7) 3 hours ago"}, } for _, tt := range tests { - re := regexp.MustCompile(tt.expected) - if got := tt.input.String(); !re.MatchString(got) { + if got := tt.input.String(); got != tt.expected { + t.Errorf("State.String(): wrong result. Want %q. Got %q.", tt.expected, got) + } + } +} + +func TestStateStateString(t *testing.T) { + started := time.Now().Add(-3 * time.Hour) + var tests = []struct { + input State + expected string + }{ + {State{Running: true, Paused: true}, "paused"}, + {State{Running: true, Restarting: true}, "restarting"}, + {State{Running: true}, "running"}, + {State{Dead: true}, "dead"}, + {State{}, "created"}, + {State{StartedAt: started}, "exited"}, + } + for _, tt := range tests { + if got := tt.input.StateString(); got != tt.expected { t.Errorf("State.String(): wrong result. Want %q. Got %q.", tt.expected, got) } } @@ -438,6 +461,7 @@ func TestInspectContainerNetwork(t *testing.T) { "MacAddress": "", "Networks": { "swl-net": { + "NetworkID": "7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812", "EndpointID": "683e3092275782a53c3b0968cc7e3a10f23264022ded9cb20490902f96fc5981", "Gateway": "", "IPAddress": "10.0.0.3", @@ -454,7 +478,8 @@ func TestInspectContainerNetwork(t *testing.T) { fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) id := "81e1bbe20b55" - exp := "10.0.0.3" + expIP := "10.0.0.3" + expNetworkID := "7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812" container, err := client.InspectContainer(id) if err != nil { @@ -471,8 +496,19 @@ func TestInspectContainerNetwork(t *testing.T) { t.Logf("%s %v", net, ip) } } - if ip != exp { - t.Errorf("InspectContainerNetworks(%q): Expected %#v. Got %#v.", id, exp, ip) + if ip != expIP { + t.Errorf("InspectContainerNetworks(%q): Expected %#v. Got %#v.", id, expIP, ip) + } + + var networkID string + for _, net := range networks.MapKeys() { + if net.Interface().(string) == container.HostConfig.NetworkMode { + networkID = networks.MapIndex(net).FieldByName("NetworkID").Interface().(string) + t.Logf("%s %v", net, networkID) + } + } + if networkID != expNetworkID { + t.Errorf("InspectContainerNetworks(%q): Expected %#v. Got %#v.", id, expNetworkID, networkID) } } else { t.Errorf("InspectContainerNetworks(%q): No method Networks for NetworkSettings", id) @@ -728,6 +764,36 @@ func TestCreateContainerWithHostConfig(t *testing.T) { } } +func TestUpdateContainer(t *testing.T) { + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" + update := UpdateContainerOptions{Memory: 12345, CpusetMems: "0,1"} + err := client.UpdateContainer(id, update) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + if req.Method != "POST" { + t.Errorf("UpdateContainer: wrong HTTP method. Want %q. Got %q.", "POST", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/update")) + if gotPath := req.URL.Path; gotPath != expectedURL.Path { + t.Errorf("UpdateContainer: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) + } + expectedContentType := "application/json" + if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { + t.Errorf("UpdateContainer: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) + } + var out UpdateContainerOptions + if err := json.NewDecoder(req.Body).Decode(&out); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(out, update) { + t.Errorf("UpdateContainer: wrong body, got: %#v, want %#v", out, update) + } +} + func TestStartContainer(t *testing.T) { fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) diff --git a/vendor/github.com/fsouza/go-dockerclient/event.go b/vendor/github.com/fsouza/go-dockerclient/event.go index eaffddb82..83b5cf52d 100644 --- a/vendor/github.com/fsouza/go-dockerclient/event.go +++ b/vendor/github.com/fsouza/go-dockerclient/event.go @@ -18,12 +18,38 @@ import ( "time" ) -// APIEvents represents an event returned by the API. +// APIEvents represents events coming from the Docker API +// The fields in the Docker API changed in API version 1.22, and +// events for more than images and containers are now fired off. +// To maintain forward and backward compatibility, go-dockerclient +// replicates the event in both the new and old format as faithfully as possible. +// +// For events that only exist in 1.22 in later, `Status` is filled in as +// `"Type:Action"` instead of just `Action` to allow for older clients to +// differentiate and not break if they rely on the pre-1.22 Status types. +// +// The transformEvent method can be consulted for more information about how +// events are translated from new/old API formats type APIEvents struct { - Status string `json:"Status,omitempty" yaml:"Status,omitempty"` - ID string `json:"ID,omitempty" yaml:"ID,omitempty"` - From string `json:"From,omitempty" yaml:"From,omitempty"` - Time int64 `json:"Time,omitempty" yaml:"Time,omitempty"` + // New API Fields in 1.22 + Action string `json:"action,omitempty"` + Type string `json:"type,omitempty"` + Actor APIActor `json:"actor,omitempty"` + + // Old API fields for < 1.22 + Status string `json:"status,omitempty"` + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` + + // Fields in both + Time int64 `json:"time,omitempty"` + TimeNano int64 `json:"timeNano,omitempty"` +} + +// APIActor represents an actor that accomplishes something for an event +type APIActor struct { + ID string `json:"id,omitempty"` + Attributes map[string]string `json:"attributes,omitempty"` } type eventMonitoringState struct { @@ -52,6 +78,7 @@ var ( // EOFEvent is sent when the event listener receives an EOF error. EOFEvent = &APIEvents{ + Type: "EOF", Status: "EOF", } ) @@ -297,8 +324,47 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan if !c.eventMonitor.isEnabled() { return } + transformEvent(&event) eventChan <- &event } }(res, conn) return nil } + +// transformEvent takes an event and determines what version it is from +// then populates both versions of the event +func transformEvent(event *APIEvents) { + // if event version is <= 1.21 there will be no Action and no Type + if event.Action == "" && event.Type == "" { + event.Action = event.Status + event.Actor.ID = event.ID + event.Actor.Attributes = map[string]string{} + switch event.Status { + case "delete", "import", "pull", "push", "tag", "untag": + event.Type = "image" + default: + event.Type = "container" + if event.From != "" { + event.Actor.Attributes["image"] = event.From + } + } + } else { + if event.Status == "" { + if event.Type == "image" || event.Type == "container" { + event.Status = event.Action + } else { + // Because just the Status has been overloaded with different Types + // if an event is not for an image or a container, we prepend the type + // to avoid problems for people relying on actions being only for + // images and containers + event.Status = event.Type + ":" + event.Action + } + } + if event.ID == "" { + event.ID = event.Actor.ID + } + if event.From == "" { + event.From = event.Actor.Attributes["image"] + } + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/event_test.go b/vendor/github.com/fsouza/go-dockerclient/event_test.go index a308538cc..e8382468e 100644 --- a/vendor/github.com/fsouza/go-dockerclient/event_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/event_test.go @@ -8,10 +8,10 @@ import ( "bufio" "crypto/tls" "crypto/x509" - "fmt" "io/ioutil" "net/http" "net/http/httptest" + "reflect" "strings" "testing" "time" @@ -51,12 +51,168 @@ func TestTLSEventListeners(t *testing.T) { } func testEventListeners(testName string, t *testing.T, buildServer func(http.Handler) *httptest.Server, buildClient func(string) (*Client, error)) { - response := `{"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + response := `{"action":"pull","type":"image","actor":{"id":"busybox:latest","attributes":{}},"time":1442421700,"timeNano":1442421700598988358} +{"action":"create","type":"container","actor":{"id":"5745704abe9caa5","attributes":{"image":"busybox"}},"time":1442421716,"timeNano":1442421716853979870} +{"action":"attach","type":"container","actor":{"id":"5745704abe9caa5","attributes":{"image":"busybox"}},"time":1442421716,"timeNano":1442421716894759198} +{"action":"start","type":"container","actor":{"id":"5745704abe9caa5","attributes":{"image":"busybox"}},"time":1442421716,"timeNano":1442421716983607193} +{"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} -` +{"Action":"create","Actor":{"Attributes":{"HAProxyMode":"http","HealthCheck":"HttpGet","HealthCheckArgs":"http://127.0.0.1:39051/status/check","ServicePort_8080":"17801","image":"datanerd.us/siteeng/sample-app-go:latest","name":"sample-app-client-go-69818c1223ddb5"},"ID":"a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16"},"Type":"container","from":"datanerd.us/siteeng/sample-app-go:latest","id":"a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16","status":"create","time":1459133932,"timeNano":1459133932961735842}` + wantResponse := []*APIEvents{ + { + Action: "pull", + Type: "image", + Actor: APIActor{ + ID: "busybox:latest", + Attributes: map[string]string{}, + }, + + Status: "pull", + ID: "busybox:latest", + + Time: 1442421700, + TimeNano: 1442421700598988358, + }, + { + Action: "create", + Type: "container", + Actor: APIActor{ + ID: "5745704abe9caa5", + Attributes: map[string]string{ + "image": "busybox", + }, + }, + + Status: "create", + ID: "5745704abe9caa5", + From: "busybox", + + Time: 1442421716, + TimeNano: 1442421716853979870, + }, + { + Action: "attach", + Type: "container", + Actor: APIActor{ + ID: "5745704abe9caa5", + Attributes: map[string]string{ + "image": "busybox", + }, + }, + + Status: "attach", + ID: "5745704abe9caa5", + From: "busybox", + + Time: 1442421716, + TimeNano: 1442421716894759198, + }, + { + Action: "start", + Type: "container", + Actor: APIActor{ + ID: "5745704abe9caa5", + Attributes: map[string]string{ + "image": "busybox", + }, + }, + + Status: "start", + ID: "5745704abe9caa5", + From: "busybox", + + Time: 1442421716, + TimeNano: 1442421716983607193, + }, + + { + Action: "create", + Type: "container", + Actor: APIActor{ + ID: "dfdf82bd3881", + Attributes: map[string]string{ + "image": "base:latest", + }, + }, + + Status: "create", + ID: "dfdf82bd3881", + From: "base:latest", + + Time: 1374067924, + }, + { + Action: "start", + Type: "container", + Actor: APIActor{ + ID: "dfdf82bd3881", + Attributes: map[string]string{ + "image": "base:latest", + }, + }, + + Status: "start", + ID: "dfdf82bd3881", + From: "base:latest", + + Time: 1374067924, + }, + { + Action: "stop", + Type: "container", + Actor: APIActor{ + ID: "dfdf82bd3881", + Attributes: map[string]string{ + "image": "base:latest", + }, + }, + + Status: "stop", + ID: "dfdf82bd3881", + From: "base:latest", + + Time: 1374067966, + }, + { + Action: "destroy", + Type: "container", + Actor: APIActor{ + ID: "dfdf82bd3881", + Attributes: map[string]string{ + "image": "base:latest", + }, + }, + + Status: "destroy", + ID: "dfdf82bd3881", + From: "base:latest", + + Time: 1374067970, + }, + { + Action: "create", + Type: "container", + Status: "create", + From: "datanerd.us/siteeng/sample-app-go:latest", + ID: "a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16", + Time: 1459133932, + TimeNano: 1459133932961735842, + Actor: APIActor{ + ID: "a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16", + Attributes: map[string]string{ + "HAProxyMode": "http", + "HealthCheck": "HttpGet", + "HealthCheckArgs": "http://127.0.0.1:39051/status/check", + "ServicePort_8080": "17801", + "image": "datanerd.us/siteeng/sample-app-go:latest", + "name": "sample-app-client-go-69818c1223ddb5", + }, + }, + }, + } server := buildServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rsc := bufio.NewScanner(strings.NewReader(response)) for rsc.Scan() { @@ -87,46 +243,16 @@ func testEventListeners(testName string, t *testing.T, buildServer func(http.Han } timeout := time.After(1 * time.Second) - var count int - for { + for i := 0; i < 9; i++ { select { case msg := <-listener: - t.Logf("Received: %v", *msg) - count++ - err = checkEvent(count, msg) - if err != nil { - t.Fatalf("Check event failed: %s", err) - } - if count == 4 { - return + t.Logf("%d: Received: %v", i, msg) + if !reflect.DeepEqual(msg, wantResponse[i]) { + t.Fatalf("%d: wanted: %#v\n got: %#v", i, wantResponse[i], msg) } case <-timeout: t.Fatalf("%s timed out waiting on events", testName) } } } - -func checkEvent(index int, event *APIEvents) error { - if event.ID != "dfdf82bd3881" { - return fmt.Errorf("event ID did not match. Expected dfdf82bd3881 got %s", event.ID) - } - if event.From != "base:latest" { - return fmt.Errorf("event from did not match. Expected base:latest got %s", event.From) - } - var status string - switch index { - case 1: - status = "create" - case 2: - status = "start" - case 3: - status = "stop" - case 4: - status = "destroy" - } - if event.Status != status { - return fmt.Errorf("event status did not match. Expected %s got %s", status, event.Status) - } - return nil -} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy_unix_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy_unix_test.go new file mode 100644 index 000000000..ecbfc172b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy_unix_test.go @@ -0,0 +1,978 @@ +// +build !windows + +// TODO Windows: Some of these tests may be salvagable and portable to Windows. + +package archive + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" +) + +func removeAllPaths(paths ...string) { + for _, path := range paths { + os.RemoveAll(path) + } +} + +func getTestTempDirs(t *testing.T) (tmpDirA, tmpDirB string) { + var err error + + if tmpDirA, err = ioutil.TempDir("", "archive-copy-test"); err != nil { + t.Fatal(err) + } + + if tmpDirB, err = ioutil.TempDir("", "archive-copy-test"); err != nil { + t.Fatal(err) + } + + return +} + +func isNotDir(err error) bool { + return strings.Contains(err.Error(), "not a directory") +} + +func joinTrailingSep(pathElements ...string) string { + joined := filepath.Join(pathElements...) + + return fmt.Sprintf("%s%c", joined, filepath.Separator) +} + +func fileContentsEqual(t *testing.T, filenameA, filenameB string) (err error) { + t.Logf("checking for equal file contents: %q and %q\n", filenameA, filenameB) + + fileA, err := os.Open(filenameA) + if err != nil { + return + } + defer fileA.Close() + + fileB, err := os.Open(filenameB) + if err != nil { + return + } + defer fileB.Close() + + hasher := sha256.New() + + if _, err = io.Copy(hasher, fileA); err != nil { + return + } + + hashA := hasher.Sum(nil) + hasher.Reset() + + if _, err = io.Copy(hasher, fileB); err != nil { + return + } + + hashB := hasher.Sum(nil) + + if !bytes.Equal(hashA, hashB) { + err = fmt.Errorf("file content hashes not equal - expected %s, got %s", hex.EncodeToString(hashA), hex.EncodeToString(hashB)) + } + + return +} + +func dirContentsEqual(t *testing.T, newDir, oldDir string) (err error) { + t.Logf("checking for equal directory contents: %q and %q\n", newDir, oldDir) + + var changes []Change + + if changes, err = ChangesDirs(newDir, oldDir); err != nil { + return + } + + if len(changes) != 0 { + err = fmt.Errorf("expected no changes between directories, but got: %v", changes) + } + + return +} + +func logDirContents(t *testing.T, dirPath string) { + logWalkedPaths := filepath.WalkFunc(func(path string, info os.FileInfo, err error) error { + if err != nil { + t.Errorf("stat error for path %q: %s", path, err) + return nil + } + + if info.IsDir() { + path = joinTrailingSep(path) + } + + t.Logf("\t%s", path) + + return nil + }) + + t.Logf("logging directory contents: %q", dirPath) + + if err := filepath.Walk(dirPath, logWalkedPaths); err != nil { + t.Fatal(err) + } +} + +func testCopyHelper(t *testing.T, srcPath, dstPath string) (err error) { + t.Logf("copying from %q to %q (not follow symbol link)", srcPath, dstPath) + + return CopyResource(srcPath, dstPath, false) +} + +func testCopyHelperFSym(t *testing.T, srcPath, dstPath string) (err error) { + t.Logf("copying from %q to %q (follow symbol link)", srcPath, dstPath) + + return CopyResource(srcPath, dstPath, true) +} + +// Basic assumptions about SRC and DST: +// 1. SRC must exist. +// 2. If SRC ends with a trailing separator, it must be a directory. +// 3. DST parent directory must exist. +// 4. If DST exists as a file, it must not end with a trailing separator. + +// First get these easy error cases out of the way. + +// Test for error when SRC does not exist. +func TestCopyErrSrcNotExists(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + if _, err := CopyInfoSourcePath(filepath.Join(tmpDirA, "file1"), false); !os.IsNotExist(err) { + t.Fatalf("expected IsNotExist error, but got %T: %s", err, err) + } +} + +// Test for error when SRC ends in a trailing +// path separator but it exists as a file. +func TestCopyErrSrcNotDir(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A with some sample files and directories. + createSampleDir(t, tmpDirA) + + if _, err := CopyInfoSourcePath(joinTrailingSep(tmpDirA, "file1"), false); !isNotDir(err) { + t.Fatalf("expected IsNotDir error, but got %T: %s", err, err) + } +} + +// Test for error when SRC is a valid file or directory, +// but the DST parent directory does not exist. +func TestCopyErrDstParentNotExists(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A with some sample files and directories. + createSampleDir(t, tmpDirA) + + srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false} + + // Try with a file source. + content, err := TarResource(srcInfo) + if err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + defer content.Close() + + // Copy to a file whose parent does not exist. + if err = CopyTo(content, srcInfo, filepath.Join(tmpDirB, "fakeParentDir", "file1")); err == nil { + t.Fatal("expected IsNotExist error, but got nil instead") + } + + if !os.IsNotExist(err) { + t.Fatalf("expected IsNotExist error, but got %T: %s", err, err) + } + + // Try with a directory source. + srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true} + + content, err = TarResource(srcInfo) + if err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + defer content.Close() + + // Copy to a directory whose parent does not exist. + if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "fakeParentDir", "fakeDstDir")); err == nil { + t.Fatal("expected IsNotExist error, but got nil instead") + } + + if !os.IsNotExist(err) { + t.Fatalf("expected IsNotExist error, but got %T: %s", err, err) + } +} + +// Test for error when DST ends in a trailing +// path separator but exists as a file. +func TestCopyErrDstNotDir(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + // Try with a file source. + srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false} + + content, err := TarResource(srcInfo) + if err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + defer content.Close() + + if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "file1")); err == nil { + t.Fatal("expected IsNotDir error, but got nil instead") + } + + if !isNotDir(err) { + t.Fatalf("expected IsNotDir error, but got %T: %s", err, err) + } + + // Try with a directory source. + srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true} + + content, err = TarResource(srcInfo) + if err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + defer content.Close() + + if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "file1")); err == nil { + t.Fatal("expected IsNotDir error, but got nil instead") + } + + if !isNotDir(err) { + t.Fatalf("expected IsNotDir error, but got %T: %s", err, err) + } +} + +// Possibilities are reduced to the remaining 10 cases: +// +// case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action +// =================================================================================================== +// A | no | - | no | - | no | create file +// B | no | - | no | - | yes | error +// C | no | - | yes | no | - | overwrite file +// D | no | - | yes | yes | - | create file in dst dir +// E | yes | no | no | - | - | create dir, copy contents +// F | yes | no | yes | no | - | error +// G | yes | no | yes | yes | - | copy dir and contents +// H | yes | yes | no | - | - | create dir, copy contents +// I | yes | yes | yes | no | - | error +// J | yes | yes | yes | yes | - | copy dir contents +// + +// A. SRC specifies a file and DST (no trailing path separator) doesn't +// exist. This should create a file with the name DST and copy the +// contents of the source file into it. +func TestCopyCaseA(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A with some sample files and directories. + createSampleDir(t, tmpDirA) + + srcPath := filepath.Join(tmpDirA, "file1") + dstPath := filepath.Join(tmpDirB, "itWorks.txt") + + var err error + + if err = testCopyHelper(t, srcPath, dstPath); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = fileContentsEqual(t, srcPath, dstPath); err != nil { + t.Fatal(err) + } + os.Remove(dstPath) + + symlinkPath := filepath.Join(tmpDirA, "symlink3") + symlinkPath1 := filepath.Join(tmpDirA, "symlink4") + linkTarget := filepath.Join(tmpDirA, "file1") + + if err = testCopyHelperFSym(t, symlinkPath, dstPath); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = fileContentsEqual(t, linkTarget, dstPath); err != nil { + t.Fatal(err) + } + os.Remove(dstPath) + if err = testCopyHelperFSym(t, symlinkPath1, dstPath); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = fileContentsEqual(t, linkTarget, dstPath); err != nil { + t.Fatal(err) + } +} + +// B. SRC specifies a file and DST (with trailing path separator) doesn't +// exist. This should cause an error because the copy operation cannot +// create a directory when copying a single file. +func TestCopyCaseB(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A with some sample files and directories. + createSampleDir(t, tmpDirA) + + srcPath := filepath.Join(tmpDirA, "file1") + dstDir := joinTrailingSep(tmpDirB, "testDir") + + var err error + + if err = testCopyHelper(t, srcPath, dstDir); err == nil { + t.Fatal("expected ErrDirNotExists error, but got nil instead") + } + + if err != ErrDirNotExists { + t.Fatalf("expected ErrDirNotExists error, but got %T: %s", err, err) + } + + symlinkPath := filepath.Join(tmpDirA, "symlink3") + + if err = testCopyHelperFSym(t, symlinkPath, dstDir); err == nil { + t.Fatal("expected ErrDirNotExists error, but got nil instead") + } + if err != ErrDirNotExists { + t.Fatalf("expected ErrDirNotExists error, but got %T: %s", err, err) + } + +} + +// C. SRC specifies a file and DST exists as a file. This should overwrite +// the file at DST with the contents of the source file. +func TestCopyCaseC(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + srcPath := filepath.Join(tmpDirA, "file1") + dstPath := filepath.Join(tmpDirB, "file2") + + var err error + + // Ensure they start out different. + if err = fileContentsEqual(t, srcPath, dstPath); err == nil { + t.Fatal("expected different file contents") + } + + if err = testCopyHelper(t, srcPath, dstPath); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = fileContentsEqual(t, srcPath, dstPath); err != nil { + t.Fatal(err) + } +} + +// C. Symbol link following version: +// SRC specifies a file and DST exists as a file. This should overwrite +// the file at DST with the contents of the source file. +func TestCopyCaseCFSym(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + symlinkPathBad := filepath.Join(tmpDirA, "symlink1") + symlinkPath := filepath.Join(tmpDirA, "symlink3") + linkTarget := filepath.Join(tmpDirA, "file1") + dstPath := filepath.Join(tmpDirB, "file2") + + var err error + + // first to test broken link + if err = testCopyHelperFSym(t, symlinkPathBad, dstPath); err == nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + // test symbol link -> symbol link -> target + // Ensure they start out different. + if err = fileContentsEqual(t, linkTarget, dstPath); err == nil { + t.Fatal("expected different file contents") + } + + if err = testCopyHelperFSym(t, symlinkPath, dstPath); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = fileContentsEqual(t, linkTarget, dstPath); err != nil { + t.Fatal(err) + } +} + +// D. SRC specifies a file and DST exists as a directory. This should place +// a copy of the source file inside it using the basename from SRC. Ensure +// this works whether DST has a trailing path separator or not. +func TestCopyCaseD(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + srcPath := filepath.Join(tmpDirA, "file1") + dstDir := filepath.Join(tmpDirB, "dir1") + dstPath := filepath.Join(dstDir, "file1") + + var err error + + // Ensure that dstPath doesn't exist. + if _, err = os.Stat(dstPath); !os.IsNotExist(err) { + t.Fatalf("did not expect dstPath %q to exist", dstPath) + } + + if err = testCopyHelper(t, srcPath, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = fileContentsEqual(t, srcPath, dstPath); err != nil { + t.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err = os.RemoveAll(dstDir); err != nil { + t.Fatalf("unable to remove dstDir: %s", err) + } + + if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + t.Fatalf("unable to make dstDir: %s", err) + } + + dstDir = joinTrailingSep(tmpDirB, "dir1") + + if err = testCopyHelper(t, srcPath, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = fileContentsEqual(t, srcPath, dstPath); err != nil { + t.Fatal(err) + } +} + +// D. Symbol link following version: +// SRC specifies a file and DST exists as a directory. This should place +// a copy of the source file inside it using the basename from SRC. Ensure +// this works whether DST has a trailing path separator or not. +func TestCopyCaseDFSym(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + srcPath := filepath.Join(tmpDirA, "symlink4") + linkTarget := filepath.Join(tmpDirA, "file1") + dstDir := filepath.Join(tmpDirB, "dir1") + dstPath := filepath.Join(dstDir, "symlink4") + + var err error + + // Ensure that dstPath doesn't exist. + if _, err = os.Stat(dstPath); !os.IsNotExist(err) { + t.Fatalf("did not expect dstPath %q to exist", dstPath) + } + + if err = testCopyHelperFSym(t, srcPath, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = fileContentsEqual(t, linkTarget, dstPath); err != nil { + t.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err = os.RemoveAll(dstDir); err != nil { + t.Fatalf("unable to remove dstDir: %s", err) + } + + if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + t.Fatalf("unable to make dstDir: %s", err) + } + + dstDir = joinTrailingSep(tmpDirB, "dir1") + + if err = testCopyHelperFSym(t, srcPath, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = fileContentsEqual(t, linkTarget, dstPath); err != nil { + t.Fatal(err) + } +} + +// E. SRC specifies a directory and DST does not exist. This should create a +// directory at DST and copy the contents of the SRC directory into the DST +// directory. Ensure this works whether DST has a trailing path separator or +// not. +func TestCopyCaseE(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A with some sample files and directories. + createSampleDir(t, tmpDirA) + + srcDir := filepath.Join(tmpDirA, "dir1") + dstDir := filepath.Join(tmpDirB, "testDir") + + var err error + + if err = testCopyHelper(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, srcDir); err != nil { + t.Log("dir contents not equal") + logDirContents(t, tmpDirA) + logDirContents(t, tmpDirB) + t.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err = os.RemoveAll(dstDir); err != nil { + t.Fatalf("unable to remove dstDir: %s", err) + } + + dstDir = joinTrailingSep(tmpDirB, "testDir") + + if err = testCopyHelper(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, srcDir); err != nil { + t.Fatal(err) + } +} + +// E. Symbol link following version: +// SRC specifies a directory and DST does not exist. This should create a +// directory at DST and copy the contents of the SRC directory into the DST +// directory. Ensure this works whether DST has a trailing path separator or +// not. +func TestCopyCaseEFSym(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A with some sample files and directories. + createSampleDir(t, tmpDirA) + + srcDir := filepath.Join(tmpDirA, "dirSymlink") + linkTarget := filepath.Join(tmpDirA, "dir1") + dstDir := filepath.Join(tmpDirB, "testDir") + + var err error + + if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, linkTarget); err != nil { + t.Log("dir contents not equal") + logDirContents(t, tmpDirA) + logDirContents(t, tmpDirB) + t.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err = os.RemoveAll(dstDir); err != nil { + t.Fatalf("unable to remove dstDir: %s", err) + } + + dstDir = joinTrailingSep(tmpDirB, "testDir") + + if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, linkTarget); err != nil { + t.Fatal(err) + } +} + +// F. SRC specifies a directory and DST exists as a file. This should cause an +// error as it is not possible to overwrite a file with a directory. +func TestCopyCaseF(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + srcDir := filepath.Join(tmpDirA, "dir1") + symSrcDir := filepath.Join(tmpDirA, "dirSymlink") + dstFile := filepath.Join(tmpDirB, "file1") + + var err error + + if err = testCopyHelper(t, srcDir, dstFile); err == nil { + t.Fatal("expected ErrCannotCopyDir error, but got nil instead") + } + + if err != ErrCannotCopyDir { + t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err) + } + + // now test with symbol link + if err = testCopyHelperFSym(t, symSrcDir, dstFile); err == nil { + t.Fatal("expected ErrCannotCopyDir error, but got nil instead") + } + + if err != ErrCannotCopyDir { + t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err) + } +} + +// G. SRC specifies a directory and DST exists as a directory. This should copy +// the SRC directory and all its contents to the DST directory. Ensure this +// works whether DST has a trailing path separator or not. +func TestCopyCaseG(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + srcDir := filepath.Join(tmpDirA, "dir1") + dstDir := filepath.Join(tmpDirB, "dir2") + resultDir := filepath.Join(dstDir, "dir1") + + var err error + + if err = testCopyHelper(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, resultDir, srcDir); err != nil { + t.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err = os.RemoveAll(dstDir); err != nil { + t.Fatalf("unable to remove dstDir: %s", err) + } + + if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + t.Fatalf("unable to make dstDir: %s", err) + } + + dstDir = joinTrailingSep(tmpDirB, "dir2") + + if err = testCopyHelper(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, resultDir, srcDir); err != nil { + t.Fatal(err) + } +} + +// G. Symbol link version: +// SRC specifies a directory and DST exists as a directory. This should copy +// the SRC directory and all its contents to the DST directory. Ensure this +// works whether DST has a trailing path separator or not. +func TestCopyCaseGFSym(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + srcDir := filepath.Join(tmpDirA, "dirSymlink") + linkTarget := filepath.Join(tmpDirA, "dir1") + dstDir := filepath.Join(tmpDirB, "dir2") + resultDir := filepath.Join(dstDir, "dirSymlink") + + var err error + + if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, resultDir, linkTarget); err != nil { + t.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err = os.RemoveAll(dstDir); err != nil { + t.Fatalf("unable to remove dstDir: %s", err) + } + + if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + t.Fatalf("unable to make dstDir: %s", err) + } + + dstDir = joinTrailingSep(tmpDirB, "dir2") + + if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, resultDir, linkTarget); err != nil { + t.Fatal(err) + } +} + +// H. SRC specifies a directory's contents only and DST does not exist. This +// should create a directory at DST and copy the contents of the SRC +// directory (but not the directory itself) into the DST directory. Ensure +// this works whether DST has a trailing path separator or not. +func TestCopyCaseH(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A with some sample files and directories. + createSampleDir(t, tmpDirA) + + srcDir := joinTrailingSep(tmpDirA, "dir1") + "." + dstDir := filepath.Join(tmpDirB, "testDir") + + var err error + + if err = testCopyHelper(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, srcDir); err != nil { + t.Log("dir contents not equal") + logDirContents(t, tmpDirA) + logDirContents(t, tmpDirB) + t.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err = os.RemoveAll(dstDir); err != nil { + t.Fatalf("unable to remove dstDir: %s", err) + } + + dstDir = joinTrailingSep(tmpDirB, "testDir") + + if err = testCopyHelper(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, srcDir); err != nil { + t.Log("dir contents not equal") + logDirContents(t, tmpDirA) + logDirContents(t, tmpDirB) + t.Fatal(err) + } +} + +// H. Symbol link following version: +// SRC specifies a directory's contents only and DST does not exist. This +// should create a directory at DST and copy the contents of the SRC +// directory (but not the directory itself) into the DST directory. Ensure +// this works whether DST has a trailing path separator or not. +func TestCopyCaseHFSym(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A with some sample files and directories. + createSampleDir(t, tmpDirA) + + srcDir := joinTrailingSep(tmpDirA, "dirSymlink") + "." + linkTarget := filepath.Join(tmpDirA, "dir1") + dstDir := filepath.Join(tmpDirB, "testDir") + + var err error + + if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, linkTarget); err != nil { + t.Log("dir contents not equal") + logDirContents(t, tmpDirA) + logDirContents(t, tmpDirB) + t.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err = os.RemoveAll(dstDir); err != nil { + t.Fatalf("unable to remove dstDir: %s", err) + } + + dstDir = joinTrailingSep(tmpDirB, "testDir") + + if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, linkTarget); err != nil { + t.Log("dir contents not equal") + logDirContents(t, tmpDirA) + logDirContents(t, tmpDirB) + t.Fatal(err) + } +} + +// I. SRC specifies a directory's contents only and DST exists as a file. This +// should cause an error as it is not possible to overwrite a file with a +// directory. +func TestCopyCaseI(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + srcDir := joinTrailingSep(tmpDirA, "dir1") + "." + symSrcDir := filepath.Join(tmpDirB, "dirSymlink") + dstFile := filepath.Join(tmpDirB, "file1") + + var err error + + if err = testCopyHelper(t, srcDir, dstFile); err == nil { + t.Fatal("expected ErrCannotCopyDir error, but got nil instead") + } + + if err != ErrCannotCopyDir { + t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err) + } + + // now try with symbol link of dir + if err = testCopyHelperFSym(t, symSrcDir, dstFile); err == nil { + t.Fatal("expected ErrCannotCopyDir error, but got nil instead") + } + + if err != ErrCannotCopyDir { + t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err) + } +} + +// J. SRC specifies a directory's contents only and DST exists as a directory. +// This should copy the contents of the SRC directory (but not the directory +// itself) into the DST directory. Ensure this works whether DST has a +// trailing path separator or not. +func TestCopyCaseJ(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + srcDir := joinTrailingSep(tmpDirA, "dir1") + "." + dstDir := filepath.Join(tmpDirB, "dir5") + + var err error + + // first to create an empty dir + if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + t.Fatalf("unable to make dstDir: %s", err) + } + + if err = testCopyHelper(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, srcDir); err != nil { + t.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err = os.RemoveAll(dstDir); err != nil { + t.Fatalf("unable to remove dstDir: %s", err) + } + + if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + t.Fatalf("unable to make dstDir: %s", err) + } + + dstDir = joinTrailingSep(tmpDirB, "dir5") + + if err = testCopyHelper(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, srcDir); err != nil { + t.Fatal(err) + } +} + +// J. Symbol link following version: +// SRC specifies a directory's contents only and DST exists as a directory. +// This should copy the contents of the SRC directory (but not the directory +// itself) into the DST directory. Ensure this works whether DST has a +// trailing path separator or not. +func TestCopyCaseJFSym(t *testing.T) { + tmpDirA, tmpDirB := getTestTempDirs(t) + defer removeAllPaths(tmpDirA, tmpDirB) + + // Load A and B with some sample files and directories. + createSampleDir(t, tmpDirA) + createSampleDir(t, tmpDirB) + + srcDir := joinTrailingSep(tmpDirA, "dirSymlink") + "." + linkTarget := filepath.Join(tmpDirA, "dir1") + dstDir := filepath.Join(tmpDirB, "dir5") + + var err error + + // first to create an empty dir + if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + t.Fatalf("unable to make dstDir: %s", err) + } + + if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, linkTarget); err != nil { + t.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err = os.RemoveAll(dstDir); err != nil { + t.Fatalf("unable to remove dstDir: %s", err) + } + + if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + t.Fatalf("unable to make dstDir: %s", err) + } + + dstDir = joinTrailingSep(tmpDirB, "dir5") + + if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil { + t.Fatalf("unexpected error %T: %s", err, err) + } + + if err = dirContentsEqual(t, dstDir, linkTarget); err != nil { + t.Fatal(err) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_unix.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_unix.go new file mode 100644 index 000000000..09d58bcbf --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_unix.go @@ -0,0 +1,14 @@ +// +build !windows + +package system + +import ( + "time" +) + +//setCTime will set the create time on a file. On Unix, the create +//time is updated as a side effect of setting the modified time, so +//no action is required. +func setCTime(path string, ctime time.Time) error { + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_windows.go new file mode 100644 index 000000000..294586846 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_windows.go @@ -0,0 +1,27 @@ +// +build windows + +package system + +import ( + "syscall" + "time" +) + +//setCTime will set the create time on a file. On Windows, this requires +//calling SetFileTime and explicitly including the create time. +func setCTime(path string, ctime time.Time) error { + ctimespec := syscall.NsecToTimespec(ctime.UnixNano()) + pathp, e := syscall.UTF16PtrFromString(path) + if e != nil { + return e + } + h, e := syscall.CreateFile(pathp, + syscall.FILE_WRITE_ATTRIBUTES, syscall.FILE_SHARE_WRITE, nil, + syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) + if e != nil { + return e + } + defer syscall.Close(h) + c := syscall.NsecToFiletime(syscall.TimespecToNsec(ctimespec)) + return syscall.SetFileTime(h, &c, nil, nil) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_openbsd.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_openbsd.go new file mode 100644 index 000000000..3c3b71fb2 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_openbsd.go @@ -0,0 +1,15 @@ +package system + +import ( + "syscall" +) + +// fromStatT creates a system.StatT type from a syscall.Stat_t type +func fromStatT(s *syscall.Stat_t) (*StatT, error) { + return &StatT{size: s.Size, + mode: uint32(s.Mode), + uid: s.Uid, + gid: s.Gid, + rdev: uint64(s.Rdev), + mtim: s.Mtim}, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_unsupported.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_unsupported.go index c6075d4ff..f53e9de4d 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_unsupported.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_unsupported.go @@ -1,4 +1,4 @@ -// +build !linux,!windows,!freebsd,!solaris +// +build !linux,!windows,!freebsd,!solaris,!openbsd package system diff --git a/vendor/github.com/fsouza/go-dockerclient/external/golang.org/x/net/context/context.go b/vendor/github.com/fsouza/go-dockerclient/external/golang.org/x/net/context/context.go index 0da42b1df..dd138571f 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/golang.org/x/net/context/context.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/golang.org/x/net/context/context.go @@ -210,13 +210,13 @@ type CancelFunc func() // call cancel as soon as the operations running in this Context complete. func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { c := newCancelCtx(parent) - propagateCancel(parent, &c) - return &c, func() { c.cancel(true, Canceled) } + propagateCancel(parent, c) + return c, func() { c.cancel(true, Canceled) } } // newCancelCtx returns an initialized cancelCtx. -func newCancelCtx(parent Context) cancelCtx { - return cancelCtx{ +func newCancelCtx(parent Context) *cancelCtx { + return &cancelCtx{ Context: parent, done: make(chan struct{}), } @@ -259,7 +259,7 @@ func parentCancelCtx(parent Context) (*cancelCtx, bool) { case *cancelCtx: return c, true case *timerCtx: - return &c.cancelCtx, true + return c.cancelCtx, true case *valueCtx: parent = c.Context default: @@ -377,7 +377,7 @@ func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { // implement Done and Err. It implements cancel by stopping its timer then // delegating to cancelCtx.cancel. type timerCtx struct { - cancelCtx + *cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time diff --git a/vendor/github.com/fsouza/go-dockerclient/image.go b/vendor/github.com/fsouza/go-dockerclient/image.go index 47da77dbe..ca4506354 100644 --- a/vendor/github.com/fsouza/go-dockerclient/image.go +++ b/vendor/github.com/fsouza/go-dockerclient/image.go @@ -32,6 +32,7 @@ type APIImages struct { // Image is the type representing a docker image and its various properties type Image struct { ID string `json:"Id" yaml:"Id"` + RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty"` Parent string `json:"Parent,omitempty" yaml:"Parent,omitempty"` Comment string `json:"Comment,omitempty" yaml:"Comment,omitempty"` Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty"` @@ -421,6 +422,17 @@ type BuildImageOptions struct { AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header ContextDir string `qs:"-"` Ulimits []ULimit `qs:"-"` + BuildArgs []BuildArg `qs:"-"` +} + +// BuildArg represents arguments that can be passed to the image when building +// it from a Dockerfile. +// +// For more details about the Docker building process, see +// http://goo.gl/tlPXPu. +type BuildArg struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty"` + Value string `json:"Value,omitempty" yaml:"Value,omitempty"` } // BuildImage builds an image from a tarball's url or a Dockerfile in the input @@ -463,6 +475,18 @@ func (c *Client) BuildImage(opts BuildImageOptions) error { } } + if len(opts.BuildArgs) > 0 { + v := make(map[string]string) + for _, arg := range opts.BuildArgs { + v[arg.Name] = arg.Value + } + if b, err := json.Marshal(v); err == nil { + item := url.Values(map[string][]string{}) + item.Add("buildargs", string(b)) + qs = fmt.Sprintf("%s&%s", qs, item.Encode()) + } + } + return c.stream("POST", fmt.Sprintf("/build?%s", qs), streamOptions{ setRawTerminal: true, rawJSONStream: opts.RawJSONStream, diff --git a/vendor/github.com/fsouza/go-dockerclient/image_test.go b/vendor/github.com/fsouza/go-dockerclient/image_test.go index c7544a643..029df6c5c 100644 --- a/vendor/github.com/fsouza/go-dockerclient/image_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/image_test.go @@ -685,6 +685,7 @@ func TestBuildImageParameters(t *testing.T) { CPUPeriod: 100000, CPUSetCPUs: "0-3", Ulimits: []ULimit{{Name: "nofile", Soft: 100, Hard: 200}}, + BuildArgs: []BuildArg{{Name: "SOME_VAR", Value: "some_value"}}, InputStream: &buf, OutputStream: &buf, } @@ -706,7 +707,8 @@ func TestBuildImageParameters(t *testing.T) { "cpuquota": {"7500"}, "cpuperiod": {"100000"}, "cpusetcpus": {"0-3"}, - "ulimits": {"[{\"Name\":\"nofile\",\"Soft\":100,\"Hard\":200}]"}, + "ulimits": {`[{"Name":"nofile","Soft":100,"Hard":200}]`}, + "buildargs": {`{"SOME_VAR":"some_value"}`}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { diff --git a/vendor/github.com/fsouza/go-dockerclient/integration_test.go b/vendor/github.com/fsouza/go-dockerclient/integration_test.go index f5aeea27a..f13c2a7e5 100644 --- a/vendor/github.com/fsouza/go-dockerclient/integration_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/integration_test.go @@ -46,7 +46,7 @@ func TestIntegrationPullCreateStartLogs(t *testing.T) { t.Error(err) } if status != 0 { - t.Error("WaitContainer(%q): wrong status. Want 0. Got %d", container.ID, status) + t.Errorf("WaitContainer(%q): wrong status. Want 0. Got %d", container.ID, status) } var stdout, stderr bytes.Buffer logsOpts := LogsOptions{ diff --git a/vendor/github.com/fsouza/go-dockerclient/misc.go b/vendor/github.com/fsouza/go-dockerclient/misc.go index 34c96531a..ce9e9750b 100644 --- a/vendor/github.com/fsouza/go-dockerclient/misc.go +++ b/vendor/github.com/fsouza/go-dockerclient/misc.go @@ -4,7 +4,10 @@ package docker -import "strings" +import ( + "encoding/json" + "strings" +) // Version returns version information about the docker server. // @@ -22,17 +25,81 @@ func (c *Client) Version() (*Env, error) { return &env, nil } +// DockerInfo contains information about the Docker server +// +// See https://goo.gl/bHUoz9 for more details. +type DockerInfo struct { + ID string + Containers int + ContainersRunning int + ContainersPaused int + ContainersStopped int + Images int + Driver string + DriverStatus [][2]string + SystemStatus [][2]string + Plugins PluginsInfo + MemoryLimit bool + SwapLimit bool + KernelMemory bool + CPUCfsPeriod bool `json:"CpuCfsPeriod"` + CPUCfsQuota bool `json:"CpuCfsQuota"` + CPUShares bool + CPUSet bool + IPv4Forwarding bool + BridgeNfIptables bool + BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` + Debug bool + NFd int + OomKillDisable bool + NGoroutines int + SystemTime string + ExecutionDriver string + LoggingDriver string + CgroupDriver string + NEventsListener int + KernelVersion string + OperatingSystem string + OSType string + Architecture string + IndexServerAddress string + NCPU int + MemTotal int64 + DockerRootDir string + HTTPProxy string `json:"HttpProxy"` + HTTPSProxy string `json:"HttpsProxy"` + NoProxy string + Name string + Labels []string + ExperimentalBuild bool + ServerVersion string + ClusterStore string + ClusterAdvertise string +} + +// PluginsInfo is a struct with the plugins registered with the docker daemon +// +// for more information, see: https://goo.gl/bHUoz9 +type PluginsInfo struct { + // List of Volume plugins registered + Volume []string + // List of Network plugins registered + Network []string + // List of Authorization plugins registered + Authorization []string +} + // Info returns system-wide information about the Docker server. // // See https://goo.gl/ElTHi2 for more details. -func (c *Client) Info() (*Env, error) { +func (c *Client) Info() (*DockerInfo, error) { resp, err := c.do("GET", "/info", doOptions{}) if err != nil { return nil, err } defer resp.Body.Close() - var info Env - if err := info.Decode(resp.Body); err != nil { + var info DockerInfo + if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { return nil, err } return &info, nil diff --git a/vendor/github.com/fsouza/go-dockerclient/misc_test.go b/vendor/github.com/fsouza/go-dockerclient/misc_test.go index ceaf076ed..824e5a4eb 100644 --- a/vendor/github.com/fsouza/go-dockerclient/misc_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/misc_test.go @@ -8,7 +8,6 @@ import ( "net/http" "net/url" "reflect" - "sort" "testing" ) @@ -71,32 +70,29 @@ func TestInfo(t *testing.T) { body := `{ "Containers":11, "Images":16, - "Debug":0, + "Debug":false, "NFd":11, "NGoroutines":21, - "MemoryLimit":1, - "SwapLimit":0 + "MemoryLimit":true, + "SwapLimit":false }` fakeRT := FakeRoundTripper{message: body, status: http.StatusOK} client := newTestClient(&fakeRT) - expected := Env{} - expected.SetInt("Containers", 11) - expected.SetInt("Images", 16) - expected.SetBool("Debug", false) - expected.SetInt("NFd", 11) - expected.SetInt("NGoroutines", 21) - expected.SetBool("MemoryLimit", true) - expected.SetBool("SwapLimit", false) + expected := &DockerInfo{ + Containers: 11, + Images: 16, + Debug: false, + NFd: 11, + NGoroutines: 21, + MemoryLimit: true, + SwapLimit: false, + } info, err := client.Info() if err != nil { t.Fatal(err) } - infoSlice := []string(*info) - expectedSlice := []string(expected) - sort.Strings(infoSlice) - sort.Strings(expectedSlice) - if !reflect.DeepEqual(expectedSlice, infoSlice) { - t.Errorf("Info(): Wrong result.\nWant %#v.\nGot %#v.", expected, *info) + if !reflect.DeepEqual(expected, info) { + t.Errorf("Info(): Wrong result.\nWant %#v.\nGot %#v.", expected, info) } req := fakeRT.requests[0] if req.Method != "GET" { diff --git a/vendor/github.com/fsouza/go-dockerclient/network.go b/vendor/github.com/fsouza/go-dockerclient/network.go index 30d54230a..b72e91a07 100644 --- a/vendor/github.com/fsouza/go-dockerclient/network.go +++ b/vendor/github.com/fsouza/go-dockerclient/network.go @@ -5,6 +5,7 @@ package docker import ( + "bytes" "encoding/json" "errors" "fmt" @@ -26,6 +27,7 @@ type Network struct { IPAM IPAMOptions Containers map[string]Endpoint Options map[string]string + Internal bool } // Endpoint contains network resources allocated and used for a container in a network @@ -55,6 +57,31 @@ func (c *Client) ListNetworks() ([]Network, error) { return networks, nil } +// NetworkFilterOpts is an aggregation of key=value that Docker +// uses to filter networks +type NetworkFilterOpts map[string]map[string]bool + +// FilteredListNetworks returns all networks with the filters applied +// +// See goo.gl/zd2mx4 for more details. +func (c *Client) FilteredListNetworks(opts NetworkFilterOpts) ([]Network, error) { + params := bytes.NewBuffer(nil) + if err := json.NewEncoder(params).Encode(&opts); err != nil { + return nil, err + } + path := "/networks?filters=" + params.String() + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var networks []Network + if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil { + return nil, err + } + return networks, nil +} + // NetworkInfo returns information about a network by its ID. // // See https://goo.gl/6GugX3 for more details. @@ -158,14 +185,40 @@ func (c *Client) RemoveNetwork(id string) error { return nil } -// NetworkConnectionOptions specify parameters to the ConnectNetwork and DisconnectNetwork function. +// NetworkConnectionOptions specify parameters to the ConnectNetwork and +// DisconnectNetwork function. // -// See https://goo.gl/6GugX3 for more details. +// See https://goo.gl/RV7BJU for more details. type NetworkConnectionOptions struct { Container string + + // EndpointConfig is only applicable to the ConnectNetwork call + EndpointConfig *EndpointConfig `json:"EndpointConfig,omitempty"` + + // Force is only applicable to the DisconnectNetwork call + Force bool } -// ConnectNetwork adds a container to a network or returns an error in case of failure. +// EndpointConfig stores network endpoint details +// +// See https://goo.gl/RV7BJU for more details. +type EndpointConfig struct { + IPAMConfig *EndpointIPAMConfig + Links []string + Aliases []string +} + +// EndpointIPAMConfig represents IPAM configurations for an +// endpoint +// +// See https://goo.gl/RV7BJU for more details. +type EndpointIPAMConfig struct { + IPv4Address string `json:",omitempty"` + IPv6Address string `json:",omitempty"` +} + +// ConnectNetwork adds a container to a network or returns an error in case of +// failure. // // See https://goo.gl/6GugX3 for more details. func (c *Client) ConnectNetwork(id string, opts NetworkConnectionOptions) error { @@ -180,7 +233,8 @@ func (c *Client) ConnectNetwork(id string, opts NetworkConnectionOptions) error return nil } -// DisconnectNetwork removes a container from a network or returns an error in case of failure. +// DisconnectNetwork removes a container from a network or returns an error in +// case of failure. // // See https://goo.gl/6GugX3 for more details. func (c *Client) DisconnectNetwork(id string, opts NetworkConnectionOptions) error { @@ -204,7 +258,8 @@ func (err *NoSuchNetwork) Error() string { return fmt.Sprintf("No such network: %s", err.ID) } -// NoSuchNetwork is the error returned when a given network or container does not exist. +// NoSuchNetworkOrContainer is the error returned when a given network or +// container does not exist. type NoSuchNetworkOrContainer struct { NetworkID string ContainerID string diff --git a/vendor/github.com/fsouza/go-dockerclient/network_test.go b/vendor/github.com/fsouza/go-dockerclient/network_test.go index 2bff70fe4..dbf628a90 100644 --- a/vendor/github.com/fsouza/go-dockerclient/network_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/network_test.go @@ -5,6 +5,7 @@ package docker import ( + "bytes" "encoding/json" "net/http" "net/url" @@ -42,6 +43,39 @@ func TestListNetworks(t *testing.T) { } } +func TestFilteredListNetworks(t *testing.T) { + jsonNetworks := `[ + { + "ID": "9fb1e39c", + "Name": "foo", + "Type": "bridge", + "Endpoints":[{"ID": "c080be979dda", "Name": "lllll2222", "Network": "9fb1e39c"}] + } +]` + var expected []Network + err := json.Unmarshal([]byte(jsonNetworks), &expected) + if err != nil { + t.Fatal(err) + } + wantQuery := "filters={\"name\":{\"blah\":true}}\n" + fakeRT := &FakeRoundTripper{message: jsonNetworks, status: http.StatusOK} + client := newTestClient(fakeRT) + opts := NetworkFilterOpts{ + "name": map[string]bool{"blah": true}, + } + containers, err := client.FilteredListNetworks(opts) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(containers, expected) { + t.Errorf("ListNetworks: Expected %#v. Got %#v.", expected, containers) + } + query := fakeRT.requests[0].URL.RawQuery + if query != wantQuery { + t.Errorf("FilteredListNetworks: Expected query: %q, got: %q", wantQuery, query) + } +} + func TestNetworkInfo(t *testing.T) { jsonNetwork := `{ "ID": "8dfafdbc3a40", @@ -118,7 +152,7 @@ func TestNetworkConnect(t *testing.T) { id := "8dfafdbc3a40" fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) - opts := NetworkConnectionOptions{"foobar"} + opts := NetworkConnectionOptions{Container: "foobar"} err := client.ConnectNetwork(id, opts) if err != nil { t.Fatal(err) @@ -134,9 +168,46 @@ func TestNetworkConnect(t *testing.T) { } } +func TestNetworkConnectWithEndpoint(t *testing.T) { + wantJSON := `{"Container":"foobar","EndpointConfig":{"IPAMConfig":{"IPv4Address":"8.8.8.8"},"Links":null,"Aliases":null},"Force":false}` + var wantObj NetworkConnectionOptions + json.NewDecoder(bytes.NewBuffer([]byte(wantJSON))).Decode(&wantObj) + id := "8dfafdbc3a40" + fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} + client := newTestClient(fakeRT) + opts := NetworkConnectionOptions{ + Container: "foobar", + EndpointConfig: &EndpointConfig{ + IPAMConfig: &EndpointIPAMConfig{ + IPv4Address: "8.8.8.8", + }, + }, + } + err := client.ConnectNetwork(id, opts) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + expectedMethod := "POST" + if req.Method != expectedMethod { + t.Errorf("ConnectNetwork(%q): Wrong HTTP method. Want %s. Got %s.", id, expectedMethod, req.Method) + } + u, _ := url.Parse(client.getURL("/networks/" + id + "/connect")) + if req.URL.Path != u.Path { + t.Errorf("ConnectNetwork(%q): Wrong request path. Want %q. Got %q.", id, u.Path, req.URL.Path) + } + var in NetworkConnectionOptions + if err := json.NewDecoder(req.Body).Decode(&in); err != nil { + t.Errorf("ConnectNetwork: error parsing JSON data sent: %q", err) + } + if !reflect.DeepEqual(in, wantObj) { + t.Errorf("ConnectNetwork: wanted %#v send, got: %#v", wantObj, in) + } +} + func TestNetworkConnectNotFound(t *testing.T) { client := newTestClient(&FakeRoundTripper{message: "no such network container", status: http.StatusNotFound}) - opts := NetworkConnectionOptions{"foobar"} + opts := NetworkConnectionOptions{Container: "foobar"} err := client.ConnectNetwork("8dfafdbc3a40", opts) if serr, ok := err.(*NoSuchNetworkOrContainer); !ok { t.Errorf("ConnectNetwork: wrong error type: %s.", serr) @@ -147,7 +218,7 @@ func TestNetworkDisconnect(t *testing.T) { id := "8dfafdbc3a40" fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) - opts := NetworkConnectionOptions{"foobar"} + opts := NetworkConnectionOptions{Container: "foobar"} err := client.DisconnectNetwork(id, opts) if err != nil { t.Fatal(err) @@ -165,7 +236,7 @@ func TestNetworkDisconnect(t *testing.T) { func TestNetworkDisconnectNotFound(t *testing.T) { client := newTestClient(&FakeRoundTripper{message: "no such network container", status: http.StatusNotFound}) - opts := NetworkConnectionOptions{"foobar"} + opts := NetworkConnectionOptions{Container: "foobar"} err := client.DisconnectNetwork("8dfafdbc3a40", opts) if serr, ok := err.(*NoSuchNetworkOrContainer); !ok { t.Errorf("DisconnectNetwork: wrong error type: %s.", serr) diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/server.go b/vendor/github.com/fsouza/go-dockerclient/testing/server.go index b16e71367..c8b086dae 100644 --- a/vendor/github.com/fsouza/go-dockerclient/testing/server.go +++ b/vendor/github.com/fsouza/go-dockerclient/testing/server.go @@ -144,6 +144,7 @@ func (s *DockerServer) buildMuxer() { s.mux.Path("/volumes/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createVolume)) s.mux.Path("/volumes/{name:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectVolume)) s.mux.Path("/volumes/{name:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeVolume)) + s.mux.Path("/info").Methods("GET").HandlerFunc(s.handlerWrapper(s.infoDocker)) } // SetHook changes the hook function used by the server. @@ -743,10 +744,9 @@ func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusNotFound) return } - var config *docker.Config + config := new(docker.Config) runConfig := r.URL.Query().Get("run") if runConfig != "" { - config = new(docker.Config) err = json.Unmarshal([]byte(runConfig), config) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -828,7 +828,8 @@ func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) { fromImageName := r.URL.Query().Get("fromImage") tag := r.URL.Query().Get("tag") image := docker.Image{ - ID: s.generateID(), + ID: s.generateID(), + Config: &docker.Config{}, } s.iMut.Lock() s.images = append(s.images, image) @@ -1244,3 +1245,86 @@ func (s *DockerServer) removeVolume(w http.ResponseWriter, r *http.Request) { s.volStore[vol.volume.Name] = nil w.WriteHeader(http.StatusNoContent) } + +func (s *DockerServer) infoDocker(w http.ResponseWriter, r *http.Request) { + s.cMut.RLock() + defer s.cMut.RUnlock() + s.iMut.RLock() + defer s.iMut.RUnlock() + var running, stopped, paused int + for _, c := range s.containers { + if c.State.Running { + running++ + } else { + stopped++ + } + if c.State.Paused { + paused++ + } + } + envs := map[string]interface{}{ + "ID": "AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB", + "Containers": len(s.containers), + "ContainersRunning": running, + "ContainersPaused": paused, + "ContainersStopped": stopped, + "Images": len(s.images), + "Driver": "aufs", + "DriverStatus": [][]string{}, + "SystemStatus": nil, + "Plugins": map[string]interface{}{ + "Volume": []string{ + "local", + }, + "Network": []string{ + "bridge", + "null", + "host", + }, + "Authorization": nil, + }, + "MemoryLimit": true, + "SwapLimit": false, + "CpuCfsPeriod": true, + "CpuCfsQuota": true, + "CPUShares": true, + "CPUSet": true, + "IPv4Forwarding": true, + "BridgeNfIptables": true, + "BridgeNfIp6tables": true, + "Debug": false, + "NFd": 79, + "OomKillDisable": true, + "NGoroutines": 101, + "SystemTime": "2016-02-25T18:13:10.25870078Z", + "ExecutionDriver": "native-0.2", + "LoggingDriver": "json-file", + "NEventsListener": 0, + "KernelVersion": "3.13.0-77-generic", + "OperatingSystem": "Ubuntu 14.04.3 LTS", + "OSType": "linux", + "Architecture": "x86_64", + "IndexServerAddress": "https://index.docker.io/v1/", + "RegistryConfig": map[string]interface{}{ + "InsecureRegistryCIDRs": []string{}, + "IndexConfigs": map[string]interface{}{}, + "Mirrors": nil, + }, + "InitSha1": "e2042dbb0fcf49bb9da199186d9a5063cda92a01", + "InitPath": "/usr/lib/docker/dockerinit", + "NCPU": 1, + "MemTotal": 2099204096, + "DockerRootDir": "/var/lib/docker", + "HttpProxy": "", + "HttpsProxy": "", + "NoProxy": "", + "Name": "vagrant-ubuntu-trusty-64", + "Labels": nil, + "ExperimentalBuild": false, + "ServerVersion": "1.10.1", + "ClusterStore": "", + "ClusterAdvertise": "", + } + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(envs) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/server_test.go b/vendor/github.com/fsouza/go-dockerclient/testing/server_test.go index ddadcaab5..b1298045b 100644 --- a/vendor/github.com/fsouza/go-dockerclient/testing/server_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/testing/server_test.go @@ -381,6 +381,9 @@ func TestCommitContainer(t *testing.T) { if got := recorder.Body.String(); got != expected { t.Errorf("CommitContainer: wrong response body. Want %q. Got %q.", expected, got) } + if server.images[0].Config == nil { + t.Error("CommitContainer: image Config should not be nil.") + } } func TestCommitContainerComplete(t *testing.T) { @@ -1059,6 +1062,9 @@ func TestPullImage(t *testing.T) { if _, ok := server.imgIDs["base"]; !ok { t.Error("PullImage: Repository should not be empty.") } + if server.images[0].Config == nil { + t.Error("PullImage: Image Config should not be nil.") + } } func TestPullImageWithTag(t *testing.T) { @@ -2101,3 +2107,26 @@ func TestUploadToContainerMissingContainer(t *testing.T) { t.Errorf("UploadToContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } + +func TestInfoDocker(t *testing.T) { + server, _ := NewServer("127.0.0.1:0", nil, nil) + addContainers(server, 1) + server.buildMuxer() + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("GET", "/info", nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Fatalf("InfoDocker: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + var infoData map[string]interface{} + err := json.Unmarshal(recorder.Body.Bytes(), &infoData) + if err != nil { + t.Fatal(err) + } + if infoData["Containers"].(float64) != 1.0 { + t.Fatalf("InfoDocker: wrong containers count. Want %f. Got %f.", 1.0, infoData["Containers"]) + } + if infoData["DockerRootDir"].(string) != "/var/lib/docker" { + t.Fatalf("InfoDocker: wrong docker root. Want /var/lib/docker. Got %s.", infoData["DockerRootDir"]) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/travis-scripts/install.bash b/vendor/github.com/fsouza/go-dockerclient/travis-scripts/install.bash new file mode 100644 index 000000000..9d1708fa9 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/travis-scripts/install.bash @@ -0,0 +1,17 @@ +#!/bin/bash -x + +# Copyright 2016 go-dockerclient authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +if [[ $TRAVIS_OS_NAME == "linux" ]]; then + sudo stop docker || true + sudo rm -rf /var/lib/docker + sudo rm -f `which docker` + + set -e + sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D + echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" | sudo tee /etc/apt/sources.list.d/docker.list + sudo apt-get update + sudo apt-get install docker-engine=${DOCKER_VERSION}-0~$(lsb_release -cs) -y --force-yes -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" +fi diff --git a/vendor/github.com/fsouza/go-dockerclient/travis-scripts/run-tests.bash b/vendor/github.com/fsouza/go-dockerclient/travis-scripts/run-tests.bash new file mode 100644 index 000000000..257919fd7 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/travis-scripts/run-tests.bash @@ -0,0 +1,15 @@ +#!/bin/bash -ex + +# Copyright 2016 go-dockerclient authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +if ! [[ $TRAVIS_GO_VERSION =~ ^1\.[34] ]]; then + make lint +fi + +make vet fmtcheck gotest + +if [[ $TRAVIS_OS_NAME == "linux" ]]; then + DOCKER_HOST=tcp://127.0.0.1:2375 make integration +fi diff --git a/vendor/manifest b/vendor/manifest index 99077b18e..c14d3958c 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -546,7 +546,7 @@ { "importpath": "github.com/fsouza/go-dockerclient", "repository": "https://github.com/fsouza/go-dockerclient", - "revision": "0099401a7342ad77e71ca9f9a57c5e72fb80f6b2", + "revision": "1b46a3bbaf08d19385915795bae730a318f3dd69", "branch": "master" }, {