From b77cd3f300dda5a152f20ae6e0388064f9cacf73 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 8 Dec 2015 11:03:42 +0000 Subject: [PATCH] Add pipe controls for Docker attach & exec. --- probe/docker/container.go | 9 +- probe/docker/container_test.go | 1 + probe/docker/controls.go | 170 +++- probe/docker/controls_test.go | 48 + probe/docker/registry.go | 7 +- probe/docker/registry_test.go | 19 + probe/docker/reporter.go | 10 + probe/docker/reporter_test.go | 12 + .../fsouza/go-dockerclient/.gitignore | 2 + .../fsouza/go-dockerclient/.travis.yml | 21 + .../github.com/fsouza/go-dockerclient/AUTHORS | 9 +- .../fsouza/go-dockerclient/Makefile | 9 + .../fsouza/go-dockerclient/README.markdown | 3 +- .../github.com/fsouza/go-dockerclient/auth.go | 2 +- .../fsouza/go-dockerclient/auth_test.go | 4 +- .../fsouza/go-dockerclient/client.go | 165 ++-- .../fsouza/go-dockerclient/client_test.go | 21 +- .../fsouza/go-dockerclient/container.go | 138 ++- .../fsouza/go-dockerclient/container_test.go | 264 ++++- .../github.com/fsouza/go-dockerclient/exec.go | 38 + .../github.com/Sirupsen/logrus/CHANGELOG.md | 49 +- .../github.com/Sirupsen/logrus/README.md | 37 +- .../github.com/Sirupsen/logrus/doc.go | 26 + .../Sirupsen/logrus/terminal_freebsd.go | 20 - .../Sirupsen/logrus/terminal_notwindows.go | 2 +- .../Sirupsen/logrus/terminal_openbsd.go | 7 - .../Sirupsen/logrus/text_formatter.go | 19 +- .../registry/api/errcode/errors.go | 259 +++++ .../registry/api/errcode/errors_test.go | 179 ++++ .../registry/api/errcode/handler.go | 44 + .../registry/api/errcode/register.go | 128 +++ .../github.com/docker/docker/errors/README.md | 58 ++ .../docker/docker/errors/builder.go | 93 ++ .../github.com/docker/docker/errors/daemon.go | 925 ++++++++++++++++++ .../github.com/docker/docker/errors/error.go | 6 + .../github.com/docker/docker/errors/image.go | 20 + .../github.com/docker/docker/errors/server.go | 36 + .../github.com/docker/docker/opts/envfile.go | 31 +- .../docker/docker/opts/envfile_test.go | 15 +- .../docker/docker/opts/hosts_unix.go | 1 + .../docker/docker/opts/hosts_windows.go | 5 +- .../github.com/docker/docker/opts/ip.go | 19 +- .../github.com/docker/docker/opts/ip_test.go | 12 +- .../github.com/docker/docker/opts/opts.go | 153 +-- .../docker/docker/opts/opts_test.go | 140 +-- .../github.com/docker/docker/opts/ulimit.go | 5 + .../docker/docker/pkg/archive/archive.go | 274 ++++-- .../docker/docker/pkg/archive/archive_test.go | 8 +- .../docker/docker/pkg/archive/archive_unix.go | 29 +- .../docker/pkg/archive/archive_windows.go | 24 +- .../pkg/archive/archive_windows_test.go | 22 + .../docker/docker/pkg/archive/changes.go | 54 +- .../docker/pkg/archive/changes_posix_test.go | 4 +- .../docker/docker/pkg/archive/changes_test.go | 33 +- .../docker/docker/pkg/archive/changes_unix.go | 15 +- .../docker/pkg/archive/changes_windows.go | 12 +- .../docker/docker/pkg/archive/copy.go | 259 +++-- .../docker/docker/pkg/archive/copy_test.go | 24 +- .../docker/docker/pkg/archive/diff.go | 94 +- .../docker/pkg/archive/testdata/broken.tar | Bin 0 -> 13824 bytes .../docker/docker/pkg/archive/utils_test.go | 2 +- .../docker/docker/pkg/archive/whiteouts.go | 23 + .../docker/docker/pkg/fileutils/fileutils.go | 12 - .../docker/pkg/fileutils/fileutils_unix.go | 22 + .../docker/pkg/fileutils/fileutils_windows.go | 7 + .../docker/docker/pkg/idtools/idtools.go | 195 ++++ .../docker/docker/pkg/idtools/idtools_unix.go | 60 ++ .../docker/pkg/idtools/idtools_unix_test.go | 243 +++++ .../docker/pkg/idtools/idtools_windows.go | 18 + .../docker/pkg/idtools/usergroupadd_linux.go | 155 +++ .../pkg/idtools/usergroupadd_unsupported.go | 12 + .../docker/docker/pkg/ioutils/bytespipe.go | 89 ++ .../docker/pkg/ioutils/bytespipe_test.go | 141 +++ .../docker/docker/pkg/ioutils/fmt.go | 8 + .../docker/docker/pkg/ioutils/readers.go | 140 +-- .../docker/docker/pkg/ioutils/readers_test.go | 18 +- .../docker/docker/pkg/ioutils/temp_unix.go | 10 + .../docker/docker/pkg/ioutils/temp_windows.go | 18 + .../docker/docker/pkg/longpath/longpath.go | 26 + .../docker/pkg/longpath/longpath_test.go | 22 + .../docker/docker/pkg/parsers/parsers.go | 116 ++- .../docker/docker/pkg/parsers/parsers_test.go | 171 +++- .../docker/docker/pkg/stdcopy/stdcopy.go | 59 +- .../docker/docker/pkg/stdcopy/stdcopy_test.go | 178 +++- .../docker/docker/pkg/system/chtimes.go | 31 + .../docker/docker/pkg/system/chtimes_test.go | 120 +++ .../docker/pkg/system/chtimes_unix_test.go | 121 +++ .../docker/pkg/system/chtimes_windows_test.go | 114 +++ .../docker/docker/pkg/system/errors.go | 1 + .../docker/pkg/system/events_windows.go | 16 +- .../docker/docker/pkg/system/filesys.go | 8 + .../docker/pkg/system/filesys_windows.go | 18 + .../docker/docker/pkg/system/lstat.go | 4 +- .../docker/pkg/system/lstat_unix_test.go | 30 + .../docker/docker/pkg/system/lstat_windows.go | 8 +- .../docker/docker/pkg/system/meminfo_linux.go | 5 - .../docker/pkg/system/meminfo_unix_test.go | 40 + .../docker/pkg/system/meminfo_unsupported.go | 1 + .../docker/docker/pkg/system/mknod.go | 6 +- .../docker/docker/pkg/system/mknod_windows.go | 2 + .../docker/docker/pkg/system/stat.go | 27 +- .../docker/docker/pkg/system/stat_freebsd.go | 6 +- .../docker/docker/pkg/system/stat_linux.go | 12 +- .../docker/pkg/system/stat_unix_test.go | 39 + .../docker/pkg/system/stat_unsupported.go | 6 +- .../docker/docker/pkg/system/stat_windows.go | 19 +- .../docker/docker/pkg/system/syscall_unix.go | 11 + .../docker/pkg/system/syscall_windows.go | 6 + .../docker/docker/pkg/system/umask.go | 2 + .../docker/docker/pkg/system/umask_windows.go | 1 + .../docker/docker/pkg/system/utimes_darwin.go | 5 +- .../docker/pkg/system/utimes_freebsd.go | 6 +- .../docker/docker/pkg/system/utimes_linux.go | 12 +- .../docker/pkg/system/utimes_unix_test.go | 68 ++ .../docker/pkg/system/utimes_unsupported.go | 5 +- .../docker/docker/pkg/system/xattrs_linux.go | 6 +- .../docker/pkg/system/xattrs_unsupported.go | 2 + .../github.com/docker/docker/volume/volume.go | 157 ++- .../docker/docker/volume/volume_test.go | 261 +++++ .../docker/docker/volume/volume_unix.go | 132 +++ .../docker/docker/volume/volume_windows.go | 181 ++++ .../external/github.com/gorilla/mux/mux.go | 9 +- .../github.com/hashicorp/go-cleanhttp/LICENSE | 363 +++++++ .../hashicorp/go-cleanhttp/README.md | 30 + .../hashicorp/go-cleanhttp/cleanhttp.go | 28 + .../runc/libcontainer/user/user.go | 39 +- .../runc/libcontainer/user/user_test.go | 36 + .../fsouza/go-dockerclient/image.go | 2 + .../fsouza/go-dockerclient/network.go | 64 +- .../fsouza/go-dockerclient/network_test.go | 4 +- .../testing/data/.dockerignore | 3 + .../fsouza/go-dockerclient/testing/server.go | 16 +- .../go-dockerclient/testing/server_test.go | 23 +- .../fsouza/go-dockerclient/volume.go | 2 +- .../fsouza/go-dockerclient/volume_test.go | 2 +- 135 files changed, 6933 insertions(+), 1015 deletions(-) create mode 100644 vendor/github.com/fsouza/go-dockerclient/.gitignore create mode 100644 vendor/github.com/fsouza/go-dockerclient/.travis.yml create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/doc.go delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_freebsd.go delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_openbsd.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/errors.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/errors_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/handler.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/register.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/README.md create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/builder.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/daemon.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/error.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/image.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/server.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/testdata/broken.tar create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/whiteouts.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils_unix.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils_windows.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_unix.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_unix_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_windows.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/bytespipe.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/bytespipe_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/temp_unix.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/temp_windows.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath/longpath.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath/longpath_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_unix_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_windows_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat_unix_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_unix_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_unix_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/syscall_unix.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/syscall_windows.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_unix_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_test.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_unix.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_windows.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/LICENSE create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/README.md create mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/cleanhttp.go create mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/.dockerignore diff --git a/probe/docker/container.go b/probe/docker/container.go index 19b8d4807..5823e3739 100644 --- a/probe/docker/container.go +++ b/probe/docker/container.go @@ -85,6 +85,7 @@ type Container interface { Hostname() string GetNode(string, []net.IP) report.Node State() string + HasTTY() bool StartGatheringStats() error StopGatheringStats() @@ -132,6 +133,10 @@ func (c *container) Hostname() string { c.container.Config.Domainname) } +func (c *container) HasTTY() bool { + return c.container.Config.Tty +} + func (c *container) State() string { if c.container.State.Paused { return StatePaused @@ -327,7 +332,9 @@ func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node { if c.container.State.Paused { result = result.WithControls(UnpauseContainer) } else if c.container.State.Running { - result = result.WithControls(RestartContainer, StopContainer, PauseContainer) + result = result.WithControls( + RestartContainer, StopContainer, PauseContainer, AttachContainer, ExecContainer, + ) } else { result = result.WithControls(StartContainer) } diff --git a/probe/docker/container_test.go b/probe/docker/container_test.go index ef74aa7b9..c83dbe8a6 100644 --- a/probe/docker/container_test.go +++ b/probe/docker/container_test.go @@ -86,6 +86,7 @@ func TestContainer(t *testing.T) { "docker_container_ips_with_scopes": report.MakeStringSet("scope;1.2.3.4"), }).WithControls( docker.RestartContainer, docker.StopContainer, docker.PauseContainer, + docker.AttachContainer, docker.ExecContainer, ).WithLatest( "docker_container_state", now, "running", ).WithMetrics(report.Metrics{ diff --git a/probe/docker/controls.go b/probe/docker/controls.go index 0db14c318..7d1270014 100644 --- a/probe/docker/controls.go +++ b/probe/docker/controls.go @@ -3,6 +3,8 @@ package docker import ( "log" + docker_client "github.com/fsouza/go-dockerclient" + "github.com/weaveworks/scope/probe/controls" "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/xfer" @@ -15,69 +17,145 @@ const ( RestartContainer = "docker_restart_container" PauseContainer = "docker_pause_container" UnpauseContainer = "docker_unpause_container" + AttachContainer = "docker_attach_container" + ExecContainer = "docker_exec_container" waitTime = 10 ) -func (r *registry) stopContainer(req xfer.Request) xfer.Response { - log.Printf("Stopping container %s", req.NodeID) - - _, containerID, ok := report.ParseContainerNodeID(req.NodeID) - if !ok { - return xfer.ResponseErrorf("Invalid ID: %s", req.NodeID) - } - +func (r *registry) stopContainer(containerID string, _ xfer.Request) xfer.Response { + log.Printf("Stopping container %s", containerID) return xfer.ResponseError(r.client.StopContainer(containerID, waitTime)) } -func (r *registry) startContainer(req xfer.Request) xfer.Response { - log.Printf("Starting container %s", req.NodeID) - - _, containerID, ok := report.ParseContainerNodeID(req.NodeID) - if !ok { - return xfer.ResponseErrorf("Invalid ID: %s", req.NodeID) - } - +func (r *registry) startContainer(containerID string, _ xfer.Request) xfer.Response { + log.Printf("Starting container %s", containerID) return xfer.ResponseError(r.client.StartContainer(containerID, nil)) } -func (r *registry) restartContainer(req xfer.Request) xfer.Response { - log.Printf("Restarting container %s", req.NodeID) - - _, containerID, ok := report.ParseContainerNodeID(req.NodeID) - if !ok { - return xfer.ResponseErrorf("Invalid ID: %s", req.NodeID) - } - +func (r *registry) restartContainer(containerID string, _ xfer.Request) xfer.Response { + log.Printf("Restarting container %s", containerID) return xfer.ResponseError(r.client.RestartContainer(containerID, waitTime)) } -func (r *registry) pauseContainer(req xfer.Request) xfer.Response { - log.Printf("Pausing container %s", req.NodeID) - - _, containerID, ok := report.ParseContainerNodeID(req.NodeID) - if !ok { - return xfer.ResponseErrorf("Invalid ID: %s", req.NodeID) - } - +func (r *registry) pauseContainer(containerID string, _ xfer.Request) xfer.Response { + log.Printf("Pausing container %s", containerID) return xfer.ResponseError(r.client.PauseContainer(containerID)) } -func (r *registry) unpauseContainer(req xfer.Request) xfer.Response { - log.Printf("Unpausing container %s", req.NodeID) - - _, containerID, ok := report.ParseContainerNodeID(req.NodeID) - if !ok { - return xfer.ResponseErrorf("Invalid ID: %s", req.NodeID) - } - +func (r *registry) unpauseContainer(containerID string, _ xfer.Request) xfer.Response { + log.Printf("Unpausing container %s", containerID) return xfer.ResponseError(r.client.UnpauseContainer(containerID)) } -func (r *registry) registerControls() { - controls.Register(StopContainer, r.stopContainer) - controls.Register(StartContainer, r.startContainer) - controls.Register(RestartContainer, r.restartContainer) - controls.Register(PauseContainer, r.pauseContainer) - controls.Register(UnpauseContainer, r.unpauseContainer) +func (r *registry) attachContainer(containerID string, req xfer.Request) xfer.Response { + c, ok := r.GetContainer(containerID) + if !ok { + return xfer.ResponseErrorf("Not found: %s", containerID) + } + + hasTTY := c.HasTTY() + id, pipe, err := controls.NewPipe(req.AppID) + if err != nil { + xfer.ResponseError(err) + } + local, _ := pipe.Ends() + cw, err := r.client.AttachToContainerNonBlocking(docker_client.AttachToContainerOptions{ + Container: containerID, + RawTerminal: hasTTY, + Stream: true, + Stdin: true, + Stdout: true, + Stderr: true, + InputStream: local, + OutputStream: local, + ErrorStream: local, + }) + if err != nil { + return xfer.ResponseError(err) + } + pipe.OnClose(func() { + if err := cw.Close(); err != nil { + log.Printf("Error closing attachment: %v", err) + return + } + log.Printf("Attachment to container %s closed.", containerID) + }) + go func() { + if err := cw.Wait(); err != nil { + log.Printf("Error waiting on exec: %v", err) + } + pipe.Close() + }() + return xfer.Response{ + Pipe: id, + RawTTY: hasTTY, + } +} + +func (r *registry) execContainer(containerID string, req xfer.Request) xfer.Response { + exec, err := r.client.CreateExec(docker_client.CreateExecOptions{ + AttachStdin: true, + AttachStdout: true, + AttachStderr: true, + Tty: true, + Cmd: []string{"/bin/sh"}, + Container: containerID, + }) + if err != nil { + xfer.ResponseError(err) + } + + id, pipe, err := controls.NewPipe(req.AppID) + if err != nil { + xfer.ResponseError(err) + } + local, _ := pipe.Ends() + cw, err := r.client.StartExecNonBlocking(exec.ID, docker_client.StartExecOptions{ + Tty: true, + RawTerminal: true, + InputStream: local, + OutputStream: local, + ErrorStream: local, + }) + if err != nil { + return xfer.ResponseError(err) + } + pipe.OnClose(func() { + if err := cw.Close(); err != nil { + log.Printf("Error closing exec: %v", err) + return + } + log.Printf("Exec on container %s closed.", containerID) + }) + go func() { + if err := cw.Wait(); err != nil { + log.Printf("Error waiting on exec: %v", err) + } + pipe.Close() + }() + return xfer.Response{ + Pipe: id, + RawTTY: true, + } +} + +func captureContainerID(f func(string, xfer.Request) xfer.Response) func(xfer.Request) xfer.Response { + return func(req xfer.Request) xfer.Response { + _, containerID, ok := report.ParseContainerNodeID(req.NodeID) + if !ok { + return xfer.ResponseErrorf("Invalid ID: %s", req.NodeID) + } + return f(containerID, req) + } +} + +func (r *registry) registerControls() { + controls.Register(StopContainer, captureContainerID(r.stopContainer)) + controls.Register(StartContainer, captureContainerID(r.startContainer)) + controls.Register(RestartContainer, captureContainerID(r.restartContainer)) + controls.Register(PauseContainer, captureContainerID(r.pauseContainer)) + controls.Register(UnpauseContainer, captureContainerID(r.unpauseContainer)) + controls.Register(AttachContainer, captureContainerID(r.attachContainer)) + controls.Register(ExecContainer, captureContainerID(r.execContainer)) } diff --git a/probe/docker/controls_test.go b/probe/docker/controls_test.go index 0a6da4767..17134e3ec 100644 --- a/probe/docker/controls_test.go +++ b/probe/docker/controls_test.go @@ -1,13 +1,17 @@ package docker_test import ( + "io" "reflect" "testing" "time" + "github.com/gorilla/websocket" + "github.com/weaveworks/scope/probe/controls" "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/report" + "github.com/weaveworks/scope/test" "github.com/weaveworks/scope/xfer" ) @@ -36,3 +40,47 @@ func TestControls(t *testing.T) { } }) } + +type mockPipe struct{} + +func (mockPipe) Ends() (io.ReadWriter, io.ReadWriter) { return nil, nil } +func (mockPipe) CopyToWebsocket(io.ReadWriter, *websocket.Conn) error { return nil } +func (mockPipe) Close() error { return nil } +func (mockPipe) Closed() bool { return false } +func (mockPipe) OnClose(func()) {} + +func TestPipes(t *testing.T) { + oldNewPipe := controls.NewPipe + defer func() { controls.NewPipe = oldNewPipe }() + controls.NewPipe = func(_ string) (string, controls.Pipe, error) { + return "pipeid", mockPipe{}, nil + } + + mdc := newMockClient() + setupStubs(mdc, func() { + registry, _ := docker.NewRegistry(10 * time.Second) + defer registry.Stop() + + test.Poll(t, 100*time.Millisecond, true, func() interface{} { + _, ok := registry.GetContainer("ping") + return ok + }) + + for _, tc := range []string{ + docker.AttachContainer, + docker.ExecContainer, + } { + result := controls.HandleControlRequest(xfer.Request{ + Control: tc, + NodeID: report.MakeContainerNodeID("", "ping"), + }) + want := xfer.Response{ + Pipe: "pipeid", + RawTTY: true, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("diff: %s", test.Diff(want, result)) + } + } + }) +} diff --git a/probe/docker/registry.go b/probe/docker/registry.go index 66e63b596..9a040f1c1 100644 --- a/probe/docker/registry.go +++ b/probe/docker/registry.go @@ -32,6 +32,7 @@ type Registry interface { WalkContainers(f func(Container)) WalkImages(f func(*docker_client.APIImages)) WatchContainerUpdates(ContainerUpdateWatcher) + GetContainer(string) (Container, bool) } // ContainerUpdateWatcher is the type of functions that get called when containers are updated. @@ -56,11 +57,15 @@ type Client interface { ListImages(docker_client.ListImagesOptions) ([]docker_client.APIImages, error) AddEventListener(chan<- *docker_client.APIEvents) error RemoveEventListener(chan *docker_client.APIEvents) error + StopContainer(string, uint) error StartContainer(string, *docker_client.HostConfig) error RestartContainer(string, uint) error PauseContainer(string) error UnpauseContainer(string) error + AttachToContainerNonBlocking(docker_client.AttachToContainerOptions) (docker_client.CloseWaiter, error) + CreateExec(docker_client.CreateExecOptions) (*docker_client.Exec, error) + StartExecNonBlocking(string, docker_client.StartExecOptions) (docker_client.CloseWaiter, error) } func newDockerClient(endpoint string) (Client, error) { @@ -302,7 +307,7 @@ func (r *registry) WalkContainers(f func(Container)) { } } -func (r *registry) getContainer(id string) (Container, bool) { +func (r *registry) GetContainer(id string) (Container, bool) { r.RLock() defer r.RUnlock() c, ok := r.containers[id] diff --git a/probe/docker/registry_test.go b/probe/docker/registry_test.go index 63456dba3..1dda7cc6e 100644 --- a/probe/docker/registry_test.go +++ b/probe/docker/registry_test.go @@ -56,6 +56,8 @@ func (c *mockContainer) GetNode(_ string, _ []net.IP) report.Node { }) } +func (c *mockContainer) HasTTY() bool { return true } + type mockDockerClient struct { sync.RWMutex apiContainers []client.APIContainers @@ -124,6 +126,23 @@ func (m *mockDockerClient) UnpauseContainer(_ string) error { return fmt.Errorf("unpaused") } +type mockCloseWaiter struct{} + +func (mockCloseWaiter) Close() error { return nil } +func (mockCloseWaiter) Wait() error { return nil } + +func (m *mockDockerClient) AttachToContainerNonBlocking(_ client.AttachToContainerOptions) (client.CloseWaiter, error) { + return mockCloseWaiter{}, nil +} + +func (m *mockDockerClient) CreateExec(client.CreateExecOptions) (*client.Exec, error) { + return &client.Exec{ID: "id"}, nil +} + +func (m *mockDockerClient) StartExecNonBlocking(string, client.StartExecOptions) (client.CloseWaiter, error) { + return mockCloseWaiter{}, nil +} + func (m *mockDockerClient) send(event *client.APIEvents) { m.RLock() defer m.RUnlock() diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go index bd1ed0dc0..0de846be9 100644 --- a/probe/docker/reporter.go +++ b/probe/docker/reporter.go @@ -92,6 +92,16 @@ func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology { Human: "Unpause", Icon: "fa-play", }) + result.Controls.AddControl(report.Control{ + ID: AttachContainer, + Human: "Attach", + Icon: "fa-desktop", + }) + result.Controls.AddControl(report.Control{ + ID: ExecContainer, + Human: "Exec /bin/sh", + Icon: "fa-terminal", + }) r.registry.WalkContainers(func(c Container) { nodeID := report.MakeContainerNodeID(r.hostID, c.ID()) diff --git a/probe/docker/reporter_test.go b/probe/docker/reporter_test.go index ac35717ea..6af889887 100644 --- a/probe/docker/reporter_test.go +++ b/probe/docker/reporter_test.go @@ -38,6 +38,8 @@ func (r *mockRegistry) WalkImages(f func(*client.APIImages)) { func (r *mockRegistry) WatchContainerUpdates(_ docker.ContainerUpdateWatcher) {} +func (r *mockRegistry) GetContainer(_ string) (docker.Container, bool) { return nil, false } + var ( mockRegistryInstance = &mockRegistry{ containersByPID: map[int]docker.Container{ @@ -85,6 +87,16 @@ func TestReporter(t *testing.T) { Human: "Unpause", Icon: "fa-play", }, + docker.AttachContainer: report.Control{ + ID: docker.AttachContainer, + Human: "Attach", + Icon: "fa-desktop", + }, + docker.ExecContainer: report.Control{ + ID: docker.ExecContainer, + Human: "Exec /bin/sh", + Icon: "fa-terminal", + }, }, } want.ContainerImage = report.Topology{ diff --git a/vendor/github.com/fsouza/go-dockerclient/.gitignore b/vendor/github.com/fsouza/go-dockerclient/.gitignore new file mode 100644 index 000000000..5f6b48eae --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/.gitignore @@ -0,0 +1,2 @@ +# temporary symlink for testing +testing/data/symlink diff --git a/vendor/github.com/fsouza/go-dockerclient/.travis.yml b/vendor/github.com/fsouza/go-dockerclient/.travis.yml new file mode 100644 index 000000000..de36cd45b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/.travis.yml @@ -0,0 +1,21 @@ +language: go +sudo: required +go: + - 1.3.3 + - 1.4.2 + - 1.5.1 + - tip +env: + - GOARCH=amd64 DOCKER_VERSION=1.7.1 + - GOARCH=386 DOCKER_VERSION=1.7.1 + - GOARCH=amd64 DOCKER_VERSION=1.8.3 + - GOARCH=386 DOCKER_VERSION=1.8.3 + - GOARCH=amd64 DOCKER_VERSION=1.9.1 + - GOARCH=386 DOCKER_VERSION=1.9.1 +install: + - make prepare_docker +script: + - make test + - DOCKER_HOST=tcp://127.0.0.1:2375 make integration +services: + - docker diff --git a/vendor/github.com/fsouza/go-dockerclient/AUTHORS b/vendor/github.com/fsouza/go-dockerclient/AUTHORS index 9b03cb127..bb5b042ab 100644 --- a/vendor/github.com/fsouza/go-dockerclient/AUTHORS +++ b/vendor/github.com/fsouza/go-dockerclient/AUTHORS @@ -6,12 +6,14 @@ Adrien Kohlbecker Aldrin Leal Andreas Jaekle Andrews Medina -Artem Sidorenko +Andrey Sibiryov Andy Goldstein +Artem Sidorenko Ben Marini Ben McCann Brendan Fosberry Brian Lalor +Brian P. Hamachek Brian Palmer Bryan Boreham Burke Libbey @@ -26,6 +28,7 @@ Craig Jellick Dan Williams Daniel, Dao Quang Minh Daniel Garcia +Daniel Hiltgen Darren Shepherd Dave Choi David Huie @@ -52,12 +55,15 @@ Jean-Baptiste Dalido Jeff Mitchell Jeffrey Hulten Jen Andre +Jérôme Laurens Johan Euphrosine +John Hughes Kamil Domanski Karan Misra Kim, Hirokuni Kyle Allan Liron Levin +Lior Yankovich Liu Peng Lorenz Leutgeb Lucas Clemente @@ -66,6 +72,7 @@ Lyon Hill Mantas Matelis Martin Sweeney Máximo Cuadros Ortiz +Michael Schmatz Michal Fojtik Mike Dillon Mrunal Patel diff --git a/vendor/github.com/fsouza/go-dockerclient/Makefile b/vendor/github.com/fsouza/go-dockerclient/Makefile index 4d5d84067..205d8f3c2 100644 --- a/vendor/github.com/fsouza/go-dockerclient/Makefile +++ b/vendor/github.com/fsouza/go-dockerclient/Makefile @@ -34,6 +34,15 @@ fmt: 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 + pretest: lint vet fmtcheck test: pretest diff --git a/vendor/github.com/fsouza/go-dockerclient/README.markdown b/vendor/github.com/fsouza/go-dockerclient/README.markdown index 9f3c0ca1b..b75a7e920 100644 --- a/vendor/github.com/fsouza/go-dockerclient/README.markdown +++ b/vendor/github.com/fsouza/go-dockerclient/README.markdown @@ -1,6 +1,5 @@ # go-dockerclient -[![Drone](https://drone.io/github.com/fsouza/go-dockerclient/status.png)](https://drone.io/github.com/fsouza/go-dockerclient/latest) [![Travis](https://img.shields.io/travis/fsouza/go-dockerclient.svg?style=flat-square)](https://travis-ci.org/fsouza/go-dockerclient) [![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fsouza/go-dockerclient) @@ -12,7 +11,7 @@ passthrough to the libnetwork remote API. Note that docker's network API is only available in docker 1.8 and above, and only enabled in docker if DOCKER_EXPERIMENTAL is defined during the docker build process. -For more details, check the [remote API documentation](http://docs.docker.com/en/latest/reference/api/docker_remote_api/). +For more details, check the [remote API documentation](http://docs.docker.com/engine/reference/api/docker_remote_api/). ## Vendoring diff --git a/vendor/github.com/fsouza/go-dockerclient/auth.go b/vendor/github.com/fsouza/go-dockerclient/auth.go index 30e3af3eb..775c70c0b 100644 --- a/vendor/github.com/fsouza/go-dockerclient/auth.go +++ b/vendor/github.com/fsouza/go-dockerclient/auth.go @@ -106,7 +106,7 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) { if err != nil { return nil, err } - userpass := strings.Split(string(data), ":") + userpass := strings.SplitN(string(data), ":", 2) if len(userpass) != 2 { return nil, ErrCannotParseDockercfg } diff --git a/vendor/github.com/fsouza/go-dockerclient/auth_test.go b/vendor/github.com/fsouza/go-dockerclient/auth_test.go index b3d4f8fc8..e53b17601 100644 --- a/vendor/github.com/fsouza/go-dockerclient/auth_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/auth_test.go @@ -13,7 +13,7 @@ import ( ) func TestAuthLegacyConfig(t *testing.T) { - auth := base64.StdEncoding.EncodeToString([]byte("user:pass")) + auth := base64.StdEncoding.EncodeToString([]byte("user:pa:ss")) read := strings.NewReader(fmt.Sprintf(`{"docker.io":{"auth":"%s","email":"user@example.com"}}`, auth)) ac, err := NewAuthConfigurations(read) if err != nil { @@ -29,7 +29,7 @@ func TestAuthLegacyConfig(t *testing.T) { 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 { + if got, want := c.Password, "pa:ss"; 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 { diff --git a/vendor/github.com/fsouza/go-dockerclient/client.go b/vendor/github.com/fsouza/go-dockerclient/client.go index 89e2bc39c..bdcfdcc6f 100644 --- a/vendor/github.com/fsouza/go-dockerclient/client.go +++ b/vendor/github.com/fsouza/go-dockerclient/client.go @@ -32,6 +32,7 @@ import ( "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/homedir" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy" + "github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp" ) const userAgent = "go-dockerclient" @@ -191,7 +192,7 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro } } return &Client{ - HTTPClient: &http.Client{}, + HTTPClient: cleanhttp.DefaultClient(), Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, @@ -294,9 +295,8 @@ func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, } tlsConfig.RootCAs = caPool } - tr := &http.Transport{ - TLSClientConfig: tlsConfig, - } + tr := cleanhttp.DefaultTransport() + tr.TLSClientConfig = tlsConfig if err != nil { return nil, err } @@ -554,30 +554,37 @@ type hijackOptions struct { data interface{} } -func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error { +type CloseWaiter interface { + io.Closer + Wait() error +} + +type waiterFunc func() error + +func (w waiterFunc) Wait() error { return w() } + +type closerFunc func() error + +func (c closerFunc) Close() error { return c() } + +func (c *Client) hijack(method, path string, hijackOptions hijackOptions) (CloseWaiter, error) { if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { err := c.checkAPIVersion() if err != nil { - return err + return nil, err } } var params io.Reader if hijackOptions.data != nil { buf, err := json.Marshal(hijackOptions.data) if err != nil { - return err + return nil, err } params = bytes.NewBuffer(buf) } - if hijackOptions.stdout == nil { - hijackOptions.stdout = ioutil.Discard - } - if hijackOptions.stderr == nil { - hijackOptions.stderr = ioutil.Discard - } req, err := http.NewRequest(method, c.getURL(path), params) if err != nil { - return err + return nil, err } req.Header.Set("Content-Type", "plain/text") req.Header.Set("Connection", "Upgrade") @@ -592,58 +599,103 @@ func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error if c.TLSConfig != nil && protocol != "unix" { dial, err = tlsDialWithDialer(c.Dialer, protocol, address, c.TLSConfig) if err != nil { - return err + return nil, err } } else { dial, err = c.Dialer.Dial(protocol, address) if err != nil { - return err + return nil, err } } - clientconn := httputil.NewClientConn(dial, nil) - defer clientconn.Close() - clientconn.Do(req) - if hijackOptions.success != nil { - hijackOptions.success <- struct{}{} - <-hijackOptions.success - } - rwc, br := clientconn.Hijack() - defer rwc.Close() - errChanOut := make(chan error, 1) - errChanIn := make(chan error, 1) + + errs := make(chan error) + quit := make(chan struct{}) go func() { - defer func() { - if hijackOptions.in != nil { - if closer, ok := hijackOptions.in.(io.Closer); ok { - closer.Close() - } - errChanIn <- nil - } - }() - var err error - if hijackOptions.setRawTerminal { - _, err = io.Copy(hijackOptions.stdout, br) + clientconn := httputil.NewClientConn(dial, nil) + defer clientconn.Close() + clientconn.Do(req) + if hijackOptions.success != nil { + hijackOptions.success <- struct{}{} + <-hijackOptions.success + } + rwc, br := clientconn.Hijack() + defer rwc.Close() + + errChanOut := make(chan error, 1) + errChanIn := make(chan error, 1) + if hijackOptions.stdout == nil && hijackOptions.stderr == nil { + close(errChanOut) } else { - _, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br) + // Only copy if hijackOptions.stdout and/or hijackOptions.stderr is actually set. + // Otherwise, if the only stream you care about is stdin, your attach session + // will "hang" until the container terminates, even though you're not reading + // stdout/stderr + if hijackOptions.stdout == nil { + hijackOptions.stdout = ioutil.Discard + } + if hijackOptions.stderr == nil { + hijackOptions.stderr = ioutil.Discard + } + + go func() { + defer func() { + if hijackOptions.in != nil { + if closer, ok := hijackOptions.in.(io.Closer); ok { + closer.Close() + } + errChanIn <- nil + } + }() + + var err error + if hijackOptions.setRawTerminal { + _, err = io.Copy(hijackOptions.stdout, br) + } else { + _, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br) + } + errChanOut <- err + }() } - errChanOut <- err - }() - go func() { - var err error - if hijackOptions.in != nil { - _, err = io.Copy(rwc, hijackOptions.in) + + go func() { + var err error + if hijackOptions.in != nil { + _, err = io.Copy(rwc, hijackOptions.in) + } + errChanIn <- err + rwc.(interface { + CloseWrite() error + }).CloseWrite() + }() + + var errIn error + select { + case errIn = <-errChanIn: + case <-quit: + return + } + + var errOut error + select { + case errOut = <-errChanOut: + case <-quit: + return + } + + if errIn != nil { + errs <- errIn + } else { + errs <- errOut } - errChanIn <- err - rwc.(interface { - CloseWrite() error - }).CloseWrite() }() - errIn := <-errChanIn - errOut := <-errChanOut - if errIn != nil { - return errIn - } - return errOut + + return struct { + closerFunc + waiterFunc + }{ + closerFunc(func() error { close(quit); return nil }), + waiterFunc(func() error { return <-errs }), + }, nil } func (c *Client) getURL(path string) string { @@ -783,6 +835,9 @@ func (e *Error) Error() string { } func parseEndpoint(endpoint string, tls bool) (*url.URL, error) { + if endpoint != "" && !strings.Contains(endpoint, "://") { + endpoint = "tcp://" + endpoint + } u, err := url.Parse(endpoint) if err != nil { return nil, ErrInvalidEndpoint diff --git a/vendor/github.com/fsouza/go-dockerclient/client_test.go b/vendor/github.com/fsouza/go-dockerclient/client_test.go index 549af1a9d..721352757 100644 --- a/vendor/github.com/fsouza/go-dockerclient/client_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/client_test.go @@ -18,6 +18,8 @@ import ( "strings" "testing" "time" + + "github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp" ) func TestNewAPIClient(t *testing.T) { @@ -168,8 +170,8 @@ func TestNewTLSVersionedClientInvalidCA(t *testing.T) { func TestNewClientInvalidEndpoint(t *testing.T) { cases := []string{ - "htp://localhost:3243", "http://localhost:a", "localhost:8080", - "", "localhost", "http://localhost:8080:8383", "http://localhost:65536", + "htp://localhost:3243", "http://localhost:a", + "", "http://localhost:8080:8383", "http://localhost:65536", "https://localhost:-20", } for _, c := range cases { @@ -183,6 +185,19 @@ func TestNewClientInvalidEndpoint(t *testing.T) { } } +func TestNewClientNoSchemeEndpoint(t *testing.T) { + cases := []string{"localhost", "localhost:8080"} + for _, c := range cases { + client, err := NewClient(c) + if client == nil { + t.Errorf("Want client for scheme-less endpoint, got ") + } + if err != nil { + t.Errorf("Got unexpected error scheme-less endpoint: %q", err) + } + } +} + func TestNewTLSClient(t *testing.T) { var tests = []struct { endpoint string @@ -445,7 +460,7 @@ func TestPingErrorWithUnixSocket(t *testing.T) { endpoint := "unix:///tmp/echo.sock" u, _ := parseEndpoint(endpoint, false) client := Client{ - HTTPClient: &http.Client{}, + HTTPClient: cleanhttp.DefaultClient(), Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, diff --git a/vendor/github.com/fsouza/go-dockerclient/container.go b/vendor/github.com/fsouza/go-dockerclient/container.go index faf12632f..f2eadd5ed 100644 --- a/vendor/github.com/fsouza/go-dockerclient/container.go +++ b/vendor/github.com/fsouza/go-dockerclient/container.go @@ -125,25 +125,38 @@ type PortBinding struct { // and its value as found in NetworkSettings should always be nil type PortMapping map[string]string +// ContainerNetwork represents the networking settings of a container per network. +type ContainerNetwork struct { + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` + GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty"` + GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty"` + IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty"` + IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"` + IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"` + Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"` + EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"` +} + // NetworkSettings contains network-related information about a container type NetworkSettings struct { - IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"` - IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"` - MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` - Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"` - Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty"` - PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty"` - Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"` - NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"` - EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"` - SandboxKey string `json:"SandboxKey,omitempty" yaml:"SandboxKey,omitempty"` - GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty"` - GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty"` - IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty"` - LinkLocalIPv6Address string `json:"LinkLocalIPv6Address,omitempty" yaml:"LinkLocalIPv6Address,omitempty"` - LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen,omitempty" yaml:"LinkLocalIPv6PrefixLen,omitempty"` - SecondaryIPAddresses []string `json:"SecondaryIPAddresses,omitempty" yaml:"SecondaryIPAddresses,omitempty"` - SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty" yaml:"SecondaryIPv6Addresses,omitempty"` + Networks map[string]ContainerNetwork `json:"Networks,omitempty" yaml:"Networks,omitempty"` + IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"` + IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"` + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` + Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"` + Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty"` + PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty"` + Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"` + NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"` + EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"` + SandboxKey string `json:"SandboxKey,omitempty" yaml:"SandboxKey,omitempty"` + GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty"` + GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty"` + IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty"` + LinkLocalIPv6Address string `json:"LinkLocalIPv6Address,omitempty" yaml:"LinkLocalIPv6Address,omitempty"` + LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen,omitempty" yaml:"LinkLocalIPv6PrefixLen,omitempty"` + SecondaryIPAddresses []string `json:"SecondaryIPAddresses,omitempty" yaml:"SecondaryIPAddresses,omitempty"` + SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty" yaml:"SecondaryIPv6Addresses,omitempty"` } // PortMappingAPI translates the port mappings as contained in NetworkSettings @@ -154,8 +167,8 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort { p, _ := parsePort(port.Port()) if len(bindings) == 0 { mapping = append(mapping, APIPort{ - PublicPort: int64(p), - Type: port.Proto(), + PrivatePort: int64(p), + Type: port.Proto(), }) continue } @@ -185,36 +198,39 @@ func parsePort(rawPort string) (int, error) { // Config does not contain the options that are specific to starting a container on a // given host. Those are contained in HostConfig type Config struct { - Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty"` - Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty"` - User string `json:"User,omitempty" yaml:"User,omitempty"` - Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` - MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` - CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` - CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` - AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"` - AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"` - AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"` - PortSpecs []string `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty"` - ExposedPorts map[Port]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty"` - Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"` - OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"` - StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"` - Env []string `json:"Env,omitempty" yaml:"Env,omitempty"` - Cmd []string `json:"Cmd" yaml:"Cmd"` - DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only - Image string `json:"Image,omitempty" yaml:"Image,omitempty"` - Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"` - VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` - VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` - WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"` - MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` - Entrypoint []string `json:"Entrypoint" yaml:"Entrypoint"` - NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"` - SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"` - OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty"` - Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"` - Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` + Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty"` + Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty"` + User string `json:"User,omitempty" yaml:"User,omitempty"` + Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` + MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` + MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty"` + KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty"` + CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` + CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` + AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"` + AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"` + AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"` + PortSpecs []string `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty"` + ExposedPorts map[Port]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty"` + StopSignal string `json:"StopSignal,omitempty" yaml:"StopSignal,omitempty"` + Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"` + OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"` + StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"` + Env []string `json:"Env,omitempty" yaml:"Env,omitempty"` + Cmd []string `json:"Cmd" yaml:"Cmd"` + DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only + Image string `json:"Image,omitempty" yaml:"Image,omitempty"` + Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"` + VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` + VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` + WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"` + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` + Entrypoint []string `json:"Entrypoint" yaml:"Entrypoint"` + NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"` + SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"` + OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty"` + Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` } // Mount represents a mount point in the container. @@ -465,6 +481,7 @@ type HostConfig struct { 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"` @@ -490,6 +507,7 @@ type HostConfig struct { 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"` } // StartContainer starts a container, returning an error in case of failure. @@ -1022,6 +1040,28 @@ func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { return &NoSuchContainer{ID: opts.Container} } path := "/containers/" + opts.Container + "/attach?" + queryString(opts) + cw, err := c.hijack("POST", path, hijackOptions{ + success: opts.Success, + setRawTerminal: opts.RawTerminal, + in: opts.InputStream, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + }) + if err != nil { + return err + } + return cw.Wait() +} + +// AttachToContainerNonBlocking attaches to a container, using the given options. +// This function does not block. +// +// See https://goo.gl/NKpkFk for more details. +func (c *Client) AttachToContainerNonBlocking(opts AttachToContainerOptions) (CloseWaiter, error) { + if opts.Container == "" { + return nil, &NoSuchContainer{ID: opts.Container} + } + path := "/containers/" + opts.Container + "/attach?" + queryString(opts) return c.hijack("POST", path, hijackOptions{ success: opts.Success, setRawTerminal: opts.RawTerminal, diff --git a/vendor/github.com/fsouza/go-dockerclient/container_test.go b/vendor/github.com/fsouza/go-dockerclient/container_test.go index 883064e74..2f93b6300 100644 --- a/vendor/github.com/fsouza/go-dockerclient/container_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/container_test.go @@ -22,6 +22,8 @@ import ( "strings" "testing" "time" + + "github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp" ) func TestStateString(t *testing.T) { @@ -263,6 +265,220 @@ func TestInspectContainer(t *testing.T) { } } +func TestInspectContainerNetwork(t *testing.T) { + jsonContainer := `{ + "Id": "81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c", + "Created": "2015-11-12T14:54:04.791485659Z", + "Path": "consul-template", + "Args": [ + "-config=/tmp/haproxy.json", + "-consul=192.168.99.120:8500" + ], + "State": { + "Status": "running", + "Running": true, + "Paused": false, + "Restarting": false, + "OOMKilled": false, + "Dead": false, + "Pid": 3196, + "ExitCode": 0, + "Error": "", + "StartedAt": "2015-11-12T14:54:05.026747471Z", + "FinishedAt": "0001-01-01T00:00:00Z" + }, + "Image": "4921c5917fc117df3dec32f4c1976635dc6c56ccd3336fe1db3477f950e78bf7", + "ResolvConfPath": "/mnt/sda1/var/lib/docker/containers/81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c/resolv.conf", + "HostnamePath": "/mnt/sda1/var/lib/docker/containers/81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c/hostname", + "HostsPath": "/mnt/sda1/var/lib/docker/containers/81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c/hosts", + "LogPath": "/mnt/sda1/var/lib/docker/containers/81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c/81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c-json.log", + "Node": { + "ID": "AUIB:LFOT:3LSF:SCFS:OYDQ:NLXD:JZNE:4INI:3DRC:ZFBB:GWCY:DWJK", + "IP": "192.168.99.121", + "Addr": "192.168.99.121:2376", + "Name": "swl-demo1", + "Cpus": 1, + "Memory": 2099945472, + "Labels": { + "executiondriver": "native-0.2", + "kernelversion": "4.1.12-boot2docker", + "operatingsystem": "Boot2Docker 1.9.0 (TCL 6.4); master : 16e4a2a - Tue Nov 3 19:49:22 UTC 2015", + "provider": "virtualbox", + "storagedriver": "aufs" + } + }, + "Name": "/docker-proxy.swl-demo1", + "RestartCount": 0, + "Driver": "aufs", + "ExecDriver": "native-0.2", + "MountLabel": "", + "ProcessLabel": "", + "AppArmorProfile": "", + "ExecIDs": null, + "HostConfig": { + "Binds": null, + "ContainerIDFile": "", + "LxcConf": [], + "Memory": 0, + "MemoryReservation": 0, + "MemorySwap": 0, + "KernelMemory": 0, + "CpuShares": 0, + "CpuPeriod": 0, + "CpusetCpus": "", + "CpusetMems": "", + "CpuQuota": 0, + "BlkioWeight": 0, + "OomKillDisable": false, + "MemorySwappiness": -1, + "Privileged": false, + "PortBindings": { + "443/tcp": [ + { + "HostIp": "", + "HostPort": "443" + } + ] + }, + "Links": null, + "PublishAllPorts": false, + "Dns": null, + "DnsOptions": null, + "DnsSearch": null, + "ExtraHosts": null, + "VolumesFrom": null, + "Devices": [], + "NetworkMode": "swl-net", + "IpcMode": "", + "PidMode": "", + "UTSMode": "", + "CapAdd": null, + "CapDrop": null, + "GroupAdd": null, + "RestartPolicy": { + "Name": "no", + "MaximumRetryCount": 0 + }, + "SecurityOpt": null, + "ReadonlyRootfs": false, + "Ulimits": null, + "LogConfig": { + "Type": "json-file", + "Config": {} + }, + "CgroupParent": "", + "ConsoleSize": [ + 0, + 0 + ], + "VolumeDriver": "" + }, + "GraphDriver": { + "Name": "aufs", + "Data": null + }, + "Mounts": [], + "Config": { + "Hostname": "81e1bbe20b55", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "ExposedPorts": { + "443/tcp": {} + }, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "DOMAIN=local.auto", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "CONSUL_TEMPLATE_VERSION=0.11.1" + ], + "Cmd": [ + "-consul=192.168.99.120:8500" + ], + "Image": "docker-proxy:latest", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": [ + "consul-template", + "-config=/tmp/haproxy.json" + ], + "OnBuild": null, + "Labels": {}, + "StopSignal": "SIGTERM" + }, + "NetworkSettings": { + "Bridge": "", + "SandboxID": "c6b903dc5c1a96113a22dbc44709e30194079bd2d262eea1eb4f38d85821f6e1", + "HairpinMode": false, + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "Ports": { + "443/tcp": [ + { + "HostIp": "192.168.99.121", + "HostPort": "443" + } + ] + }, + "SandboxKey": "/var/run/docker/netns/c6b903dc5c1a", + "SecondaryIPAddresses": null, + "SecondaryIPv6Addresses": null, + "EndpointID": "", + "Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "MacAddress": "", + "Networks": { + "swl-net": { + "EndpointID": "683e3092275782a53c3b0968cc7e3a10f23264022ded9cb20490902f96fc5981", + "Gateway": "", + "IPAddress": "10.0.0.3", + "IPPrefixLen": 24, + "IPv6Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "MacAddress": "02:42:0a:00:00:03" + } + } + } +}` + + fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} + client := newTestClient(fakeRT) + id := "81e1bbe20b55" + exp := "10.0.0.3" + + container, err := client.InspectContainer(id) + if err != nil { + t.Fatal(err) + } + + s := reflect.Indirect(reflect.ValueOf(container.NetworkSettings)) + networks := s.FieldByName("Networks") + if networks.IsValid() { + var ip string + for _, net := range networks.MapKeys() { + if net.Interface().(string) == container.HostConfig.NetworkMode { + ip = networks.MapIndex(net).FieldByName("IPAddress").Interface().(string) + t.Logf("%s %v", net, ip) + } + } + if ip != exp { + t.Errorf("InspectContainerNetworks(%q): Expected %#v. Got %#v.", id, exp, ip) + } + } else { + t.Errorf("InspectContainerNetworks(%q): No method Networks for NetworkSettings", id) + } + +} + func TestInspectContainerNegativeSwap(t *testing.T) { jsonContainer := `{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", @@ -1088,6 +1304,52 @@ func TestAttachToContainerNilStderr(t *testing.T) { } } +func TestAttachToContainerStdinOnly(t *testing.T) { + var reader = strings.NewReader("send value") + serverFinished := make(chan struct{}) + clientFinished := make(chan struct{}) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + hj, ok := w.(http.Hijacker) + if !ok { + t.Fatal("cannot hijack server connection") + } + conn, _, err := hj.Hijack() + if err != nil { + t.Fatal(err) + } + // wait for client to indicate it's finished + <-clientFinished + // inform test that the server has finished + close(serverFinished) + conn.Close() + })) + defer server.Close() + client, _ := NewClient(server.URL) + client.SkipServerVersionCheck = true + success := make(chan struct{}) + opts := AttachToContainerOptions{ + Container: "a123456", + InputStream: reader, + Stdin: true, + Stdout: false, + Stderr: false, + Stream: true, + RawTerminal: false, + Success: success, + } + go func() { + if err := client.AttachToContainer(opts); err != nil { + t.Error(err) + } + // client's attach session is over + close(clientFinished) + }() + success <- <-success + // wait for server to finish handling attach + <-serverFinished +} + func TestAttachToContainerRawTerminalFalse(t *testing.T) { input := strings.NewReader("send value") var req http.Request @@ -1371,7 +1633,7 @@ func TestExportContainerViaUnixSocket(t *testing.T) { endpoint := "unix://" + tempSocket u, _ := parseEndpoint(endpoint, false) client := Client{ - HTTPClient: &http.Client{}, + HTTPClient: cleanhttp.DefaultClient(), Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, diff --git a/vendor/github.com/fsouza/go-dockerclient/exec.go b/vendor/github.com/fsouza/go-dockerclient/exec.go index f3b705fa0..e164bfb64 100644 --- a/vendor/github.com/fsouza/go-dockerclient/exec.go +++ b/vendor/github.com/fsouza/go-dockerclient/exec.go @@ -101,6 +101,44 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error { return nil } + cw, err := c.hijack("POST", path, hijackOptions{ + success: opts.Success, + setRawTerminal: opts.RawTerminal, + in: opts.InputStream, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + data: opts, + }) + if err != nil { + return err + } + return cw.Wait() +} + +// StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is +// true, it returns after starting the exec command. Otherwise, it sets up an +// interactive session with the exec command. +// +// See https://goo.gl/iQCnto for more details +func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) { + if id == "" { + return nil, &NoSuchExec{ID: id} + } + + path := fmt.Sprintf("/exec/%s/start", id) + + if opts.Detach { + resp, err := c.do("POST", path, doOptions{data: opts}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchExec{ID: id} + } + return nil, err + } + defer resp.Body.Close() + return nil, nil + } + return c.hijack("POST", path, hijackOptions{ success: opts.Success, setRawTerminal: opts.RawTerminal, diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/CHANGELOG.md b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/CHANGELOG.md index a38715497..ecc843272 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/CHANGELOG.md +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/CHANGELOG.md @@ -1,26 +1,55 @@ -# (Unreleased) +# 0.9.0 (Unreleased) -logrus/core: improve performance of text formatter by 40% -logrus/core: expose `LevelHooks` type +* logrus/text_formatter: don't emit empty msg +* logrus/hooks/airbrake: move out of main repository +* logrus/hooks/sentry: move out of main repository +* logrus/hooks/papertrail: move out of main repository +* logrus/hooks/bugsnag: move out of main repository + +# 0.8.7 + +* logrus/core: fix possible race (#216) +* logrus/doc: small typo fixes and doc improvements + + +# 0.8.6 + +* hooks/raven: allow passing an initialized client + +# 0.8.5 + +* logrus/core: revert #208 + +# 0.8.4 + +* formatter/text: fix data race (#218) + +# 0.8.3 + +* logrus/core: fix entry log level (#208) +* logrus/core: improve performance of text formatter by 40% +* logrus/core: expose `LevelHooks` type +* logrus/core: add support for DragonflyBSD and NetBSD +* formatter/text: print structs more verbosely # 0.8.2 -logrus: fix more Fatal family functions +* logrus: fix more Fatal family functions # 0.8.1 -logrus: fix not exiting on `Fatalf` and `Fatalln` +* logrus: fix not exiting on `Fatalf` and `Fatalln` # 0.8.0 -logrus: defaults to stderr instead of stdout -hooks/sentry: add special field for `*http.Request` -formatter/text: ignore Windows for colors +* logrus: defaults to stderr instead of stdout +* hooks/sentry: add special field for `*http.Request` +* formatter/text: ignore Windows for colors # 0.7.3 -formatter/\*: allow configuration of timestamp layout +* formatter/\*: allow configuration of timestamp layout # 0.7.2 -formatter/text: Add configuration option for time format (#158) +* formatter/text: Add configuration option for time format (#158) diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/README.md b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/README.md index 4be378476..53d27d449 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/README.md +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/README.md @@ -75,17 +75,12 @@ package main import ( "os" log "github.com/Sirupsen/logrus" - "github.com/Sirupsen/logrus/hooks/airbrake" ) func init() { // Log as JSON instead of the default ASCII formatter. log.SetFormatter(&log.JSONFormatter{}) - // Use the Airbrake hook to report errors that have Error severity or above to - // an exception tracker. You can create custom hooks, see the Hooks section. - log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development")) - // Output to stderr instead of stdout, could also be a file. log.SetOutput(os.Stderr) @@ -182,13 +177,16 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in ```go import ( log "github.com/Sirupsen/logrus" - "github.com/Sirupsen/logrus/hooks/airbrake" + "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake" logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" "log/syslog" ) func init() { - log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development")) + + // Use the Airbrake hook to report errors that have Error severity or above to + // an exception tracker. You can create custom hooks, see the Hooks section. + log.AddHook(airbrake.NewHook(123, "xyz", "production")) hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") if err != nil { @@ -198,25 +196,30 @@ func init() { } } ``` - +Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). | Hook | Description | | ----- | ----------- | -| [Airbrake](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) | Send errors to an exception tracking service compatible with the Airbrake API. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | -| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. | +| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. | +| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | +| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. | | [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. | -| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | -| [Sentry](https://github.com/Sirupsen/logrus/blob/master/hooks/sentry/sentry.go) | Send errors to the Sentry error logging and aggregation service. | +| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | +| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. | | [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. | | [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) | | [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. | | [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` | -| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) | +| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) | | [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) | | [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem | | [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger | | [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail | | [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar | +| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd | +| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb | +| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb | +| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit | #### Level logging @@ -272,7 +275,7 @@ init() { // do something here to set environment depending on an environment variable // or command-line flag if Environment == "production" { - log.SetFormatter(&logrus.JSONFormatter{}) + log.SetFormatter(&log.JSONFormatter{}) } else { // The TextFormatter is default, you don't actually have to do this. log.SetFormatter(&log.TextFormatter{}) @@ -294,10 +297,10 @@ The built-in logging formatters are: field to `true`. To force no colored output even if there is a TTY set the `DisableColors` field to `true` * `logrus.JSONFormatter`. Logs fields as JSON. -* `logrus_logstash.LogstashFormatter`. Logs fields as Logstash Events (http://logstash.net). +* `logrus/formatters/logstash.LogstashFormatter`. Logs fields as [Logstash](http://logstash.net) Events. ```go - logrus.SetFormatter(&logrus_logstash.LogstashFormatter{Type: “application_name"}) + logrus.SetFormatter(&logstash.LogstashFormatter{Type: “application_name"}) ``` Third party logging formatters: @@ -315,7 +318,7 @@ type MyJSONFormatter struct { log.SetFormatter(new(MyJSONFormatter)) -func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { +func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) { // Note this doesn't include Time, Level and Message which are available on // the Entry. Consult `godoc` on information about those fields or read the // source of the official loggers. diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/doc.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/doc.go new file mode 100644 index 000000000..dddd5f877 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/doc.go @@ -0,0 +1,26 @@ +/* +Package logrus is a structured logger for Go, completely API compatible with the standard library logger. + + +The simplest way to use Logrus is simply the package-level exported logger: + + package main + + import ( + log "github.com/Sirupsen/logrus" + ) + + func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "number": 1, + "size": 10, + }).Info("A walrus appears") + } + +Output: + time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10 + +For a full guide visit https://github.com/Sirupsen/logrus +*/ +package logrus diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_freebsd.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_freebsd.go deleted file mode 100644 index 0428ee5d5..000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_freebsd.go +++ /dev/null @@ -1,20 +0,0 @@ -/* - Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin. -*/ -package logrus - -import ( - "syscall" -) - -const ioctlReadTermios = syscall.TIOCGETA - -type Termios struct { - Iflag uint32 - Oflag uint32 - Cflag uint32 - Lflag uint32 - Cc [20]uint8 - Ispeed uint32 - Ospeed uint32 -} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_notwindows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_notwindows.go index b8bebc13e..4bb537602 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_notwindows.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_notwindows.go @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build linux darwin freebsd openbsd +// +build linux darwin freebsd openbsd netbsd dragonfly package logrus diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_openbsd.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_openbsd.go deleted file mode 100644 index af609a53d..000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_openbsd.go +++ /dev/null @@ -1,7 +0,0 @@ -package logrus - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA - -type Termios syscall.Termios diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/text_formatter.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/text_formatter.go index 2e6fe1bdd..06ef20233 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/text_formatter.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/text_formatter.go @@ -73,17 +73,20 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { isColorTerminal := isTerminal && (runtime.GOOS != "windows") isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors - if f.TimestampFormat == "" { - f.TimestampFormat = DefaultTimestampFormat + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = DefaultTimestampFormat } if isColored { - f.printColored(b, entry, keys) + f.printColored(b, entry, keys, timestampFormat) } else { if !f.DisableTimestamp { - f.appendKeyValue(b, "time", entry.Time.Format(f.TimestampFormat)) + f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) } f.appendKeyValue(b, "level", entry.Level.String()) - f.appendKeyValue(b, "msg", entry.Message) + if entry.Message != "" { + f.appendKeyValue(b, "msg", entry.Message) + } for _, key := range keys { f.appendKeyValue(b, key, entry.Data[key]) } @@ -93,7 +96,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { return b.Bytes(), nil } -func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string) { +func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) { var levelColor int switch entry.Level { case DebugLevel: @@ -111,11 +114,11 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin if !f.FullTimestamp { fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) } else { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(f.TimestampFormat), entry.Message) + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message) } for _, k := range keys { v := entry.Data[k] - fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v) + fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v) } } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/errors.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/errors.go new file mode 100644 index 000000000..fdaddbcf8 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/errors.go @@ -0,0 +1,259 @@ +package errcode + +import ( + "encoding/json" + "fmt" + "strings" +) + +// ErrorCoder is the base interface for ErrorCode and Error allowing +// users of each to just call ErrorCode to get the real ID of each +type ErrorCoder interface { + ErrorCode() ErrorCode +} + +// ErrorCode represents the error type. The errors are serialized via strings +// and the integer format may change and should *never* be exported. +type ErrorCode int + +var _ error = ErrorCode(0) + +// ErrorCode just returns itself +func (ec ErrorCode) ErrorCode() ErrorCode { + return ec +} + +// Error returns the ID/Value +func (ec ErrorCode) Error() string { + return ec.Descriptor().Value +} + +// Descriptor returns the descriptor for the error code. +func (ec ErrorCode) Descriptor() ErrorDescriptor { + d, ok := errorCodeToDescriptors[ec] + + if !ok { + return ErrorCodeUnknown.Descriptor() + } + + return d +} + +// String returns the canonical identifier for this error code. +func (ec ErrorCode) String() string { + return ec.Descriptor().Value +} + +// Message returned the human-readable error message for this error code. +func (ec ErrorCode) Message() string { + return ec.Descriptor().Message +} + +// MarshalText encodes the receiver into UTF-8-encoded text and returns the +// result. +func (ec ErrorCode) MarshalText() (text []byte, err error) { + return []byte(ec.String()), nil +} + +// UnmarshalText decodes the form generated by MarshalText. +func (ec *ErrorCode) UnmarshalText(text []byte) error { + desc, ok := idToDescriptors[string(text)] + + if !ok { + desc = ErrorCodeUnknown.Descriptor() + } + + *ec = desc.Code + + return nil +} + +// WithDetail creates a new Error struct based on the passed-in info and +// set the Detail property appropriately +func (ec ErrorCode) WithDetail(detail interface{}) Error { + return Error{ + Code: ec, + Message: ec.Message(), + }.WithDetail(detail) +} + +// WithArgs creates a new Error struct and sets the Args slice +func (ec ErrorCode) WithArgs(args ...interface{}) Error { + return Error{ + Code: ec, + Message: ec.Message(), + }.WithArgs(args...) +} + +// Error provides a wrapper around ErrorCode with extra Details provided. +type Error struct { + Code ErrorCode `json:"code"` + Message string `json:"message"` + Detail interface{} `json:"detail,omitempty"` + + // TODO(duglin): See if we need an "args" property so we can do the + // variable substitution right before showing the message to the user +} + +var _ error = Error{} + +// ErrorCode returns the ID/Value of this Error +func (e Error) ErrorCode() ErrorCode { + return e.Code +} + +// Error returns a human readable representation of the error. +func (e Error) Error() string { + return fmt.Sprintf("%s: %s", + strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)), + e.Message) +} + +// WithDetail will return a new Error, based on the current one, but with +// some Detail info added +func (e Error) WithDetail(detail interface{}) Error { + return Error{ + Code: e.Code, + Message: e.Message, + Detail: detail, + } +} + +// WithArgs uses the passed-in list of interface{} as the substitution +// variables in the Error's Message string, but returns a new Error +func (e Error) WithArgs(args ...interface{}) Error { + return Error{ + Code: e.Code, + Message: fmt.Sprintf(e.Code.Message(), args...), + Detail: e.Detail, + } +} + +// ErrorDescriptor provides relevant information about a given error code. +type ErrorDescriptor struct { + // Code is the error code that this descriptor describes. + Code ErrorCode + + // Value provides a unique, string key, often captilized with + // underscores, to identify the error code. This value is used as the + // keyed value when serializing api errors. + Value string + + // Message is a short, human readable decription of the error condition + // included in API responses. + Message string + + // Description provides a complete account of the errors purpose, suitable + // for use in documentation. + Description string + + // HTTPStatusCode provides the http status code that is associated with + // this error condition. + HTTPStatusCode int +} + +// ParseErrorCode returns the value by the string error code. +// `ErrorCodeUnknown` will be returned if the error is not known. +func ParseErrorCode(value string) ErrorCode { + ed, ok := idToDescriptors[value] + if ok { + return ed.Code + } + + return ErrorCodeUnknown +} + +// Errors provides the envelope for multiple errors and a few sugar methods +// for use within the application. +type Errors []error + +var _ error = Errors{} + +func (errs Errors) Error() string { + switch len(errs) { + case 0: + return "" + case 1: + return errs[0].Error() + default: + msg := "errors:\n" + for _, err := range errs { + msg += err.Error() + "\n" + } + return msg + } +} + +// Len returns the current number of errors. +func (errs Errors) Len() int { + return len(errs) +} + +// MarshalJSON converts slice of error, ErrorCode or Error into a +// slice of Error - then serializes +func (errs Errors) MarshalJSON() ([]byte, error) { + var tmpErrs struct { + Errors []Error `json:"errors,omitempty"` + } + + for _, daErr := range errs { + var err Error + + switch daErr.(type) { + case ErrorCode: + err = daErr.(ErrorCode).WithDetail(nil) + case Error: + err = daErr.(Error) + default: + err = ErrorCodeUnknown.WithDetail(daErr) + + } + + // If the Error struct was setup and they forgot to set the + // Message field (meaning its "") then grab it from the ErrCode + msg := err.Message + if msg == "" { + msg = err.Code.Message() + } + + tmpErrs.Errors = append(tmpErrs.Errors, Error{ + Code: err.Code, + Message: msg, + Detail: err.Detail, + }) + } + + return json.Marshal(tmpErrs) +} + +// UnmarshalJSON deserializes []Error and then converts it into slice of +// Error or ErrorCode +func (errs *Errors) UnmarshalJSON(data []byte) error { + var tmpErrs struct { + Errors []Error + } + + if err := json.Unmarshal(data, &tmpErrs); err != nil { + return err + } + + var newErrs Errors + for _, daErr := range tmpErrs.Errors { + // If Message is empty or exactly matches the Code's message string + // then just use the Code, no need for a full Error struct + if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) { + // Error's w/o details get converted to ErrorCode + newErrs = append(newErrs, daErr.Code) + } else { + // Error's w/ details are untouched + newErrs = append(newErrs, Error{ + Code: daErr.Code, + Message: daErr.Message, + Detail: daErr.Detail, + }) + } + } + + *errs = newErrs + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/errors_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/errors_test.go new file mode 100644 index 000000000..27fb1cec7 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/errors_test.go @@ -0,0 +1,179 @@ +package errcode + +import ( + "encoding/json" + "net/http" + "reflect" + "testing" +) + +// TestErrorCodes ensures that error code format, mappings and +// marshaling/unmarshaling. round trips are stable. +func TestErrorCodes(t *testing.T) { + if len(errorCodeToDescriptors) == 0 { + t.Fatal("errors aren't loaded!") + } + + for ec, desc := range errorCodeToDescriptors { + if ec != desc.Code { + t.Fatalf("error code in descriptor isn't correct, %q != %q", ec, desc.Code) + } + + if idToDescriptors[desc.Value].Code != ec { + t.Fatalf("error code in idToDesc isn't correct, %q != %q", idToDescriptors[desc.Value].Code, ec) + } + + if ec.Message() != desc.Message { + t.Fatalf("ec.Message doesn't mtach desc.Message: %q != %q", ec.Message(), desc.Message) + } + + // Test (de)serializing the ErrorCode + p, err := json.Marshal(ec) + if err != nil { + t.Fatalf("couldn't marshal ec %v: %v", ec, err) + } + + if len(p) <= 0 { + t.Fatalf("expected content in marshaled before for error code %v", ec) + } + + // First, unmarshal to interface and ensure we have a string. + var ecUnspecified interface{} + if err := json.Unmarshal(p, &ecUnspecified); err != nil { + t.Fatalf("error unmarshaling error code %v: %v", ec, err) + } + + if _, ok := ecUnspecified.(string); !ok { + t.Fatalf("expected a string for error code %v on unmarshal got a %T", ec, ecUnspecified) + } + + // Now, unmarshal with the error code type and ensure they are equal + var ecUnmarshaled ErrorCode + if err := json.Unmarshal(p, &ecUnmarshaled); err != nil { + t.Fatalf("error unmarshaling error code %v: %v", ec, err) + } + + if ecUnmarshaled != ec { + t.Fatalf("unexpected error code during error code marshal/unmarshal: %v != %v", ecUnmarshaled, ec) + } + } + +} + +// TestErrorsManagement does a quick check of the Errors type to ensure that +// members are properly pushed and marshaled. +var ErrorCodeTest1 = Register("v2.errors", ErrorDescriptor{ + Value: "TEST1", + Message: "test error 1", + Description: `Just a test message #1.`, + HTTPStatusCode: http.StatusInternalServerError, +}) + +var ErrorCodeTest2 = Register("v2.errors", ErrorDescriptor{ + Value: "TEST2", + Message: "test error 2", + Description: `Just a test message #2.`, + HTTPStatusCode: http.StatusNotFound, +}) + +var ErrorCodeTest3 = Register("v2.errors", ErrorDescriptor{ + Value: "TEST3", + Message: "Sorry %q isn't valid", + Description: `Just a test message #3.`, + HTTPStatusCode: http.StatusNotFound, +}) + +func TestErrorsManagement(t *testing.T) { + var errs Errors + + errs = append(errs, ErrorCodeTest1) + errs = append(errs, ErrorCodeTest2.WithDetail( + map[string]interface{}{"digest": "sometestblobsumdoesntmatter"})) + errs = append(errs, ErrorCodeTest3.WithArgs("BOOGIE")) + errs = append(errs, ErrorCodeTest3.WithArgs("BOOGIE").WithDetail("data")) + + p, err := json.Marshal(errs) + + if err != nil { + t.Fatalf("error marashaling errors: %v", err) + } + + expectedJSON := `{"errors":[` + + `{"code":"TEST1","message":"test error 1"},` + + `{"code":"TEST2","message":"test error 2","detail":{"digest":"sometestblobsumdoesntmatter"}},` + + `{"code":"TEST3","message":"Sorry \"BOOGIE\" isn't valid"},` + + `{"code":"TEST3","message":"Sorry \"BOOGIE\" isn't valid","detail":"data"}` + + `]}` + + if string(p) != expectedJSON { + t.Fatalf("unexpected json:\ngot:\n%q\n\nexpected:\n%q", string(p), expectedJSON) + } + + // Now test the reverse + var unmarshaled Errors + if err := json.Unmarshal(p, &unmarshaled); err != nil { + t.Fatalf("unexpected error unmarshaling error envelope: %v", err) + } + + if !reflect.DeepEqual(unmarshaled, errs) { + t.Fatalf("errors not equal after round trip:\nunmarshaled:\n%#v\n\nerrs:\n%#v", unmarshaled, errs) + } + + // Test the arg substitution stuff + e1 := unmarshaled[3].(Error) + exp1 := `Sorry "BOOGIE" isn't valid` + if e1.Message != exp1 { + t.Fatalf("Wrong msg, got:\n%q\n\nexpected:\n%q", e1.Message, exp1) + } + + exp1 = "test3: " + exp1 + if e1.Error() != exp1 { + t.Fatalf("Error() didn't return the right string, got:%s\nexpected:%s", e1.Error(), exp1) + } + + // Test again with a single value this time + errs = Errors{ErrorCodeUnknown} + expectedJSON = "{\"errors\":[{\"code\":\"UNKNOWN\",\"message\":\"unknown error\"}]}" + p, err = json.Marshal(errs) + + if err != nil { + t.Fatalf("error marashaling errors: %v", err) + } + + if string(p) != expectedJSON { + t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON) + } + + // Now test the reverse + unmarshaled = nil + if err := json.Unmarshal(p, &unmarshaled); err != nil { + t.Fatalf("unexpected error unmarshaling error envelope: %v", err) + } + + if !reflect.DeepEqual(unmarshaled, errs) { + t.Fatalf("errors not equal after round trip:\nunmarshaled:\n%#v\n\nerrs:\n%#v", unmarshaled, errs) + } + + // Verify that calling WithArgs() more than once does the right thing. + // Meaning creates a new Error and uses the ErrorCode Message + e1 = ErrorCodeTest3.WithArgs("test1") + e2 := e1.WithArgs("test2") + if &e1 == &e2 { + t.Fatalf("args: e2 and e1 should not be the same, but they are") + } + if e2.Message != `Sorry "test2" isn't valid` { + t.Fatalf("e2 had wrong message: %q", e2.Message) + } + + // Verify that calling WithDetail() more than once does the right thing. + // Meaning creates a new Error and overwrites the old detail field + e1 = ErrorCodeTest3.WithDetail("stuff1") + e2 = e1.WithDetail("stuff2") + if &e1 == &e2 { + t.Fatalf("detail: e2 and e1 should not be the same, but they are") + } + if e2.Detail != `stuff2` { + t.Fatalf("e2 had wrong detail: %q", e2.Detail) + } + +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/handler.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/handler.go new file mode 100644 index 000000000..49a64a86e --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/handler.go @@ -0,0 +1,44 @@ +package errcode + +import ( + "encoding/json" + "net/http" +) + +// ServeJSON attempts to serve the errcode in a JSON envelope. It marshals err +// and sets the content-type header to 'application/json'. It will handle +// ErrorCoder and Errors, and if necessary will create an envelope. +func ServeJSON(w http.ResponseWriter, err error) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + var sc int + + switch errs := err.(type) { + case Errors: + if len(errs) < 1 { + break + } + + if err, ok := errs[0].(ErrorCoder); ok { + sc = err.ErrorCode().Descriptor().HTTPStatusCode + } + case ErrorCoder: + sc = errs.ErrorCode().Descriptor().HTTPStatusCode + err = Errors{err} // create an envelope. + default: + // We just have an unhandled error type, so just place in an envelope + // and move along. + err = Errors{err} + } + + if sc == 0 { + sc = http.StatusInternalServerError + } + + w.WriteHeader(sc) + + if err := json.NewEncoder(w).Encode(err); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/register.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/register.go new file mode 100644 index 000000000..01c34384b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode/register.go @@ -0,0 +1,128 @@ +package errcode + +import ( + "fmt" + "net/http" + "sort" + "sync" +) + +var ( + errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{} + idToDescriptors = map[string]ErrorDescriptor{} + groupToDescriptors = map[string][]ErrorDescriptor{} +) + +var ( + // ErrorCodeUnknown is a generic error that can be used as a last + // resort if there is no situation-specific error message that can be used + ErrorCodeUnknown = Register("errcode", ErrorDescriptor{ + Value: "UNKNOWN", + Message: "unknown error", + Description: `Generic error returned when the error does not have an + API classification.`, + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeUnsupported is returned when an operation is not supported. + ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{ + Value: "UNSUPPORTED", + Message: "The operation is unsupported.", + Description: `The operation was unsupported due to a missing + implementation or invalid set of parameters.`, + HTTPStatusCode: http.StatusMethodNotAllowed, + }) + + // ErrorCodeUnauthorized is returned if a request requires + // authentication. + ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{ + Value: "UNAUTHORIZED", + Message: "authentication required", + Description: `The access controller was unable to authenticate + the client. Often this will be accompanied by a + Www-Authenticate HTTP response header indicating how to + authenticate.`, + HTTPStatusCode: http.StatusUnauthorized, + }) + + // ErrorCodeDenied is returned if a client does not have sufficient + // permission to perform an action. + ErrorCodeDenied = Register("errcode", ErrorDescriptor{ + Value: "DENIED", + Message: "requested access to the resource is denied", + Description: `The access controller denied access for the + operation on a resource.`, + HTTPStatusCode: http.StatusForbidden, + }) + + // ErrorCodeUnavailable provides a common error to report unavialability + // of a service or endpoint. + ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{ + Value: "UNAVAILABLE", + Message: "service unavailable", + Description: "Returned when a service is not available", + HTTPStatusCode: http.StatusServiceUnavailable, + }) +) + +var nextCode = 1000 +var registerLock sync.Mutex + +// Register will make the passed-in error known to the environment and +// return a new ErrorCode +func Register(group string, descriptor ErrorDescriptor) ErrorCode { + registerLock.Lock() + defer registerLock.Unlock() + + descriptor.Code = ErrorCode(nextCode) + + if _, ok := idToDescriptors[descriptor.Value]; ok { + panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value)) + } + if _, ok := errorCodeToDescriptors[descriptor.Code]; ok { + panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code)) + } + + groupToDescriptors[group] = append(groupToDescriptors[group], descriptor) + errorCodeToDescriptors[descriptor.Code] = descriptor + idToDescriptors[descriptor.Value] = descriptor + + nextCode++ + return descriptor.Code +} + +type byValue []ErrorDescriptor + +func (a byValue) Len() int { return len(a) } +func (a byValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value } + +// GetGroupNames returns the list of Error group names that are registered +func GetGroupNames() []string { + keys := []string{} + + for k := range groupToDescriptors { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// GetErrorCodeGroup returns the named group of error descriptors +func GetErrorCodeGroup(name string) []ErrorDescriptor { + desc := groupToDescriptors[name] + sort.Sort(byValue(desc)) + return desc +} + +// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are +// registered, irrespective of what group they're in +func GetErrorAllDescriptors() []ErrorDescriptor { + result := []ErrorDescriptor{} + + for _, group := range GetGroupNames() { + result = append(result, GetErrorCodeGroup(group)...) + } + sort.Sort(byValue(result)) + return result +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/README.md b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/README.md new file mode 100644 index 000000000..81fa04ccc --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/README.md @@ -0,0 +1,58 @@ +Docker 'errors' package +======================= + +This package contains all of the error messages generated by the Docker +engine that might be exposed via the Docker engine's REST API. + +Each top-level engine package will have its own file in this directory +so that there's a clear grouping of errors, instead of just one big +file. The errors for each package are defined here instead of within +their respective package structure so that Docker CLI code that may need +to import these error definition files will not need to know or understand +the engine's package/directory structure. In other words, all they should +need to do is import `.../docker/errors` and they will automatically +pick up all Docker engine defined errors. This also gives the engine +developers the freedom to change the engine packaging structure (e.g. to +CRUD packages) without worrying about breaking existing clients. + +These errors are defined using the 'errcode' package. The `errcode` package +allows for each error to be typed and include all information necessary to +have further processing done on them if necessary. In particular, each error +includes: + +* Value - a unique string (in all caps) associated with this error. +Typically, this string is the same name as the variable name of the error +(w/o the `ErrorCode` text) but in all caps. + +* Message - the human readable sentence that will be displayed for this +error. It can contain '%s' substitutions that allows for the code generating +the error to specify values that will be inserted in the string prior to +being displayed to the end-user. The `WithArgs()` function can be used to +specify the insertion strings. Note, the evaluation of the strings will be +done at the time `WithArgs()` is called. + +* Description - additional human readable text to further explain the +circumstances of the error situation. + +* HTTPStatusCode - when the error is returned back to a CLI, this value +will be used to populate the HTTP status code. If not present the default +value will be `StatusInternalServerError`, 500. + +Not all errors generated within the engine's executable will be propagated +back to the engine's API layer. For example, it is expected that errors +generated by vendored code (under `docker/vendor`) and packaged code +(under `docker/pkg`) will be converted into errors defined by this package. + +When processing an errcode error, if you are looking for a particular +error then you can do something like: + +``` +import derr "github.com/docker/docker/errors" + +... + +err := someFunc() +if err.ErrorCode() == derr.ErrorCodeNoSuchContainer { + ... +} +``` diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/builder.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/builder.go new file mode 100644 index 000000000..07a33bbf7 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/builder.go @@ -0,0 +1,93 @@ +package errors + +// This file contains all of the errors that can be generated from the +// docker/builder component. + +import ( + "net/http" + + "github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode" +) + +var ( + // ErrorCodeAtLeastOneArg is generated when the parser comes across a + // Dockerfile command that doesn't have any args. + ErrorCodeAtLeastOneArg = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "ATLEASTONEARG", + Message: "%s requires at least one argument", + Description: "The specified command requires at least one argument", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeExactlyOneArg is generated when the parser comes across a + // Dockerfile command that requires exactly one arg but got less/more. + ErrorCodeExactlyOneArg = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EXACTLYONEARG", + Message: "%s requires exactly one argument", + Description: "The specified command requires exactly one argument", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeAtLeastTwoArgs is generated when the parser comes across a + // Dockerfile command that requires at least two args but got less. + ErrorCodeAtLeastTwoArgs = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "ATLEASTTWOARGS", + Message: "%s requires at least two arguments", + Description: "The specified command requires at least two arguments", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeTooManyArgs is generated when the parser comes across a + // Dockerfile command that has more args than it should + ErrorCodeTooManyArgs = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "TOOMANYARGS", + Message: "Bad input to %s, too many args", + Description: "The specified command was passed too many arguments", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeChainOnBuild is generated when the parser comes across a + // Dockerfile command that is trying to chain ONBUILD commands. + ErrorCodeChainOnBuild = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "CHAINONBUILD", + Message: "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed", + Description: "ONBUILD Dockerfile commands aren't allow on ONBUILD commands", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeBadOnBuildCmd is generated when the parser comes across a + // an ONBUILD Dockerfile command with an invalid trigger/command. + ErrorCodeBadOnBuildCmd = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "BADONBUILDCMD", + Message: "%s isn't allowed as an ONBUILD trigger", + Description: "The specified ONBUILD command isn't allowed", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeMissingFrom is generated when the Dockerfile is missing + // a FROM command. + ErrorCodeMissingFrom = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "MISSINGFROM", + Message: "Please provide a source image with `from` prior to run", + Description: "The Dockerfile is missing a FROM command", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNotOnWindows is generated when the specified Dockerfile + // command is not supported on Windows. + ErrorCodeNotOnWindows = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOTONWINDOWS", + Message: "%s is not supported on Windows", + Description: "The specified Dockerfile command is not supported on Windows", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeEmpty is generated when the specified Volume string + // is empty. + ErrorCodeVolumeEmpty = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMEEMPTY", + Message: "Volume specified can not be an empty string", + Description: "The specified volume can not be an empty string", + HTTPStatusCode: http.StatusInternalServerError, + }) +) diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/daemon.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/daemon.go new file mode 100644 index 000000000..29d077a9a --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/daemon.go @@ -0,0 +1,925 @@ +package errors + +// This file contains all of the errors that can be generated from the +// docker/daemon component. + +import ( + "net/http" + + "github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode" +) + +var ( + // ErrorCodeNoSuchContainer is generated when we look for a container by + // name or ID and we can't find it. + ErrorCodeNoSuchContainer = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOSUCHCONTAINER", + Message: "no such id: %s", + Description: "The specified container can not be found", + HTTPStatusCode: http.StatusNotFound, + }) + + // ErrorCodeUnregisteredContainer is generated when we try to load + // a storage driver for an unregistered container + ErrorCodeUnregisteredContainer = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "UNREGISTEREDCONTAINER", + Message: "Can't load storage driver for unregistered container %s", + Description: "An attempt was made to load the storage driver for a container that is not registered with the daemon", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeContainerBeingRemoved is generated when an attempt to start + // a container is made but its in the process of being removed, or is dead. + ErrorCodeContainerBeingRemoved = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "CONTAINERBEINGREMOVED", + Message: "Container is marked for removal and cannot be started.", + Description: "An attempt was made to start a container that is in the process of being deleted", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeUnpauseContainer is generated when we attempt to stop a + // container but its paused. + ErrorCodeUnpauseContainer = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "UNPAUSECONTAINER", + Message: "Container %s is paused. Unpause the container before stopping", + Description: "The specified container is paused, before it can be stopped it must be unpaused", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeAlreadyPaused is generated when we attempt to pause a + // container when its already paused. + ErrorCodeAlreadyPaused = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "ALREADYPAUSED", + Message: "Container %s is already paused", + Description: "The specified container is already in the paused state", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNotPaused is generated when we attempt to unpause a + // container when its not paused. + ErrorCodeNotPaused = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOTPAUSED", + Message: "Container %s is not paused", + Description: "The specified container can not be unpaused because it is not in a paused state", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeImageUnregContainer is generated when we attempt to get the + // image of an unknown/unregistered container. + ErrorCodeImageUnregContainer = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "IMAGEUNREGCONTAINER", + Message: "Can't get image of unregistered container", + Description: "An attempt to retrieve the image of a container was made but the container is not registered", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeEmptyID is generated when an ID is the emptry string. + ErrorCodeEmptyID = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EMPTYID", + Message: "Invalid empty id", + Description: "An attempt was made to register a container but the container's ID can not be an empty string", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeLoggingFactory is generated when we could not load the + // log driver. + ErrorCodeLoggingFactory = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "LOGGINGFACTORY", + Message: "Failed to get logging factory: %v", + Description: "An attempt was made to register a container but the container's ID can not be an empty string", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeInitLogger is generated when we could not initialize + // the logging driver. + ErrorCodeInitLogger = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "INITLOGGER", + Message: "Failed to initialize logging driver: %v", + Description: "An error occurred while trying to initialize the logging driver", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNotRunning is generated when we need to verify that + // a container is running, but its not. + ErrorCodeNotRunning = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOTRUNNING", + Message: "Container %s is not running", + Description: "The specified action can not be taken due to the container not being in a running state", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeLinkNotRunning is generated when we try to link to a + // container that is not running. + ErrorCodeLinkNotRunning = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "LINKNOTRUNNING", + Message: "Cannot link to a non running container: %s AS %s", + Description: "An attempt was made to link to a container but the container is not in a running state", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeDeviceInfo is generated when there is an error while trying + // to get info about a custom device. + // container that is not running. + ErrorCodeDeviceInfo = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "DEVICEINFO", + Message: "error gathering device information while adding custom device %q: %s", + Description: "There was an error while trying to retrieve the information about a custom device", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeEmptyEndpoint is generated when the endpoint for a port + // map is nil. + ErrorCodeEmptyEndpoint = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EMPTYENDPOINT", + Message: "invalid endpoint while building port map info", + Description: "The specified endpoint for the port mapping is empty", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeEmptyNetwork is generated when the networkSettings for a port + // map is nil. + ErrorCodeEmptyNetwork = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EMPTYNETWORK", + Message: "invalid networksettings while building port map info", + Description: "The specified endpoint for the port mapping is empty", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeParsingPort is generated when there is an error parsing + // a "port" string. + ErrorCodeParsingPort = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "PARSINGPORT", + Message: "Error parsing Port value(%v):%v", + Description: "There was an error while trying to parse the specified 'port' value", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNoSandbox is generated when we can't find the specified + // sandbox(network) by ID. + ErrorCodeNoSandbox = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOSANDBOX", + Message: "error locating sandbox id %s: %v", + Description: "There was an error trying to located the specified networking sandbox", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNetworkUpdate is generated when there is an error while + // trying update a network/sandbox config. + ErrorCodeNetworkUpdate = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NETWORKUPDATE", + Message: "Update network failed: %v", + Description: "There was an error trying to update the configuration information of the specified network sandbox", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNetworkRefresh is generated when there is an error while + // trying refresh a network/sandbox config. + ErrorCodeNetworkRefresh = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NETWORKREFRESH", + Message: "Update network failed: Failure in refresh sandbox %s: %v", + Description: "There was an error trying to refresh the configuration information of the specified network sandbox", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeHostPort is generated when there was an error while trying + // to parse a "host/port" string. + ErrorCodeHostPort = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "HOSTPORT", + Message: "Error parsing HostPort value(%s):%v", + Description: "There was an error trying to parse the specified 'HostPort' value", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNetworkConflict is generated when we try to publish a service + // in network mode. + ErrorCodeNetworkConflict = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NETWORKCONFLICT", + Message: "conflicting options: publishing a service and network mode", + Description: "It is not possible to publish a service when it is in network mode", + HTTPStatusCode: http.StatusConflict, + }) + + // ErrorCodeJoinInfo is generated when we failed to update a container's + // join info. + ErrorCodeJoinInfo = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "JOININFO", + Message: "Updating join info failed: %v", + Description: "There was an error during an attempt update a container's join information", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeIPCRunning is generated when we try to join a container's + // IPC but its not running. + ErrorCodeIPCRunning = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "IPCRUNNING", + Message: "cannot join IPC of a non running container: %s", + Description: "An attempt was made to join the IPC of a container, but the container is not running", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNotADir is generated when we try to create a directory + // but the path isn't a dir. + ErrorCodeNotADir = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOTADIR", + Message: "Cannot mkdir: %s is not a directory", + Description: "An attempt was made create a directory, but the location in which it is being created is not a directory", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeParseContainer is generated when the reference to a + // container doesn't include a ":" (another container). + ErrorCodeParseContainer = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "PARSECONTAINER", + Message: "no container specified to join network", + Description: "The specified reference to a container is missing a ':' as a separator between 'container' and 'name'/'id'", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeJoinSelf is generated when we try to network to ourselves. + ErrorCodeJoinSelf = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "JOINSELF", + Message: "cannot join own network", + Description: "An attempt was made to have a container join its own network", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeJoinRunning is generated when we try to network to ourselves. + ErrorCodeJoinRunning = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "JOINRUNNING", + Message: "cannot join network of a non running container: %s", + Description: "An attempt to join the network of a container, but that container isn't running", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeModeNotContainer is generated when we try to network to + // another container but the mode isn't 'container'. + ErrorCodeModeNotContainer = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "MODENOTCONTAINER", + Message: "network mode not set to container", + Description: "An attempt was made to connect to a container's network but the mode wasn't set to 'container'", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRemovingVolume is generated when we try remove a mount + // point (volume) but fail. + ErrorCodeRemovingVolume = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "REMOVINGVOLUME", + Message: "Error removing volumes:\n%v", + Description: "There was an error while trying to remove the mount point (volume) of a container", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeInvalidNetworkMode is generated when an invalid network + // mode value is specified. + ErrorCodeInvalidNetworkMode = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "INVALIDNETWORKMODE", + Message: "invalid network mode: %s", + Description: "The specified networking mode is not valid", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeGetGraph is generated when there was an error while + // trying to find a graph/image. + ErrorCodeGetGraph = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "GETGRAPH", + Message: "Failed to graph.Get on ImageID %s - %s", + Description: "There was an error trying to retrieve the image for the specified image ID", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeGetLayer is generated when there was an error while + // trying to retrieve a particular layer of an image. + ErrorCodeGetLayer = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "GETLAYER", + Message: "Failed to get layer path from graphdriver %s for ImageID %s - %s", + Description: "There was an error trying to retrieve the layer of the specified image", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodePutLayer is generated when there was an error while + // trying to 'put' a particular layer of an image. + ErrorCodePutLayer = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "PUTLAYER", + Message: "Failed to put layer path from graphdriver %s for ImageID %s - %s", + Description: "There was an error trying to store a layer for the specified image", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeGetLayerMetadata is generated when there was an error while + // trying to retrieve the metadata of a layer of an image. + ErrorCodeGetLayerMetadata = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "GETLAYERMETADATA", + Message: "Failed to get layer metadata - %s", + Description: "There was an error trying to retrieve the metadata of a layer for the specified image", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeEmptyConfig is generated when the input config data + // is empty. + ErrorCodeEmptyConfig = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EMPTYCONFIG", + Message: "Config cannot be empty in order to create a container", + Description: "While trying to create a container, the specified configuration information was empty", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNoSuchImageHash is generated when we can't find the + // specified image by its hash + ErrorCodeNoSuchImageHash = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOSUCHIMAGEHASH", + Message: "No such image: %s", + Description: "An attempt was made to find an image by its hash, but the lookup failed", + HTTPStatusCode: http.StatusNotFound, + }) + + // ErrorCodeNoSuchImageTag is generated when we can't find the + // specified image byt its name/tag. + ErrorCodeNoSuchImageTag = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOSUCHIMAGETAG", + Message: "No such image: %s:%s", + Description: "An attempt was made to find an image by its name/tag, but the lookup failed", + HTTPStatusCode: http.StatusNotFound, + }) + + // ErrorCodeMountOverFile is generated when we try to mount a volume + // over an existing file (but not a dir). + ErrorCodeMountOverFile = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "MOUNTOVERFILE", + Message: "cannot mount volume over existing file, file exists %s", + Description: "An attempt was made to mount a volume at the same location as a pre-existing file", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeMountSetup is generated when we can't define a mount point + // due to the source and destination being undefined. + ErrorCodeMountSetup = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "MOUNTSETUP", + Message: "Unable to setup mount point, neither source nor volume defined", + Description: "An attempt was made to setup a mount point, but the source and destination are undefined", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeInvalidMode is generated when we the mode of a volume/bind + // mount is invalid. + ErrorCodeVolumeInvalidMode = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMEINVALIDMODE", + Message: "invalid mode: %s", + Description: "An invalid 'mode' was specified", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeInvalid is generated when the format fo the + // volume specification isn't valid. + ErrorCodeVolumeInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMEINVALID", + Message: "Invalid volume specification: %s", + Description: "An invalid 'volume' was specified in the mount request", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeAbs is generated when path to a volume isn't absolute. + ErrorCodeVolumeAbs = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMEABS", + Message: "Invalid volume destination path: %s mount path must be absolute.", + Description: "An invalid 'destination' path was specified in the mount request, it must be an absolute path", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeName is generated when the name of named volume isn't valid. + ErrorCodeVolumeName = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUME_NAME_INVALID", + Message: "%s includes invalid characters for a local volume name, only %s are allowed", + Description: "The name of volume is invalid", + HTTPStatusCode: http.StatusBadRequest, + }) + + // ErrorCodeVolumeSlash is generated when destination path to a volume is / + ErrorCodeVolumeSlash = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMESLASH", + Message: "Invalid specification: destination can't be '/' in '%s'", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeDestIsC is generated the destination is c: (Windows specific) + ErrorCodeVolumeDestIsC = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMEDESTISC", + Message: "Destination drive letter in '%s' cannot be c:", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeDestIsCRoot is generated the destination path is c:\ (Windows specific) + ErrorCodeVolumeDestIsCRoot = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMEDESTISCROOT", + Message: `Destination path in '%s' cannot be c:\`, + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeSourceNotFound is generated the source directory could not be found (Windows specific) + ErrorCodeVolumeSourceNotFound = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMESOURCENOTFOUND", + Message: "Source directory '%s' could not be found: %v", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeSourceNotDirectory is generated the source is not a directory (Windows specific) + ErrorCodeVolumeSourceNotDirectory = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMESOURCENOTDIRECTORY", + Message: "Source '%s' is not a directory", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeFromBlank is generated when path to a volume is blank. + ErrorCodeVolumeFromBlank = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMEFROMBLANK", + Message: "malformed volumes-from specification: %s", + Description: "An invalid 'destination' path was specified in the mount request, it must not be blank", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeDup is generated when we try to mount two volumes + // to the same path. + ErrorCodeVolumeDup = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMEDUP", + Message: "Duplicate bind mount %s", + Description: "An attempt was made to mount a volume but the specified destination location is already used in a previous mount", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeNoSourceForMount is generated when no source directory + // for a volume mount was found. (Windows specific) + ErrorCodeVolumeNoSourceForMount = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMENOSOURCEFORMOUNT", + Message: "No source for mount name %q driver %q destination %s", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeVolumeNameReservedWord is generated when the name in a volume + // uses a reserved word for filenames. (Windows specific) + ErrorCodeVolumeNameReservedWord = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUMENAMERESERVEDWORD", + Message: "Volume name %q cannot be a reserved word for Windows filenames", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeCantUnpause is generated when there's an error while trying + // to unpause a container. + ErrorCodeCantUnpause = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "CANTUNPAUSE", + Message: "Cannot unpause container %s: %s", + Description: "An error occurred while trying to unpause the specified container", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodePSError is generated when trying to run 'ps'. + ErrorCodePSError = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "PSError", + Message: "Error running ps: %s", + Description: "There was an error trying to run the 'ps' command in the specified container", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNoPID is generated when looking for the PID field in the + // ps output. + ErrorCodeNoPID = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOPID", + Message: "Couldn't find PID field in ps output", + Description: "There was no 'PID' field in the output of the 'ps' command that was executed", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeBadPID is generated when we can't convert a PID to an int. + ErrorCodeBadPID = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "BADPID", + Message: "Unexpected pid '%s': %s", + Description: "While trying to parse the output of the 'ps' command, the 'PID' field was not an integer", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNoTop is generated when we try to run 'top' but can't + // because we're on windows. + ErrorCodeNoTop = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOTOP", + Message: "Top is not supported on Windows", + Description: "The 'top' command is not supported on Windows", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeStopped is generated when we try to stop a container + // that is already stopped. + ErrorCodeStopped = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "STOPPED", + Message: "Container already stopped", + Description: "An attempt was made to stop a container, but the container is already stopped", + HTTPStatusCode: http.StatusNotModified, + }) + + // ErrorCodeCantStop is generated when we try to stop a container + // but failed for some reason. + ErrorCodeCantStop = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "CANTSTOP", + Message: "Cannot stop container %s: %s\n", + Description: "An error occurred while tring to stop the specified container", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeBadCPUFields is generated when the number of CPU fields is + // less than 8. + ErrorCodeBadCPUFields = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "BADCPUFIELDS", + Message: "invalid number of cpu fields", + Description: "While reading the '/proc/stat' file, the number of 'cpu' fields is less than 8", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeBadCPUInt is generated the CPU field can't be parsed as an int. + ErrorCodeBadCPUInt = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "BADCPUINT", + Message: "Unable to convert value %s to int: %s", + Description: "While reading the '/proc/stat' file, the 'CPU' field could not be parsed as an integer", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeBadStatFormat is generated the output of the stat info + // isn't parseable. + ErrorCodeBadStatFormat = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "BADSTATFORMAT", + Message: "invalid stat format", + Description: "There was an error trying to parse the '/proc/stat' file", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeTimedOut is generated when a timer expires. + ErrorCodeTimedOut = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "TIMEDOUT", + Message: "Timed out: %v", + Description: "A timer expired", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeAlreadyRemoving is generated when we try to remove a + // container that is already being removed. + ErrorCodeAlreadyRemoving = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "ALREADYREMOVING", + Message: "Status is already RemovalInProgress", + Description: "An attempt to remove a container was made, but the container is already in the process of being removed", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeStartPaused is generated when we start a paused container. + ErrorCodeStartPaused = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "STARTPAUSED", + Message: "Cannot start a paused container, try unpause instead.", + Description: "An attempt to start a container was made, but the container is paused. Unpause it first", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeAlreadyStarted is generated when we try to start a container + // that is already running. + ErrorCodeAlreadyStarted = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "ALREADYSTARTED", + Message: "Container already started", + Description: "An attempt to start a container was made, but the container is already started", + HTTPStatusCode: http.StatusNotModified, + }) + + // ErrorCodeHostConfigStart is generated when a HostConfig is passed + // into the start command. + ErrorCodeHostConfigStart = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "HOSTCONFIGSTART", + Message: "Supplying a hostconfig on start is not supported. It should be supplied on create", + Description: "The 'start' command does not accept 'HostConfig' data, try using the 'create' command instead", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeCantStart is generated when an error occurred while + // trying to start a container. + ErrorCodeCantStart = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "CANTSTART", + Message: "Cannot start container %s: %s", + Description: "There was an error while trying to start a container", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeCantRestart is generated when an error occurred while + // trying to restart a container. + ErrorCodeCantRestart = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "CANTRESTART", + Message: "Cannot restart container %s: %s", + Description: "There was an error while trying to restart a container", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeEmptyRename is generated when one of the names on a + // rename is empty. + ErrorCodeEmptyRename = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EMPTYRENAME", + Message: "Neither old nor new names may be empty", + Description: "An attempt was made to rename a container but either the old or new names were blank", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRenameTaken is generated when we try to rename but the + // new name isn't available. + ErrorCodeRenameTaken = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RENAMETAKEN", + Message: "Error when allocating new name: %s", + Description: "The new name specified on the 'rename' command is already being used", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRenameDelete is generated when we try to rename but + // failed trying to delete the old container. + ErrorCodeRenameDelete = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RENAMEDELETE", + Message: "Failed to delete container %q: %v", + Description: "There was an error trying to delete the specified container", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodePauseError is generated when we try to pause a container + // but failed. + ErrorCodePauseError = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "PAUSEERROR", + Message: "Cannot pause container %s: %s", + Description: "There was an error trying to pause the specified container", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNeedStream is generated when we try to stream a container's + // logs but no output stream was specified. + ErrorCodeNeedStream = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NEEDSTREAM", + Message: "You must choose at least one stream", + Description: "While trying to stream a container's logs, no output stream was specified", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeDanglingOne is generated when we try to specify more than one + // 'dangling' specifier. + ErrorCodeDanglingOne = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "DANLGINGONE", + Message: "Conflict: cannot use more than 1 value for `dangling` filter", + Description: "The specified 'dangling' filter may not have more than one value", + HTTPStatusCode: http.StatusConflict, + }) + + // ErrorCodeImgDelUsed is generated when we try to delete an image + // but it is being used. + ErrorCodeImgDelUsed = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "IMGDELUSED", + Message: "conflict: unable to remove repository reference %q (must force) - container %s is using its referenced image %s", + Description: "An attempt was made to delete an image but it is currently being used", + HTTPStatusCode: http.StatusConflict, + }) + + // ErrorCodeImgNoParent is generated when we try to find an image's + // parent but its not in the graph. + ErrorCodeImgNoParent = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "IMGNOPARENT", + Message: "unable to get parent image: %v", + Description: "There was an error trying to find an image's parent, it was not in the graph", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeExportFailed is generated when an export fails. + ErrorCodeExportFailed = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EXPORTFAILED", + Message: "%s: %s", + Description: "There was an error during an export operation", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeExecResize is generated when we try to resize an exec + // but its not running. + ErrorCodeExecResize = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EXECRESIZE", + Message: "Exec %s is not running, so it can not be resized.", + Description: "An attempt was made to resize an 'exec', but the 'exec' is not running", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeContainerNotRunning is generated when we try to get the info + // on an exec but the container is not running. + ErrorCodeContainerNotRunning = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "CONTAINERNOTRUNNING", + Message: "Container %s is not running: %s", + Description: "An attempt was made to retrieve the information about an 'exec' but the container is not running", + HTTPStatusCode: http.StatusConflict, + }) + + // ErrorCodeNoExecID is generated when we try to get the info + // on an exec but it can't be found. + ErrorCodeNoExecID = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOEXECID", + Message: "No such exec instance '%s' found in daemon", + Description: "The specified 'exec' instance could not be found", + HTTPStatusCode: http.StatusNotFound, + }) + + // ErrorCodeExecPaused is generated when we try to start an exec + // but the container is paused. + ErrorCodeExecPaused = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EXECPAUSED", + Message: "Container %s is paused, unpause the container before exec", + Description: "An attempt to start an 'exec' was made, but the owning container is paused", + HTTPStatusCode: http.StatusConflict, + }) + + // ErrorCodeExecRunning is generated when we try to start an exec + // but its already running. + ErrorCodeExecRunning = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EXECRUNNING", + Message: "Error: Exec command %s is already running", + Description: "An attempt to start an 'exec' was made, but 'exec' is already running", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeExecCantRun is generated when we try to start an exec + // but it failed for some reason. + ErrorCodeExecCantRun = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EXECCANTRUN", + Message: "Cannot run exec command %s in container %s: %s", + Description: "An attempt to start an 'exec' was made, but an error occurred", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeExecAttach is generated when we try to attach to an exec + // but failed. + ErrorCodeExecAttach = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EXECATTACH", + Message: "attach failed with error: %s", + Description: "There was an error while trying to attach to an 'exec'", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeExecContainerStopped is generated when we try to start + // an exec but then the container stopped. + ErrorCodeExecContainerStopped = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "EXECCONTAINERSTOPPED", + Message: "container stopped while running exec", + Description: "An attempt was made to start an 'exec' but the owning container is in the 'stopped' state", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeDefaultName is generated when we try to delete the + // default name of a container. + ErrorCodeDefaultName = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "DEFAULTNAME", + Message: "Conflict, cannot remove the default name of the container", + Description: "An attempt to delete the default name of a container was made, but that is not allowed", + HTTPStatusCode: http.StatusConflict, + }) + + // ErrorCodeNoParent is generated when we try to delete a container + // but we can't find its parent image. + ErrorCodeNoParent = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOPARENT", + Message: "Cannot get parent %s for name %s", + Description: "An attempt was made to delete a container but its parent image could not be found", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeCantDestroy is generated when we try to delete a container + // but failed for some reason. + ErrorCodeCantDestroy = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "CANTDESTROY", + Message: "Cannot destroy container %s: %v", + Description: "An attempt was made to delete a container but it failed", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRmRunning is generated when we try to delete a container + // but its still running. + ErrorCodeRmRunning = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RMRUNNING", + Message: "Conflict, You cannot remove a running container. Stop the container before attempting removal or use -f", + Description: "An attempt was made to delete a container but the container is still running, try to either stop it first or use '-f'", + HTTPStatusCode: http.StatusConflict, + }) + + // ErrorCodeRmFailed is generated when we try to delete a container + // but it failed for some reason. + ErrorCodeRmFailed = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RMFAILED", + Message: "Could not kill running container, cannot remove - %v", + Description: "An error occurred while trying to delete a running container", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRmNotFound is generated when we try to delete a container + // but couldn't find it. + ErrorCodeRmNotFound = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RMNOTFOUND", + Message: "Could not kill running container, cannot remove - %v", + Description: "An attempt to delete a container was made but the container could not be found", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRmState is generated when we try to delete a container + // but couldn't set its state to RemovalInProgress. + ErrorCodeRmState = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RMSTATE", + Message: "Failed to set container state to RemovalInProgress: %s", + Description: "An attempt to delete a container was made, but there as an error trying to set its state to 'RemovalInProgress'", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRmDriverFS is generated when we try to delete a container + // but the driver failed to delete its filesystem. + ErrorCodeRmDriverFS = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RMDRIVERFS", + Message: "Driver %s failed to remove root filesystem %s: %s", + Description: "While trying to delete a container, the driver failed to remove the root filesystem", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRmInit is generated when we try to delete a container + // but failed deleting its init filesystem. + ErrorCodeRmInit = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RMINIT", + Message: "Driver %s failed to remove init filesystem %s: %s", + Description: "While trying to delete a container, the driver failed to remove the init filesystem", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRmFS is generated when we try to delete a container + // but failed deleting its filesystem. + ErrorCodeRmFS = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RMFS", + Message: "Unable to remove filesystem for %v: %v", + Description: "While trying to delete a container, the driver failed to remove the filesystem", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRmExecDriver is generated when we try to delete a container + // but failed deleting its exec driver data. + ErrorCodeRmExecDriver = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RMEXECDRIVER", + Message: "Unable to remove execdriver data for %s: %s", + Description: "While trying to delete a container, there was an error trying to remove th exec driver data", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeRmVolumeInUse is generated when we try to delete a container + // but failed deleting a volume because its being used. + ErrorCodeRmVolumeInUse = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RMVOLUMEINUSE", + Message: "Conflict: %v", + Description: "While trying to delete a container, one of its volumes is still being used", + HTTPStatusCode: http.StatusConflict, + }) + + // ErrorCodeRmVolume is generated when we try to delete a container + // but failed deleting a volume. + ErrorCodeRmVolume = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "RMVOLUME", + Message: "Error while removing volume %s: %v", + Description: "While trying to delete a container, there was an error trying to delete one of its volumes", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeInvalidCpusetCpus is generated when user provided cpuset CPUs + // are invalid. + ErrorCodeInvalidCpusetCpus = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "INVALIDCPUSETCPUS", + Message: "Invalid value %s for cpuset cpus.", + Description: "While verifying the container's 'HostConfig', CpusetCpus value was in an incorrect format", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeInvalidCpusetMems is generated when user provided cpuset mems + // are invalid. + ErrorCodeInvalidCpusetMems = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "INVALIDCPUSETMEMS", + Message: "Invalid value %s for cpuset mems.", + Description: "While verifying the container's 'HostConfig', CpusetMems value was in an incorrect format", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNotAvailableCpusetCpus is generated when user provided cpuset + // CPUs aren't available in the container's cgroup. + ErrorCodeNotAvailableCpusetCpus = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOTAVAILABLECPUSETCPUS", + Message: "Requested CPUs are not available - requested %s, available: %s.", + Description: "While verifying the container's 'HostConfig', cpuset CPUs provided aren't available in the container's cgroup available set", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeNotAvailableCpusetMems is generated when user provided cpuset + // memory nodes aren't available in the container's cgroup. + ErrorCodeNotAvailableCpusetMems = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NOTAVAILABLECPUSETMEMS", + Message: "Requested memory nodes are not available - requested %s, available: %s.", + Description: "While verifying the container's 'HostConfig', cpuset memory nodes provided aren't available in the container's cgroup available set", + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorVolumeNameTaken is generated when an error occurred while + // trying to create a volume that has existed using different driver. + ErrorVolumeNameTaken = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUME_NAME_TAKEN", + Message: "A volume name %s already exists with the %s driver. Choose a different volume name.", + Description: "An attempt to create a volume using a driver but the volume already exists with a different driver", + HTTPStatusCode: http.StatusInternalServerError, + }) +) diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/error.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/error.go new file mode 100644 index 000000000..37222d443 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/error.go @@ -0,0 +1,6 @@ +package errors + +// This file contains all of the errors that can be generated from the +// docker engine but are not tied to any specific top-level component. + +const errGroup = "engine" diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/image.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/image.go new file mode 100644 index 000000000..c104b1bb7 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/image.go @@ -0,0 +1,20 @@ +package errors + +// This file contains all of the errors that can be generated from the +// docker/image component. + +import ( + "net/http" + + "github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode" +) + +var ( + // ErrorCodeInvalidImageID is generated when image id specified is incorrectly formatted. + ErrorCodeInvalidImageID = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "INVALIDIMAGEID", + Message: "image ID '%s' is invalid ", + Description: "The specified image id is incorrectly formatted", + HTTPStatusCode: http.StatusInternalServerError, + }) +) diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/server.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/server.go new file mode 100644 index 000000000..580b47f74 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors/server.go @@ -0,0 +1,36 @@ +package errors + +import ( + "net/http" + + "github.com/fsouza/go-dockerclient/external/github.com/docker/distribution/registry/api/errcode" +) + +var ( + // ErrorCodeNewerClientVersion is generated when a request from a client + // specifies a higher version than the server supports. + ErrorCodeNewerClientVersion = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NEWERCLIENTVERSION", + Message: "client is newer than server (client API version: %s, server API version: %s)", + Description: "The client version is higher than the server version", + HTTPStatusCode: http.StatusBadRequest, + }) + + // ErrorCodeOldClientVersion is generated when a request from a client + // specifies a version lower than the minimum version supported by the server. + ErrorCodeOldClientVersion = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "OLDCLIENTVERSION", + Message: "client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", + Description: "The client version is too old for the server", + HTTPStatusCode: http.StatusBadRequest, + }) + + // ErrorNetworkControllerNotEnabled is generated when the networking stack in not enabled + // for certain platforms, like windows. + ErrorNetworkControllerNotEnabled = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NETWORK_CONTROLLER_NOT_ENABLED", + Message: "the network controller is not enabled for this platform", + Description: "Docker's networking stack is disabled for this platform", + HTTPStatusCode: http.StatusNotFound, + }) +) diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/envfile.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/envfile.go index b854227e8..ba8b4f201 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/envfile.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/envfile.go @@ -4,18 +4,22 @@ import ( "bufio" "fmt" "os" - "regexp" "strings" ) -var ( - // EnvironmentVariableRegexp A regexp to validate correct environment variables - // Environment variables set by the user must have a name consisting solely of - // alphabetics, numerics, and underscores - the first of which must not be numeric. - EnvironmentVariableRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$") -) - -// ParseEnvFile Read in a line delimited file with environment variables enumerated +// ParseEnvFile reads a file with environment variables enumerated by lines +// +// ``Environment variable names used by the utilities in the Shell and +// Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase +// letters, digits, and the '_' (underscore) from the characters defined in +// Portable Character Set and do not begin with a digit. *But*, other +// characters may be permitted by an implementation; applications shall +// tolerate the presence of such names.'' +// -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html +// +// As of #16585, it's up to application inside docker to validate or not +// environment variables, that's why we just strip leading whitespace and +// nothing more. func ParseEnvFile(filename string) ([]string, error) { fh, err := os.Open(filename) if err != nil { @@ -26,17 +30,18 @@ func ParseEnvFile(filename string) ([]string, error) { lines := []string{} scanner := bufio.NewScanner(fh) for scanner.Scan() { - line := scanner.Text() + // trim the line from all leading whitespace first + line := strings.TrimLeft(scanner.Text(), whiteSpaces) // line is not empty, and not starting with '#' if len(line) > 0 && !strings.HasPrefix(line, "#") { data := strings.SplitN(line, "=", 2) // trim the front of a variable, but nothing else variable := strings.TrimLeft(data[0], whiteSpaces) - - if !EnvironmentVariableRegexp.MatchString(variable) { - return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' is not a valid environment variable", variable)} + if strings.ContainsAny(variable, whiteSpaces) { + return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' has white spaces", variable)} } + if len(data) > 1 { // pass the value through, no trimming diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/envfile_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/envfile_test.go index cd0ca8f32..a172267b5 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/envfile_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/envfile_test.go @@ -28,8 +28,15 @@ func TestParseEnvFileGoodFile(t *testing.T) { # comment _foobar=foobaz +with.dots=working +and_underscore=working too ` - + // Adding a newline + a line with pure whitespace. + // This is being done like this instead of the block above + // because it's common for editors to trim trailing whitespace + // from lines, which becomes annoying since that's the + // exact thing we need to test. + content += "\n \t " tmpFile := tmpFileWithContent(content, t) defer os.Remove(tmpFile) @@ -42,6 +49,8 @@ _foobar=foobaz "foo=bar", "baz=quux", "_foobar=foobaz", + "with.dots=working", + "and_underscore=working too", } if !reflect.DeepEqual(lines, expectedLines) { @@ -91,7 +100,7 @@ func TestParseEnvFileBadlyFormattedFile(t *testing.T) { if _, ok := err.(ErrBadEnvVariable); !ok { t.Fatalf("Expected a ErrBadEnvVariable, got [%v]", err) } - expectedMessage := "poorly formatted environment: variable 'f ' is not a valid environment variable" + expectedMessage := "poorly formatted environment: variable 'f ' has white spaces" if err.Error() != expectedMessage { t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error()) } @@ -126,7 +135,7 @@ another invalid line` if _, ok := err.(ErrBadEnvVariable); !ok { t.Fatalf("Expected a ErrBadEnvvariable, got [%v]", err) } - expectedMessage := "poorly formatted environment: variable 'first line' is not a valid environment variable" + expectedMessage := "poorly formatted environment: variable 'first line' has white spaces" if err.Error() != expectedMessage { t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error()) } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/hosts_unix.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/hosts_unix.go index a29335e60..611407a9d 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/hosts_unix.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/hosts_unix.go @@ -4,4 +4,5 @@ package opts import "fmt" +// DefaultHost constant defines the default host string used by docker on other hosts than Windows var DefaultHost = fmt.Sprintf("unix://%s", DefaultUnixSocket) diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/hosts_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/hosts_windows.go index 55eac2aac..ec52e9a70 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/hosts_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/hosts_windows.go @@ -2,6 +2,5 @@ package opts -import "fmt" - -var DefaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) +// DefaultHost constant defines the default host string used by docker on Windows +var DefaultHost = DefaultTCPHost diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ip.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ip.go index b1f958755..d787b56ca 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ip.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ip.go @@ -5,20 +5,25 @@ import ( "net" ) -// IpOpt type that hold an IP -type IpOpt struct { +// IPOpt holds an IP. It is used to store values from CLI flags. +type IPOpt struct { *net.IP } -func NewIpOpt(ref *net.IP, defaultVal string) *IpOpt { - o := &IpOpt{ +// NewIPOpt creates a new IPOpt from a reference net.IP and a +// string representation of an IP. If the string is not a valid +// IP it will fallback to the specified reference. +func NewIPOpt(ref *net.IP, defaultVal string) *IPOpt { + o := &IPOpt{ IP: ref, } o.Set(defaultVal) return o } -func (o *IpOpt) Set(val string) error { +// Set sets an IPv4 or IPv6 address from a given string. If the given +// string is not parsable as an IP address it returns an error. +func (o *IPOpt) Set(val string) error { ip := net.ParseIP(val) if ip == nil { return fmt.Errorf("%s is not an ip address", val) @@ -27,7 +32,9 @@ func (o *IpOpt) Set(val string) error { return nil } -func (o *IpOpt) String() string { +// String returns the IP address stored in the IPOpt. If stored IP is a +// nil pointer, it returns an empty string. +func (o *IPOpt) String() string { if *o.IP == nil { return "" } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ip_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ip_test.go index b6b526a57..1027d84a0 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ip_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ip_test.go @@ -10,7 +10,7 @@ func TestIpOptString(t *testing.T) { var ip net.IP for _, address := range addresses { - stringAddress := NewIpOpt(&ip, address).String() + stringAddress := NewIPOpt(&ip, address).String() if stringAddress != address { t.Fatalf("IpOpt string should be `%s`, not `%s`", address, stringAddress) } @@ -21,7 +21,7 @@ func TestNewIpOptInvalidDefaultVal(t *testing.T) { ip := net.IPv4(127, 0, 0, 1) defaultVal := "Not an ip" - ipOpt := NewIpOpt(&ip, defaultVal) + ipOpt := NewIPOpt(&ip, defaultVal) expected := "127.0.0.1" if ipOpt.String() != expected { @@ -33,7 +33,7 @@ func TestNewIpOptValidDefaultVal(t *testing.T) { ip := net.IPv4(127, 0, 0, 1) defaultVal := "192.168.1.1" - ipOpt := NewIpOpt(&ip, defaultVal) + ipOpt := NewIPOpt(&ip, defaultVal) expected := "192.168.1.1" if ipOpt.String() != expected { @@ -43,11 +43,11 @@ func TestNewIpOptValidDefaultVal(t *testing.T) { func TestIpOptSetInvalidVal(t *testing.T) { ip := net.IPv4(127, 0, 0, 1) - ipOpt := &IpOpt{IP: &ip} + ipOpt := &IPOpt{IP: &ip} - invalidIp := "invalid ip" + invalidIP := "invalid ip" expectedError := "invalid ip is not an ip address" - err := ipOpt.Set(invalidIp) + err := ipOpt.Set(invalidIP) if err == nil || err.Error() != expectedError { t.Fatalf("Expected an Error with [%v], got [%v]", expectedError, err.Error()) } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/opts.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/opts.go index aa409b99e..df85a09e3 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/opts.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/opts.go @@ -9,36 +9,45 @@ import ( "strings" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/parsers" - "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume" ) var ( alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) - // DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080 - DefaultHTTPHost = "127.0.0.1" - // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker -d -H tcp:// + // DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080 + DefaultHTTPHost = "localhost" + + // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp:// // TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter // is not supplied. A better longer term solution would be to use a named // pipe as the default on the Windows daemon. + // These are the IANA registered port numbers for use with Docker + // see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker DefaultHTTPPort = 2375 // Default HTTP Port + // DefaultTLSHTTPPort Default HTTP Port used when TLS enabled + DefaultTLSHTTPPort = 2376 // Default TLS encrypted HTTP Port // DefaultUnixSocket Path for the unix socket. // Docker daemon by default always listens on the default unix socket DefaultUnixSocket = "/var/run/docker.sock" + // DefaultTCPHost constant defines the default host string used by docker on Windows + DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) + // DefaultTLSHost constant defines the default host string used by docker for TLS sockets + DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort) ) -// ListOpts type that hold a list of values and a validation function. +// ListOpts holds a list of values and a validation function. type ListOpts struct { values *[]string validator ValidatorFctType } -// NewListOpts Create a new ListOpts with the specified validator. +// NewListOpts creates a new ListOpts with the specified validator. func NewListOpts(validator ValidatorFctType) ListOpts { var values []string return *NewListOptsRef(&values, validator) } +// NewListOptsRef creates a new ListOpts with the specified values and validator. func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { return &ListOpts{ values: values, @@ -64,7 +73,7 @@ func (opts *ListOpts) Set(value string) error { return nil } -// Delete remove the given element from the slice. +// Delete removes the specified element from the slice. func (opts *ListOpts) Delete(key string) { for i, k := range *opts.values { if k == key { @@ -76,7 +85,6 @@ func (opts *ListOpts) Delete(key string) { // GetMap returns the content of values in a map in order to avoid // duplicates. -// FIXME: can we remove this? func (opts *ListOpts) GetMap() map[string]struct{} { ret := make(map[string]struct{}) for _, k := range *opts.values { @@ -85,13 +93,12 @@ func (opts *ListOpts) GetMap() map[string]struct{} { return ret } -// GetAll returns the values' slice. -// FIXME: Can we remove this? +// GetAll returns the values of slice. func (opts *ListOpts) GetAll() []string { return (*opts.values) } -// Get checks the existence of the given key. +// Get checks the existence of the specified key. func (opts *ListOpts) Get(key string) bool { for _, k := range *opts.values { if k == key { @@ -106,7 +113,7 @@ func (opts *ListOpts) Len() int { return len((*opts.values)) } -//MapOpts type that holds a map of values and a validation function. +//MapOpts holds a map of values and a validation function. type MapOpts struct { values map[string]string validator ValidatorFctType @@ -131,10 +138,16 @@ func (opts *MapOpts) Set(value string) error { return nil } +// GetAll returns the values of MapOpts as a map. +func (opts *MapOpts) GetAll() map[string]string { + return opts.values +} + func (opts *MapOpts) String() string { return fmt.Sprintf("%v", map[string]string((opts.values))) } +// NewMapOpts creates a new MapOpts with the specified map of values and a validator. func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { if values == nil { values = make(map[string]string) @@ -145,13 +158,13 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { } } -// ValidatorFctType validator that return a validate string and/or an error +// ValidatorFctType defines a validator function that returns a validated string and/or an error. type ValidatorFctType func(val string) (string, error) -// ValidatorFctListType validator that return a validate list of string and/or an error +// ValidatorFctListType defines a validator function that returns a validated list of string and/or an error type ValidatorFctListType func(val string) ([]string, error) -// ValidateAttach Validates that the specified string is a valid attach option. +// ValidateAttach validates that the specified string is a valid attach option. func ValidateAttach(val string) (string, error) { s := strings.ToLower(val) for _, str := range []string{"stdin", "stdout", "stderr"} { @@ -162,7 +175,7 @@ func ValidateAttach(val string) (string, error) { return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR") } -// ValidateLink Validates that the specified string has a valid link format (containerName:alias). +// ValidateLink validates that the specified string has a valid link format (containerName:alias). func ValidateLink(val string) (string, error) { if _, _, err := parsers.ParseLink(val); err != nil { return val, err @@ -170,53 +183,66 @@ func ValidateLink(val string) (string, error) { return val, nil } -// ValidateDevice Validate a path for devices +// ValidDeviceMode checks if the mode for device is valid or not. +// Valid mode is a composition of r (read), w (write), and m (mknod). +func ValidDeviceMode(mode string) bool { + var legalDeviceMode = map[rune]bool{ + 'r': true, + 'w': true, + 'm': true, + } + if mode == "" { + return false + } + for _, c := range mode { + if !legalDeviceMode[c] { + return false + } + legalDeviceMode[c] = false + } + return true +} + +// ValidateDevice validates a path for devices // It will make sure 'val' is in the form: // [host-dir:]container-path[:mode] +// It also validates the device mode. func ValidateDevice(val string) (string, error) { - return validatePath(val, false) + return validatePath(val, ValidDeviceMode) } -// ValidatePath Validate a path for volumes -// It will make sure 'val' is in the form: -// [host-dir:]container-path[:rw|ro] -// It will also validate the mount mode. -func ValidatePath(val string) (string, error) { - return validatePath(val, true) -} - -func validatePath(val string, validateMountMode bool) (string, error) { +func validatePath(val string, validator func(string) bool) (string, error) { var containerPath string var mode string if strings.Count(val, ":") > 2 { - return val, fmt.Errorf("bad format for volumes: %s", val) + return val, fmt.Errorf("bad format for path: %s", val) } - splited := strings.SplitN(val, ":", 3) - if splited[0] == "" { - return val, fmt.Errorf("bad format for volumes: %s", val) + split := strings.SplitN(val, ":", 3) + if split[0] == "" { + return val, fmt.Errorf("bad format for path: %s", val) } - switch len(splited) { + switch len(split) { case 1: - containerPath = splited[0] + containerPath = split[0] val = path.Clean(containerPath) case 2: - if isValid, _ := volume.ValidateMountMode(splited[1]); validateMountMode && isValid { - containerPath = splited[0] - mode = splited[1] + if isValid := validator(split[1]); isValid { + containerPath = split[0] + mode = split[1] val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) } else { - containerPath = splited[1] - val = fmt.Sprintf("%s:%s", splited[0], path.Clean(containerPath)) + containerPath = split[1] + val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) } case 3: - containerPath = splited[1] - mode = splited[2] - if isValid, _ := volume.ValidateMountMode(splited[2]); validateMountMode && !isValid { - return val, fmt.Errorf("bad mount mode specified : %s", mode) + containerPath = split[1] + mode = split[2] + if isValid := validator(split[2]); !isValid { + return val, fmt.Errorf("bad mode specified: %s", mode) } - val = fmt.Sprintf("%s:%s:%s", splited[0], containerPath, mode) + val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) } if !path.IsAbs(containerPath) { @@ -225,24 +251,24 @@ func validatePath(val string, validateMountMode bool) (string, error) { return val, nil } -// ValidateEnv Validate an environment variable and returns it -// It will use EnvironmentVariableRegexp to ensure the name of the environment variable is valid. +// ValidateEnv validates an environment variable and returns it. // If no value is specified, it returns the current value using os.Getenv. +// +// As on ParseEnvFile and related to #16585, environment variable names +// are not validate what so ever, it's up to application inside docker +// to validate them or not. func ValidateEnv(val string) (string, error) { arr := strings.Split(val, "=") if len(arr) > 1 { return val, nil } - if !EnvironmentVariableRegexp.MatchString(arr[0]) { - return val, ErrBadEnvVariable{fmt.Sprintf("variable '%s' is not a valid environment variable", val)} - } if !doesEnvExist(val) { return val, nil } return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil } -// ValidateIPAddress Validates an Ip address +// ValidateIPAddress validates an Ip address. func ValidateIPAddress(val string) (string, error) { var ip = net.ParseIP(strings.TrimSpace(val)) if ip != nil { @@ -251,7 +277,7 @@ func ValidateIPAddress(val string) (string, error) { return "", fmt.Errorf("%s is not an ip address", val) } -// ValidateMACAddress Validates a MAC address +// ValidateMACAddress validates a MAC address. func ValidateMACAddress(val string) (string, error) { _, err := net.ParseMAC(strings.TrimSpace(val)) if err != nil { @@ -260,8 +286,8 @@ func ValidateMACAddress(val string) (string, error) { return val, nil } -// ValidateDNSSearch Validates domain for resolvconf search configuration. -// A zero length domain is represented by . +// ValidateDNSSearch validates domain for resolvconf search configuration. +// A zero length domain is represented by a dot (.). func ValidateDNSSearch(val string) (string, error) { if val = strings.Trim(val, " "); val == "." { return val, nil @@ -280,8 +306,8 @@ func validateDomain(val string) (string, error) { return "", fmt.Errorf("%s is not a valid domain", val) } -// ValidateExtraHost Validate that the given string is a valid extrahost and returns it -// ExtraHost are in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6) +// ValidateExtraHost validates that the specified string is a valid extrahost and returns it. +// ExtraHost are in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6). func ValidateExtraHost(val string) (string, error) { // allow for IPv6 addresses in extra hosts by only splitting on first ":" arr := strings.SplitN(val, ":", 2) @@ -294,8 +320,8 @@ func ValidateExtraHost(val string) (string, error) { return val, nil } -// ValidateLabel Validate that the given string is a valid label, and returns it -// Labels are in the form on key=value +// ValidateLabel validates that the specified string is a valid label, and returns it. +// Labels are in the form on key=value. func ValidateLabel(val string) (string, error) { if strings.Count(val, "=") < 1 { return "", fmt.Errorf("bad attribute format: %s", val) @@ -303,9 +329,20 @@ func ValidateLabel(val string) (string, error) { return val, nil } -// ValidateHost Validate that the given string is a valid host and returns it +// ValidateHost validates that the specified string is a valid host and returns it. func ValidateHost(val string) (string, error) { - host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val) + _, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val) + if err != nil { + return val, err + } + // Note: unlike most flag validators, we don't return the mutated value here + // we need to know what the user entered later (using ParseHost) to adjust for tls + return val, nil +} + +// ParseHost and set defaults for a Daemon host string +func ParseHost(defaultHost, val string) (string, error) { + host, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val) if err != nil { return val, err } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/opts_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/opts_test.go index f08df30be..e02d3f8ea 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/opts_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/opts_test.go @@ -3,6 +3,7 @@ package opts import ( "fmt" "os" + "runtime" "strings" "testing" ) @@ -273,58 +274,6 @@ func TestValidateLink(t *testing.T) { } } -func TestValidatePath(t *testing.T) { - valid := []string{ - "/home", - "/home:/home", - "/home:/something/else", - "/with space", - "/home:/with space", - "relative:/absolute-path", - "hostPath:/containerPath:ro", - "/hostPath:/containerPath:rw", - "/rw:/ro", - "/path:rw", - "/path:ro", - "/rw:rw", - } - invalid := map[string]string{ - "": "bad format for volumes: ", - "./": "./ is not an absolute path", - "../": "../ is not an absolute path", - "/:../": "../ is not an absolute path", - "/:path": "path is not an absolute path", - ":": "bad format for volumes: :", - "/tmp:": " is not an absolute path", - ":test": "bad format for volumes: :test", - ":/test": "bad format for volumes: :/test", - "tmp:": " is not an absolute path", - ":test:": "bad format for volumes: :test:", - "::": "bad format for volumes: ::", - ":::": "bad format for volumes: :::", - "/tmp:::": "bad format for volumes: /tmp:::", - ":/tmp::": "bad format for volumes: :/tmp::", - "path:ro": "path is not an absolute path", - "/path:/path:sw": "bad mount mode specified : sw", - "/path:/path:rwz": "bad mount mode specified : rwz", - } - - for _, path := range valid { - if _, err := ValidatePath(path); err != nil { - t.Fatalf("ValidatePath(`%q`) should succeed: error %q", path, err) - } - } - - for path, expectedError := range invalid { - if _, err := ValidatePath(path); err == nil { - t.Fatalf("ValidatePath(`%q`) should have failed validation", path) - } else { - if err.Error() != expectedError { - t.Fatalf("ValidatePath(`%q`) error should contain %q, got %q", path, expectedError, err.Error()) - } - } - } -} func TestValidateDevice(t *testing.T) { valid := []string{ "/home", @@ -333,27 +282,30 @@ func TestValidateDevice(t *testing.T) { "/with space", "/home:/with space", "relative:/absolute-path", - "hostPath:/containerPath:ro", + "hostPath:/containerPath:r", "/hostPath:/containerPath:rw", "/hostPath:/containerPath:mrw", } invalid := map[string]string{ - "": "bad format for volumes: ", + "": "bad format for path: ", "./": "./ is not an absolute path", "../": "../ is not an absolute path", "/:../": "../ is not an absolute path", "/:path": "path is not an absolute path", - ":": "bad format for volumes: :", + ":": "bad format for path: :", "/tmp:": " is not an absolute path", - ":test": "bad format for volumes: :test", - ":/test": "bad format for volumes: :/test", + ":test": "bad format for path: :test", + ":/test": "bad format for path: :/test", "tmp:": " is not an absolute path", - ":test:": "bad format for volumes: :test:", - "::": "bad format for volumes: ::", - ":::": "bad format for volumes: :::", - "/tmp:::": "bad format for volumes: /tmp:::", - ":/tmp::": "bad format for volumes: :/tmp::", + ":test:": "bad format for path: :test:", + "::": "bad format for path: ::", + ":::": "bad format for path: :::", + "/tmp:::": "bad format for path: /tmp:::", + ":/tmp::": "bad format for path: :/tmp::", "path:ro": "ro is not an absolute path", + "path:rr": "rr is not an absolute path", + "a:/b:ro": "bad mode specified: ro", + "a:/b:rr": "bad mode specified: rr", } for _, path := range valid { @@ -374,35 +326,23 @@ func TestValidateDevice(t *testing.T) { } func TestValidateEnv(t *testing.T) { - invalids := map[string]string{ - "some spaces": "poorly formatted environment: variable 'some spaces' is not a valid environment variable", - "asd!qwe": "poorly formatted environment: variable 'asd!qwe' is not a valid environment variable", - "1asd": "poorly formatted environment: variable '1asd' is not a valid environment variable", - "123": "poorly formatted environment: variable '123' is not a valid environment variable", - } valids := map[string]string{ - "a": "a", - "something": "something", - "_=a": "_=a", - "env1=value1": "env1=value1", - "_env1=value1": "_env1=value1", - "env2=value2=value3": "env2=value2=value3", - "env3=abc!qwe": "env3=abc!qwe", - "env_4=value 4": "env_4=value 4", - "PATH": fmt.Sprintf("PATH=%v", os.Getenv("PATH")), - "PATH=something": "PATH=something", - } - for value, expectedError := range invalids { - _, err := ValidateEnv(value) - if err == nil { - t.Fatalf("Expected ErrBadEnvVariable, got nothing") - } - if _, ok := err.(ErrBadEnvVariable); !ok { - t.Fatalf("Expected ErrBadEnvVariable, got [%s]", err) - } - if err.Error() != expectedError { - t.Fatalf("Expected ErrBadEnvVariable with message [%s], got [%s]", expectedError, err.Error()) - } + "a": "a", + "something": "something", + "_=a": "_=a", + "env1=value1": "env1=value1", + "_env1=value1": "_env1=value1", + "env2=value2=value3": "env2=value2=value3", + "env3=abc!qwe": "env3=abc!qwe", + "env_4=value 4": "env_4=value 4", + "PATH": fmt.Sprintf("PATH=%v", os.Getenv("PATH")), + "PATH=something": "PATH=something", + "asd!qwe": "asd!qwe", + "1asd": "1asd", + "123": "123", + "some space": "some space", + " some space before": " some space before", + "some space after ": "some space after ", } for value, expected := range valids { actual, err := ValidateEnv(value) @@ -432,22 +372,30 @@ func TestValidateLabel(t *testing.T) { } } -func TestValidateHost(t *testing.T) { +func TestParseHost(t *testing.T) { invalid := map[string]string{ "anything": "Invalid bind address format: anything", "something with spaces": "Invalid bind address format: something with spaces", "://": "Invalid bind address format: ://", "unknown://": "Invalid bind address format: unknown://", - "tcp://": "Invalid proto, expected tcp: ", "tcp://:port": "Invalid bind address format: :port", "tcp://invalid": "Invalid bind address format: invalid", "tcp://invalid:port": "Invalid bind address format: invalid:port", } + const defaultHTTPHost = "tcp://127.0.0.1:2375" + var defaultHOST = "unix:///var/run/docker.sock" + + if runtime.GOOS == "windows" { + defaultHOST = defaultHTTPHost + } valid := map[string]string{ + "": defaultHOST, "fd://": "fd://", "fd://something": "fd://something", - "tcp://:2375": "tcp://127.0.0.1:2375", // default ip address - "tcp://:2376": "tcp://127.0.0.1:2376", // default ip address + "tcp://host:": "tcp://host:2375", + "tcp://": "tcp://localhost:2375", + "tcp://:2375": "tcp://localhost:2375", // default ip address + "tcp://:2376": "tcp://localhost:2376", // default ip address "tcp://0.0.0.0:8080": "tcp://0.0.0.0:8080", "tcp://192.168.0.0:12000": "tcp://192.168.0.0:12000", "tcp://192.168:8080": "tcp://192.168:8080", @@ -458,12 +406,12 @@ func TestValidateHost(t *testing.T) { } for value, errorMessage := range invalid { - if _, err := ValidateHost(value); err == nil || err.Error() != errorMessage { + if _, err := ParseHost(defaultHTTPHost, value); err == nil || err.Error() != errorMessage { t.Fatalf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err) } } for value, expected := range valid { - if actual, err := ValidateHost(value); err != nil || actual != expected { + if actual, err := ParseHost(defaultHTTPHost, value); err != nil || actual != expected { t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) } } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ulimit.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ulimit.go index 54f6c4e3f..7cd480791 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ulimit.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts/ulimit.go @@ -6,10 +6,12 @@ import ( "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ulimit" ) +// UlimitOpt defines a map of Ulimits type UlimitOpt struct { values *map[string]*ulimit.Ulimit } +// NewUlimitOpt creates a new UlimitOpt func NewUlimitOpt(ref *map[string]*ulimit.Ulimit) *UlimitOpt { if ref == nil { ref = &map[string]*ulimit.Ulimit{} @@ -17,6 +19,7 @@ func NewUlimitOpt(ref *map[string]*ulimit.Ulimit) *UlimitOpt { return &UlimitOpt{ref} } +// Set validates a Ulimit and sets its name as a key in UlimitOpt func (o *UlimitOpt) Set(val string) error { l, err := ulimit.Parse(val) if err != nil { @@ -28,6 +31,7 @@ func (o *UlimitOpt) Set(val string) error { return nil } +// String returns Ulimit values as a string. func (o *UlimitOpt) String() string { var out []string for _, v := range *o.values { @@ -37,6 +41,7 @@ func (o *UlimitOpt) String() string { return fmt.Sprintf("%v", out) } +// GetList returns a slice of pointers to Ulimits. func (o *UlimitOpt) GetList() []*ulimit.Ulimit { var ulimits []*ulimit.Ulimit for _, v := range *o.values { diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive.go index 7306840b6..fb3327f12 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive.go @@ -19,35 +19,50 @@ import ( "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils" + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools" + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/pools" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/promise" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" ) type ( - Archive io.ReadCloser - ArchiveReader io.Reader - Compression int + // Archive is a type of io.ReadCloser which has two interfaces Read and Closer. + Archive io.ReadCloser + // Reader is a type of io.Reader. + Reader io.Reader + // Compression is the state represtents if compressed or not. + Compression int + // TarChownOptions wraps the chown options UID and GID. TarChownOptions struct { UID, GID int } + // TarOptions wraps the tar options. TarOptions struct { IncludeFiles []string ExcludePatterns []string Compression Compression NoLchown bool + UIDMaps []idtools.IDMap + GIDMaps []idtools.IDMap ChownOpts *TarChownOptions - Name string IncludeSourceDir bool // When unpacking, specifies whether overwriting a directory with a // non-directory is allowed and vice versa. NoOverwriteDirNonDir bool + // For each include when creating an archive, the included name will be + // replaced with the matching name from this map. + RebaseNames map[string]string } // Archiver allows the reuse of most utility functions of this package - // with a pluggable Untar function. + // with a pluggable Untar function. Also, to facilitate the passing of + // specific id mappings for untar, an archiver can be created with maps + // which will then be passed to Untar operations Archiver struct { - Untar func(io.Reader, string, *TarOptions) error + Untar func(io.Reader, string, *TarOptions) error + UIDMaps []idtools.IDMap + GIDMaps []idtools.IDMap } // breakoutError is used to differentiate errors related to breaking out @@ -57,17 +72,23 @@ type ( ) var ( + // ErrNotImplemented is the error message of function not implemented. ErrNotImplemented = errors.New("Function not implemented") - defaultArchiver = &Archiver{Untar} + defaultArchiver = &Archiver{Untar: Untar, UIDMaps: nil, GIDMaps: nil} ) const ( + // Uncompressed represents the uncompressed. Uncompressed Compression = iota + // Bzip2 is bzip2 compression algorithm. Bzip2 + // Gzip is gzip compression algorithm. Gzip + // Xz is xz compression algorithm. Xz ) +// IsArchive checks if it is a archive by the header. func IsArchive(header []byte) bool { compression := DetectCompression(header) if compression != Uncompressed { @@ -78,6 +99,7 @@ func IsArchive(header []byte) bool { return err == nil } +// DetectCompression detects the compression algorithm of the source. func DetectCompression(source []byte) Compression { for compression, m := range map[Compression][]byte{ Bzip2: {0x42, 0x5A, 0x68}, @@ -95,12 +117,13 @@ func DetectCompression(source []byte) Compression { return Uncompressed } -func xzDecompress(archive io.Reader) (io.ReadCloser, error) { +func xzDecompress(archive io.Reader) (io.ReadCloser, <-chan struct{}, error) { args := []string{"xz", "-d", "-c", "-q"} - return CmdStream(exec.Command(args[0], args[1:]...), archive) + return cmdStream(exec.Command(args[0], args[1:]...), archive) } +// DecompressStream decompress the archive and returns a ReaderCloser with the decompressed archive. func DecompressStream(archive io.Reader) (io.ReadCloser, error) { p := pools.BufioReader32KPool buf := p.Get(archive) @@ -126,17 +149,21 @@ func DecompressStream(archive io.Reader) (io.ReadCloser, error) { readBufWrapper := p.NewReadCloserWrapper(buf, bz2Reader) return readBufWrapper, nil case Xz: - xzReader, err := xzDecompress(buf) + xzReader, chdone, err := xzDecompress(buf) if err != nil { return nil, err } readBufWrapper := p.NewReadCloserWrapper(buf, xzReader) - return readBufWrapper, nil + return ioutils.NewReadCloserWrapper(readBufWrapper, func() error { + <-chdone + return readBufWrapper.Close() + }), nil default: return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) } } +// CompressStream compresses the dest with specified compression algorithm. func CompressStream(dest io.WriteCloser, compression Compression) (io.WriteCloser, error) { p := pools.BufioWriter32KPool buf := p.Get(dest) @@ -157,6 +184,7 @@ func CompressStream(dest io.WriteCloser, compression Compression) (io.WriteClose } } +// Extension returns the extension of a file that uses the specified compression algorithm. func (compression *Compression) Extension() string { switch *compression { case Uncompressed: @@ -177,6 +205,8 @@ type tarAppender struct { // for hardlink mapping SeenFiles map[uint64]string + UIDMaps []idtools.IDMap + GIDMaps []idtools.IDMap } // canonicalTarName provides a platform-independent and consistent posix-style @@ -219,14 +249,14 @@ func (ta *tarAppender) addTarFile(path, name string) error { } hdr.Name = name - nlink, inode, err := setHeaderForSpecialDevice(hdr, ta, name, fi.Sys()) + inode, err := setHeaderForSpecialDevice(hdr, ta, name, fi.Sys()) if err != nil { return err } - // if it's a regular file and has more than 1 link, + // if it's not a directory and has more than 1 link, // it's hardlinked, so set the type flag accordingly - if fi.Mode().IsRegular() && nlink > 1 { + if !fi.IsDir() && hasHardlinks(fi) { // a link should have a name that it links too // and that linked name should be first in the tar archive if oldpath, ok := ta.SeenFiles[inode]; ok { @@ -244,6 +274,25 @@ func (ta *tarAppender) addTarFile(path, name string) error { hdr.Xattrs["security.capability"] = string(capability) } + //handle re-mapping container ID mappings back to host ID mappings before + //writing tar headers/files + if ta.UIDMaps != nil || ta.GIDMaps != nil { + uid, gid, err := getFileUIDGID(fi.Sys()) + if err != nil { + return err + } + xUID, err := idtools.ToContainer(uid, ta.UIDMaps) + if err != nil { + return err + } + xGID, err := idtools.ToContainer(gid, ta.GIDMaps) + if err != nil { + return err + } + hdr.Uid = xUID + hdr.Gid = xGID + } + if err := ta.TarWriter.WriteHeader(hdr); err != nil { return err } @@ -358,19 +407,19 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L return err } - ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} - // syscall.UtimesNano doesn't support a NOFOLLOW flag atm + // system.Chtimes doesn't support a NOFOLLOW flag atm if hdr.Typeflag == tar.TypeLink { if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { - if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform { + if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil { return err } } } else if hdr.Typeflag != tar.TypeSymlink { - if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform { + if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil { return err } } else { + ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := system.LUtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform { return err } @@ -388,6 +437,10 @@ func Tar(path string, compression Compression) (io.ReadCloser, error) { // paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { + // Fix the source path to work with long path names. This is a no-op + // on platforms other than Windows. + srcPath = fixVolumePathPrefix(srcPath) + patterns, patDirs, exceptions, err := fileutils.CleanPatterns(options.ExcludePatterns) if err != nil { @@ -406,6 +459,8 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) TarWriter: tar.NewWriter(compressWriter), Buffer: pools.BufioWriter32KPool.Get(nil), SeenFiles: make(map[uint64]string), + UIDMaps: options.UIDMaps, + GIDMaps: options.GIDMaps, } defer func() { @@ -454,11 +509,10 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) seen := make(map[string]bool) - var renamedRelFilePath string // For when tar.Options.Name is set for _, include := range options.IncludeFiles { - // We can't use filepath.Join(srcPath, include) because this will - // clean away a trailing "." or "/" which may be important. - walkRoot := strings.Join([]string{srcPath, include}, string(filepath.Separator)) + rebaseName := options.RebaseNames[include] + + walkRoot := getWalkRoot(srcPath, include) filepath.Walk(walkRoot, func(filePath string, f os.FileInfo, err error) error { if err != nil { logrus.Debugf("Tar: Can't stat file %s to tar: %s", srcPath, err) @@ -503,14 +557,17 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) } seen[relFilePath] = true - // TODO Windows: Verify if this needs to be os.Pathseparator - // Rename the base resource - if options.Name != "" && filePath == srcPath+"/"+filepath.Base(relFilePath) { - renamedRelFilePath = relFilePath - } - // Set this to make sure the items underneath also get renamed - if options.Name != "" { - relFilePath = strings.Replace(relFilePath, renamedRelFilePath, options.Name, 1) + // Rename the base resource. + if rebaseName != "" { + var replacement string + if rebaseName != string(filepath.Separator) { + // Special case the root directory to replace with an + // empty string instead so that we don't end up with + // double slashes in the paths. + replacement = rebaseName + } + + relFilePath = strings.Replace(relFilePath, include, replacement, 1) } if err := ta.addTarFile(filePath, relFilePath); err != nil { @@ -524,12 +581,17 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) return pipeReader, nil } +// Unpack unpacks the decompressedArchive to dest with options. func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error { tr := tar.NewReader(decompressedArchive) trBuf := pools.BufioReader32KPool.Get(nil) defer pools.BufioReader32KPool.Put(trBuf) var dirs []*tar.Header + remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps) + if err != nil { + return err + } // Iterate through the files in the archive. loop: @@ -607,6 +669,28 @@ loop: } trBuf.Reset(tr) + // if the options contain a uid & gid maps, convert header uid/gid + // entries using the maps such that lchown sets the proper mapped + // uid/gid after writing the file. We only perform this mapping if + // the file isn't already owned by the remapped root UID or GID, as + // that specific uid/gid has no mapping from container -> host, and + // those files already have the proper ownership for inside the + // container. + if hdr.Uid != remappedRootUID { + xUID, err := idtools.ToHost(hdr.Uid, options.UIDMaps) + if err != nil { + return err + } + hdr.Uid = xUID + } + if hdr.Gid != remappedRootGID { + xGID, err := idtools.ToHost(hdr.Gid, options.GIDMaps) + if err != nil { + return err + } + hdr.Gid = xGID + } + if err := createTarFile(path, dest, hdr, trBuf, !options.NoLchown, options.ChownOpts); err != nil { return err } @@ -620,8 +704,8 @@ loop: for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) - ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} - if err := syscall.UtimesNano(path, ts); err != nil { + + if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil { return err } } @@ -637,7 +721,7 @@ func Untar(tarArchive io.Reader, dest string, options *TarOptions) error { return untarHandler(tarArchive, dest, options, true) } -// Untar reads a stream of bytes from `archive`, parses it as a tar archive, +// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive, // and unpacks it into the directory at `dest`. // The archive must be an uncompressed stream. func UntarUncompressed(tarArchive io.Reader, dest string, options *TarOptions) error { @@ -657,7 +741,7 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp options.ExcludePatterns = []string{} } - var r io.Reader = tarArchive + r := tarArchive if decompress { decompressedArchive, err := DecompressStream(tarArchive) if err != nil { @@ -670,6 +754,8 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp return Unpack(r, dest, options) } +// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. +// If either Tar or Untar fails, TarUntar aborts and returns the error. func (archiver *Archiver) TarUntar(src, dst string) error { logrus.Debugf("TarUntar(%s %s)", src, dst) archive, err := TarWithOptions(src, &TarOptions{Compression: Uncompressed}) @@ -677,7 +763,15 @@ func (archiver *Archiver) TarUntar(src, dst string) error { return err } defer archive.Close() - return archiver.Untar(archive, dst, nil) + + var options *TarOptions + if archiver.UIDMaps != nil || archiver.GIDMaps != nil { + options = &TarOptions{ + UIDMaps: archiver.UIDMaps, + GIDMaps: archiver.GIDMaps, + } + } + return archiver.Untar(archive, dst, options) } // TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. @@ -686,13 +780,21 @@ func TarUntar(src, dst string) error { return defaultArchiver.TarUntar(src, dst) } +// UntarPath untar a file from path to a destination, src is the source tar file path. func (archiver *Archiver) UntarPath(src, dst string) error { archive, err := os.Open(src) if err != nil { return err } defer archive.Close() - if err := archiver.Untar(archive, dst, nil); err != nil { + var options *TarOptions + if archiver.UIDMaps != nil || archiver.GIDMaps != nil { + options = &TarOptions{ + UIDMaps: archiver.UIDMaps, + GIDMaps: archiver.GIDMaps, + } + } + if err := archiver.Untar(archive, dst, options); err != nil { return err } return nil @@ -704,6 +806,10 @@ func UntarPath(src, dst string) error { return defaultArchiver.UntarPath(src, dst) } +// CopyWithTar creates a tar archive of filesystem path `src`, and +// unpacks it at filesystem path `dst`. +// The archive is streamed directly with fixed buffering and no +// intermediary disk IO. func (archiver *Archiver) CopyWithTar(src, dst string) error { srcSt, err := os.Stat(src) if err != nil { @@ -714,7 +820,7 @@ func (archiver *Archiver) CopyWithTar(src, dst string) error { } // Create dst, copy src's content into it logrus.Debugf("Creating dest directory: %s", dst) - if err := system.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) { + if err := system.MkdirAll(dst, 0755); err != nil { return err } logrus.Debugf("Calling TarUntar(%s, %s)", src, dst) @@ -729,6 +835,9 @@ func CopyWithTar(src, dst string) error { return defaultArchiver.CopyWithTar(src, dst) } +// CopyFileWithTar emulates the behavior of the 'cp' command-line +// for a single file. It copies a regular file from path `src` to +// path `dst`, and preserves all its metadata. func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { logrus.Debugf("CopyFileWithTar(%s, %s)", src, dst) srcSt, err := os.Stat(src) @@ -746,7 +855,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { dst = filepath.Join(dst, filepath.Base(src)) } // Create the holding directory if necessary - if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) { + if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil { return err } @@ -767,6 +876,28 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { hdr.Name = filepath.Base(dst) hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) + remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(archiver.UIDMaps, archiver.GIDMaps) + if err != nil { + return err + } + + // only perform mapping if the file being copied isn't already owned by the + // uid or gid of the remapped root in the container + if remappedRootUID != hdr.Uid { + xUID, err := idtools.ToHost(hdr.Uid, archiver.UIDMaps) + if err != nil { + return err + } + hdr.Uid = xUID + } + if remappedRootGID != hdr.Gid { + xGID, err := idtools.ToHost(hdr.Gid, archiver.GIDMaps) + if err != nil { + return err + } + hdr.Gid = xGID + } + tw := tar.NewWriter(w) defer tw.Close() if err := tw.WriteHeader(hdr); err != nil { @@ -782,7 +913,12 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { err = er } }() - return archiver.Untar(r, filepath.Dir(dst), nil) + + err = archiver.Untar(r, filepath.Dir(dst), nil) + if err != nil { + r.CloseWithError(err) + } + return err } // CopyFileWithTar emulates the behavior of the 'cp' command-line @@ -797,57 +933,33 @@ func CopyFileWithTar(src, dst string) (err error) { return defaultArchiver.CopyFileWithTar(src, dst) } -// CmdStream executes a command, and returns its stdout as a stream. +// cmdStream executes a command, and returns its stdout as a stream. // If the command fails to run or doesn't complete successfully, an error // will be returned, including anything written on stderr. -func CmdStream(cmd *exec.Cmd, input io.Reader) (io.ReadCloser, error) { - if input != nil { - stdin, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - // Write stdin if any - go func() { - io.Copy(stdin, input) - stdin.Close() - }() - } - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - stderr, err := cmd.StderrPipe() - if err != nil { - return nil, err - } +func cmdStream(cmd *exec.Cmd, input io.Reader) (io.ReadCloser, <-chan struct{}, error) { + chdone := make(chan struct{}) + cmd.Stdin = input pipeR, pipeW := io.Pipe() - errChan := make(chan []byte) - // Collect stderr, we will use it in case of an error - go func() { - errText, e := ioutil.ReadAll(stderr) - if e != nil { - errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")") - } - errChan <- errText - }() + cmd.Stdout = pipeW + var errBuf bytes.Buffer + cmd.Stderr = &errBuf + + // Run the command and return the pipe + if err := cmd.Start(); err != nil { + return nil, nil, err + } + // Copy stdout to the returned pipe go func() { - _, err := io.Copy(pipeW, stdout) - if err != nil { - pipeW.CloseWithError(err) - } - errText := <-errChan if err := cmd.Wait(); err != nil { - pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errText)) + pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errBuf.String())) } else { pipeW.Close() } + close(chdone) }() - // Run the command and return the pipe - if err := cmd.Start(); err != nil { - return nil, err - } - return pipeR, nil + + return pipeR, chdone, nil } // NewTempArchive reads the content of src into a temporary file, and returns the contents @@ -872,6 +984,8 @@ func NewTempArchive(src Archive, dir string) (*TempArchive, error) { return &TempArchive{File: f, Size: size}, nil } +// TempArchive is a temporary archive. The archive can only be read once - as soon as reading completes, +// the file will be deleted. type TempArchive struct { *os.File Size int64 // Pre-computed from Stat().Size() as a convenience diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_test.go index 4bb4f6ff5..3e2772816 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_test.go @@ -160,7 +160,7 @@ func TestExtensionXz(t *testing.T) { func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") - out, err := CmdStream(cmd, nil) + out, _, err := cmdStream(cmd, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -181,7 +181,7 @@ func TestCmdStreamLargeStderr(t *testing.T) { func TestCmdStreamBad(t *testing.T) { badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") - out, err := CmdStream(badCmd, nil) + out, _, err := cmdStream(badCmd, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -196,7 +196,7 @@ func TestCmdStreamBad(t *testing.T) { func TestCmdStreamGood(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0") - out, err := CmdStream(cmd, nil) + out, _, err := cmdStream(cmd, nil) if err != nil { t.Fatal(err) } @@ -695,7 +695,7 @@ func TestTarWithOptions(t *testing.T) { {&TarOptions{ExcludePatterns: []string{"2"}}, 1}, {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2}, {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2}, - {&TarOptions{Name: "test", IncludeFiles: []string{"1"}}, 4}, + {&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4}, } for _, testCase := range cases { changes, err := tarUntar(t, origin, testCase.opts) diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_unix.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_unix.go index 5c754373f..07693e37c 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_unix.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_unix.go @@ -6,11 +6,26 @@ import ( "archive/tar" "errors" "os" + "path/filepath" "syscall" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" ) +// fixVolumePathPrefix does platform specific processing to ensure that if +// the path being passed in is not in a volume path format, convert it to one. +func fixVolumePathPrefix(srcPath string) string { + return srcPath +} + +// getWalkRoot calculates the root path when performing a TarWithOptions. +// We use a seperate function as this is platform specific. On Linux, we +// can't use filepath.Join(srcPath,include) because this will clean away +// a trailing "." or "/" which may be important. +func getWalkRoot(srcPath string, include string) string { + return srcPath + string(filepath.Separator) + include +} + // CanonicalTarNameForPath returns platform-specific filepath // to canonical posix-style path for tar archival. p is relative // path. @@ -25,7 +40,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { return perm // noop for unix as golang APIs provide perm bits correctly } -func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) { +func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) { s, ok := stat.(*syscall.Stat_t) if !ok { @@ -33,10 +48,9 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st return } - nlink = uint32(s.Nlink) inode = uint64(s.Ino) - // Currently go does not fil in the major/minors + // Currently go does not fill in the major/minors if s.Mode&syscall.S_IFBLK != 0 || s.Mode&syscall.S_IFCHR != 0 { hdr.Devmajor = int64(major(uint64(s.Rdev))) @@ -46,6 +60,15 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st return } +func getFileUIDGID(stat interface{}) (int, int, error) { + s, ok := stat.(*syscall.Stat_t) + + if !ok { + return -1, -1, errors.New("cannot convert stat value to syscall.Stat_t") + } + return int(s.Uid), int(s.Gid), nil +} + func major(device uint64) uint64 { return (device >> 8) & 0xfff } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_windows.go index 10db4bd00..fbabc03a4 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_windows.go @@ -6,10 +6,25 @@ import ( "archive/tar" "fmt" "os" + "path/filepath" "strings" + + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath" ) -// canonicalTarNameForPath returns platform-specific filepath +// fixVolumePathPrefix does platform specific processing to ensure that if +// the path being passed in is not in a volume path format, convert it to one. +func fixVolumePathPrefix(srcPath string) string { + return longpath.AddPrefix(srcPath) +} + +// getWalkRoot calculates the root path when performing a TarWithOptions. +// We use a seperate function as this is platform specific. +func getWalkRoot(srcPath string, include string) string { + return filepath.Join(srcPath, include) +} + +// CanonicalTarNameForPath returns platform-specific filepath // to canonical posix-style path for tar archival. p is relative // path. func CanonicalTarNameForPath(p string) (string, error) { @@ -34,7 +49,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { return perm } -func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) { +func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) { // do nothing. no notion of Rdev, Inode, Nlink in stat on Windows return } @@ -48,3 +63,8 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { return nil } + +func getFileUIDGID(stat interface{}) (int, int, error) { + // no notion of file ownership mapping yet on Windows + return 0, 0, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_windows_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_windows_test.go index 72bc71e06..b7abc4022 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_windows_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/archive_windows_test.go @@ -3,10 +3,32 @@ package archive import ( + "io/ioutil" "os" + "path/filepath" "testing" ) +func TestCopyFileWithInvalidDest(t *testing.T) { + folder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(folder) + dest := "c:dest" + srcFolder := filepath.Join(folder, "src") + src := filepath.Join(folder, "src", "src") + err = os.MkdirAll(srcFolder, 0740) + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(src, []byte("content"), 0777) + err = CopyWithTar(src, dest) + if err == nil { + t.Fatalf("archiver.CopyWithTar should throw an error on invalid dest.") + } +} + func TestCanonicalTarNameForPath(t *testing.T) { cases := []struct { in, expected string diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes.go index c7838e859..12ec40163 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes.go @@ -14,18 +14,27 @@ import ( "time" "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus" + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/pools" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" ) +// ChangeType represents the change type. type ChangeType int const ( + // ChangeModify represents the modify operation. ChangeModify = iota + // ChangeAdd represents the add operation. ChangeAdd + // ChangeDelete represents the delete operation. ChangeDelete ) +// Change represents a change, it wraps the change type and path. +// It describes changes of the files in the path respect to the +// parent layers. The change could be modify, add, delete. +// This is used for layer diff. type Change struct { Path string Kind ChangeType @@ -94,7 +103,7 @@ func Changes(layers []string, rw string) ([]Change, error) { } // Skip AUFS metadata - if matched, err := filepath.Match(string(os.PathSeparator)+".wh..wh.*", path); err != nil || matched { + if matched, err := filepath.Match(string(os.PathSeparator)+WhiteoutMetaPrefix+"*", path); err != nil || matched { return err } @@ -105,8 +114,8 @@ func Changes(layers []string, rw string) ([]Change, error) { // Find out what kind of modification happened file := filepath.Base(path) // If there is a whiteout, then the file was removed - if strings.HasPrefix(file, ".wh.") { - originalFile := file[len(".wh."):] + if strings.HasPrefix(file, WhiteoutPrefix) { + originalFile := file[len(WhiteoutPrefix):] change.Path = filepath.Join(filepath.Dir(path), originalFile) change.Kind = ChangeDelete } else { @@ -161,20 +170,22 @@ func Changes(layers []string, rw string) ([]Change, error) { return changes, nil } +// FileInfo describes the information of a file. type FileInfo struct { parent *FileInfo name string - stat *system.Stat_t + stat *system.StatT children map[string]*FileInfo capability []byte added bool } -func (root *FileInfo) LookUp(path string) *FileInfo { +// LookUp looks up the file information of a file. +func (info *FileInfo) LookUp(path string) *FileInfo { // As this runs on the daemon side, file paths are OS specific. - parent := root + parent := info if path == string(os.PathSeparator) { - return root + return info } pathElements := strings.Split(path, string(os.PathSeparator)) @@ -275,6 +286,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { } +// Changes add changes to file information. func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { var changes []Change @@ -316,13 +328,29 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { // ChangesSize calculates the size in bytes of the provided changes, based on newDir. func ChangesSize(newDir string, changes []Change) int64 { - var size int64 + var ( + size int64 + sf = make(map[uint64]struct{}) + ) for _, change := range changes { if change.Kind == ChangeModify || change.Kind == ChangeAdd { file := filepath.Join(newDir, change.Path) - fileInfo, _ := os.Lstat(file) + fileInfo, err := os.Lstat(file) + if err != nil { + logrus.Errorf("Can not stat %q: %s", file, err) + continue + } + if fileInfo != nil && !fileInfo.IsDir() { - size += fileInfo.Size() + if hasHardlinks(fileInfo) { + inode := getIno(fileInfo) + if _, ok := sf[inode]; !ok { + size += fileInfo.Size() + sf[inode] = struct{}{} + } + } else { + size += fileInfo.Size() + } } } } @@ -330,13 +358,15 @@ func ChangesSize(newDir string, changes []Change) int64 { } // ExportChanges produces an Archive from the provided changes, relative to dir. -func ExportChanges(dir string, changes []Change) (Archive, error) { +func ExportChanges(dir string, changes []Change, uidMaps, gidMaps []idtools.IDMap) (Archive, error) { reader, writer := io.Pipe() go func() { ta := &tarAppender{ TarWriter: tar.NewWriter(writer), Buffer: pools.BufioWriter32KPool.Get(nil), SeenFiles: make(map[uint64]string), + UIDMaps: uidMaps, + GIDMaps: gidMaps, } // this buffer is needed for the duration of this piped stream defer pools.BufioWriter32KPool.Put(ta.Buffer) @@ -351,7 +381,7 @@ func ExportChanges(dir string, changes []Change) (Archive, error) { if change.Kind == ChangeDelete { whiteOutDir := filepath.Dir(change.Path) whiteOutBase := filepath.Base(change.Path) - whiteOut := filepath.Join(whiteOutDir, ".wh."+whiteOutBase) + whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase) timestamp := time.Now() hdr := &tar.Header{ Name: whiteOut[1:], diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_posix_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_posix_test.go index 9d528e614..5a3282b5a 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_posix_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_posix_test.go @@ -61,7 +61,7 @@ func TestHardLinkOrder(t *testing.T) { sort.Sort(changesByPath(changes)) // ExportChanges - ar, err := ExportChanges(dest, changes) + ar, err := ExportChanges(dest, changes, nil, nil) if err != nil { t.Fatal(err) } @@ -73,7 +73,7 @@ func TestHardLinkOrder(t *testing.T) { // reverse sort sort.Sort(sort.Reverse(changesByPath(changes))) // ExportChanges - arRev, err := ExportChanges(dest, changes) + arRev, err := ExportChanges(dest, changes, nil, nil) if err != nil { t.Fatal(err) } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_test.go index 509bdb2e6..52daaa643 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_test.go @@ -410,7 +410,7 @@ func TestApplyLayer(t *testing.T) { t.Fatal(err) } - layer, err := ExportChanges(dst, changes) + layer, err := ExportChanges(dst, changes, nil, nil) if err != nil { t.Fatal(err) } @@ -434,6 +434,35 @@ func TestApplyLayer(t *testing.T) { } } +func TestChangesSizeWithHardlinks(t *testing.T) { + srcDir, err := ioutil.TempDir("", "docker-test-srcDir") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(srcDir) + + destDir, err := ioutil.TempDir("", "docker-test-destDir") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(destDir) + + creationSize, err := prepareUntarSourceDirectory(100, destDir, true) + if err != nil { + t.Fatal(err) + } + + changes, err := ChangesDirs(destDir, srcDir) + if err != nil { + t.Fatal(err) + } + + got := ChangesSize(destDir, changes) + if got != int64(creationSize) { + t.Errorf("Expected %d bytes of changes, got %d", creationSize, got) + } +} + func TestChangesSizeWithNoChanges(t *testing.T) { size := ChangesSize("/tmp", nil) if size != 0 { @@ -468,7 +497,7 @@ func TestChangesSize(t *testing.T) { } size := ChangesSize(parentPath, changes) if size != 6 { - t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) + t.Fatalf("Expected 6 bytes of changes, got %d", size) } } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_unix.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_unix.go index dc1ea608b..6646b4dfd 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_unix.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_unix.go @@ -3,16 +3,17 @@ package archive import ( + "os" "syscall" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" ) -func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool { +func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool { // Don't look at size for dirs, its not a good measure of change if oldStat.Mode() != newStat.Mode() || - oldStat.Uid() != newStat.Uid() || - oldStat.Gid() != newStat.Gid() || + oldStat.UID() != newStat.UID() || + oldStat.GID() != newStat.GID() || oldStat.Rdev() != newStat.Rdev() || // Don't look at size for dirs, its not a good measure of change (oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR && @@ -25,3 +26,11 @@ func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool { func (info *FileInfo) isDir() bool { return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0 } + +func getIno(fi os.FileInfo) uint64 { + return uint64(fi.Sys().(*syscall.Stat_t).Ino) +} + +func hasHardlinks(fi os.FileInfo) bool { + return fi.Sys().(*syscall.Stat_t).Nlink > 1 +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_windows.go index 6026575e5..2d8708d0a 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/changes_windows.go @@ -1,10 +1,12 @@ package archive import ( + "os" + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" ) -func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool { +func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool { // Don't look at size for dirs, its not a good measure of change if oldStat.ModTime() != newStat.ModTime() || @@ -18,3 +20,11 @@ func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool { func (info *FileInfo) isDir() bool { return info.parent == nil || info.stat.IsDir() } + +func getIno(fi os.FileInfo) (inode uint64) { + return +} + +func hasHardlinks(fi os.FileInfo) bool { + return false +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy.go index 576f336ba..251c9bd99 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy.go @@ -6,11 +6,11 @@ import ( "io" "io/ioutil" "os" - "path" "path/filepath" "strings" "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus" + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" ) // Errors used or returned by this file. @@ -29,8 +29,12 @@ var ( // path already ends in a `.` path segment, then another is not added. If the // clean path already ends in a path separator, then another is not added. func PreserveTrailingDotOrSeparator(cleanedPath, originalPath string) string { - if !SpecifiesCurrentDir(cleanedPath) && SpecifiesCurrentDir(originalPath) { - if !HasTrailingPathSeparator(cleanedPath) { + // Ensure paths are in platform semantics + cleanedPath = normalizePath(cleanedPath) + originalPath = normalizePath(originalPath) + + if !specifiesCurrentDir(cleanedPath) && specifiesCurrentDir(originalPath) { + if !hasTrailingPathSeparator(cleanedPath) { // Add a separator if it doesn't already end with one (a cleaned // path would only end in a separator if it is the root). cleanedPath += string(filepath.Separator) @@ -38,60 +42,60 @@ func PreserveTrailingDotOrSeparator(cleanedPath, originalPath string) string { cleanedPath += "." } - if !HasTrailingPathSeparator(cleanedPath) && HasTrailingPathSeparator(originalPath) { + if !hasTrailingPathSeparator(cleanedPath) && hasTrailingPathSeparator(originalPath) { cleanedPath += string(filepath.Separator) } return cleanedPath } -// AssertsDirectory returns whether the given path is +// assertsDirectory returns whether the given path is // asserted to be a directory, i.e., the path ends with // a trailing '/' or `/.`, assuming a path separator of `/`. -func AssertsDirectory(path string) bool { - return HasTrailingPathSeparator(path) || SpecifiesCurrentDir(path) +func assertsDirectory(path string) bool { + return hasTrailingPathSeparator(path) || specifiesCurrentDir(path) } -// HasTrailingPathSeparator returns whether the given +// hasTrailingPathSeparator returns whether the given // path ends with the system's path separator character. -func HasTrailingPathSeparator(path string) bool { +func hasTrailingPathSeparator(path string) bool { return len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) } -// SpecifiesCurrentDir returns whether the given path specifies +// specifiesCurrentDir returns whether the given path specifies // a "current directory", i.e., the last path segment is `.`. -func SpecifiesCurrentDir(path string) bool { +func specifiesCurrentDir(path string) bool { return filepath.Base(path) == "." } -// SplitPathDirEntry splits the given path between its -// parent directory and its basename in that directory. -func SplitPathDirEntry(localizedPath string) (dir, base string) { - normalizedPath := filepath.ToSlash(localizedPath) - vol := filepath.VolumeName(normalizedPath) - normalizedPath = normalizedPath[len(vol):] +// SplitPathDirEntry splits the given path between its directory name and its +// basename by first cleaning the path but preserves a trailing "." if the +// original path specified the current directory. +func SplitPathDirEntry(path string) (dir, base string) { + cleanedPath := filepath.Clean(normalizePath(path)) - if normalizedPath == "/" { - // Specifies the root path. - return filepath.FromSlash(vol + normalizedPath), "." + if specifiesCurrentDir(path) { + cleanedPath += string(filepath.Separator) + "." } - trimmedPath := vol + strings.TrimRight(normalizedPath, "/") - - dir = filepath.FromSlash(path.Dir(trimmedPath)) - base = filepath.FromSlash(path.Base(trimmedPath)) - - return dir, base + return filepath.Dir(cleanedPath), filepath.Base(cleanedPath) } -// TarResource archives the resource at the given sourcePath into a Tar +// TarResource archives the resource described by the given CopyInfo to a Tar // archive. A non-nil error is returned if sourcePath does not exist or is // asserted to be a directory but exists as another type of file. // // This function acts as a convenient wrapper around TarWithOptions, which // requires a directory as the source path. TarResource accepts either a // directory or a file path and correctly sets the Tar options. -func TarResource(sourcePath string) (content Archive, err error) { +func TarResource(sourceInfo CopyInfo) (content Archive, err error) { + return TarResourceRebase(sourceInfo.Path, sourceInfo.RebaseName) +} + +// TarResourceRebase is like TarResource but renames the first path element of +// items in the resulting tar archive to match the given rebaseName if not "". +func TarResourceRebase(sourcePath, rebaseName string) (content Archive, err error) { + sourcePath = normalizePath(sourcePath) if _, err = os.Lstat(sourcePath); err != nil { // Catches the case where the source does not exist or is not a // directory if asserted to be a directory, as this also causes an @@ -99,22 +103,6 @@ func TarResource(sourcePath string) (content Archive, err error) { return } - if len(sourcePath) > 1 && HasTrailingPathSeparator(sourcePath) { - // In the case where the source path is a symbolic link AND it ends - // with a path separator, we will want to evaluate the symbolic link. - trimmedPath := sourcePath[:len(sourcePath)-1] - stat, err := os.Lstat(trimmedPath) - if err != nil { - return nil, err - } - - if stat.Mode()&os.ModeSymlink != 0 { - if sourcePath, err = filepath.EvalSymlinks(trimmedPath); err != nil { - return nil, err - } - } - } - // Separate the source path between it's directory and // the entry in that directory which we are archiving. sourceDir, sourceBase := SplitPathDirEntry(sourcePath) @@ -127,39 +115,150 @@ func TarResource(sourcePath string) (content Archive, err error) { Compression: Uncompressed, IncludeFiles: filter, IncludeSourceDir: true, + RebaseNames: map[string]string{ + sourceBase: rebaseName, + }, }) } // CopyInfo holds basic info about the source // or destination path of a copy operation. type CopyInfo struct { - Path string - Exists bool - IsDir bool + Path string + Exists bool + IsDir bool + RebaseName string } -// CopyInfoStatPath stats the given path to create a CopyInfo -// struct representing that resource. If mustExist is true, then -// it is an error if there is no file or directory at the given path. -func CopyInfoStatPath(path string, mustExist bool) (CopyInfo, error) { - pathInfo := CopyInfo{Path: path} +// CopyInfoSourcePath stats the given path to create a CopyInfo +// struct representing that resource for the source of an archive copy +// operation. The given path should be an absolute local path. A source path +// has all symlinks evaluated that appear before the last path separator ("/" +// on Unix). As it is to be a copy source, the path must exist. +func CopyInfoSourcePath(path string) (CopyInfo, error) { + // Split the given path into its Directory and Base components. We will + // evaluate symlinks in the directory component then append the base. + path = normalizePath(path) + dirPath, basePath := filepath.Split(path) - fileInfo, err := os.Lstat(path) - - if err == nil { - pathInfo.Exists, pathInfo.IsDir = true, fileInfo.IsDir() - } else if os.IsNotExist(err) && !mustExist { - err = nil + resolvedDirPath, err := filepath.EvalSymlinks(dirPath) + if err != nil { + return CopyInfo{}, err } - return pathInfo, err + // resolvedDirPath will have been cleaned (no trailing path separators) so + // we can manually join it with the base path element. + resolvedPath := resolvedDirPath + string(filepath.Separator) + basePath + + var rebaseName string + if hasTrailingPathSeparator(path) && filepath.Base(path) != filepath.Base(resolvedPath) { + // In the case where the path had a trailing separator and a symlink + // evaluation has changed the last path component, we will need to + // rebase the name in the archive that is being copied to match the + // originally requested name. + rebaseName = filepath.Base(path) + } + + stat, err := os.Lstat(resolvedPath) + if err != nil { + return CopyInfo{}, err + } + + return CopyInfo{ + Path: resolvedPath, + Exists: true, + IsDir: stat.IsDir(), + RebaseName: rebaseName, + }, nil +} + +// CopyInfoDestinationPath stats the given path to create a CopyInfo +// struct representing that resource for the destination of an archive copy +// operation. The given path should be an absolute local path. +func CopyInfoDestinationPath(path string) (info CopyInfo, err error) { + maxSymlinkIter := 10 // filepath.EvalSymlinks uses 255, but 10 already seems like a lot. + path = normalizePath(path) + originalPath := path + + stat, err := os.Lstat(path) + + if err == nil && stat.Mode()&os.ModeSymlink == 0 { + // The path exists and is not a symlink. + return CopyInfo{ + Path: path, + Exists: true, + IsDir: stat.IsDir(), + }, nil + } + + // While the path is a symlink. + for n := 0; err == nil && stat.Mode()&os.ModeSymlink != 0; n++ { + if n > maxSymlinkIter { + // Don't follow symlinks more than this arbitrary number of times. + return CopyInfo{}, errors.New("too many symlinks in " + originalPath) + } + + // The path is a symbolic link. We need to evaluate it so that the + // destination of the copy operation is the link target and not the + // link itself. This is notably different than CopyInfoSourcePath which + // only evaluates symlinks before the last appearing path separator. + // Also note that it is okay if the last path element is a broken + // symlink as the copy operation should create the target. + var linkTarget string + + linkTarget, err = os.Readlink(path) + if err != nil { + return CopyInfo{}, err + } + + if !system.IsAbs(linkTarget) { + // Join with the parent directory. + dstParent, _ := SplitPathDirEntry(path) + linkTarget = filepath.Join(dstParent, linkTarget) + } + + path = linkTarget + stat, err = os.Lstat(path) + } + + if err != nil { + // It's okay if the destination path doesn't exist. We can still + // continue the copy operation if the parent directory exists. + if !os.IsNotExist(err) { + return CopyInfo{}, err + } + + // Ensure destination parent dir exists. + dstParent, _ := SplitPathDirEntry(path) + + parentDirStat, err := os.Lstat(dstParent) + if err != nil { + return CopyInfo{}, err + } + if !parentDirStat.IsDir() { + return CopyInfo{}, ErrNotDirectory + } + + return CopyInfo{Path: path}, nil + } + + // The path exists after resolving symlinks. + return CopyInfo{ + Path: path, + Exists: true, + IsDir: stat.IsDir(), + }, nil } // PrepareArchiveCopy prepares the given srcContent archive, which should // contain the archived resource described by srcInfo, to the destination // described by dstInfo. Returns the possibly modified content archive along // with the path to the destination directory which it should be extracted to. -func PrepareArchiveCopy(srcContent ArchiveReader, srcInfo, dstInfo CopyInfo) (dstDir string, content Archive, err error) { +func PrepareArchiveCopy(srcContent Reader, srcInfo, dstInfo CopyInfo) (dstDir string, content Archive, err error) { + // Ensure in platform semantics + srcInfo.Path = normalizePath(srcInfo.Path) + dstInfo.Path = normalizePath(dstInfo.Path) + // Separate the destination path between its directory and base // components in case the source archive contents need to be rebased. dstDir, dstBase := SplitPathDirEntry(dstInfo.Path) @@ -189,7 +288,7 @@ func PrepareArchiveCopy(srcContent ArchiveReader, srcInfo, dstInfo CopyInfo) (ds // The source content entries will have to be renamed to have a // basename which matches the destination path's basename. return dstDir, rebaseArchiveEntries(srcContent, srcBase, dstBase), nil - case AssertsDirectory(dstInfo.Path): + case assertsDirectory(dstInfo.Path): // The destination does not exist and is asserted to be created as a // directory, but the source content is not a directory. This is an // error condition since you cannot create a directory from a file @@ -208,8 +307,15 @@ func PrepareArchiveCopy(srcContent ArchiveReader, srcInfo, dstInfo CopyInfo) (ds } // rebaseArchiveEntries rewrites the given srcContent archive replacing -// an occurance of oldBase with newBase at the beginning of entry names. -func rebaseArchiveEntries(srcContent ArchiveReader, oldBase, newBase string) Archive { +// an occurrence of oldBase with newBase at the beginning of entry names. +func rebaseArchiveEntries(srcContent Reader, oldBase, newBase string) Archive { + if oldBase == string(os.PathSeparator) { + // If oldBase specifies the root directory, use an empty string as + // oldBase instead so that newBase doesn't replace the path separator + // that all paths will start with. + oldBase = "" + } + rebased, w := io.Pipe() go func() { @@ -255,15 +361,19 @@ func CopyResource(srcPath, dstPath string) error { err error ) + // Ensure in platform semantics + srcPath = normalizePath(srcPath) + dstPath = normalizePath(dstPath) + // Clean the source and destination paths. srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath) dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath) - if srcInfo, err = CopyInfoStatPath(srcPath, true); err != nil { + if srcInfo, err = CopyInfoSourcePath(srcPath); err != nil { return err } - content, err := TarResource(srcPath) + content, err := TarResource(srcInfo) if err != nil { return err } @@ -274,25 +384,14 @@ func CopyResource(srcPath, dstPath string) error { // CopyTo handles extracting the given content whose // entries should be sourced from srcInfo to dstPath. -func CopyTo(content ArchiveReader, srcInfo CopyInfo, dstPath string) error { - dstInfo, err := CopyInfoStatPath(dstPath, false) +func CopyTo(content Reader, srcInfo CopyInfo, dstPath string) error { + // The destination path need not exist, but CopyInfoDestinationPath will + // ensure that at least the parent directory exists. + dstInfo, err := CopyInfoDestinationPath(normalizePath(dstPath)) if err != nil { return err } - if !dstInfo.Exists { - // Ensure destination parent dir exists. - dstParent, _ := SplitPathDirEntry(dstPath) - - dstStat, err := os.Lstat(dstParent) - if err != nil { - return err - } - if !dstStat.IsDir() { - return ErrNotDirectory - } - } - dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo) if err != nil { return err diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy_test.go index dd0b32362..25f1811a9 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/copy_test.go @@ -138,13 +138,7 @@ func TestCopyErrSrcNotExists(t *testing.T) { tmpDirA, tmpDirB := getTestTempDirs(t) defer removeAllPaths(tmpDirA, tmpDirB) - content, err := TarResource(filepath.Join(tmpDirA, "file1")) - if err == nil { - content.Close() - t.Fatal("expected IsNotExist error, but got nil instead") - } - - if !os.IsNotExist(err) { + if _, err := CopyInfoSourcePath(filepath.Join(tmpDirA, "file1")); !os.IsNotExist(err) { t.Fatalf("expected IsNotExist error, but got %T: %s", err, err) } } @@ -158,13 +152,7 @@ func TestCopyErrSrcNotDir(t *testing.T) { // Load A with some sample files and directories. createSampleDir(t, tmpDirA) - content, err := TarResource(joinTrailingSep(tmpDirA, "file1")) - if err == nil { - content.Close() - t.Fatal("expected IsNotDir error, but got nil instead") - } - - if !isNotDir(err) { + if _, err := CopyInfoSourcePath(joinTrailingSep(tmpDirA, "file1")); !isNotDir(err) { t.Fatalf("expected IsNotDir error, but got %T: %s", err, err) } } @@ -181,7 +169,7 @@ func TestCopyErrDstParentNotExists(t *testing.T) { srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false} // Try with a file source. - content, err := TarResource(srcInfo.Path) + content, err := TarResource(srcInfo) if err != nil { t.Fatalf("unexpected error %T: %s", err, err) } @@ -199,7 +187,7 @@ func TestCopyErrDstParentNotExists(t *testing.T) { // Try with a directory source. srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true} - content, err = TarResource(srcInfo.Path) + content, err = TarResource(srcInfo) if err != nil { t.Fatalf("unexpected error %T: %s", err, err) } @@ -228,7 +216,7 @@ func TestCopyErrDstNotDir(t *testing.T) { // Try with a file source. srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false} - content, err := TarResource(srcInfo.Path) + content, err := TarResource(srcInfo) if err != nil { t.Fatalf("unexpected error %T: %s", err, err) } @@ -245,7 +233,7 @@ func TestCopyErrDstNotDir(t *testing.T) { // Try with a directory source. srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true} - content, err = TarResource(srcInfo.Path) + content, err = TarResource(srcInfo) if err != nil { t.Fatalf("unexpected error %T: %s", err, err) } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/diff.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/diff.go index 10a63a051..5ec71110a 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/diff.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/diff.go @@ -9,23 +9,40 @@ import ( "path/filepath" "runtime" "strings" - "syscall" "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus" + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/pools" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" ) -func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { +// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be +// compressed or uncompressed. +// Returns the size in bytes of the contents of the layer. +func UnpackLayer(dest string, layer Reader, options *TarOptions) (size int64, err error) { tr := tar.NewReader(layer) trBuf := pools.BufioReader32KPool.Get(tr) defer pools.BufioReader32KPool.Put(trBuf) var dirs []*tar.Header + if options == nil { + options = &TarOptions{} + } + if options.ExcludePatterns == nil { + options.ExcludePatterns = []string{} + } + remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps) + if err != nil { + return 0, err + } + aufsTempdir := "" aufsHardlinks := make(map[string]*tar.Header) + if options == nil { + options = &TarOptions{} + } // Iterate through the files in the archive. for { hdr, err := tr.Next() @@ -55,7 +72,7 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { // TODO Windows. Once the registry is aware of what images are Windows- // specific or Linux-specific, this warning should be changed to an error // to cater for the situation where someone does manage to upload a Linux - // image but have it tagged as Windows inadvertantly. + // image but have it tagged as Windows inadvertently. if runtime.GOOS == "windows" { if strings.Contains(hdr.Name, ":") { logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name) @@ -80,11 +97,11 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { } // Skip AUFS metadata dirs - if strings.HasPrefix(hdr.Name, ".wh..wh.") { + if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) { // Regular files inside /.wh..wh.plnk can be used as hardlink targets // We don't want this directory, but we need the files in them so that // such hardlinks can be resolved. - if strings.HasPrefix(hdr.Name, ".wh..wh.plnk") && hdr.Typeflag == tar.TypeReg { + if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg { basename := filepath.Base(hdr.Name) aufsHardlinks[basename] = hdr if aufsTempdir == "" { @@ -97,7 +114,10 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { return 0, err } } - continue + + if hdr.Name != WhiteoutOpaqueDir { + continue + } } path := filepath.Join(dest, hdr.Name) rel, err := filepath.Rel(dest, path) @@ -111,11 +131,25 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { } base := filepath.Base(path) - if strings.HasPrefix(base, ".wh.") { - originalBase := base[len(".wh."):] - originalPath := filepath.Join(filepath.Dir(path), originalBase) - if err := os.RemoveAll(originalPath); err != nil { - return 0, err + if strings.HasPrefix(base, WhiteoutPrefix) { + dir := filepath.Dir(path) + if base == WhiteoutOpaqueDir { + fi, err := os.Lstat(dir) + if err != nil && !os.IsNotExist(err) { + return 0, err + } + if err := os.RemoveAll(dir); err != nil { + return 0, err + } + if err := os.Mkdir(dir, fi.Mode()&os.ModePerm); err != nil { + return 0, err + } + } else { + originalBase := base[len(WhiteoutPrefix):] + originalPath := filepath.Join(dir, originalBase) + if err := os.RemoveAll(originalPath); err != nil { + return 0, err + } } } else { // If path exits we almost always just want to remove and replace it. @@ -136,7 +170,7 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so // we manually retarget these into the temporary files we extracted them into - if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), ".wh..wh.plnk") { + if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) { linkBasename := filepath.Base(hdr.Linkname) srcHdr = aufsHardlinks[linkBasename] if srcHdr == nil { @@ -150,6 +184,27 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { srcData = tmpFile } + // if the options contain a uid & gid maps, convert header uid/gid + // entries using the maps such that lchown sets the proper mapped + // uid/gid after writing the file. We only perform this mapping if + // the file isn't already owned by the remapped root UID or GID, as + // that specific uid/gid has no mapping from container -> host, and + // those files already have the proper ownership for inside the + // container. + if srcHdr.Uid != remappedRootUID { + xUID, err := idtools.ToHost(srcHdr.Uid, options.UIDMaps) + if err != nil { + return 0, err + } + srcHdr.Uid = xUID + } + if srcHdr.Gid != remappedRootGID { + xGID, err := idtools.ToHost(srcHdr.Gid, options.GIDMaps) + if err != nil { + return 0, err + } + srcHdr.Gid = xGID + } if err := createTarFile(path, dest, srcHdr, srcData, true, nil); err != nil { return 0, err } @@ -164,8 +219,7 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) - ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} - if err := syscall.UtimesNano(path, ts); err != nil { + if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil { return 0, err } } @@ -177,20 +231,20 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { // and applies it to the directory `dest`. The stream `layer` can be // compressed or uncompressed. // Returns the size in bytes of the contents of the layer. -func ApplyLayer(dest string, layer ArchiveReader) (int64, error) { - return applyLayerHandler(dest, layer, true) +func ApplyLayer(dest string, layer Reader) (int64, error) { + return applyLayerHandler(dest, layer, &TarOptions{}, true) } // ApplyUncompressedLayer parses a diff in the standard layer format from // `layer`, and applies it to the directory `dest`. The stream `layer` // can only be uncompressed. // Returns the size in bytes of the contents of the layer. -func ApplyUncompressedLayer(dest string, layer ArchiveReader) (int64, error) { - return applyLayerHandler(dest, layer, false) +func ApplyUncompressedLayer(dest string, layer Reader, options *TarOptions) (int64, error) { + return applyLayerHandler(dest, layer, options, false) } // do the bulk load of ApplyLayer, but allow for not calling DecompressStream -func applyLayerHandler(dest string, layer ArchiveReader, decompress bool) (int64, error) { +func applyLayerHandler(dest string, layer Reader, options *TarOptions, decompress bool) (int64, error) { dest = filepath.Clean(dest) // We need to be able to set any perms @@ -206,5 +260,5 @@ func applyLayerHandler(dest string, layer ArchiveReader, decompress bool) (int64 return 0, err } } - return UnpackLayer(dest, layer) + return UnpackLayer(dest, layer, options) } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/testdata/broken.tar b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/testdata/broken.tar new file mode 100644 index 0000000000000000000000000000000000000000..8f10ea6b87d3eb4fed572349dfe87695603b10a5 GIT binary patch literal 13824 zcmeHN>rxv>7UtLfn5Q@^l8gXrG&7O_li)0oQBai)6v9rjo&-ixOPXbFo(r;aaqT1Q zi|o_vJM6y3ey8Um2^?(fm~vH66==Hq^tqqYr_U$~f~3CkaX-4=)VFkfMbAE0zj=1W zFdGeXOK)!KtrgwSO|!8=t&huAhCPiFI|54|O6#g{AByje_D5`gZ4lbN_tD%y+P?+6 zW}mCyJbT6dM$<6v?SB_8uxS5j5M6u>C%C=+&BoS!{NIK7SFYLLXgq9fL;u??&1{)C_QVb?f0pB4xfD_C1pX2f z=LE&>$4O)llEszRik&8tAi~^>9~IXb2tQsXkop&XF!hz8gWXO)O@R9>nS~7H1w&*U zWf1ryXPidjED|qMClc|F!YuB;N}eT-8}IBqwJ!w!F&$m$r;a;(N7!YIEb7h<=ej}& zT~f;Cd!ZOC&mX2n zv4)UvkOa{z8}jxVC6bTq+3^R;Sok8c6EQsN&k9^`&h(Hc32JVwt-Hrj<{`vG3V< zCk?#){6BW>!9@+(L2u}{Jos}CZh!u_HaA;$dH(--^ZzaF-*=tS5&i^O)@Me!3BwBQ`@=VE zIl)Fp0MG z@%2K`G+^8HA?T&;xGZB%_q<@Vt&(_!w-gfXxk@mb9|fb)1BuBGk_ptuvx%G~pq0Kb zb&?6Szj_3#ClOiI_3vu1e+mOX z9k`Og2B5RmN7LGZ)c;3%E%Ip__9KKUf&G&zD9jkJNr-{ibNby{ds> zUrSU_0z^Wf<)}gE{Jb22kgArW_I#nO79{eFvL6rZP*4oJ7H%7}fn5i&1ZT@5hDK4~ z(U`5S#`Fws86Z{2P=gP6usiI=mKaOr@4W|(?6Ye5$Oayf(LUxEb zaN*HO8gZBg{sZJ1)pg4>36^kmC*dQ2;oE@^#)cw_*aI^!cM=y1Rqga(?Ey`Mja44@ zco?Vs7`J_y5ir%m6vXp*y&Gb{4lfBvR0R>wjxNBA^zHAzdc;~eK6(s=AB|{$OM8p} zp9LwiIkAyG5Q$+F3`7h$CPJbL(j-h1h61!ZViYo4dBXOg@lop12w4VYz!&$vL+Po-n0lE6B8Y;6$Ar89(FQ zU43m0VVC)g+}A0GY(H3=vGXH;5|6sFnZk+NN-WF&+)64KnDBNmlR?P<{j247c6ZGs zY`hF!K4&Hi(0r~#=6sH0f#>;~|6uT_GuPArovwt~PT&t2-pNh;x9aMe7i;!lK!(<$ z?d`g5*7a@bJ?(y(Y4ln98)|Cinp8V=gdKs-N$TT&k8N344C6y&*H}a~{9Pg&%cB8( zs3gwCMEH-=;aI?u+)#>TQj}R!`jyO-QsK*KZS|lK9+9#7oV0B(la+@sRbyfJf~*mY z#+u;OA2B@66aq^nOW6`=t5qYdRV{oFkE8T+GhJI-*NldTtcr!I|PQf({z2i zZs;`}x~m6ks)bXh@+($$(s>pJ`5X6~16{UfoJC(mW1b(MtJcpN$ZBT3r1B`&Cx9{-iF=!{A}z(ob033DW~d!*9$cfm zVNC%z6l$8Qz0LiPv&`A!8a*yd3zi-in+*e-!2$MiQNyE>1xX!65{vsnGKkf9!|0+OGBAb= z5*&U!Rl91sZq^%6Di#9<<87G)rv;99!{p6oE&}gq)LXeeJT)kYlsjz{ehkbMY(O`q zGvc6vviAh-6>EFt+I|*)$Z&%o;(ob2LAmI= zd);1Ux&vAHF3sW+ZYtInM5`7V!gWe@@A3}gzBN4OzKHcFXhsnBZ62vkM}c;c8?C16|}T)I>F_`E4y<`7O_Uv z_IIGuK3}j6k8x0(NE^)|N^6ztuoF5wcqyCPP4-b>1H5)kQM(q_kYzo37tjs2w1@@5 z)pou5q*BNKlggS#-4TOxF*--bZwQgZIP>8>Wh4R6qJg1trGj7P+M9C-U$bgV0-Bbc zM}8SyaI1`5o3Hn=gK~dij~yq2v7>PXETRIqq!En36W>+P9az*N;)5;FK054lzkPPH zcY4hR*Orc{l5us$Y*nZ!(@__9wdDn6|B~BL+;v!B^Cr(N`)UtH54-56s#rGO&e@Q}~KNYPdQ94MZxA|gP9PSIqe@Ff$9bNNvws)xH zUYfZ#^MIJly?f4ly_CL`QQoB~o&>3jKAlL=*#tHX$;*%#;^sVnJHGU0={L0dh$?du z$V*u|2o=sbG6HQV;$?~-5Xh?Gjf~m#{@1wY+1@T!Us<#xZ;2Rn{Y@!B=|jZ;TY#GL zQet9G=4h_z5?#7$NWf6BJyZ3f$1aFp02S_lpyVtB;|niLX54VbZP`xU1YMSiGnf#! zBhWBJBLfCg3eCtIG~av^x3Yo4twnBx#0a&E>6G9&~+z{;Wn%CtG>DYD1(pjqYiYL oJsf9Rk?Q4-IWqA2mih3}{ZBUT=3UD@m3s}`Yv5i3pOOat4?XSI`2YX_ literal 0 HcmV?d00001 diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/utils_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/utils_test.go index f5cacea8f..98719032f 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/utils_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/utils_test.go @@ -16,7 +16,7 @@ var testUntarFns = map[string]func(string, io.Reader) error{ return Untar(r, dest, nil) }, "applylayer": func(dest string, r io.Reader) error { - _, err := ApplyLayer(dest, ArchiveReader(r)) + _, err := ApplyLayer(dest, Reader(r)) return err }, } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/whiteouts.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/whiteouts.go new file mode 100644 index 000000000..3d9c31321 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive/whiteouts.go @@ -0,0 +1,23 @@ +package archive + +// Whiteouts are files with a special meaning for the layered filesystem. +// Docker uses AUFS whiteout files inside exported archives. In other +// filesystems these files are generated/handled on tar creation/extraction. + +// WhiteoutPrefix prefix means file is a whiteout. If this is followed by a +// filename this means that file has been removed from the base layer. +const WhiteoutPrefix = ".wh." + +// WhiteoutMetaPrefix prefix means whiteout has a special meaning and is not +// for remoing an actaul file. Normally these files are excluded from exported +// archives. +const WhiteoutMetaPrefix = WhiteoutPrefix + WhiteoutPrefix + +// WhiteoutLinkDir is a directory AUFS uses for storing hardlink links to other +// layers. Normally these should not go into exported archives and all changed +// hardlinks should be copied to the top layer. +const WhiteoutLinkDir = WhiteoutMetaPrefix + "plnk" + +// WhiteoutOpaqueDir file means directory has been made opaque - meaning +// readdir calls to this directory do not follow to lower layers. +const WhiteoutOpaqueDir = WhiteoutMetaPrefix + ".opq" diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils.go index 1b8cadc63..5559732a0 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "path/filepath" "strings" @@ -143,17 +142,6 @@ func CopyFile(src, dst string) (int64, error) { return io.Copy(df, sf) } -// GetTotalUsedFds Returns the number of used File Descriptors by -// reading it via /proc filesystem. -func GetTotalUsedFds() int { - if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil { - logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) - } else { - return len(fds) - } - return -1 -} - // ReadSymlinkedDirectory returns the target directory of a symlink. // The target of the symbolic link may not be a file. func ReadSymlinkedDirectory(path string) (string, error) { diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils_unix.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils_unix.go new file mode 100644 index 000000000..7e00802c1 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils_unix.go @@ -0,0 +1,22 @@ +// +build linux freebsd + +package fileutils + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus" +) + +// GetTotalUsedFds Returns the number of used File Descriptors by +// reading it via /proc filesystem. +func GetTotalUsedFds() int { + if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil { + logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) + } else { + return len(fds) + } + return -1 +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils_windows.go new file mode 100644 index 000000000..5ec21cace --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils_windows.go @@ -0,0 +1,7 @@ +package fileutils + +// GetTotalUsedFds Returns the number of used File Descriptors. Not supported +// on Windows. +func GetTotalUsedFds() int { + return -1 +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools.go new file mode 100644 index 000000000..a1301ee97 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools.go @@ -0,0 +1,195 @@ +package idtools + +import ( + "bufio" + "fmt" + "os" + "sort" + "strconv" + "strings" +) + +// IDMap contains a single entry for user namespace range remapping. An array +// of IDMap entries represents the structure that will be provided to the Linux +// kernel for creating a user namespace. +type IDMap struct { + ContainerID int `json:"container_id"` + HostID int `json:"host_id"` + Size int `json:"size"` +} + +type subIDRange struct { + Start int + Length int +} + +type ranges []subIDRange + +func (e ranges) Len() int { return len(e) } +func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] } +func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start } + +const ( + subuidFileName string = "/etc/subuid" + subgidFileName string = "/etc/subgid" +) + +// MkdirAllAs creates a directory (include any along the path) and then modifies +// ownership to the requested uid/gid. If the directory already exists, this +// function will still change ownership to the requested uid/gid pair. +func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { + return mkdirAs(path, mode, ownerUID, ownerGID, true, true) +} + +// MkdirAllNewAs creates a directory (include any along the path) and then modifies +// ownership ONLY of newly created directories to the requested uid/gid. If the +// directories along the path exist, no change of ownership will be performed +func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { + return mkdirAs(path, mode, ownerUID, ownerGID, true, false) +} + +// MkdirAs creates a directory and then modifies ownership to the requested uid/gid. +// If the directory already exists, this function still changes ownership +func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { + return mkdirAs(path, mode, ownerUID, ownerGID, false, true) +} + +// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. +// If the maps are empty, then the root uid/gid will default to "real" 0/0 +func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) { + var uid, gid int + + if uidMap != nil { + xUID, err := ToHost(0, uidMap) + if err != nil { + return -1, -1, err + } + uid = xUID + } + if gidMap != nil { + xGID, err := ToHost(0, gidMap) + if err != nil { + return -1, -1, err + } + gid = xGID + } + return uid, gid, nil +} + +// ToContainer takes an id mapping, and uses it to translate a +// host ID to the remapped ID. If no map is provided, then the translation +// assumes a 1-to-1 mapping and returns the passed in id +func ToContainer(hostID int, idMap []IDMap) (int, error) { + if idMap == nil { + return hostID, nil + } + for _, m := range idMap { + if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) { + contID := m.ContainerID + (hostID - m.HostID) + return contID, nil + } + } + return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID) +} + +// ToHost takes an id mapping and a remapped ID, and translates the +// ID to the mapped host ID. If no map is provided, then the translation +// assumes a 1-to-1 mapping and returns the passed in id # +func ToHost(contID int, idMap []IDMap) (int, error) { + if idMap == nil { + return contID, nil + } + for _, m := range idMap { + if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) { + hostID := m.HostID + (contID - m.ContainerID) + return hostID, nil + } + } + return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID) +} + +// CreateIDMappings takes a requested user and group name and +// using the data from /etc/sub{uid,gid} ranges, creates the +// proper uid and gid remapping ranges for that user/group pair +func CreateIDMappings(username, groupname string) ([]IDMap, []IDMap, error) { + subuidRanges, err := parseSubuid(username) + if err != nil { + return nil, nil, err + } + subgidRanges, err := parseSubgid(groupname) + if err != nil { + return nil, nil, err + } + if len(subuidRanges) == 0 { + return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username) + } + if len(subgidRanges) == 0 { + return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname) + } + + return createIDMap(subuidRanges), createIDMap(subgidRanges), nil +} + +func createIDMap(subidRanges ranges) []IDMap { + idMap := []IDMap{} + + // sort the ranges by lowest ID first + sort.Sort(subidRanges) + containerID := 0 + for _, idrange := range subidRanges { + idMap = append(idMap, IDMap{ + ContainerID: containerID, + HostID: idrange.Start, + Size: idrange.Length, + }) + containerID = containerID + idrange.Length + } + return idMap +} + +func parseSubuid(username string) (ranges, error) { + return parseSubidFile(subuidFileName, username) +} + +func parseSubgid(username string) (ranges, error) { + return parseSubidFile(subgidFileName, username) +} + +func parseSubidFile(path, username string) (ranges, error) { + var rangeList ranges + + subidFile, err := os.Open(path) + if err != nil { + return rangeList, err + } + defer subidFile.Close() + + s := bufio.NewScanner(subidFile) + for s.Scan() { + if err := s.Err(); err != nil { + return rangeList, err + } + + text := strings.TrimSpace(s.Text()) + if text == "" { + continue + } + parts := strings.Split(text, ":") + if len(parts) != 3 { + return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path) + } + if parts[0] == username { + // return the first entry for a user; ignores potential for multiple ranges per user + startid, err := strconv.Atoi(parts[1]) + if err != nil { + return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err) + } + length, err := strconv.Atoi(parts[2]) + if err != nil { + return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err) + } + rangeList = append(rangeList, subIDRange{startid, length}) + } + } + return rangeList, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_unix.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_unix.go new file mode 100644 index 000000000..0444307d2 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_unix.go @@ -0,0 +1,60 @@ +// +build !windows + +package idtools + +import ( + "os" + "path/filepath" + + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" +) + +func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error { + // make an array containing the original path asked for, plus (for mkAll == true) + // all path components leading up to the complete path that don't exist before we MkdirAll + // so that we can chown all of them properly at the end. If chownExisting is false, we won't + // chown the full directory path if it exists + var paths []string + if _, err := os.Stat(path); err != nil && os.IsNotExist(err) { + paths = []string{path} + } else if err == nil && chownExisting { + if err := os.Chown(path, ownerUID, ownerGID); err != nil { + return err + } + // short-circuit--we were called with an existing directory and chown was requested + return nil + } else if err == nil { + // nothing to do; directory path fully exists already and chown was NOT requested + return nil + } + + if mkAll { + // walk back to "/" looking for directories which do not exist + // and add them to the paths array for chown after creation + dirPath := path + for { + dirPath = filepath.Dir(dirPath) + if dirPath == "/" { + break + } + if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) { + paths = append(paths, dirPath) + } + } + if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) { + return err + } + } else { + if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) { + return err + } + } + // even if it existed, we will chown the requested path + any subpaths that + // didn't exist when we called MkdirAll + for _, pathComponent := range paths { + if err := os.Chown(pathComponent, ownerUID, ownerGID); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_unix_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_unix_test.go new file mode 100644 index 000000000..55b338c96 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_unix_test.go @@ -0,0 +1,243 @@ +// +build !windows + +package idtools + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "syscall" + "testing" +) + +type node struct { + uid int + gid int +} + +func TestMkdirAllAs(t *testing.T) { + dirName, err := ioutil.TempDir("", "mkdirall") + if err != nil { + t.Fatalf("Couldn't create temp dir: %v", err) + } + defer os.RemoveAll(dirName) + + testTree := map[string]node{ + "usr": {0, 0}, + "usr/bin": {0, 0}, + "lib": {33, 33}, + "lib/x86_64": {45, 45}, + "lib/x86_64/share": {1, 1}, + } + + if err := buildTree(dirName, testTree); err != nil { + t.Fatal(err) + } + + // test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid + if err := MkdirAllAs(filepath.Join(dirName, "usr", "share"), 0755, 99, 99); err != nil { + t.Fatal(err) + } + testTree["usr/share"] = node{99, 99} + verifyTree, err := readTree(dirName, "") + if err != nil { + t.Fatal(err) + } + if err := compareTrees(testTree, verifyTree); err != nil { + t.Fatal(err) + } + + // test 2-deep new directories--both should be owned by the uid/gid pair + if err := MkdirAllAs(filepath.Join(dirName, "lib", "some", "other"), 0755, 101, 101); err != nil { + t.Fatal(err) + } + testTree["lib/some"] = node{101, 101} + testTree["lib/some/other"] = node{101, 101} + verifyTree, err = readTree(dirName, "") + if err != nil { + t.Fatal(err) + } + if err := compareTrees(testTree, verifyTree); err != nil { + t.Fatal(err) + } + + // test a directory that already exists; should be chowned, but nothing else + if err := MkdirAllAs(filepath.Join(dirName, "usr"), 0755, 102, 102); err != nil { + t.Fatal(err) + } + testTree["usr"] = node{102, 102} + verifyTree, err = readTree(dirName, "") + if err != nil { + t.Fatal(err) + } + if err := compareTrees(testTree, verifyTree); err != nil { + t.Fatal(err) + } +} + +func TestMkdirAllNewAs(t *testing.T) { + + dirName, err := ioutil.TempDir("", "mkdirnew") + if err != nil { + t.Fatalf("Couldn't create temp dir: %v", err) + } + defer os.RemoveAll(dirName) + + testTree := map[string]node{ + "usr": {0, 0}, + "usr/bin": {0, 0}, + "lib": {33, 33}, + "lib/x86_64": {45, 45}, + "lib/x86_64/share": {1, 1}, + } + + if err := buildTree(dirName, testTree); err != nil { + t.Fatal(err) + } + + // test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid + if err := MkdirAllNewAs(filepath.Join(dirName, "usr", "share"), 0755, 99, 99); err != nil { + t.Fatal(err) + } + testTree["usr/share"] = node{99, 99} + verifyTree, err := readTree(dirName, "") + if err != nil { + t.Fatal(err) + } + if err := compareTrees(testTree, verifyTree); err != nil { + t.Fatal(err) + } + + // test 2-deep new directories--both should be owned by the uid/gid pair + if err := MkdirAllNewAs(filepath.Join(dirName, "lib", "some", "other"), 0755, 101, 101); err != nil { + t.Fatal(err) + } + testTree["lib/some"] = node{101, 101} + testTree["lib/some/other"] = node{101, 101} + verifyTree, err = readTree(dirName, "") + if err != nil { + t.Fatal(err) + } + if err := compareTrees(testTree, verifyTree); err != nil { + t.Fatal(err) + } + + // test a directory that already exists; should NOT be chowned + if err := MkdirAllNewAs(filepath.Join(dirName, "usr"), 0755, 102, 102); err != nil { + t.Fatal(err) + } + verifyTree, err = readTree(dirName, "") + if err != nil { + t.Fatal(err) + } + if err := compareTrees(testTree, verifyTree); err != nil { + t.Fatal(err) + } +} + +func TestMkdirAs(t *testing.T) { + + dirName, err := ioutil.TempDir("", "mkdir") + if err != nil { + t.Fatalf("Couldn't create temp dir: %v", err) + } + defer os.RemoveAll(dirName) + + testTree := map[string]node{ + "usr": {0, 0}, + } + if err := buildTree(dirName, testTree); err != nil { + t.Fatal(err) + } + + // test a directory that already exists; should just chown to the requested uid/gid + if err := MkdirAs(filepath.Join(dirName, "usr"), 0755, 99, 99); err != nil { + t.Fatal(err) + } + testTree["usr"] = node{99, 99} + verifyTree, err := readTree(dirName, "") + if err != nil { + t.Fatal(err) + } + if err := compareTrees(testTree, verifyTree); err != nil { + t.Fatal(err) + } + + // create a subdir under a dir which doesn't exist--should fail + if err := MkdirAs(filepath.Join(dirName, "usr", "bin", "subdir"), 0755, 102, 102); err == nil { + t.Fatalf("Trying to create a directory with Mkdir where the parent doesn't exist should have failed") + } + + // create a subdir under an existing dir; should only change the ownership of the new subdir + if err := MkdirAs(filepath.Join(dirName, "usr", "bin"), 0755, 102, 102); err != nil { + t.Fatal(err) + } + testTree["usr/bin"] = node{102, 102} + verifyTree, err = readTree(dirName, "") + if err != nil { + t.Fatal(err) + } + if err := compareTrees(testTree, verifyTree); err != nil { + t.Fatal(err) + } +} + +func buildTree(base string, tree map[string]node) error { + for path, node := range tree { + fullPath := filepath.Join(base, path) + if err := os.MkdirAll(fullPath, 0755); err != nil { + return fmt.Errorf("Couldn't create path: %s; error: %v", fullPath, err) + } + if err := os.Chown(fullPath, node.uid, node.gid); err != nil { + return fmt.Errorf("Couldn't chown path: %s; error: %v", fullPath, err) + } + } + return nil +} + +func readTree(base, root string) (map[string]node, error) { + tree := make(map[string]node) + + dirInfos, err := ioutil.ReadDir(base) + if err != nil { + return nil, fmt.Errorf("Couldn't read directory entries for %q: %v", base, err) + } + + for _, info := range dirInfos { + s := &syscall.Stat_t{} + if err := syscall.Stat(filepath.Join(base, info.Name()), s); err != nil { + return nil, fmt.Errorf("Can't stat file %q: %v", filepath.Join(base, info.Name()), err) + } + tree[filepath.Join(root, info.Name())] = node{int(s.Uid), int(s.Gid)} + if info.IsDir() { + // read the subdirectory + subtree, err := readTree(filepath.Join(base, info.Name()), filepath.Join(root, info.Name())) + if err != nil { + return nil, err + } + for path, nodeinfo := range subtree { + tree[path] = nodeinfo + } + } + } + return tree, nil +} + +func compareTrees(left, right map[string]node) error { + if len(left) != len(right) { + return fmt.Errorf("Trees aren't the same size") + } + for path, nodeLeft := range left { + if nodeRight, ok := right[path]; ok { + if nodeRight.uid != nodeLeft.uid || nodeRight.gid != nodeLeft.gid { + // mismatch + return fmt.Errorf("mismatched ownership for %q: expected: %d:%d, got: %d:%d", path, + nodeLeft.uid, nodeLeft.gid, nodeRight.uid, nodeRight.gid) + } + continue + } + return fmt.Errorf("right tree didn't contain path %q", path) + } + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_windows.go new file mode 100644 index 000000000..d5ec992db --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/idtools_windows.go @@ -0,0 +1,18 @@ +// +build windows + +package idtools + +import ( + "os" + + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" +) + +// Platforms such as Windows do not support the UID/GID concept. So make this +// just a wrapper around system.MkdirAll. +func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error { + if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) { + return err + } + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go new file mode 100644 index 000000000..c1eedff10 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go @@ -0,0 +1,155 @@ +package idtools + +import ( + "fmt" + "os/exec" + "path/filepath" + "strings" + "syscall" +) + +// add a user and/or group to Linux /etc/passwd, /etc/group using standard +// Linux distribution commands: +// adduser --uid --shell /bin/login --no-create-home --disabled-login --ingroup +// useradd -M -u -s /bin/nologin -N -g +// addgroup --gid +// groupadd -g + +const baseUID int = 10000 +const baseGID int = 10000 +const idMAX int = 65534 + +var ( + userCommand string + groupCommand string + + cmdTemplates = map[string]string{ + "adduser": "--uid %d --shell /bin/false --no-create-home --disabled-login --ingroup %s %s", + "useradd": "-M -u %d -s /bin/false -N -g %s %s", + "addgroup": "--gid %d %s", + "groupadd": "-g %d %s", + } +) + +func init() { + // set up which commands are used for adding users/groups dependent on distro + if _, err := resolveBinary("adduser"); err == nil { + userCommand = "adduser" + } else if _, err := resolveBinary("useradd"); err == nil { + userCommand = "useradd" + } + if _, err := resolveBinary("addgroup"); err == nil { + groupCommand = "addgroup" + } else if _, err := resolveBinary("groupadd"); err == nil { + groupCommand = "groupadd" + } +} + +func resolveBinary(binname string) (string, error) { + binaryPath, err := exec.LookPath(binname) + if err != nil { + return "", err + } + resolvedPath, err := filepath.EvalSymlinks(binaryPath) + if err != nil { + return "", err + } + //only return no error if the final resolved binary basename + //matches what was searched for + if filepath.Base(resolvedPath) == binname { + return resolvedPath, nil + } + return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath) +} + +// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair +// and calls the appropriate helper function to add the group and then +// the user to the group in /etc/group and /etc/passwd respectively. +// This new user's /etc/sub{uid,gid} ranges will be used for user namespace +// mapping ranges in containers. +func AddNamespaceRangesUser(name string) (int, int, error) { + // Find unused uid, gid pair + uid, err := findUnusedUID(baseUID) + if err != nil { + return -1, -1, fmt.Errorf("Unable to find unused UID: %v", err) + } + gid, err := findUnusedGID(baseGID) + if err != nil { + return -1, -1, fmt.Errorf("Unable to find unused GID: %v", err) + } + + // First add the group that we will use + if err := addGroup(name, gid); err != nil { + return -1, -1, fmt.Errorf("Error adding group %q: %v", name, err) + } + // Add the user as a member of the group + if err := addUser(name, uid, name); err != nil { + return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err) + } + return uid, gid, nil +} + +func addUser(userName string, uid int, groupName string) error { + + if userCommand == "" { + return fmt.Errorf("Cannot add user; no useradd/adduser binary found") + } + args := fmt.Sprintf(cmdTemplates[userCommand], uid, groupName, userName) + return execAddCmd(userCommand, args) +} + +func addGroup(groupName string, gid int) error { + + if groupCommand == "" { + return fmt.Errorf("Cannot add group; no groupadd/addgroup binary found") + } + args := fmt.Sprintf(cmdTemplates[groupCommand], gid, groupName) + // only error out if the error isn't that the group already exists + // if the group exists then our needs are already met + if err := execAddCmd(groupCommand, args); err != nil && !strings.Contains(err.Error(), "already exists") { + return err + } + return nil +} + +func execAddCmd(cmd, args string) error { + execCmd := exec.Command(cmd, strings.Split(args, " ")...) + out, err := execCmd.CombinedOutput() + if err != nil { + return fmt.Errorf("Failed to add user/group with error: %v; output: %q", err, string(out)) + } + return nil +} + +func findUnusedUID(startUID int) (int, error) { + return findUnused("passwd", startUID) +} + +func findUnusedGID(startGID int) (int, error) { + return findUnused("group", startGID) +} + +func findUnused(file string, id int) (int, error) { + for { + cmdStr := fmt.Sprintf("cat /etc/%s | cut -d: -f3 | grep '^%d$'", file, id) + cmd := exec.Command("sh", "-c", cmdStr) + if err := cmd.Run(); err != nil { + // if a non-zero return code occurs, then we know the ID was not found + // and is usable + if exiterr, ok := err.(*exec.ExitError); ok { + // The program has exited with an exit code != 0 + if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { + if status.ExitStatus() == 1 { + //no match, we can use this ID + return id, nil + } + } + } + return -1, fmt.Errorf("Error looking in /etc/%s for unused ID: %v", file, err) + } + id++ + if id > idMAX { + return -1, fmt.Errorf("Maximum id in %q reached with finding unused numeric ID", file) + } + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go new file mode 100644 index 000000000..d98b354cb --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go @@ -0,0 +1,12 @@ +// +build !linux + +package idtools + +import "fmt" + +// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair +// and calls the appropriate helper function to add the group and then +// the user to the group in /etc/group and /etc/passwd respectively. +func AddNamespaceRangesUser(name string) (int, int, error) { + return -1, -1, fmt.Errorf("No support for adding users or groups on this OS") +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/bytespipe.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/bytespipe.go new file mode 100644 index 000000000..932e1d1bc --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/bytespipe.go @@ -0,0 +1,89 @@ +package ioutils + +const maxCap = 1e6 + +// BytesPipe is io.ReadWriter which works similarly to pipe(queue). +// All written data could be read only once. Also BytesPipe is allocating +// and releasing new byte slices to adjust to current needs, so there won't be +// overgrown buffer after high load peak. +// BytesPipe isn't goroutine-safe, caller must synchronize it if needed. +type BytesPipe struct { + buf [][]byte // slice of byte-slices of buffered data + lastRead int // index in the first slice to a read point + bufLen int // length of data buffered over the slices +} + +// NewBytesPipe creates new BytesPipe, initialized by specified slice. +// If buf is nil, then it will be initialized with slice which cap is 64. +// buf will be adjusted in a way that len(buf) == 0, cap(buf) == cap(buf). +func NewBytesPipe(buf []byte) *BytesPipe { + if cap(buf) == 0 { + buf = make([]byte, 0, 64) + } + return &BytesPipe{ + buf: [][]byte{buf[:0]}, + } +} + +// Write writes p to BytesPipe. +// It can allocate new []byte slices in a process of writing. +func (bp *BytesPipe) Write(p []byte) (n int, err error) { + for { + // write data to the last buffer + b := bp.buf[len(bp.buf)-1] + // copy data to the current empty allocated area + n := copy(b[len(b):cap(b)], p) + // increment buffered data length + bp.bufLen += n + // include written data in last buffer + bp.buf[len(bp.buf)-1] = b[:len(b)+n] + + // if there was enough room to write all then break + if len(p) == n { + break + } + + // more data: write to the next slice + p = p[n:] + // allocate slice that has twice the size of the last unless maximum reached + nextCap := 2 * cap(bp.buf[len(bp.buf)-1]) + if maxCap < nextCap { + nextCap = maxCap + } + // add new byte slice to the buffers slice and continue writing + bp.buf = append(bp.buf, make([]byte, 0, nextCap)) + } + return +} + +func (bp *BytesPipe) len() int { + return bp.bufLen - bp.lastRead +} + +// Read reads bytes from BytesPipe. +// Data could be read only once. +func (bp *BytesPipe) Read(p []byte) (n int, err error) { + for { + read := copy(p, bp.buf[0][bp.lastRead:]) + n += read + bp.lastRead += read + if bp.len() == 0 { + // we have read everything. reset to the beginning. + bp.lastRead = 0 + bp.bufLen -= len(bp.buf[0]) + bp.buf[0] = bp.buf[0][:0] + break + } + // break if everything was read + if len(p) == read { + break + } + // more buffered data and more asked. read from next slice. + p = p[read:] + bp.lastRead = 0 + bp.bufLen -= len(bp.buf[0]) + bp.buf[0] = nil // throw away old slice + bp.buf = bp.buf[1:] // switch to next + } + return +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/bytespipe_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/bytespipe_test.go new file mode 100644 index 000000000..62b1a186f --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/bytespipe_test.go @@ -0,0 +1,141 @@ +package ioutils + +import ( + "crypto/sha1" + "encoding/hex" + "testing" +) + +func TestBytesPipeRead(t *testing.T) { + buf := NewBytesPipe(nil) + buf.Write([]byte("12")) + buf.Write([]byte("34")) + buf.Write([]byte("56")) + buf.Write([]byte("78")) + buf.Write([]byte("90")) + rd := make([]byte, 4) + n, err := buf.Read(rd) + if err != nil { + t.Fatal(err) + } + if n != 4 { + t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 4) + } + if string(rd) != "1234" { + t.Fatalf("Read %s, but must be %s", rd, "1234") + } + n, err = buf.Read(rd) + if err != nil { + t.Fatal(err) + } + if n != 4 { + t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 4) + } + if string(rd) != "5678" { + t.Fatalf("Read %s, but must be %s", rd, "5679") + } + n, err = buf.Read(rd) + if err != nil { + t.Fatal(err) + } + if n != 2 { + t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 2) + } + if string(rd[:n]) != "90" { + t.Fatalf("Read %s, but must be %s", rd, "90") + } +} + +func TestBytesPipeWrite(t *testing.T) { + buf := NewBytesPipe(nil) + buf.Write([]byte("12")) + buf.Write([]byte("34")) + buf.Write([]byte("56")) + buf.Write([]byte("78")) + buf.Write([]byte("90")) + if string(buf.buf[0]) != "1234567890" { + t.Fatalf("Buffer %s, must be %s", buf.buf, "1234567890") + } +} + +// Write and read in different speeds/chunk sizes and check valid data is read. +func TestBytesPipeWriteRandomChunks(t *testing.T) { + cases := []struct{ iterations, writesPerLoop, readsPerLoop int }{ + {100, 10, 1}, + {1000, 10, 5}, + {1000, 100, 0}, + {1000, 5, 6}, + {10000, 50, 25}, + } + + testMessage := []byte("this is a random string for testing") + // random slice sizes to read and write + writeChunks := []int{25, 35, 15, 20} + readChunks := []int{5, 45, 20, 25} + + for _, c := range cases { + // first pass: write directly to hash + hash := sha1.New() + for i := 0; i < c.iterations*c.writesPerLoop; i++ { + if _, err := hash.Write(testMessage[:writeChunks[i%len(writeChunks)]]); err != nil { + t.Fatal(err) + } + } + expected := hex.EncodeToString(hash.Sum(nil)) + + // write/read through buffer + buf := NewBytesPipe(nil) + hash.Reset() + for i := 0; i < c.iterations; i++ { + for w := 0; w < c.writesPerLoop; w++ { + buf.Write(testMessage[:writeChunks[(i*c.writesPerLoop+w)%len(writeChunks)]]) + } + for r := 0; r < c.readsPerLoop; r++ { + p := make([]byte, readChunks[(i*c.readsPerLoop+r)%len(readChunks)]) + n, _ := buf.Read(p) + hash.Write(p[:n]) + } + } + // read rest of the data from buffer + for i := 0; ; i++ { + p := make([]byte, readChunks[(c.iterations*c.readsPerLoop+i)%len(readChunks)]) + n, _ := buf.Read(p) + if n == 0 { + break + } + hash.Write(p[:n]) + } + actual := hex.EncodeToString(hash.Sum(nil)) + + if expected != actual { + t.Fatalf("BytesPipe returned invalid data. Expected checksum %v, got %v", expected, actual) + } + + } +} + +func BenchmarkBytesPipeWrite(b *testing.B) { + for i := 0; i < b.N; i++ { + buf := NewBytesPipe(nil) + for j := 0; j < 1000; j++ { + buf.Write([]byte("pretty short line, because why not?")) + } + } +} + +func BenchmarkBytesPipeRead(b *testing.B) { + rd := make([]byte, 1024) + for i := 0; i < b.N; i++ { + b.StopTimer() + buf := NewBytesPipe(nil) + for j := 0; j < 1000; j++ { + buf.Write(make([]byte, 1024)) + } + b.StartTimer() + for j := 0; j < 1000; j++ { + if n, _ := buf.Read(rd); n != 1024 { + b.Fatalf("Wrong number of bytes: %d", n) + } + } + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/fmt.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/fmt.go index 801132ff3..0b04b0ba3 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/fmt.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/fmt.go @@ -12,3 +12,11 @@ func FprintfIfNotEmpty(w io.Writer, format, value string) (int, error) { } return 0, nil } + +// FprintfIfTrue prints the boolean value if it's true +func FprintfIfTrue(w io.Writer, format string, ok bool) (int, error) { + if ok { + return fmt.Fprintf(w, format, ok) + } + return 0, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/readers.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/readers.go index ff09baad1..54dd312bb 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/readers.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/readers.go @@ -1,14 +1,10 @@ package ioutils import ( - "bytes" - "crypto/rand" "crypto/sha256" "encoding/hex" "io" - "math/big" "sync" - "time" ) type readCloserWrapper struct { @@ -20,6 +16,7 @@ func (r *readCloserWrapper) Close() error { return r.closer() } +// NewReadCloserWrapper returns a new io.ReadCloser. func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser { return &readCloserWrapper{ Reader: r, @@ -40,6 +37,7 @@ func (r *readerErrWrapper) Read(p []byte) (int, error) { return n, err } +// NewReaderErrWrapper returns a new io.Reader. func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader { return &readerErrWrapper{ reader: r, @@ -53,41 +51,27 @@ func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader { // expanding buffer. type bufReader struct { sync.Mutex - buf *bytes.Buffer - reader io.Reader - err error - wait sync.Cond - drainBuf []byte - reuseBuf []byte - maxReuse int64 - resetTimeout time.Duration - bufLenResetThreshold int64 - maxReadDataReset int64 + buf io.ReadWriter + reader io.Reader + err error + wait sync.Cond + drainBuf []byte } -func NewBufReader(r io.Reader) *bufReader { - var timeout int - if randVal, err := rand.Int(rand.Reader, big.NewInt(120)); err == nil { - timeout = int(randVal.Int64()) + 180 - } else { - timeout = 300 - } +// NewBufReader returns a new bufReader. +func NewBufReader(r io.Reader) io.ReadCloser { reader := &bufReader{ - buf: &bytes.Buffer{}, - drainBuf: make([]byte, 1024), - reuseBuf: make([]byte, 4096), - maxReuse: 1000, - resetTimeout: time.Second * time.Duration(timeout), - bufLenResetThreshold: 100 * 1024, - maxReadDataReset: 10 * 1024 * 1024, - reader: r, + buf: NewBytesPipe(nil), + reader: r, + drainBuf: make([]byte, 1024), } reader.wait.L = &reader.Mutex go reader.drain() return reader } -func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer *bytes.Buffer) *bufReader { +// NewBufReaderWithDrainbufAndBuffer returns a BufReader with drainBuffer and buffer. +func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer io.ReadWriter) io.ReadCloser { reader := &bufReader{ buf: buffer, drainBuf: drainBuffer, @@ -99,97 +83,24 @@ func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer * } func (r *bufReader) drain() { - var ( - duration time.Duration - lastReset time.Time - now time.Time - reset bool - bufLen int64 - dataSinceReset int64 - maxBufLen int64 - reuseBufLen int64 - reuseCount int64 - ) - reuseBufLen = int64(len(r.reuseBuf)) - lastReset = time.Now() for { + //Call to scheduler is made to yield from this goroutine. + //This avoids goroutine looping here when n=0,err=nil, fixes code hangs when run with GCC Go. + callSchedulerIfNecessary() n, err := r.reader.Read(r.drainBuf) - dataSinceReset += int64(n) r.Lock() - bufLen = int64(r.buf.Len()) - if bufLen > maxBufLen { - maxBufLen = bufLen - } - - // Avoid unbounded growth of the buffer over time. - // This has been discovered to be the only non-intrusive - // solution to the unbounded growth of the buffer. - // Alternative solutions such as compression, multiple - // buffers, channels and other similar pieces of code - // were reducing throughput, overall Docker performance - // or simply crashed Docker. - // This solution releases the buffer when specific - // conditions are met to avoid the continuous resizing - // of the buffer for long lived containers. - // - // Move data to the front of the buffer if it's - // smaller than what reuseBuf can store - if bufLen > 0 && reuseBufLen >= bufLen { - n, _ := r.buf.Read(r.reuseBuf) - r.buf.Write(r.reuseBuf[0:n]) - // Take action if the buffer has been reused too many - // times and if there's data in the buffer. - // The timeout is also used as means to avoid doing - // these operations more often or less often than - // required. - // The various conditions try to detect heavy activity - // in the buffer which might be indicators of heavy - // growth of the buffer. - } else if reuseCount >= r.maxReuse && bufLen > 0 { - now = time.Now() - duration = now.Sub(lastReset) - timeoutReached := duration >= r.resetTimeout - - // The timeout has been reached and the - // buffered data couldn't be moved to the front - // of the buffer, so the buffer gets reset. - if timeoutReached && bufLen > reuseBufLen { - reset = true - } - // The amount of buffered data is too high now, - // reset the buffer. - if timeoutReached && maxBufLen >= r.bufLenResetThreshold { - reset = true - } - // Reset the buffer if a certain amount of - // data has gone through the buffer since the - // last reset. - if timeoutReached && dataSinceReset >= r.maxReadDataReset { - reset = true - } - // The buffered data is moved to a fresh buffer, - // swap the old buffer with the new one and - // reset all counters. - if reset { - newbuf := &bytes.Buffer{} - newbuf.ReadFrom(r.buf) - r.buf = newbuf - lastReset = now - reset = false - dataSinceReset = 0 - maxBufLen = 0 - reuseCount = 0 - } - } if err != nil { r.err = err } else { - r.buf.Write(r.drainBuf[0:n]) + if n == 0 { + // nothing written, no need to signal + r.Unlock() + continue + } + r.buf.Write(r.drainBuf[:n]) } - reuseCount++ r.wait.Signal() r.Unlock() - callSchedulerIfNecessary() if err != nil { break } @@ -211,6 +122,7 @@ func (r *bufReader) Read(p []byte) (n int, err error) { } } +// Close closes the bufReader func (r *bufReader) Close() error { closer, ok := r.reader.(io.ReadCloser) if !ok { @@ -219,6 +131,7 @@ func (r *bufReader) Close() error { return closer.Close() } +// HashData returns the sha256 sum of src. func HashData(src io.Reader) (string, error) { h := sha256.New() if _, err := io.Copy(h, src); err != nil { @@ -227,6 +140,8 @@ func HashData(src io.Reader) (string, error) { return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil } +// OnEOFReader wraps a io.ReadCloser and a function +// the function will run at the end of file or close the file. type OnEOFReader struct { Rc io.ReadCloser Fn func() @@ -240,6 +155,7 @@ func (r *OnEOFReader) Read(p []byte) (n int, err error) { return } +// Close closes the file and run the function. func (r *OnEOFReader) Close() error { err := r.Rc.Close() r.runFunc() diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/readers_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/readers_test.go index 0a39b6ec6..5c26a2a14 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/readers_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/readers_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "strings" "testing" + "time" ) // Implement io.Reader @@ -61,8 +62,8 @@ func TestNewBufReaderWithDrainbufAndBuffer(t *testing.T) { reader, writer := io.Pipe() drainBuffer := make([]byte, 1024) - buffer := bytes.Buffer{} - bufreader := NewBufReaderWithDrainbufAndBuffer(reader, drainBuffer, &buffer) + buffer := NewBytesPipe(nil) + bufreader := NewBufReaderWithDrainbufAndBuffer(reader, drainBuffer, buffer) // Write everything down to a Pipe // Usually, a pipe should block but because of the buffered reader, @@ -76,7 +77,11 @@ func TestNewBufReaderWithDrainbufAndBuffer(t *testing.T) { // Drain the reader *after* everything has been written, just to verify // it is indeed buffering - <-done + select { + case <-done: + case <-time.After(1 * time.Second): + t.Fatal("timeout") + } output, err := ioutil.ReadAll(bufreader) if err != nil { @@ -124,13 +129,16 @@ func TestBufReaderCloseWithNonReaderCloser(t *testing.T) { } // implements io.ReadCloser -type simpleReaderCloser struct{} +type simpleReaderCloser struct { + err error +} func (r *simpleReaderCloser) Read(p []byte) (n int, err error) { - return 0, nil + return 0, r.err } func (r *simpleReaderCloser) Close() error { + r.err = io.EOF return nil } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/temp_unix.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/temp_unix.go new file mode 100644 index 000000000..1539ad21b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/temp_unix.go @@ -0,0 +1,10 @@ +// +build !windows + +package ioutils + +import "io/ioutil" + +// TempDir on Unix systems is equivalent to ioutil.TempDir. +func TempDir(dir, prefix string) (string, error) { + return ioutil.TempDir(dir, prefix) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/temp_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/temp_windows.go new file mode 100644 index 000000000..72c0bc597 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils/temp_windows.go @@ -0,0 +1,18 @@ +// +build windows + +package ioutils + +import ( + "io/ioutil" + + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath" +) + +// TempDir is the equivalent of ioutil.TempDir, except that the result is in Windows longpath format. +func TempDir(dir, prefix string) (string, error) { + tempDir, err := ioutil.TempDir(dir, prefix) + if err != nil { + return "", err + } + return longpath.AddPrefix(tempDir), nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath/longpath.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath/longpath.go new file mode 100644 index 000000000..9b15bfff4 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath/longpath.go @@ -0,0 +1,26 @@ +// longpath introduces some constants and helper functions for handling long paths +// in Windows, which are expected to be prepended with `\\?\` and followed by either +// a drive letter, a UNC server\share, or a volume identifier. + +package longpath + +import ( + "strings" +) + +// Prefix is the longpath prefix for Windows file paths. +const Prefix = `\\?\` + +// AddPrefix will add the Windows long path prefix to the path provided if +// it does not already have it. +func AddPrefix(path string) string { + if !strings.HasPrefix(path, Prefix) { + if strings.HasPrefix(path, `\\`) { + // This is a UNC path, so we need to add 'UNC' to the path as well. + path = Prefix + `UNC` + path[1:] + } else { + path = Prefix + path + } + } + return path +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath/longpath_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath/longpath_test.go new file mode 100644 index 000000000..01865eff0 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath/longpath_test.go @@ -0,0 +1,22 @@ +package longpath + +import ( + "strings" + "testing" +) + +func TestStandardLongPath(t *testing.T) { + c := `C:\simple\path` + longC := AddPrefix(c) + if !strings.EqualFold(longC, `\\?\C:\simple\path`) { + t.Errorf("Wrong long path returned. Original = %s ; Long = %s", c, longC) + } +} + +func TestUNCLongPath(t *testing.T) { + c := `\\server\share\path` + longC := AddPrefix(c) + if !strings.EqualFold(longC, `\\?\UNC\server\share\path`) { + t.Errorf("Wrong UNC long path returned. Original = %s ; Long = %s", c, longC) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/parsers/parsers.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/parsers/parsers.go index e326a1191..a604a9e12 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/parsers/parsers.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/parsers/parsers.go @@ -5,6 +5,7 @@ package parsers import ( "fmt" + "net" "net/url" "path" "runtime" @@ -12,18 +13,20 @@ import ( "strings" ) -// ParseHost parses the specified address and returns an address that will be used as the host. +// ParseDockerDaemonHost parses the specified address and returns an address that will be used as the host. // Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr -// FIXME: Change this not to receive default value as parameter -func ParseHost(defaultTCPAddr, defaultUnixAddr, addr string) (string, error) { +// defaultUnixAddr must be a absolute file path (no `unix://` prefix) +// defaultTCPAddr must be the full `tcp://host:port` form +func ParseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defaultAddr, addr string) (string, error) { addr = strings.TrimSpace(addr) if addr == "" { - if runtime.GOOS != "windows" { - addr = fmt.Sprintf("unix://%s", defaultUnixAddr) - } else { - // Note - defaultTCPAddr already includes tcp:// prefix - addr = defaultTCPAddr + if defaultAddr == defaultTLSHost { + return defaultTLSHost, nil } + if runtime.GOOS != "windows" { + return fmt.Sprintf("unix://%s", defaultUnixAddr), nil + } + return defaultTCPAddr, nil } addrParts := strings.Split(addr, "://") if len(addrParts) == 1 { @@ -59,29 +62,48 @@ func ParseUnixAddr(addr string, defaultAddr string) (string, error) { // ParseTCPAddr parses and validates that the specified address is a valid TCP // address. It returns a formatted TCP address, either using the address parsed -// from addr, or the contents of defaultAddr if addr is a blank string. -func ParseTCPAddr(addr string, defaultAddr string) (string, error) { - addr = strings.TrimPrefix(addr, "tcp://") +// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string. +// tryAddr is expected to have already been Trim()'d +// defaultAddr must be in the full `tcp://host:port` form +func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) { + if tryAddr == "" || tryAddr == "tcp://" { + return defaultAddr, nil + } + addr := strings.TrimPrefix(tryAddr, "tcp://") if strings.Contains(addr, "://") || addr == "" { - return "", fmt.Errorf("Invalid proto, expected tcp: %s", addr) + return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr) } u, err := url.Parse("tcp://" + addr) if err != nil { return "", err } - hostParts := strings.Split(u.Host, ":") - if len(hostParts) != 2 { - return "", fmt.Errorf("Invalid bind address format: %s", addr) - } - host := hostParts[0] - if host == "" { - host = defaultAddr + + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) } - p, err := strconv.Atoi(hostParts[1]) + defaultAddr = strings.TrimPrefix(defaultAddr, "tcp://") + defaultHost, defaultPort, err := net.SplitHostPort(defaultAddr) + if err != nil { + return "", err + } + + if host == "" { + host = defaultHost + } + if port == "" { + port = defaultPort + } + p, err := strconv.Atoi(port) if err != nil && p == 0 { - return "", fmt.Errorf("Invalid bind address format: %s", addr) + return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) + } + + if net.ParseIP(host).To4() == nil && strings.Contains(host, ":") { + // This is either an ipv6 address + host = "[" + host + "]" } return fmt.Sprintf("tcp://%s:%d%s", host, p, u.Path), nil } @@ -116,7 +138,7 @@ func PartParser(template, data string) (map[string]string, error) { out = make(map[string]string, len(templateParts)) ) if len(parts) != len(templateParts) { - return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template) + return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template) } for i, t := range templateParts { @@ -185,3 +207,53 @@ func ParseLink(val string) (string, string, error) { } return arr[0], arr[1], nil } + +// ParseUintList parses and validates the specified string as the value +// found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be +// one of the formats below. Note that duplicates are actually allowed in the +// input string. It returns a `map[int]bool` with available elements from `val` +// set to `true`. +// Supported formats: +// 7 +// 1-6 +// 0,3-4,7,8-10 +// 0-0,0,1-7 +// 03,1-3 <- this is gonna get parsed as [1,2,3] +// 3,2,1 +// 0-2,3,1 +func ParseUintList(val string) (map[int]bool, error) { + if val == "" { + return map[int]bool{}, nil + } + + availableInts := make(map[int]bool) + split := strings.Split(val, ",") + errInvalidFormat := fmt.Errorf("invalid format: %s", val) + + for _, r := range split { + if !strings.Contains(r, "-") { + v, err := strconv.Atoi(r) + if err != nil { + return nil, errInvalidFormat + } + availableInts[v] = true + } else { + split := strings.SplitN(r, "-", 2) + min, err := strconv.Atoi(split[0]) + if err != nil { + return nil, errInvalidFormat + } + max, err := strconv.Atoi(split[1]) + if err != nil { + return nil, errInvalidFormat + } + if max < min { + return nil, errInvalidFormat + } + for i := min; i <= max; i++ { + availableInts[i] = true + } + } + } + return availableInts, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/parsers/parsers_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/parsers/parsers_test.go index 903c66afb..47b45281c 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/parsers/parsers_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/parsers/parsers_test.go @@ -1,52 +1,123 @@ package parsers import ( + "reflect" + "runtime" "strings" "testing" ) -func TestParseHost(t *testing.T) { +func TestParseDockerDaemonHost(t *testing.T) { var ( - defaultHTTPHost = "127.0.0.1" - defaultUnix = "/var/run/docker.sock" + defaultHTTPHost = "tcp://localhost:2375" + defaultHTTPSHost = "tcp://localhost:2376" + defaultUnix = "/var/run/docker.sock" + defaultHOST = "unix:///var/run/docker.sock" ) + if runtime.GOOS == "windows" { + defaultHOST = defaultHTTPHost + } invalids := map[string]string{ - "0.0.0.0": "Invalid bind address format: 0.0.0.0", - "tcp://": "Invalid proto, expected tcp: ", - "tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d", - "tcp:a.b.c.d/path": "Invalid bind address format: tcp:a.b.c.d/path", - "udp://127.0.0.1": "Invalid bind address format: udp://127.0.0.1", - "udp://127.0.0.1:2375": "Invalid bind address format: udp://127.0.0.1:2375", + "0.0.0.0": "Invalid bind address format: 0.0.0.0", + "tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d", + "tcp:a.b.c.d/path": "Invalid bind address format: tcp:a.b.c.d/path", + "udp://127.0.0.1": "Invalid bind address format: udp://127.0.0.1", + "udp://127.0.0.1:2375": "Invalid bind address format: udp://127.0.0.1:2375", + "tcp://unix:///run/docker.sock": "Invalid bind address format: unix", + "tcp": "Invalid bind address format: tcp", + "unix": "Invalid bind address format: unix", + "fd": "Invalid bind address format: fd", } valids := map[string]string{ - "0.0.0.1:5555": "tcp://0.0.0.1:5555", - "0.0.0.1:5555/path": "tcp://0.0.0.1:5555/path", - ":6666": "tcp://127.0.0.1:6666", - ":6666/path": "tcp://127.0.0.1:6666/path", - "tcp://:7777": "tcp://127.0.0.1:7777", - "tcp://:7777/path": "tcp://127.0.0.1:7777/path", - "": "unix:///var/run/docker.sock", + "0.0.0.1:": "tcp://0.0.0.1:2375", + "0.0.0.1:5555": "tcp://0.0.0.1:5555", + "0.0.0.1:5555/path": "tcp://0.0.0.1:5555/path", + "[::1]:": "tcp://[::1]:2375", + "[::1]:5555/path": "tcp://[::1]:5555/path", + "[0:0:0:0:0:0:0:1]:": "tcp://[0:0:0:0:0:0:0:1]:2375", + "[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path", + ":6666": "tcp://localhost:6666", + ":6666/path": "tcp://localhost:6666/path", + "": defaultHOST, + " ": defaultHOST, + " ": defaultHOST, + "tcp://": defaultHTTPHost, + "tcp://:7777": "tcp://localhost:7777", + "tcp://:7777/path": "tcp://localhost:7777/path", + " tcp://:7777/path ": "tcp://localhost:7777/path", "unix:///run/docker.sock": "unix:///run/docker.sock", "unix://": "unix:///var/run/docker.sock", "fd://": "fd://", "fd://something": "fd://something", + "localhost:": "tcp://localhost:2375", + "localhost:5555": "tcp://localhost:5555", + "localhost:5555/path": "tcp://localhost:5555/path", } for invalidAddr, expectedError := range invalids { - if addr, err := ParseHost(defaultHTTPHost, defaultUnix, invalidAddr); err == nil || err.Error() != expectedError { + if addr, err := ParseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", invalidAddr); err == nil || err.Error() != expectedError { t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr) } } for validAddr, expectedAddr := range valids { - if addr, err := ParseHost(defaultHTTPHost, defaultUnix, validAddr); err != nil || addr != expectedAddr { - t.Errorf("%v -> expected %v, got %v", validAddr, expectedAddr, addr) + if addr, err := ParseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", validAddr); err != nil || addr != expectedAddr { + t.Errorf("%v -> expected %v, got (%v) addr (%v)", validAddr, expectedAddr, err, addr) + } + } +} + +func TestParseTCP(t *testing.T) { + var ( + defaultHTTPHost = "tcp://127.0.0.1:2376" + ) + invalids := map[string]string{ + "0.0.0.0": "Invalid bind address format: 0.0.0.0", + "tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d", + "tcp:a.b.c.d/path": "Invalid bind address format: tcp:a.b.c.d/path", + "udp://127.0.0.1": "Invalid proto, expected tcp: udp://127.0.0.1", + "udp://127.0.0.1:2375": "Invalid proto, expected tcp: udp://127.0.0.1:2375", + } + valids := map[string]string{ + "": defaultHTTPHost, + "tcp://": defaultHTTPHost, + "0.0.0.1:": "tcp://0.0.0.1:2376", + "0.0.0.1:5555": "tcp://0.0.0.1:5555", + "0.0.0.1:5555/path": "tcp://0.0.0.1:5555/path", + ":6666": "tcp://127.0.0.1:6666", + ":6666/path": "tcp://127.0.0.1:6666/path", + "tcp://:7777": "tcp://127.0.0.1:7777", + "tcp://:7777/path": "tcp://127.0.0.1:7777/path", + "[::1]:": "tcp://[::1]:2376", + "[::1]:5555": "tcp://[::1]:5555", + "[::1]:5555/path": "tcp://[::1]:5555/path", + "[0:0:0:0:0:0:0:1]:": "tcp://[0:0:0:0:0:0:0:1]:2376", + "[0:0:0:0:0:0:0:1]:5555": "tcp://[0:0:0:0:0:0:0:1]:5555", + "[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path", + "localhost:": "tcp://localhost:2376", + "localhost:5555": "tcp://localhost:5555", + "localhost:5555/path": "tcp://localhost:5555/path", + } + for invalidAddr, expectedError := range invalids { + if addr, err := ParseTCPAddr(invalidAddr, defaultHTTPHost); err == nil || err.Error() != expectedError { + t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr) + } + } + for validAddr, expectedAddr := range valids { + if addr, err := ParseTCPAddr(validAddr, defaultHTTPHost); err != nil || addr != expectedAddr { + t.Errorf("%v -> expected %v, got %v and addr %v", validAddr, expectedAddr, err, addr) } } } func TestParseInvalidUnixAddrInvalid(t *testing.T) { - if _, err := ParseUnixAddr("unix://tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" { + if _, err := ParseUnixAddr("tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" { t.Fatalf("Expected an error, got %v", err) } + if _, err := ParseUnixAddr("unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" { + t.Fatalf("Expected an error, got %v", err) + } + if v, err := ParseUnixAddr("", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" { + t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock") + } } func TestParseRepositoryTag(t *testing.T) { @@ -79,29 +150,6 @@ func TestParseRepositoryTag(t *testing.T) { } } -func TestParsePortMapping(t *testing.T) { - if _, err := PartParser("ip:public:private", "192.168.1.1:80"); err == nil { - t.Fatalf("Expected an error, got %v", err) - } - data, err := PartParser("ip:public:private", "192.168.1.1:80:8080") - if err != nil { - t.Fatal(err) - } - - if len(data) != 3 { - t.FailNow() - } - if data["ip"] != "192.168.1.1" { - t.Fail() - } - if data["public"] != "80" { - t.Fail() - } - if data["private"] != "8080" { - t.Fail() - } -} - func TestParseKeyValueOpt(t *testing.T) { invalids := map[string]string{ "": "Unable to parse key/value option: ", @@ -208,3 +256,40 @@ func TestParseLink(t *testing.T) { t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err) } } + +func TestParseUintList(t *testing.T) { + valids := map[string]map[int]bool{ + "": {}, + "7": {7: true}, + "1-6": {1: true, 2: true, 3: true, 4: true, 5: true, 6: true}, + "0-7": {0: true, 1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true}, + "0,3-4,7,8-10": {0: true, 3: true, 4: true, 7: true, 8: true, 9: true, 10: true}, + "0-0,0,1-4": {0: true, 1: true, 2: true, 3: true, 4: true}, + "03,1-3": {1: true, 2: true, 3: true}, + "3,2,1": {1: true, 2: true, 3: true}, + "0-2,3,1": {0: true, 1: true, 2: true, 3: true}, + } + for k, v := range valids { + out, err := ParseUintList(k) + if err != nil { + t.Fatalf("Expected not to fail, got %v", err) + } + if !reflect.DeepEqual(out, v) { + t.Fatalf("Expected %v, got %v", v, out) + } + } + + invalids := []string{ + "this", + "1--", + "1-10,,10", + "10-1", + "-1", + "-1,0", + } + for _, v := range invalids { + if out, err := ParseUintList(v); err == nil { + t.Fatalf("Expected failure with %s but got %v", v, out) + } + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy/stdcopy.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy/stdcopy.go index 63b3df79f..b2c60046a 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy/stdcopy.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy/stdcopy.go @@ -9,19 +9,26 @@ import ( ) const ( - StdWriterPrefixLen = 8 - StdWriterFdIndex = 0 - StdWriterSizeIndex = 4 + stdWriterPrefixLen = 8 + stdWriterFdIndex = 0 + stdWriterSizeIndex = 4 + + startingBufLen = 32*1024 + stdWriterPrefixLen + 1 ) -type StdType [StdWriterPrefixLen]byte +// StdType prefixes type and length to standard stream. +type StdType [stdWriterPrefixLen]byte var ( - Stdin StdType = StdType{0: 0} - Stdout StdType = StdType{0: 1} - Stderr StdType = StdType{0: 2} + // Stdin represents standard input stream type. + Stdin = StdType{0: 0} + // Stdout represents standard output stream type. + Stdout = StdType{0: 1} + // Stderr represents standard error steam type. + Stderr = StdType{0: 2} ) +// StdWriter is wrapper of io.Writer with extra customized info. type StdWriter struct { io.Writer prefix StdType @@ -36,10 +43,10 @@ func (w *StdWriter) Write(buf []byte) (n int, err error) { binary.BigEndian.PutUint32(w.prefix[4:], uint32(len(buf))) n1, err = w.Writer.Write(w.prefix[:]) if err != nil { - n = n1 - StdWriterPrefixLen + n = n1 - stdWriterPrefixLen } else { n2, err = w.Writer.Write(buf) - n = n1 + n2 - StdWriterPrefixLen + n = n1 + n2 - stdWriterPrefixLen } if n < 0 { n = 0 @@ -61,7 +68,7 @@ func NewStdWriter(w io.Writer, t StdType) *StdWriter { } } -var ErrInvalidStdHeader = errors.New("Unrecognized input header") +var errInvalidStdHeader = errors.New("Unrecognized input header") // StdCopy is a modified version of io.Copy. // @@ -75,7 +82,7 @@ var ErrInvalidStdHeader = errors.New("Unrecognized input header") // `written` will hold the total number of bytes written to `dstout` and `dsterr`. func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) { var ( - buf = make([]byte, 32*1024+StdWriterPrefixLen+1) + buf = make([]byte, startingBufLen) bufLen = len(buf) nr, nw int er, ew error @@ -85,12 +92,12 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) for { // Make sure we have at least a full header - for nr < StdWriterPrefixLen { + for nr < stdWriterPrefixLen { var nr2 int nr2, er = src.Read(buf[nr:]) nr += nr2 if er == io.EOF { - if nr < StdWriterPrefixLen { + if nr < stdWriterPrefixLen { logrus.Debugf("Corrupted prefix: %v", buf[:nr]) return written, nil } @@ -103,7 +110,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) } // Check the first byte to know where to write - switch buf[StdWriterFdIndex] { + switch buf[stdWriterFdIndex] { case 0: fallthrough case 1: @@ -113,30 +120,30 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) // Write on stderr out = dsterr default: - logrus.Debugf("Error selecting output fd: (%d)", buf[StdWriterFdIndex]) - return 0, ErrInvalidStdHeader + logrus.Debugf("Error selecting output fd: (%d)", buf[stdWriterFdIndex]) + return 0, errInvalidStdHeader } // Retrieve the size of the frame - frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4])) + frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4])) logrus.Debugf("framesize: %d", frameSize) // Check if the buffer is big enough to read the frame. // Extend it if necessary. - if frameSize+StdWriterPrefixLen > bufLen { - logrus.Debugf("Extending buffer cap by %d (was %d)", frameSize+StdWriterPrefixLen-bufLen+1, len(buf)) - buf = append(buf, make([]byte, frameSize+StdWriterPrefixLen-bufLen+1)...) + if frameSize+stdWriterPrefixLen > bufLen { + logrus.Debugf("Extending buffer cap by %d (was %d)", frameSize+stdWriterPrefixLen-bufLen+1, len(buf)) + buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...) bufLen = len(buf) } // While the amount of bytes read is less than the size of the frame + header, we keep reading - for nr < frameSize+StdWriterPrefixLen { + for nr < frameSize+stdWriterPrefixLen { var nr2 int nr2, er = src.Read(buf[nr:]) nr += nr2 if er == io.EOF { - if nr < frameSize+StdWriterPrefixLen { - logrus.Debugf("Corrupted frame: %v", buf[StdWriterPrefixLen:nr]) + if nr < frameSize+stdWriterPrefixLen { + logrus.Debugf("Corrupted frame: %v", buf[stdWriterPrefixLen:nr]) return written, nil } break @@ -148,7 +155,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) } // Write the retrieved frame (without header) - nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen]) + nw, ew = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen]) if ew != nil { logrus.Debugf("Error writing frame: %s", ew) return 0, ew @@ -161,8 +168,8 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) written += int64(nw) // Move the rest of the buffer to the beginning - copy(buf, buf[frameSize+StdWriterPrefixLen:]) + copy(buf, buf[frameSize+stdWriterPrefixLen:]) // Move the index - nr -= frameSize + StdWriterPrefixLen + nr -= frameSize + stdWriterPrefixLen } } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy/stdcopy_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy/stdcopy_test.go index a9fd73a49..88d88d41e 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy/stdcopy_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy/stdcopy_test.go @@ -2,6 +2,8 @@ package stdcopy import ( "bytes" + "errors" + "io" "io/ioutil" "strings" "testing" @@ -45,7 +47,143 @@ func TestWrite(t *testing.T) { t.Fatalf("Error while writing with StdWrite") } if n != len(data) { - t.Fatalf("Write should have writen %d byte but wrote %d.", len(data), n) + t.Fatalf("Write should have written %d byte but wrote %d.", len(data), n) + } +} + +type errWriter struct { + n int + err error +} + +func (f *errWriter) Write(buf []byte) (int, error) { + return f.n, f.err +} + +func TestWriteWithWriterError(t *testing.T) { + expectedError := errors.New("expected") + expectedReturnedBytes := 10 + writer := NewStdWriter(&errWriter{ + n: stdWriterPrefixLen + expectedReturnedBytes, + err: expectedError}, Stdout) + data := []byte("This won't get written, sigh") + n, err := writer.Write(data) + if err != expectedError { + t.Fatalf("Didn't get expected error.") + } + if n != expectedReturnedBytes { + t.Fatalf("Didn't get expected writen bytes %d, got %d.", + expectedReturnedBytes, n) + } +} + +func TestWriteDoesNotReturnNegativeWrittenBytes(t *testing.T) { + writer := NewStdWriter(&errWriter{n: -1}, Stdout) + data := []byte("This won't get written, sigh") + actual, _ := writer.Write(data) + if actual != 0 { + t.Fatalf("Expected returned written bytes equal to 0, got %d", actual) + } +} + +func getSrcBuffer(stdOutBytes, stdErrBytes []byte) (buffer *bytes.Buffer, err error) { + buffer = new(bytes.Buffer) + dstOut := NewStdWriter(buffer, Stdout) + _, err = dstOut.Write(stdOutBytes) + if err != nil { + return + } + dstErr := NewStdWriter(buffer, Stderr) + _, err = dstErr.Write(stdErrBytes) + return +} + +func TestStdCopyWriteAndRead(t *testing.T) { + stdOutBytes := []byte(strings.Repeat("o", startingBufLen)) + stdErrBytes := []byte(strings.Repeat("e", startingBufLen)) + buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes) + if err != nil { + t.Fatal(err) + } + written, err := StdCopy(ioutil.Discard, ioutil.Discard, buffer) + if err != nil { + t.Fatal(err) + } + expectedTotalWritten := len(stdOutBytes) + len(stdErrBytes) + if written != int64(expectedTotalWritten) { + t.Fatalf("Expected to have total of %d bytes written, got %d", expectedTotalWritten, written) + } +} + +type customReader struct { + n int + err error + totalCalls int + correctCalls int + src *bytes.Buffer +} + +func (f *customReader) Read(buf []byte) (int, error) { + f.totalCalls++ + if f.totalCalls <= f.correctCalls { + return f.src.Read(buf) + } + return f.n, f.err +} + +func TestStdCopyReturnsErrorReadingHeader(t *testing.T) { + expectedError := errors.New("error") + reader := &customReader{ + err: expectedError} + written, err := StdCopy(ioutil.Discard, ioutil.Discard, reader) + if written != 0 { + t.Fatalf("Expected 0 bytes read, got %d", written) + } + if err != expectedError { + t.Fatalf("Didn't get expected error") + } +} + +func TestStdCopyReturnsErrorReadingFrame(t *testing.T) { + expectedError := errors.New("error") + stdOutBytes := []byte(strings.Repeat("o", startingBufLen)) + stdErrBytes := []byte(strings.Repeat("e", startingBufLen)) + buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes) + if err != nil { + t.Fatal(err) + } + reader := &customReader{ + correctCalls: 1, + n: stdWriterPrefixLen + 1, + err: expectedError, + src: buffer} + written, err := StdCopy(ioutil.Discard, ioutil.Discard, reader) + if written != 0 { + t.Fatalf("Expected 0 bytes read, got %d", written) + } + if err != expectedError { + t.Fatalf("Didn't get expected error") + } +} + +func TestStdCopyDetectsCorruptedFrame(t *testing.T) { + stdOutBytes := []byte(strings.Repeat("o", startingBufLen)) + stdErrBytes := []byte(strings.Repeat("e", startingBufLen)) + buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes) + if err != nil { + t.Fatal(err) + } + reader := &customReader{ + correctCalls: 1, + n: stdWriterPrefixLen + 1, + err: io.EOF, + src: buffer} + written, err := StdCopy(ioutil.Discard, ioutil.Discard, reader) + if written != startingBufLen { + t.Fatalf("Expected 0 bytes read, got %d", written) + } + if err != nil { + t.Fatal("Didn't get nil error") } } @@ -71,6 +209,44 @@ func TestStdCopyWithCorruptedPrefix(t *testing.T) { } } +func TestStdCopyReturnsWriteErrors(t *testing.T) { + stdOutBytes := []byte(strings.Repeat("o", startingBufLen)) + stdErrBytes := []byte(strings.Repeat("e", startingBufLen)) + buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes) + if err != nil { + t.Fatal(err) + } + expectedError := errors.New("expected") + + dstOut := &errWriter{err: expectedError} + + written, err := StdCopy(dstOut, ioutil.Discard, buffer) + if written != 0 { + t.Fatalf("StdCopy should have written 0, but has written %d", written) + } + if err != expectedError { + t.Fatalf("Didn't get expected error, got %v", err) + } +} + +func TestStdCopyDetectsNotFullyWrittenFrames(t *testing.T) { + stdOutBytes := []byte(strings.Repeat("o", startingBufLen)) + stdErrBytes := []byte(strings.Repeat("e", startingBufLen)) + buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes) + if err != nil { + t.Fatal(err) + } + dstOut := &errWriter{n: startingBufLen - 10} + + written, err := StdCopy(dstOut, ioutil.Discard, buffer) + if written != 0 { + t.Fatalf("StdCopy should have return 0 written bytes, but returned %d", written) + } + if err != io.ErrShortWrite { + t.Fatalf("Didn't get expected io.ErrShortWrite error") + } +} + func BenchmarkWrite(b *testing.B) { w := NewStdWriter(ioutil.Discard, Stdout) data := []byte("Test line for testing stdwriter performance\n") diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes.go new file mode 100644 index 000000000..31ed9ff10 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes.go @@ -0,0 +1,31 @@ +package system + +import ( + "os" + "time" +) + +// Chtimes changes the access time and modified time of a file at the given path +func Chtimes(name string, atime time.Time, mtime time.Time) error { + unixMinTime := time.Unix(0, 0) + // The max Unix time is 33 bits set + unixMaxTime := unixMinTime.Add((1<<33 - 1) * time.Second) + + // If the modified time is prior to the Unix Epoch, or after the + // end of Unix Time, os.Chtimes has undefined behavior + // default to Unix Epoch in this case, just in case + + if atime.Before(unixMinTime) || atime.After(unixMaxTime) { + atime = unixMinTime + } + + if mtime.Before(unixMinTime) || mtime.After(unixMaxTime) { + mtime = unixMinTime + } + + if err := os.Chtimes(name, atime, mtime); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_test.go new file mode 100644 index 000000000..f65a4b807 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_test.go @@ -0,0 +1,120 @@ +package system + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" +) + +// prepareTempFile creates a temporary file in a temporary directory. +func prepareTempFile(t *testing.T) (string, string) { + dir, err := ioutil.TempDir("", "docker-system-test") + if err != nil { + t.Fatal(err) + } + + file := filepath.Join(dir, "exist") + if err := ioutil.WriteFile(file, []byte("hello"), 0644); err != nil { + t.Fatal(err) + } + return file, dir +} + +// TestChtimes tests Chtimes on a tempfile. Test only mTime, because aTime is OS dependent +func TestChtimes(t *testing.T) { + file, dir := prepareTempFile(t) + defer os.RemoveAll(dir) + + beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second) + unixEpochTime := time.Unix(0, 0) + afterUnixEpochTime := time.Unix(100, 0) + // The max Unix time is 33 bits set + unixMaxTime := unixEpochTime.Add((1<<33 - 1) * time.Second) + afterUnixMaxTime := unixMaxTime.Add(100 * time.Second) + + // Test both aTime and mTime set to Unix Epoch + Chtimes(file, unixEpochTime, unixEpochTime) + + f, err := os.Stat(file) + if err != nil { + t.Fatal(err) + } + + if f.ModTime() != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime()) + } + + // Test aTime before Unix Epoch and mTime set to Unix Epoch + Chtimes(file, beforeUnixEpochTime, unixEpochTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + if f.ModTime() != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime()) + } + + // Test aTime set to Unix Epoch and mTime before Unix Epoch + Chtimes(file, unixEpochTime, beforeUnixEpochTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + if f.ModTime() != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime()) + } + + // Test both aTime and mTime set to after Unix Epoch (valid time) + Chtimes(file, afterUnixEpochTime, afterUnixEpochTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + if f.ModTime() != afterUnixEpochTime { + t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, f.ModTime()) + } + + // Test both aTime and mTime set to Unix max time + Chtimes(file, unixMaxTime, unixMaxTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + if f.ModTime() != unixMaxTime { + t.Fatalf("Expected: %s, got: %s", unixMaxTime, f.ModTime()) + } + + // Test aTime after Unix max time and mTime set to Unix max time + Chtimes(file, afterUnixMaxTime, unixMaxTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + if f.ModTime() != unixMaxTime { + t.Fatalf("Expected: %s, got: %s", unixMaxTime, f.ModTime()) + } + + // Test aTime set to Unix Epoch and mTime before Unix Epoch + Chtimes(file, unixMaxTime, afterUnixMaxTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + if f.ModTime() != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime()) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_unix_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_unix_test.go new file mode 100644 index 000000000..6998bbef5 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_unix_test.go @@ -0,0 +1,121 @@ +// +build linux freebsd + +package system + +import ( + "os" + "syscall" + "testing" + "time" +) + +// TestChtimes tests Chtimes access time on a tempfile on Linux +func TestChtimesLinux(t *testing.T) { + file, dir := prepareTempFile(t) + defer os.RemoveAll(dir) + + beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second) + unixEpochTime := time.Unix(0, 0) + afterUnixEpochTime := time.Unix(100, 0) + // The max Unix time is 33 bits set + unixMaxTime := unixEpochTime.Add((1<<33 - 1) * time.Second) + afterUnixMaxTime := unixMaxTime.Add(100 * time.Second) + + // Test both aTime and mTime set to Unix Epoch + Chtimes(file, unixEpochTime, unixEpochTime) + + f, err := os.Stat(file) + if err != nil { + t.Fatal(err) + } + + stat := f.Sys().(*syscall.Stat_t) + aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + if aTime != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime) + } + + // Test aTime before Unix Epoch and mTime set to Unix Epoch + Chtimes(file, beforeUnixEpochTime, unixEpochTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + stat = f.Sys().(*syscall.Stat_t) + aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + if aTime != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime) + } + + // Test aTime set to Unix Epoch and mTime before Unix Epoch + Chtimes(file, unixEpochTime, beforeUnixEpochTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + stat = f.Sys().(*syscall.Stat_t) + aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + if aTime != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime) + } + + // Test both aTime and mTime set to after Unix Epoch (valid time) + Chtimes(file, afterUnixEpochTime, afterUnixEpochTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + stat = f.Sys().(*syscall.Stat_t) + aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + if aTime != afterUnixEpochTime { + t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime) + } + + // Test both aTime and mTime set to Unix max time + Chtimes(file, unixMaxTime, unixMaxTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + stat = f.Sys().(*syscall.Stat_t) + aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + if aTime != unixMaxTime { + t.Fatalf("Expected: %s, got: %s", unixMaxTime, aTime) + } + + // Test aTime after Unix max time and mTime set to Unix max time + Chtimes(file, afterUnixMaxTime, unixMaxTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + stat = f.Sys().(*syscall.Stat_t) + aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + if aTime != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime) + } + + // Test aTime set to Unix Epoch and mTime before Unix Epoch + Chtimes(file, unixMaxTime, afterUnixMaxTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + stat = f.Sys().(*syscall.Stat_t) + aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + if aTime != unixMaxTime { + t.Fatalf("Expected: %s, got: %s", unixMaxTime, aTime) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_windows_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_windows_test.go new file mode 100644 index 000000000..f09c40284 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/chtimes_windows_test.go @@ -0,0 +1,114 @@ +// +build windows + +package system + +import ( + "os" + "syscall" + "testing" + "time" +) + +// TestChtimes tests Chtimes access time on a tempfile on Windows +func TestChtimesWindows(t *testing.T) { + file, dir := prepareTempFile(t) + defer os.RemoveAll(dir) + + beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second) + unixEpochTime := time.Unix(0, 0) + afterUnixEpochTime := time.Unix(100, 0) + // The max Unix time is 33 bits set + unixMaxTime := unixEpochTime.Add((1<<33 - 1) * time.Second) + afterUnixMaxTime := unixMaxTime.Add(100 * time.Second) + + // Test both aTime and mTime set to Unix Epoch + Chtimes(file, unixEpochTime, unixEpochTime) + + f, err := os.Stat(file) + if err != nil { + t.Fatal(err) + } + + aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) + if aTime != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime) + } + + // Test aTime before Unix Epoch and mTime set to Unix Epoch + Chtimes(file, beforeUnixEpochTime, unixEpochTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) + if aTime != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime) + } + + // Test aTime set to Unix Epoch and mTime before Unix Epoch + Chtimes(file, unixEpochTime, beforeUnixEpochTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) + if aTime != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime) + } + + // Test both aTime and mTime set to after Unix Epoch (valid time) + Chtimes(file, afterUnixEpochTime, afterUnixEpochTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) + if aTime != afterUnixEpochTime { + t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime) + } + + // Test both aTime and mTime set to Unix max time + Chtimes(file, unixMaxTime, unixMaxTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) + if aTime != unixMaxTime { + t.Fatalf("Expected: %s, got: %s", unixMaxTime, aTime) + } + + // Test aTime after Unix max time and mTime set to Unix max time + Chtimes(file, afterUnixMaxTime, unixMaxTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) + if aTime != unixEpochTime { + t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime) + } + + // Test aTime set to Unix Epoch and mTime before Unix Epoch + Chtimes(file, unixMaxTime, afterUnixMaxTime) + + f, err = os.Stat(file) + if err != nil { + t.Fatal(err) + } + + aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) + if aTime != unixMaxTime { + t.Fatalf("Expected: %s, got: %s", unixMaxTime, aTime) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/errors.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/errors.go index 63045186f..288318985 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/errors.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/errors.go @@ -5,5 +5,6 @@ import ( ) var ( + // ErrNotSupportedPlatform means the platform is not supported. ErrNotSupportedPlatform = errors.New("platform and architecture is not supported") ) diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/events_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/events_windows.go index 23f7c618b..04e2de787 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/events_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/events_windows.go @@ -8,11 +8,6 @@ import ( "unsafe" ) -const ( - EVENT_ALL_ACCESS = 0x1F0003 - EVENT_MODIFY_STATUS = 0x0002 -) - var ( procCreateEvent = modkernel32.NewProc("CreateEventW") procOpenEvent = modkernel32.NewProc("OpenEventW") @@ -21,13 +16,14 @@ var ( procPulseEvent = modkernel32.NewProc("PulseEvent") ) +// CreateEvent implements win32 CreateEventW func in golang. It will create an event object. func CreateEvent(eventAttributes *syscall.SecurityAttributes, manualReset bool, initialState bool, name string) (handle syscall.Handle, err error) { namep, _ := syscall.UTF16PtrFromString(name) - var _p1 uint32 = 0 + var _p1 uint32 if manualReset { _p1 = 1 } - var _p2 uint32 = 0 + var _p2 uint32 if initialState { _p2 = 1 } @@ -40,9 +36,10 @@ func CreateEvent(eventAttributes *syscall.SecurityAttributes, manualReset bool, return } +// OpenEvent implements win32 OpenEventW func in golang. It opens an event object. func OpenEvent(desiredAccess uint32, inheritHandle bool, name string) (handle syscall.Handle, err error) { namep, _ := syscall.UTF16PtrFromString(name) - var _p1 uint32 = 0 + var _p1 uint32 if inheritHandle { _p1 = 1 } @@ -55,14 +52,17 @@ func OpenEvent(desiredAccess uint32, inheritHandle bool, name string) (handle sy return } +// SetEvent implements win32 SetEvent func in golang. func SetEvent(handle syscall.Handle) (err error) { return setResetPulse(handle, procSetEvent) } +// ResetEvent implements win32 ResetEvent func in golang. func ResetEvent(handle syscall.Handle) (err error) { return setResetPulse(handle, procResetEvent) } +// PulseEvent implements win32 PulseEvent func in golang. func PulseEvent(handle syscall.Handle) (err error) { return setResetPulse(handle, procPulseEvent) } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/filesys.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/filesys.go index e1f70e8da..c14feb849 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/filesys.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/filesys.go @@ -4,8 +4,16 @@ package system import ( "os" + "path/filepath" ) +// MkdirAll creates a directory named path along with any necessary parents, +// with permission specified by attribute perm for all dir created. func MkdirAll(path string, perm os.FileMode) error { return os.MkdirAll(path, perm) } + +// IsAbs is a platform-specific wrapper for filepath.IsAbs. +func IsAbs(path string) bool { + return filepath.IsAbs(path) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/filesys_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/filesys_windows.go index 90b500608..16823d551 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/filesys_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/filesys_windows.go @@ -4,7 +4,9 @@ package system import ( "os" + "path/filepath" "regexp" + "strings" "syscall" ) @@ -62,3 +64,19 @@ func MkdirAll(path string, perm os.FileMode) error { } return nil } + +// IsAbs is a platform-specific wrapper for filepath.IsAbs. On Windows, +// golang filepath.IsAbs does not consider a path \windows\system32 as absolute +// as it doesn't start with a drive-letter/colon combination. However, in +// docker we need to verify things such as WORKDIR /windows/system32 in +// a Dockerfile (which gets translated to \windows\system32 when being processed +// by the daemon. This SHOULD be treated as absolute from a docker processing +// perspective. +func IsAbs(path string) bool { + if !filepath.IsAbs(path) { + if !strings.HasPrefix(path, string(os.PathSeparator)) { + return false + } + } + return true +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat.go index d0e43b370..bd23c4d50 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat.go @@ -7,10 +7,10 @@ import ( ) // Lstat takes a path to a file and returns -// a system.Stat_t type pertaining to that file. +// a system.StatT type pertaining to that file. // // Throws an error if the file does not exist -func Lstat(path string) (*Stat_t, error) { +func Lstat(path string) (*StatT, error) { s := &syscall.Stat_t{} if err := syscall.Lstat(path, s); err != nil { return nil, err diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat_unix_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat_unix_test.go new file mode 100644 index 000000000..062cf53bf --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat_unix_test.go @@ -0,0 +1,30 @@ +// +build linux freebsd + +package system + +import ( + "os" + "testing" +) + +// TestLstat tests Lstat for existing and non existing files +func TestLstat(t *testing.T) { + file, invalid, _, dir := prepareFiles(t) + defer os.RemoveAll(dir) + + statFile, err := Lstat(file) + if err != nil { + t.Fatal(err) + } + if statFile == nil { + t.Fatal("returned empty stat for existing file") + } + + statInvalid, err := Lstat(invalid) + if err == nil { + t.Fatal("did not return error for non-existing file") + } + if statInvalid != nil { + t.Fatal("returned non-nil stat for non-existing file") + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat_windows.go index eee1be26e..49e87eb40 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/lstat_windows.go @@ -6,21 +6,17 @@ import ( "os" ) -// Some explanation for my own sanity, and hopefully maintainers in the -// future. -// // Lstat calls os.Lstat to get a fileinfo interface back. // This is then copied into our own locally defined structure. // Note the Linux version uses fromStatT to do the copy back, // but that not strictly necessary when already in an OS specific module. - -func Lstat(path string) (*Stat_t, error) { +func Lstat(path string) (*StatT, error) { fi, err := os.Lstat(path) if err != nil { return nil, err } - return &Stat_t{ + return &StatT{ name: fi.Name(), size: fi.Size(), mode: fi.Mode(), diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_linux.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_linux.go index 41f2bab60..9d83304ff 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_linux.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_linux.go @@ -2,7 +2,6 @@ package system import ( "bufio" - "errors" "io" "os" "strconv" @@ -11,10 +10,6 @@ import ( "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/units" ) -var ( - ErrMalformed = errors.New("malformed file") -) - // ReadMemInfo retrieves memory statistics of the host system and returns a // MemInfo type. func ReadMemInfo() (*MemInfo, error) { diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_unix_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_unix_test.go new file mode 100644 index 000000000..681bbfc13 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_unix_test.go @@ -0,0 +1,40 @@ +// +build linux freebsd + +package system + +import ( + "strings" + "testing" + + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/units" +) + +// TestMemInfo tests parseMemInfo with a static meminfo string +func TestMemInfo(t *testing.T) { + const input = ` + MemTotal: 1 kB + MemFree: 2 kB + SwapTotal: 3 kB + SwapFree: 4 kB + Malformed1: + Malformed2: 1 + Malformed3: 2 MB + Malformed4: X kB + ` + meminfo, err := parseMemInfo(strings.NewReader(input)) + if err != nil { + t.Fatal(err) + } + if meminfo.MemTotal != 1*units.KiB { + t.Fatalf("Unexpected MemTotal: %d", meminfo.MemTotal) + } + if meminfo.MemFree != 2*units.KiB { + t.Fatalf("Unexpected MemFree: %d", meminfo.MemFree) + } + if meminfo.SwapTotal != 3*units.KiB { + t.Fatalf("Unexpected SwapTotal: %d", meminfo.SwapTotal) + } + if meminfo.SwapFree != 4*units.KiB { + t.Fatalf("Unexpected SwapFree: %d", meminfo.SwapFree) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_unsupported.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_unsupported.go index 604d33875..82ddd30c1 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_unsupported.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/meminfo_unsupported.go @@ -2,6 +2,7 @@ package system +// ReadMemInfo is not supported on platforms other than linux and windows. func ReadMemInfo() (*MemInfo, error) { return nil, ErrNotSupportedPlatform } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/mknod.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/mknod.go index 26617eb08..73958182b 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/mknod.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/mknod.go @@ -7,14 +7,16 @@ import ( ) // Mknod creates a filesystem node (file, device special file or named pipe) named path -// with attributes specified by mode and dev +// with attributes specified by mode and dev. func Mknod(path string, mode uint32, dev int) error { return syscall.Mknod(path, mode, dev) } +// Mkdev is used to build the value of linux devices (in /dev/) which specifies major +// and minor number of the newly created device special file. // Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes. // They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major, -// then the top 12 bits of the minor +// then the top 12 bits of the minor. func Mkdev(major int64, minor int64) uint32 { return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff)) } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/mknod_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/mknod_windows.go index 1811542ab..2e863c021 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/mknod_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/mknod_windows.go @@ -2,10 +2,12 @@ package system +// Mknod is not implemented on Windows. func Mknod(path string, mode uint32, dev int) error { return ErrNotSupportedPlatform } +// Mkdev is not implemented on Windows. func Mkdev(major int64, minor int64) uint32 { panic("Mkdev not implemented on Windows.") } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat.go index e2ecfe52f..087034c5e 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat.go @@ -6,9 +6,9 @@ import ( "syscall" ) -// Stat_t type contains status of a file. It contains metadata -// like permission, owner, group, size, etc about a file -type Stat_t struct { +// StatT type contains status of a file. It contains metadata +// like permission, owner, group, size, etc about a file. +type StatT struct { mode uint32 uid uint32 gid uint32 @@ -17,30 +17,37 @@ type Stat_t struct { mtim syscall.Timespec } -func (s Stat_t) Mode() uint32 { +// Mode returns file's permission mode. +func (s StatT) Mode() uint32 { return s.mode } -func (s Stat_t) Uid() uint32 { +// UID returns file's user id of owner. +func (s StatT) UID() uint32 { return s.uid } -func (s Stat_t) Gid() uint32 { +// GID returns file's group id of owner. +func (s StatT) GID() uint32 { return s.gid } -func (s Stat_t) Rdev() uint64 { +// Rdev returns file's device ID (if it's special file). +func (s StatT) Rdev() uint64 { return s.rdev } -func (s Stat_t) Size() int64 { +// Size returns file's size. +func (s StatT) Size() int64 { return s.size } -func (s Stat_t) Mtim() syscall.Timespec { +// Mtim returns file's last modification time. +func (s StatT) Mtim() syscall.Timespec { return s.mtim } -func (s Stat_t) GetLastModification() syscall.Timespec { +// GetLastModification returns file's last modification time. +func (s StatT) GetLastModification() syscall.Timespec { return s.Mtim() } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_freebsd.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_freebsd.go index 4b2198b3a..d0fb6f151 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_freebsd.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_freebsd.go @@ -5,8 +5,8 @@ import ( ) // fromStatT converts a syscall.Stat_t type to a system.Stat_t type -func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { - return &Stat_t{size: s.Size, +func fromStatT(s *syscall.Stat_t) (*StatT, error) { + return &StatT{size: s.Size, mode: uint32(s.Mode), uid: s.Uid, gid: s.Gid, @@ -18,7 +18,7 @@ func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { // a system.Stat_t type pertaining to that file. // // Throws an error if the file does not exist -func Stat(path string) (*Stat_t, error) { +func Stat(path string) (*StatT, error) { s := &syscall.Stat_t{} if err := syscall.Stat(path, s); err != nil { return nil, err diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_linux.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_linux.go index 80262d951..8b1eded13 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_linux.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_linux.go @@ -5,8 +5,8 @@ import ( ) // fromStatT converts a syscall.Stat_t type to a system.Stat_t type -func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { - return &Stat_t{size: s.Size, +func fromStatT(s *syscall.Stat_t) (*StatT, error) { + return &StatT{size: s.Size, mode: s.Mode, uid: s.Uid, gid: s.Gid, @@ -14,17 +14,17 @@ func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { mtim: s.Mtim}, nil } -// FromStatT exists only on linux, and loads a system.Stat_t from a +// FromStatT exists only on linux, and loads a system.StatT from a // syscal.Stat_t. -func FromStatT(s *syscall.Stat_t) (*Stat_t, error) { +func FromStatT(s *syscall.Stat_t) (*StatT, error) { return fromStatT(s) } // Stat takes a path to a file and returns -// a system.Stat_t type pertaining to that file. +// a system.StatT type pertaining to that file. // // Throws an error if the file does not exist -func Stat(path string) (*Stat_t, error) { +func Stat(path string) (*StatT, error) { s := &syscall.Stat_t{} if err := syscall.Stat(path, s); err != nil { return nil, err diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_unix_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_unix_test.go new file mode 100644 index 000000000..dee8d30a1 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_unix_test.go @@ -0,0 +1,39 @@ +// +build linux freebsd + +package system + +import ( + "os" + "syscall" + "testing" +) + +// TestFromStatT tests fromStatT for a tempfile +func TestFromStatT(t *testing.T) { + file, _, _, dir := prepareFiles(t) + defer os.RemoveAll(dir) + + stat := &syscall.Stat_t{} + err := syscall.Lstat(file, stat) + + s, err := fromStatT(stat) + if err != nil { + t.Fatal(err) + } + + if stat.Mode != s.Mode() { + t.Fatal("got invalid mode") + } + if stat.Uid != s.UID() { + t.Fatal("got invalid uid") + } + if stat.Gid != s.GID() { + t.Fatal("got invalid gid") + } + if stat.Rdev != s.Rdev() { + t.Fatal("got invalid rdev") + } + if stat.Mtim != s.Mtim() { + t.Fatal("got invalid mtim") + } +} 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 5251ae212..381ea8211 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 @@ -6,9 +6,9 @@ import ( "syscall" ) -// fromStatT creates a system.Stat_t type from a syscall.Stat_t type -func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { - return &Stat_t{size: s.Size, +// 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, diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_windows.go index b1fd39e83..39490c625 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/stat_windows.go @@ -7,7 +7,9 @@ import ( "time" ) -type Stat_t struct { +// StatT type contains status of a file. It contains metadata +// like name, permission, size, etc about a file. +type StatT struct { name string size int64 mode os.FileMode @@ -15,22 +17,27 @@ type Stat_t struct { isDir bool } -func (s Stat_t) Name() string { +// Name returns file's name. +func (s StatT) Name() string { return s.name } -func (s Stat_t) Size() int64 { +// Size returns file's size. +func (s StatT) Size() int64 { return s.size } -func (s Stat_t) Mode() os.FileMode { +// Mode returns file's permission mode. +func (s StatT) Mode() os.FileMode { return s.mode } -func (s Stat_t) ModTime() time.Time { +// ModTime returns file's last modification time. +func (s StatT) ModTime() time.Time { return s.modTime } -func (s Stat_t) IsDir() bool { +// IsDir returns whether file is actually a directory. +func (s StatT) IsDir() bool { return s.isDir } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/syscall_unix.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/syscall_unix.go new file mode 100644 index 000000000..50054765a --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/syscall_unix.go @@ -0,0 +1,11 @@ +// +build linux freebsd + +package system + +import "syscall" + +// Unmount is a platform-specific helper function to call +// the unmount syscall. +func Unmount(dest string) { + syscall.Unmount(dest, 0) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/syscall_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/syscall_windows.go new file mode 100644 index 000000000..3a3a55b26 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/syscall_windows.go @@ -0,0 +1,6 @@ +package system + +// Unmount is a platform-specific helper function to call +// the unmount syscall. Not supported on Windows +func Unmount(dest string) { +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/umask.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/umask.go index fddbecd39..c670fcd75 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/umask.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/umask.go @@ -6,6 +6,8 @@ import ( "syscall" ) +// Umask sets current process's file mode creation mask to newmask +// and return oldmask. func Umask(newmask int) (oldmask int, err error) { return syscall.Umask(newmask), nil } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/umask_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/umask_windows.go index 3be563f89..13f1de176 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/umask_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/umask_windows.go @@ -2,6 +2,7 @@ package system +// Umask is not supported on the windows platform. func Umask(newmask int) (oldmask int, err error) { // should not be called on cli code path return 0, ErrNotSupportedPlatform diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_darwin.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_darwin.go index 4c6002fe8..0a1619754 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_darwin.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_darwin.go @@ -2,10 +2,7 @@ package system import "syscall" +// LUtimesNano is not supported by darwin platform. func LUtimesNano(path string, ts []syscall.Timespec) error { return ErrNotSupportedPlatform } - -func UtimesNano(path string, ts []syscall.Timespec) error { - return syscall.UtimesNano(path, ts) -} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_freebsd.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_freebsd.go index ceaa044c1..e2eac3b55 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_freebsd.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_freebsd.go @@ -5,6 +5,8 @@ import ( "unsafe" ) +// LUtimesNano is used to change access and modification time of the specified path. +// It's used for symbol link file because syscall.UtimesNano doesn't support a NOFOLLOW flag atm. func LUtimesNano(path string, ts []syscall.Timespec) error { var _path *byte _path, err := syscall.BytePtrFromString(path) @@ -18,7 +20,3 @@ func LUtimesNano(path string, ts []syscall.Timespec) error { return nil } - -func UtimesNano(path string, ts []syscall.Timespec) error { - return syscall.UtimesNano(path, ts) -} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_linux.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_linux.go index 8f9029827..007bfa8c0 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_linux.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_linux.go @@ -5,10 +5,12 @@ import ( "unsafe" ) +// LUtimesNano is used to change access and modification time of the speficied path. +// It's used for symbol link file because syscall.UtimesNano doesn't support a NOFOLLOW flag atm. func LUtimesNano(path string, ts []syscall.Timespec) error { // These are not currently available in syscall - AT_FDCWD := -100 - AT_SYMLINK_NOFOLLOW := 0x100 + atFdCwd := -100 + atSymLinkNoFollow := 0x100 var _path *byte _path, err := syscall.BytePtrFromString(path) @@ -16,13 +18,9 @@ func LUtimesNano(path string, ts []syscall.Timespec) error { return err } - if _, _, err := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(AT_FDCWD), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), uintptr(AT_SYMLINK_NOFOLLOW), 0, 0); err != 0 && err != syscall.ENOSYS { + if _, _, err := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(atFdCwd), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), uintptr(atSymLinkNoFollow), 0, 0); err != 0 && err != syscall.ENOSYS { return err } return nil } - -func UtimesNano(path string, ts []syscall.Timespec) error { - return syscall.UtimesNano(path, ts) -} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_unix_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_unix_test.go new file mode 100644 index 000000000..1ee0d099f --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_unix_test.go @@ -0,0 +1,68 @@ +// +build linux freebsd + +package system + +import ( + "io/ioutil" + "os" + "path/filepath" + "syscall" + "testing" +) + +// prepareFiles creates files for testing in the temp directory +func prepareFiles(t *testing.T) (string, string, string, string) { + dir, err := ioutil.TempDir("", "docker-system-test") + if err != nil { + t.Fatal(err) + } + + file := filepath.Join(dir, "exist") + if err := ioutil.WriteFile(file, []byte("hello"), 0644); err != nil { + t.Fatal(err) + } + + invalid := filepath.Join(dir, "doesnt-exist") + + symlink := filepath.Join(dir, "symlink") + if err := os.Symlink(file, symlink); err != nil { + t.Fatal(err) + } + + return file, invalid, symlink, dir +} + +func TestLUtimesNano(t *testing.T) { + file, invalid, symlink, dir := prepareFiles(t) + defer os.RemoveAll(dir) + + before, err := os.Stat(file) + if err != nil { + t.Fatal(err) + } + + ts := []syscall.Timespec{{0, 0}, {0, 0}} + if err := LUtimesNano(symlink, ts); err != nil { + t.Fatal(err) + } + + symlinkInfo, err := os.Lstat(symlink) + if err != nil { + t.Fatal(err) + } + if before.ModTime().Unix() == symlinkInfo.ModTime().Unix() { + t.Fatal("The modification time of the symlink should be different") + } + + fileInfo, err := os.Stat(file) + if err != nil { + t.Fatal(err) + } + if before.ModTime().Unix() != fileInfo.ModTime().Unix() { + t.Fatal("The modification time of the file should be same") + } + + if err := LUtimesNano(invalid, ts); err == nil { + t.Fatal("Doesn't return an error on a non-existing file") + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_unsupported.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_unsupported.go index adf2734f2..50c3a0436 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_unsupported.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/utimes_unsupported.go @@ -4,10 +4,7 @@ package system import "syscall" +// LUtimesNano is not supported on platforms other than linux, freebsd and darwin. func LUtimesNano(path string, ts []syscall.Timespec) error { return ErrNotSupportedPlatform } - -func UtimesNano(path string, ts []syscall.Timespec) error { - return ErrNotSupportedPlatform -} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/xattrs_linux.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/xattrs_linux.go index 00edb201b..d2e2c0579 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/xattrs_linux.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/xattrs_linux.go @@ -5,7 +5,9 @@ import ( "unsafe" ) -// Returns a nil slice and nil error if the xattr is not set +// Lgetxattr retrieves the value of the extended attribute identified by attr +// and associated with the given path in the file system. +// It will returns a nil slice and nil error if the xattr is not set. func Lgetxattr(path string, attr string) ([]byte, error) { pathBytes, err := syscall.BytePtrFromString(path) if err != nil { @@ -36,6 +38,8 @@ func Lgetxattr(path string, attr string) ([]byte, error) { var _zero uintptr +// Lsetxattr sets the value of the extended attribute identified by attr +// and associated with the given path in the file system. func Lsetxattr(path string, attr string, data []byte, flags int) error { pathBytes, err := syscall.BytePtrFromString(path) if err != nil { diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/xattrs_unsupported.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/xattrs_unsupported.go index 0060c167d..0114f2227 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/xattrs_unsupported.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system/xattrs_unsupported.go @@ -2,10 +2,12 @@ package system +// Lgetxattr is not supported on platforms other than linux. func Lgetxattr(path string, attr string) ([]byte, error) { return nil, ErrNotSupportedPlatform } +// Lsetxattr is not supported on platforms other than linux. func Lsetxattr(path string, attr string, data []byte, flags int) error { return ErrNotSupportedPlatform } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume.go index 19c9d77ad..d986207c8 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume.go @@ -1,5 +1,15 @@ package volume +import ( + "os" + "runtime" + "strings" + + "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus" + derr "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors" + "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" +) + // DefaultDriverName is the driver name used for the driver // implemented in the local package. const DefaultDriverName string = "local" @@ -9,7 +19,7 @@ type Driver interface { // Name returns the name of the volume driver. Name() string // Create makes a new volume with the given id. - Create(string) (Volume, error) + Create(name string, opts map[string]string) (Volume, error) // Remove deletes the volume. Remove(Volume) error } @@ -29,33 +39,134 @@ type Volume interface { Unmount() error } -// read-write modes -var rwModes = map[string]bool{ - "rw": true, - "rw,Z": true, - "rw,z": true, - "z,rw": true, - "Z,rw": true, - "Z": true, - "z": true, +// MountPoint is the intersection point between a volume and a container. It +// specifies which volume is to be used and where inside a container it should +// be mounted. +type MountPoint struct { + Source string // Container host directory + Destination string // Inside the container + RW bool // True if writable + Name string // Name set by user + Driver string // Volume driver to use + Volume Volume `json:"-"` + + // Note Mode is not used on Windows + Mode string `json:"Relabel"` // Originally field was `Relabel`" } -// read-only modes -var roModes = map[string]bool{ - "ro": true, - "ro,Z": true, - "ro,z": true, - "z,ro": true, - "Z,ro": true, +// Setup sets up a mount point by either mounting the volume if it is +// configured, or creating the source directory if supplied. +func (m *MountPoint) Setup() (string, error) { + if m.Volume != nil { + return m.Volume.Mount() + } + if len(m.Source) > 0 { + if _, err := os.Stat(m.Source); err != nil { + if !os.IsNotExist(err) { + return "", err + } + if runtime.GOOS != "windows" { // Windows does not have deprecation issues here + logrus.Warnf("Auto-creating non-existant volume host path %s, this is deprecated and will be removed soon", m.Source) + if err := system.MkdirAll(m.Source, 0755); err != nil { + return "", err + } + } + } + return m.Source, nil + } + return "", derr.ErrorCodeMountSetup } -// ValidateMountMode will make sure the mount mode is valid. -// returns if it's a valid mount mode and if it's read-write or not. -func ValidateMountMode(mode string) (bool, bool) { - return roModes[mode] || rwModes[mode], rwModes[mode] +// Path returns the path of a volume in a mount point. +func (m *MountPoint) Path() string { + if m.Volume != nil { + return m.Volume.Path() + } + return m.Source } -// ReadWrite tells you if a mode string is a valid read-only mode or not. +// ValidMountMode will make sure the mount mode is valid. +// returns if it's a valid mount mode or not. +func ValidMountMode(mode string) bool { + return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)] +} + +// ReadWrite tells you if a mode string is a valid read-write mode or not. func ReadWrite(mode string) bool { - return rwModes[mode] + return rwModes[strings.ToLower(mode)] +} + +// ParseVolumesFrom ensure that the supplied volumes-from is valid. +func ParseVolumesFrom(spec string) (string, string, error) { + if len(spec) == 0 { + return "", "", derr.ErrorCodeVolumeFromBlank.WithArgs(spec) + } + + specParts := strings.SplitN(spec, ":", 2) + id := specParts[0] + mode := "rw" + + if len(specParts) == 2 { + mode = specParts[1] + if !ValidMountMode(mode) { + return "", "", derr.ErrorCodeVolumeInvalidMode.WithArgs(mode) + } + } + return id, mode, nil +} + +// SplitN splits raw into a maximum of n parts, separated by a separator colon. +// A separator colon is the last `:` character in the regex `[/:\\]?[a-zA-Z]:` (note `\\` is `\` escaped). +// This allows to correctly split strings such as `C:\foo:D:\:rw`. +func SplitN(raw string, n int) []string { + var array []string + if len(raw) == 0 || raw[0] == ':' { + // invalid + return nil + } + // numberOfParts counts the number of parts separated by a separator colon + numberOfParts := 0 + // left represents the left-most cursor in raw, updated at every `:` character considered as a separator. + left := 0 + // right represents the right-most cursor in raw incremented with the loop. Note this + // starts at index 1 as index 0 is already handle above as a special case. + for right := 1; right < len(raw); right++ { + // stop parsing if reached maximum number of parts + if n >= 0 && numberOfParts >= n { + break + } + if raw[right] != ':' { + continue + } + potentialDriveLetter := raw[right-1] + if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') { + if right > 1 { + beforePotentialDriveLetter := raw[right-2] + if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '/' && beforePotentialDriveLetter != '\\' { + // e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`. + array = append(array, raw[left:right]) + left = right + 1 + numberOfParts++ + } + // else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing. + } + // if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing. + } else { + // if `:` is not preceded by a potential drive letter, then consider it as a delimiter. + array = append(array, raw[left:right]) + left = right + 1 + numberOfParts++ + } + } + // need to take care of the last part + if left < len(raw) { + if n >= 0 && numberOfParts >= n { + // if the maximum number of parts is reached, just append the rest to the last part + // left-1 is at the last `:` that needs to be included since not considered a separator. + array[n-1] += raw[left-1:] + } else { + array = append(array, raw[left:]) + } + } + return array } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_test.go new file mode 100644 index 000000000..5ce3d9fa9 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_test.go @@ -0,0 +1,261 @@ +package volume + +import ( + "runtime" + "strings" + "testing" +) + +func TestParseMountSpec(t *testing.T) { + var ( + valid []string + invalid map[string]string + ) + + if runtime.GOOS == "windows" { + valid = []string{ + `d:\`, + `d:`, + `d:\path`, + `d:\path with space`, + // TODO Windows post TP4 - readonly support `d:\pathandmode:ro`, + `c:\:d:\`, + `c:\windows\:d:`, + `c:\windows:d:\s p a c e`, + `c:\windows:d:\s p a c e:RW`, + `c:\program files:d:\s p a c e i n h o s t d i r`, + `0123456789name:d:`, + `MiXeDcAsEnAmE:d:`, + `name:D:`, + `name:D::rW`, + `name:D::RW`, + // TODO Windows post TP4 - readonly support `name:D::RO`, + `c:/:d:/forward/slashes/are/good/too`, + // TODO Windows post TP4 - readonly support `c:/:d:/including with/spaces:ro`, + `c:\Windows`, // With capital + `c:\Program Files (x86)`, // With capitals and brackets + } + invalid = map[string]string{ + ``: "Invalid volume specification: ", + `.`: "Invalid volume specification: ", + `..\`: "Invalid volume specification: ", + `c:\:..\`: "Invalid volume specification: ", + `c:\:d:\:xyzzy`: "Invalid volume specification: ", + `c:`: "cannot be c:", + `c:\`: `cannot be c:\`, + `c:\notexist:d:`: `The system cannot find the file specified`, + `c:\windows\system32\ntdll.dll:d:`: `Source 'c:\windows\system32\ntdll.dll' is not a directory`, + `name<:d:`: `Invalid volume specification`, + `name>:d:`: `Invalid volume specification`, + `name::d:`: `Invalid volume specification`, + `name":d:`: `Invalid volume specification`, + `name\:d:`: `Invalid volume specification`, + `name*:d:`: `Invalid volume specification`, + `name|:d:`: `Invalid volume specification`, + `name?:d:`: `Invalid volume specification`, + `name/:d:`: `Invalid volume specification`, + `d:\pathandmode:rw`: `Invalid volume specification`, + `con:d:`: `cannot be a reserved word for Windows filenames`, + `PRN:d:`: `cannot be a reserved word for Windows filenames`, + `aUx:d:`: `cannot be a reserved word for Windows filenames`, + `nul:d:`: `cannot be a reserved word for Windows filenames`, + `com1:d:`: `cannot be a reserved word for Windows filenames`, + `com2:d:`: `cannot be a reserved word for Windows filenames`, + `com3:d:`: `cannot be a reserved word for Windows filenames`, + `com4:d:`: `cannot be a reserved word for Windows filenames`, + `com5:d:`: `cannot be a reserved word for Windows filenames`, + `com6:d:`: `cannot be a reserved word for Windows filenames`, + `com7:d:`: `cannot be a reserved word for Windows filenames`, + `com8:d:`: `cannot be a reserved word for Windows filenames`, + `com9:d:`: `cannot be a reserved word for Windows filenames`, + `lpt1:d:`: `cannot be a reserved word for Windows filenames`, + `lpt2:d:`: `cannot be a reserved word for Windows filenames`, + `lpt3:d:`: `cannot be a reserved word for Windows filenames`, + `lpt4:d:`: `cannot be a reserved word for Windows filenames`, + `lpt5:d:`: `cannot be a reserved word for Windows filenames`, + `lpt6:d:`: `cannot be a reserved word for Windows filenames`, + `lpt7:d:`: `cannot be a reserved word for Windows filenames`, + `lpt8:d:`: `cannot be a reserved word for Windows filenames`, + `lpt9:d:`: `cannot be a reserved word for Windows filenames`, + } + + } else { + valid = []string{ + "/home", + "/home:/home", + "/home:/something/else", + "/with space", + "/home:/with space", + "relative:/absolute-path", + "hostPath:/containerPath:ro", + "/hostPath:/containerPath:rw", + "/rw:/ro", + } + invalid = map[string]string{ + "": "Invalid volume specification", + "./": "Invalid volume destination", + "../": "Invalid volume destination", + "/:../": "Invalid volume destination", + "/:path": "Invalid volume destination", + ":": "Invalid volume specification", + "/tmp:": "Invalid volume destination", + ":test": "Invalid volume specification", + ":/test": "Invalid volume specification", + "tmp:": "Invalid volume destination", + ":test:": "Invalid volume specification", + "::": "Invalid volume specification", + ":::": "Invalid volume specification", + "/tmp:::": "Invalid volume specification", + ":/tmp::": "Invalid volume specification", + "/path:rw": "Invalid volume specification", + "/path:ro": "Invalid volume specification", + "/rw:rw": "Invalid volume specification", + "path:ro": "Invalid volume specification", + "/path:/path:sw": "invalid mode: sw", + "/path:/path:rwz": "invalid mode: rwz", + } + } + + for _, path := range valid { + if _, err := ParseMountSpec(path, "local"); err != nil { + t.Fatalf("ParseMountSpec(`%q`) should succeed: error %q", path, err) + } + } + + for path, expectedError := range invalid { + if _, err := ParseMountSpec(path, "local"); err == nil { + t.Fatalf("ParseMountSpec(`%q`) should have failed validation. Err %v", path, err) + } else { + if !strings.Contains(err.Error(), expectedError) { + t.Fatalf("ParseMountSpec(`%q`) error should contain %q, got %v", path, expectedError, err.Error()) + } + } + } +} + +func TestSplitN(t *testing.T) { + for _, x := range []struct { + input string + n int + expected []string + }{ + {`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}}, + {`:C:\foo:d:`, -1, nil}, + {`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}}, + {`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}}, + {`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}}, + + {`d:\`, -1, []string{`d:\`}}, + {`d:`, -1, []string{`d:`}}, + {`d:\path`, -1, []string{`d:\path`}}, + {`d:\path with space`, -1, []string{`d:\path with space`}}, + {`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}}, + {`c:\:d:\`, -1, []string{`c:\`, `d:\`}}, + {`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}}, + {`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}}, + {`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}}, + {`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}}, + {`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}}, + {`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}}, + {`name:D:`, -1, []string{`name`, `D:`}}, + {`name:D::rW`, -1, []string{`name`, `D:`, `rW`}}, + {`name:D::RW`, -1, []string{`name`, `D:`, `RW`}}, + {`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}}, + {`c:\Windows`, -1, []string{`c:\Windows`}}, + {`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}}, + + {``, -1, nil}, + {`.`, -1, []string{`.`}}, + {`..\`, -1, []string{`..\`}}, + {`c:\:..\`, -1, []string{`c:\`, `..\`}}, + {`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}}, + } { + res := SplitN(x.input, x.n) + if len(res) < len(x.expected) { + t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res) + } + for i, e := range res { + if e != x.expected[i] { + t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res) + } + } + } +} + +// testParseMountSpec is a structure used by TestParseMountSpecSplit for +// specifying test cases for the ParseMountSpec() function. +type testParseMountSpec struct { + bind string + driver string + expDest string + expSource string + expName string + expDriver string + expRW bool + fail bool +} + +func TestParseMountSpecSplit(t *testing.T) { + var cases []testParseMountSpec + if runtime.GOOS == "windows" { + cases = []testParseMountSpec{ + {`c:\:d:`, "local", `d:`, `c:\`, ``, "", true, false}, + {`c:\:d:\`, "local", `d:\`, `c:\`, ``, "", true, false}, + // TODO Windows post TP4 - Add readonly support {`c:\:d:\:ro`, "local", `d:\`, `c:\`, ``, "", false, false}, + {`c:\:d:\:rw`, "local", `d:\`, `c:\`, ``, "", true, false}, + {`c:\:d:\:foo`, "local", `d:\`, `c:\`, ``, "", false, true}, + {`name:d::rw`, "local", `d:`, ``, `name`, "local", true, false}, + {`name:d:`, "local", `d:`, ``, `name`, "local", true, false}, + // TODO Windows post TP4 - Add readonly support {`name:d::ro`, "local", `d:`, ``, `name`, "local", false, false}, + {`name:c:`, "", ``, ``, ``, "", true, true}, + {`driver/name:c:`, "", ``, ``, ``, "", true, true}, + } + } else { + cases = []testParseMountSpec{ + {"/tmp:/tmp1", "", "/tmp1", "/tmp", "", "", true, false}, + {"/tmp:/tmp2:ro", "", "/tmp2", "/tmp", "", "", false, false}, + {"/tmp:/tmp3:rw", "", "/tmp3", "/tmp", "", "", true, false}, + {"/tmp:/tmp4:foo", "", "", "", "", "", false, true}, + {"name:/named1", "", "/named1", "", "name", "local", true, false}, + {"name:/named2", "external", "/named2", "", "name", "external", true, false}, + {"name:/named3:ro", "local", "/named3", "", "name", "local", false, false}, + {"local/name:/tmp:rw", "", "/tmp", "", "local/name", "local", true, false}, + {"/tmp:tmp", "", "", "", "", "", true, true}, + } + } + + for _, c := range cases { + m, err := ParseMountSpec(c.bind, c.driver) + if c.fail { + if err == nil { + t.Fatalf("Expected error, was nil, for spec %s\n", c.bind) + } + continue + } + + if m == nil || err != nil { + t.Fatalf("ParseMountSpec failed for spec %s driver %s error %v\n", c.bind, c.driver, err.Error()) + continue + } + + if m.Destination != c.expDest { + t.Fatalf("Expected destination %s, was %s, for spec %s\n", c.expDest, m.Destination, c.bind) + } + + if m.Source != c.expSource { + t.Fatalf("Expected source %s, was %s, for spec %s\n", c.expSource, m.Source, c.bind) + } + + if m.Name != c.expName { + t.Fatalf("Expected name %s, was %s for spec %s\n", c.expName, m.Name, c.bind) + } + + if m.Driver != c.expDriver { + t.Fatalf("Expected driver %s, was %s, for spec %s\n", c.expDriver, m.Driver, c.bind) + } + + if m.RW != c.expRW { + t.Fatalf("Expected RW %v, was %v for spec %s\n", c.expRW, m.RW, c.bind) + } + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_unix.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_unix.go new file mode 100644 index 000000000..db0fea5b8 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_unix.go @@ -0,0 +1,132 @@ +// +build linux freebsd darwin + +package volume + +import ( + "fmt" + "path/filepath" + "strings" + + derr "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors" +) + +// read-write modes +var rwModes = map[string]bool{ + "rw": true, + "rw,Z": true, + "rw,z": true, + "z,rw": true, + "Z,rw": true, + "Z": true, + "z": true, +} + +// read-only modes +var roModes = map[string]bool{ + "ro": true, + "ro,Z": true, + "ro,z": true, + "z,ro": true, + "Z,ro": true, +} + +// BackwardsCompatible decides whether this mount point can be +// used in old versions of Docker or not. +// Only bind mounts and local volumes can be used in old versions of Docker. +func (m *MountPoint) BackwardsCompatible() bool { + return len(m.Source) > 0 || m.Driver == DefaultDriverName +} + +// HasResource checks whether the given absolute path for a container is in +// this mount point. If the relative path starts with `../` then the resource +// is outside of this mount point, but we can't simply check for this prefix +// because it misses `..` which is also outside of the mount, so check both. +func (m *MountPoint) HasResource(absolutePath string) bool { + relPath, err := filepath.Rel(m.Destination, absolutePath) + return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator)) +} + +// ParseMountSpec validates the configuration of mount information is valid. +func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) { + spec = filepath.ToSlash(spec) + + mp := &MountPoint{ + RW: true, + } + if strings.Count(spec, ":") > 2 { + return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec) + } + + arr := strings.SplitN(spec, ":", 3) + if arr[0] == "" { + return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec) + } + + switch len(arr) { + case 1: + // Just a destination path in the container + mp.Destination = filepath.Clean(arr[0]) + case 2: + if isValid := ValidMountMode(arr[1]); isValid { + // Destination + Mode is not a valid volume - volumes + // cannot include a mode. eg /foo:rw + return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec) + } + // Host Source Path or Name + Destination + mp.Source = arr[0] + mp.Destination = arr[1] + case 3: + // HostSourcePath+DestinationPath+Mode + mp.Source = arr[0] + mp.Destination = arr[1] + mp.Mode = arr[2] // Mode field is used by SELinux to decide whether to apply label + if !ValidMountMode(mp.Mode) { + return nil, derr.ErrorCodeVolumeInvalidMode.WithArgs(mp.Mode) + } + mp.RW = ReadWrite(mp.Mode) + default: + return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec) + } + + //validate the volumes destination path + mp.Destination = filepath.Clean(mp.Destination) + if !filepath.IsAbs(mp.Destination) { + return nil, derr.ErrorCodeVolumeAbs.WithArgs(mp.Destination) + } + + // Destination cannot be "/" + if mp.Destination == "/" { + return nil, derr.ErrorCodeVolumeSlash.WithArgs(spec) + } + + name, source := ParseVolumeSource(mp.Source) + if len(source) == 0 { + mp.Source = "" // Clear it out as we previously assumed it was not a name + mp.Driver = volumeDriver + if len(mp.Driver) == 0 { + mp.Driver = DefaultDriverName + } + } else { + mp.Source = filepath.Clean(source) + } + + mp.Name = name + + return mp, nil +} + +// ParseVolumeSource parses the origin sources that's mounted into the container. +// It returns a name and a source. It looks to see if the spec passed in +// is an absolute file. If it is, it assumes the spec is a source. If not, +// it assumes the spec is a name. +func ParseVolumeSource(spec string) (string, string) { + if !filepath.IsAbs(spec) { + return spec, "" + } + return "", spec +} + +// IsVolumeNameValid checks a volume name in a platform specific manner. +func IsVolumeNameValid(name string) (bool, error) { + return true, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_windows.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_windows.go new file mode 100644 index 000000000..3f0278015 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/volume/volume_windows.go @@ -0,0 +1,181 @@ +package volume + +import ( + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus" + derr "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/errors" +) + +// read-write modes +var rwModes = map[string]bool{ + "rw": true, +} + +// read-only modes +var roModes = map[string]bool{ + "ro": true, +} + +const ( + // Spec should be in the format [source:]destination[:mode] + // + // Examples: c:\foo bar:d:rw + // c:\foo:d:\bar + // myname:d: + // d:\ + // + // Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See + // https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to + // test is https://regex-golang.appspot.com/assets/html/index.html + // + // Useful link for referencing named capturing groups: + // http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex + // + // There are three match groups: source, destination and mode. + // + + // RXHostDir is the first option of a source + RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*` + // RXName is the second option of a source + RXName = `[^\\/:*?"<>|\r\n]+` + // RXReservedNames are reserved names not possible on Windows + RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])` + + // RXSource is the combined possiblities for a source + RXSource = `((?P((` + RXHostDir + `)|(` + RXName + `))):)?` + + // Source. Can be either a host directory, a name, or omitted: + // HostDir: + // - Essentially using the folder solution from + // https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html + // but adding case insensitivity. + // - Must be an absolute path such as c:\path + // - Can include spaces such as `c:\program files` + // - And then followed by a colon which is not in the capture group + // - And can be optional + // Name: + // - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx) + // - And then followed by a colon which is not in the capture group + // - And can be optional + + // RXDestination is the regex expression for the mount destination + RXDestination = `(?P([a-z]):((?:\\[^\\/:*?"<>\r\n]+)*\\?))` + // Destination (aka container path): + // - Variation on hostdir but can be a drive followed by colon as well + // - If a path, must be absolute. Can include spaces + // - Drive cannot be c: (explicitly checked in code, not RegEx) + // + + // RXMode is the regex expression for the mode of the mount + RXMode = `(:(?P(?i)rw))?` + // Temporarily for TP4, disabling the use of ro as it's not supported yet + // in the platform. TODO Windows: `(:(?P(?i)ro|rw))?` + // mode (optional) + // - Hopefully self explanatory in comparison to above. + // - Colon is not in the capture group + // +) + +// ParseMountSpec validates the configuration of mount information is valid. +func ParseMountSpec(spec string, volumeDriver string) (*MountPoint, error) { + var specExp = regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`) + + // Ensure in platform semantics for matching. The CLI will send in Unix semantics. + match := specExp.FindStringSubmatch(filepath.FromSlash(strings.ToLower(spec))) + + // Must have something back + if len(match) == 0 { + return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec) + } + + // Pull out the sub expressions from the named capture groups + matchgroups := make(map[string]string) + for i, name := range specExp.SubexpNames() { + matchgroups[name] = strings.ToLower(match[i]) + } + + mp := &MountPoint{ + Source: matchgroups["source"], + Destination: matchgroups["destination"], + RW: true, + } + if strings.ToLower(matchgroups["mode"]) == "ro" { + mp.RW = false + } + + // Volumes cannot include an explicitly supplied mode eg c:\path:rw + if mp.Source == "" && mp.Destination != "" && matchgroups["mode"] != "" { + return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec) + } + + // Note: No need to check if destination is absolute as it must be by + // definition of matching the regex. + + if filepath.VolumeName(mp.Destination) == mp.Destination { + // Ensure the destination path, if a drive letter, is not the c drive + if strings.ToLower(mp.Destination) == "c:" { + return nil, derr.ErrorCodeVolumeDestIsC.WithArgs(spec) + } + } else { + // So we know the destination is a path, not drive letter. Clean it up. + mp.Destination = filepath.Clean(mp.Destination) + // Ensure the destination path, if a path, is not the c root directory + if strings.ToLower(mp.Destination) == `c:\` { + return nil, derr.ErrorCodeVolumeDestIsCRoot.WithArgs(spec) + } + } + + // See if the source is a name instead of a host directory + if len(mp.Source) > 0 { + validName, err := IsVolumeNameValid(mp.Source) + if err != nil { + return nil, err + } + if validName { + // OK, so the source is a name. + mp.Name = mp.Source + mp.Source = "" + + // Set the driver accordingly + mp.Driver = volumeDriver + if len(mp.Driver) == 0 { + mp.Driver = DefaultDriverName + } + } else { + // OK, so the source must be a host directory. Make sure it's clean. + mp.Source = filepath.Clean(mp.Source) + } + } + + // Ensure the host path source, if supplied, exists and is a directory + if len(mp.Source) > 0 { + var fi os.FileInfo + var err error + if fi, err = os.Stat(mp.Source); err != nil { + return nil, derr.ErrorCodeVolumeSourceNotFound.WithArgs(mp.Source, err) + } + if !fi.IsDir() { + return nil, derr.ErrorCodeVolumeSourceNotDirectory.WithArgs(mp.Source) + } + } + + logrus.Debugf("MP: Source '%s', Dest '%s', RW %t, Name '%s', Driver '%s'", mp.Source, mp.Destination, mp.RW, mp.Name, mp.Driver) + return mp, nil +} + +// IsVolumeNameValid checks a volume name in a platform specific manner. +func IsVolumeNameValid(name string) (bool, error) { + nameExp := regexp.MustCompile(`^` + RXName + `$`) + if !nameExp.MatchString(name) { + return false, nil + } + nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`) + if nameExp.MatchString(name) { + return false, derr.ErrorCodeVolumeNameReservedWord.WithArgs(name) + } + return true, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/mux.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/mux.go index b32e1a051..21c9d9ae7 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/mux.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/mux.go @@ -70,7 +70,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Clean path to canonical form and redirect. if p := cleanPath(req.URL.Path); p != req.URL.Path { - // Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query. + // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query. // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: // http://code.google.com/p/go/issues/detail?id=5252 url := *req.URL @@ -365,6 +365,8 @@ func uniqueVars(s1, s2 []string) error { return nil } +// checkPairs returns the count of strings passed in, and an error if +// the count is not an even number. func checkPairs(pairs ...string) (int, error) { length := len(pairs) if length%2 != 0 { @@ -374,7 +376,8 @@ func checkPairs(pairs ...string) (int, error) { return length, nil } -// mapFromPairs converts variadic string parameters to a string map. +// mapFromPairsToString converts variadic string parameters to a +// string to string map. func mapFromPairsToString(pairs ...string) (map[string]string, error) { length, err := checkPairs(pairs...) if err != nil { @@ -387,6 +390,8 @@ func mapFromPairsToString(pairs ...string) (map[string]string, error) { return m, nil } +// mapFromPairsToRegex converts variadic string paramers to a +// string to regex map. func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { length, err := checkPairs(pairs...) if err != nil { diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/LICENSE b/vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/LICENSE new file mode 100644 index 000000000..e87a115e4 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/LICENSE @@ -0,0 +1,363 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. + diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/README.md b/vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/README.md new file mode 100644 index 000000000..036e5313f --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/README.md @@ -0,0 +1,30 @@ +# cleanhttp + +Functions for accessing "clean" Go http.Client values + +------------- + +The Go standard library contains a default `http.Client` called +`http.DefaultClient`. It is a common idiom in Go code to start with +`http.DefaultClient` and tweak it as necessary, and in fact, this is +encouraged; from the `http` package documentation: + +> The Client's Transport typically has internal state (cached TCP connections), +so Clients should be reused instead of created as needed. Clients are safe for +concurrent use by multiple goroutines. + +Unfortunately, this is a shared value, and it is not uncommon for libraries to +assume that they are free to modify it at will. With enough dependencies, it +can be very easy to encounter strange problems and race conditions due to +manipulation of this shared value across libraries and goroutines (clients are +safe for concurrent use, but writing values to the client struct itself is not +protected). + +Making things worse is the fact that a bare `http.Client` will use a default +`http.Transport` called `http.DefaultTransport`, which is another global value +that behaves the same way. So it is not simply enough to replace +`http.DefaultClient` with `&http.Client{}`. + +This repository provides some simple functions to get a "clean" `http.Client` +-- one that uses the same default values as the Go standard library, but +returns a client that does not share any state with other clients. diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/cleanhttp.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/cleanhttp.go new file mode 100644 index 000000000..1676d79cb --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp/cleanhttp.go @@ -0,0 +1,28 @@ +package cleanhttp + +import ( + "net" + "net/http" + "time" +) + +// DefaultTransport returns a new http.Transport with the same default values +// as http.DefaultTransport +func DefaultTransport() *http.Transport { + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + } +} + +// DefaultClient returns a new http.Client with the same default values as +// http.Client, but with a non-shared Transport +func DefaultClient() *http.Client { + return &http.Client{ + Transport: DefaultTransport(), + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/opencontainers/runc/libcontainer/user/user.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/opencontainers/runc/libcontainer/user/user.go index 964e31bfd..e6375ea4d 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/opencontainers/runc/libcontainer/user/user.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/opencontainers/runc/libcontainer/user/user.go @@ -349,21 +349,26 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) ( return user, nil } -// GetAdditionalGroups looks up a list of groups by name or group id against -// against the given /etc/group formatted data. If a group name cannot be found, -// an error will be returned. If a group id cannot be found, it will be returned -// as-is. +// GetAdditionalGroups looks up a list of groups by name or group id +// against the given /etc/group formatted data. If a group name cannot +// be found, an error will be returned. If a group id cannot be found, +// or the given group data is nil, the id will be returned as-is +// provided it is in the legal range. func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) { - groups, err := ParseGroupFilter(group, func(g Group) bool { - for _, ag := range additionalGroups { - if g.Name == ag || strconv.Itoa(g.Gid) == ag { - return true + var groups = []Group{} + if group != nil { + var err error + groups, err = ParseGroupFilter(group, func(g Group) bool { + for _, ag := range additionalGroups { + if g.Name == ag || strconv.Itoa(g.Gid) == ag { + return true + } } + return false + }) + if err != nil { + return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err) } - return false - }) - if err != nil { - return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err) } gidMap := make(map[int]struct{}) @@ -401,13 +406,13 @@ func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, err return gids, nil } -// Wrapper around GetAdditionalGroups that opens the groupPath given and gives -// it as an argument to GetAdditionalGroups. +// GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups +// that opens the groupPath given and gives it as an argument to +// GetAdditionalGroups. func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) { group, err := os.Open(groupPath) - if err != nil { - return nil, fmt.Errorf("Failed to open group file: %v", err) + if err == nil { + defer group.Close() } - defer group.Close() return GetAdditionalGroups(additionalGroups, group) } diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/opencontainers/runc/libcontainer/user/user_test.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/opencontainers/runc/libcontainer/user/user_test.go index 0e37ac3dd..53b2289bf 100644 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/opencontainers/runc/libcontainer/user/user_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/external/github.com/opencontainers/runc/libcontainer/user/user_test.go @@ -434,3 +434,39 @@ this is just some garbage data } } } + +func TestGetAdditionalGroupsNumeric(t *testing.T) { + tests := []struct { + groups []string + expected []int + hasError bool + }{ + { + // numeric groups only + groups: []string{"1234", "5678"}, + expected: []int{1234, 5678}, + }, + { + // numeric and alphabetic + groups: []string{"1234", "fake"}, + expected: nil, + hasError: true, + }, + } + + for _, test := range tests { + gids, err := GetAdditionalGroups(test.groups, nil) + if test.hasError && err == nil { + t.Errorf("Parse(%#v) expects error but has none", test) + continue + } + if !test.hasError && err != nil { + t.Errorf("Parse(%#v) has error %v", test, err) + continue + } + sort.Sort(sort.IntSlice(gids)) + if !reflect.DeepEqual(gids, test.expected) { + t.Errorf("Gids(%v), expect %v from groups %v", gids, test.expected, test.groups) + } + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/image.go b/vendor/github.com/fsouza/go-dockerclient/image.go index 9d21da286..fd13bc23f 100644 --- a/vendor/github.com/fsouza/go-dockerclient/image.go +++ b/vendor/github.com/fsouza/go-dockerclient/image.go @@ -43,6 +43,7 @@ type Image struct { Architecture string `json:"Architecture,omitempty" yaml:"Architecture,omitempty"` Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"` VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"` + RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty"` } // ImagePre012 serves the same purpose as the Image type except that it is for @@ -89,6 +90,7 @@ type ListImagesOptions struct { All bool Filters map[string][]string Digests bool + Filter string } // ListImages returns the list of available images in the server. diff --git a/vendor/github.com/fsouza/go-dockerclient/network.go b/vendor/github.com/fsouza/go-dockerclient/network.go index 8fa7091e4..a7fc152de 100644 --- a/vendor/github.com/fsouza/go-dockerclient/network.go +++ b/vendor/github.com/fsouza/go-dockerclient/network.go @@ -17,26 +17,30 @@ var ErrNetworkAlreadyExists = errors.New("network already exists") // Network represents a network. // -// See https://goo.gl/FDkCdQ for more details. +// See https://goo.gl/1kmPKZ for more details. type Network struct { - Name string `json:"name"` - ID string `json:"id"` - Type string `json:"type"` - Endpoints []*Endpoint `json:"endpoints"` + Name string + ID string `json:"Id"` + Scope string + Driver string + Containers map[string]Endpoint + Options map[string]string } -// Endpoint represents an endpoint. +// Endpoint contains network resources allocated and used for a container in a network // -// See https://goo.gl/FDkCdQ for more details. +// See https://goo.gl/1kmPKZ for more details. type Endpoint struct { - Name string `json:"name"` - ID string `json:"id"` - Network string `json:"network"` + Name string + ID string `json:"EndpointID"` + MacAddress string + IPv4Address string + IPv6Address string } // ListNetworks returns all networks. // -// See https://goo.gl/4hCNtZ for more details. +// See https://goo.gl/1kmPKZ for more details. func (c *Client) ListNetworks() ([]Network, error) { resp, err := c.do("GET", "/networks", doOptions{}) if err != nil { @@ -52,7 +56,7 @@ func (c *Client) ListNetworks() ([]Network, error) { // NetworkInfo returns information about a network by its ID. // -// See https://goo.gl/4hCNtZ for more details. +// See https://goo.gl/1kmPKZ for more details. func (c *Client) NetworkInfo(id string) (*Network, error) { path := "/networks/" + id resp, err := c.do("GET", path, doOptions{}) @@ -73,21 +77,41 @@ func (c *Client) NetworkInfo(id string) (*Network, error) { // CreateNetworkOptions specify parameters to the CreateNetwork function and // (for now) is the expected body of the "create network" http request message // -// See https://goo.gl/FDkCdQ for more details. +// See https://goo.gl/1kmPKZ for more details. type CreateNetworkOptions struct { - Name string `json:"name"` - NetworkType string `json:"network_type"` - Options map[string]interface{} `json:"options"` + Name string `json:"Name"` + CheckDuplicate bool `json:"CheckDuplicate"` + Driver string `json:"Driver"` + IPAM IPAMOptions `json:"IPAM"` + Options map[string]interface{} `json:"options"` +} + +// IPAMOptions controls IP Address Management when creating a network +// +// See https://goo.gl/T8kRVH for more details. +type IPAMOptions struct { + Driver string `json:"Driver"` + Config []IPAMConfig `json:"IPAMConfig"` +} + +// IPAMConfig represents IPAM configurations +// +// See https://goo.gl/T8kRVH for more details. +type IPAMConfig struct { + Subnet string `json:",omitempty"` + IPRange string `json:",omitempty"` + Gateway string `json:",omitempty"` + AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` } // CreateNetwork creates a new network, returning the network instance, // or an error in case of failure. // -// See https://goo.gl/FDkCdQ for more details. +// See https://goo.gl/1kmPKZ for more details. func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) { resp, err := c.do( "POST", - "/networks", + "/networks/create", doOptions{ data: opts, }, @@ -113,14 +137,14 @@ func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) { network.Name = opts.Name network.ID = cnr.ID - network.Type = opts.NetworkType + network.Driver = opts.Driver return &network, nil } // RemoveNetwork removes a network or an error in case of failure. // -// See https://goo.gl/FDkCdQ for more details. +// See https://goo.gl/1kmPKZ for more details. func (c *Client) RemoveNetwork(id string) error { resp, err := c.do("DELETE", "/networks/"+id, doOptions{}) if err != nil { diff --git a/vendor/github.com/fsouza/go-dockerclient/network_test.go b/vendor/github.com/fsouza/go-dockerclient/network_test.go index b5aef2081..9e6be3082 100644 --- a/vendor/github.com/fsouza/go-dockerclient/network_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/network_test.go @@ -75,7 +75,7 @@ func TestNetworkCreate(t *testing.T) { jsonNetwork := `{ "ID": "8dfafdbc3a40", "Name": "foobar", - "Type": "bridge" + "Driver": "bridge" }` var expected Network err := json.Unmarshal([]byte(jsonNetwork), &expected) @@ -84,7 +84,7 @@ func TestNetworkCreate(t *testing.T) { } client := newTestClient(&FakeRoundTripper{message: jsonID, status: http.StatusOK}) - opts := CreateNetworkOptions{"foobar", "bridge", nil} + opts := CreateNetworkOptions{"foobar", false, "bridge", IPAMOptions{}, nil} network, err := client.CreateNetwork(opts) if err != nil { t.Fatal(err) diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/.dockerignore b/vendor/github.com/fsouza/go-dockerclient/testing/data/.dockerignore new file mode 100644 index 000000000..027e8c20e --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/.dockerignore @@ -0,0 +1,3 @@ +container.tar +dockerfile.tar +foofile diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/server.go b/vendor/github.com/fsouza/go-dockerclient/testing/server.go index 5052e2d05..d24876b2b 100644 --- a/vendor/github.com/fsouza/go-dockerclient/testing/server.go +++ b/vendor/github.com/fsouza/go-dockerclient/testing/server.go @@ -665,12 +665,12 @@ func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] force := r.URL.Query().Get("force") - _, index, err := s.findContainer(id) + container, index, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } - if s.containers[index].State.Running && force != "1" { + if container.State.Running && force != "1" { msg := "Error: API error (406): Impossible to remove a running container, please stop it first" http.Error(w, msg, http.StatusInternalServerError) return @@ -678,8 +678,10 @@ func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) s.cMut.Lock() defer s.cMut.Unlock() - s.containers[index] = s.containers[len(s.containers)-1] - s.containers = s.containers[:len(s.containers)-1] + if s.containers[index].ID == id || s.containers[index].Name == id { + s.containers[index] = s.containers[len(s.containers)-1] + s.containers = s.containers[:len(s.containers)-1] + } } func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { @@ -1083,9 +1085,9 @@ func (s *DockerServer) createNetwork(w http.ResponseWriter, r *http.Request) { generatedID := s.generateID() network := docker.Network{ - Name: config.Name, - ID: generatedID, - Type: config.NetworkType, + Name: config.Name, + ID: generatedID, + Driver: config.Driver, } s.netMut.Lock() s.networks = append(s.networks, &network) 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 c59f78c90..78d5bb1bd 100644 --- a/vendor/github.com/fsouza/go-dockerclient/testing/server_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/testing/server_test.go @@ -1749,14 +1749,13 @@ func addNetworks(server *DockerServer, n int) { for i := 0; i < n; i++ { netid := fmt.Sprintf("%x", rand.Int()%10000) network := docker.Network{ - Name: netid, - ID: fmt.Sprintf("%x", rand.Int()%10000), - Type: "bridge", - Endpoints: []*docker.Endpoint{ - { - Name: "blah", - ID: fmt.Sprintf("%x", rand.Int()%10000), - Network: netid, + Name: netid, + ID: fmt.Sprintf("%x", rand.Int()%10000), + Driver: "bridge", + Containers: map[string]docker.Endpoint{ + "blah": { + Name: "blah", + ID: fmt.Sprintf("%x", rand.Int()%10000), }, }, } @@ -1777,10 +1776,10 @@ func TestListNetworks(t *testing.T) { expected := make([]docker.Network, 2) for i, network := range server.networks { expected[i] = docker.Network{ - ID: network.ID, - Name: network.Name, - Type: network.Type, - Endpoints: network.Endpoints, + ID: network.ID, + Name: network.Name, + Driver: network.Driver, + Containers: network.Containers, } } var got []docker.Network diff --git a/vendor/github.com/fsouza/go-dockerclient/volume.go b/vendor/github.com/fsouza/go-dockerclient/volume.go index a989a6eee..0e57cb122 100644 --- a/vendor/github.com/fsouza/go-dockerclient/volume.go +++ b/vendor/github.com/fsouza/go-dockerclient/volume.go @@ -75,7 +75,7 @@ type CreateVolumeOptions struct { // // See https://goo.gl/pBUbZ9 for more details. func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) { - resp, err := c.do("POST", "/volumes", doOptions{data: opts}) + resp, err := c.do("POST", "/volumes/create", doOptions{data: opts}) if err != nil { return nil, err } diff --git a/vendor/github.com/fsouza/go-dockerclient/volume_test.go b/vendor/github.com/fsouza/go-dockerclient/volume_test.go index 9707c09cd..e6bcca95f 100644 --- a/vendor/github.com/fsouza/go-dockerclient/volume_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/volume_test.go @@ -72,7 +72,7 @@ func TestCreateVolume(t *testing.T) { if req.Method != expectedMethod { t.Errorf("CreateVolume(): Wrong HTTP method. Want %s. Got %s.", expectedMethod, req.Method) } - u, _ := url.Parse(client.getURL("/volumes")) + u, _ := url.Parse(client.getURL("/volumes/create")) if req.URL.Path != u.Path { t.Errorf("CreateVolume(): Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) }