From 2c12cc57984e28a7084ef77334494791e6d1e3ed Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Thu, 22 Feb 2018 02:30:51 -0800 Subject: [PATCH 1/3] Move containerIsStopped/containerIsInState to integration/internal/container This fix moves helper functions containerIsStopped and containerIsInState to integration/internal/container, so that they could be used outside of integration/container. Signed-off-by: Yong Tang Upstream-commit: eda311c18f388ed4541dc44dcfba08cd4347a685 Component: engine --- .../engine/integration/container/diff_test.go | 2 +- .../integration/container/inspect_test.go | 2 +- .../engine/integration/container/kill_test.go | 16 ++++---- .../integration/container/links_linux_test.go | 2 +- .../engine/integration/container/nat_test.go | 4 +- .../integration/container/pause_test.go | 8 ++-- .../integration/container/rename_test.go | 18 ++++---- .../integration/container/resize_test.go | 6 +-- .../integration/container/stats_test.go | 2 +- .../engine/integration/container/stop_test.go | 37 ++--------------- .../container/update_linux_test.go | 2 +- .../integration/container/update_test.go | 2 +- .../integration/internal/container/states.go | 41 +++++++++++++++++++ 13 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 components/engine/integration/internal/container/states.go diff --git a/components/engine/integration/container/diff_test.go b/components/engine/integration/container/diff_test.go index 63ac19e9ad..df60236998 100644 --- a/components/engine/integration/container/diff_test.go +++ b/components/engine/integration/container/diff_test.go @@ -29,7 +29,7 @@ func TestDiffFilenameShownInOutput(t *testing.T) { // a "Files/" prefix. lookingFor := containertypes.ContainerChangeResponseItem{Kind: archive.ChangeAdd, Path: "/foo/bar"} if testEnv.OSType == "windows" { - poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(60*time.Second)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(60*time.Second)) lookingFor = containertypes.ContainerChangeResponseItem{Kind: archive.ChangeModify, Path: "Files/foo/bar"} } diff --git a/components/engine/integration/container/inspect_test.go b/components/engine/integration/container/inspect_test.go index 5ecd287bfe..2eace554e4 100644 --- a/components/engine/integration/container/inspect_test.go +++ b/components/engine/integration/container/inspect_test.go @@ -30,7 +30,7 @@ func TestInspectCpusetInConfigPre120(t *testing.T) { c.HostConfig.Resources.CpusetCpus = "0" }, ) - poll.WaitOn(t, containerIsInState(ctx, client, name, "exited"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, name, "exited"), poll.WithDelay(100*time.Millisecond)) _, body, err := client.ContainerInspectWithRaw(ctx, name, false) require.NoError(t, err) diff --git a/components/engine/integration/container/kill_test.go b/components/engine/integration/container/kill_test.go index d399213ac6..09e37ac0d1 100644 --- a/components/engine/integration/container/kill_test.go +++ b/components/engine/integration/container/kill_test.go @@ -23,11 +23,11 @@ func TestKillContainerInvalidSignal(t *testing.T) { err := client.ContainerKill(ctx, id, "0") require.EqualError(t, err, "Error response from daemon: Invalid signal: 0") - poll.WaitOn(t, containerIsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond)) err = client.ContainerKill(ctx, id, "SIG42") require.EqualError(t, err, "Error response from daemon: Invalid signal: SIG42") - poll.WaitOn(t, containerIsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond)) } func TestKillContainer(t *testing.T) { @@ -64,7 +64,7 @@ func TestKillContainer(t *testing.T) { err := client.ContainerKill(ctx, id, tc.signal) require.NoError(t, err) - poll.WaitOn(t, containerIsInState(ctx, client, id, tc.status), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, id, tc.status), poll.WithDelay(100*time.Millisecond)) }) } } @@ -104,7 +104,7 @@ func TestKillWithStopSignalAndRestartPolicies(t *testing.T) { err := client.ContainerKill(ctx, id, "TERM") require.NoError(t, err) - poll.WaitOn(t, containerIsInState(ctx, client, id, tc.status), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, id, tc.status), poll.WithDelay(100*time.Millisecond)) }) } } @@ -141,11 +141,11 @@ func TestKillDifferentUserContainer(t *testing.T) { id := container.Run(t, ctx, client, func(c *container.TestContainerConfig) { c.Config.User = "daemon" }) - poll.WaitOn(t, containerIsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond)) err := client.ContainerKill(ctx, id, "SIGKILL") require.NoError(t, err) - poll.WaitOn(t, containerIsInState(ctx, client, id, "exited"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, id, "exited"), poll.WithDelay(100*time.Millisecond)) } func TestInspectOomKilledTrue(t *testing.T) { @@ -160,7 +160,7 @@ func TestInspectOomKilledTrue(t *testing.T) { c.HostConfig.Resources.Memory = 32 * 1024 * 1024 }) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) inspect, err := client.ContainerInspect(ctx, cID) require.NoError(t, err) @@ -177,7 +177,7 @@ func TestInspectOomKilledFalse(t *testing.T) { name := "testoomkilled" cID := container.Run(t, ctx, client, container.WithName(name), container.WithCmd("sh", "-c", "echo hello world")) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) inspect, err := client.ContainerInspect(ctx, cID) require.NoError(t, err) diff --git a/components/engine/integration/container/links_linux_test.go b/components/engine/integration/container/links_linux_test.go index e2e0b9d9ea..87b27e321b 100644 --- a/components/engine/integration/container/links_linux_test.go +++ b/components/engine/integration/container/links_linux_test.go @@ -31,7 +31,7 @@ func TestLinksEtcHostsContentMatch(t *testing.T) { cID := container.Run(t, ctx, client, container.WithCmd("cat", "/etc/hosts"), container.WithNetworkMode("host")) - poll.WaitOn(t, containerIsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond)) body, err := client.ContainerLogs(ctx, cID, types.ContainerLogsOptions{ ShowStdout: true, diff --git a/components/engine/integration/container/nat_test.go b/components/engine/integration/container/nat_test.go index df3451fbc5..ad93e59f49 100644 --- a/components/engine/integration/container/nat_test.go +++ b/components/engine/integration/container/nat_test.go @@ -69,7 +69,7 @@ func TestNetworkLoopbackNat(t *testing.T) { cID := container.Run(t, ctx, client, container.WithCmd("sh", "-c", fmt.Sprintf("stty raw && nc -w 5 %s 8080", endpoint.String())), container.WithTty(true), container.WithNetworkMode("container:server")) - poll.WaitOn(t, containerIsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond)) body, err := client.ContainerLogs(ctx, cID, types.ContainerLogsOptions{ ShowStdout: true, @@ -98,7 +98,7 @@ func startServerContainer(t *testing.T, msg string, port int) string { } }) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) return cID } diff --git a/components/engine/integration/container/pause_test.go b/components/engine/integration/container/pause_test.go index 4e64357221..cacc3621de 100644 --- a/components/engine/integration/container/pause_test.go +++ b/components/engine/integration/container/pause_test.go @@ -27,7 +27,7 @@ func TestPause(t *testing.T) { name := "testeventpause" cID := container.Run(t, ctx, client, container.WithName(name)) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) since := request.DaemonUnixTime(ctx, t, client, testEnv) @@ -59,7 +59,7 @@ func TestPauseFailsOnWindowsServerContainers(t *testing.T) { ctx := context.Background() cID := container.Run(t, ctx, client) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) err := client.ContainerPause(ctx, cID) testutil.ErrorContains(t, err, "cannot pause Windows Server Containers") @@ -73,7 +73,7 @@ func TestPauseStopPausedContainer(t *testing.T) { ctx := context.Background() cID := container.Run(t, ctx, client) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) err := client.ContainerPause(ctx, cID) require.NoError(t, err) @@ -81,7 +81,7 @@ func TestPauseStopPausedContainer(t *testing.T) { err = client.ContainerStop(ctx, cID, nil) require.NoError(t, err) - poll.WaitOn(t, containerIsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond)) } func getEventActions(t *testing.T, messages <-chan events.Message, errs <-chan error) []string { diff --git a/components/engine/integration/container/rename_test.go b/components/engine/integration/container/rename_test.go index da2bed17c7..2138ee578e 100644 --- a/components/engine/integration/container/rename_test.go +++ b/components/engine/integration/container/rename_test.go @@ -51,7 +51,7 @@ func TestRenameStoppedContainer(t *testing.T) { oldName := "first_name" cID := container.Run(t, ctx, client, container.WithName(oldName), container.WithCmd("sh")) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) inspect, err := client.ContainerInspect(ctx, cID) require.NoError(t, err) @@ -73,7 +73,7 @@ func TestRenameRunningContainerAndReuse(t *testing.T) { oldName := "first_name" cID := container.Run(t, ctx, client, container.WithName(oldName)) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) newName := "new_name" + stringid.GenerateNonCryptoID() err := client.ContainerRename(ctx, oldName, newName) @@ -87,7 +87,7 @@ func TestRenameRunningContainerAndReuse(t *testing.T) { testutil.ErrorContains(t, err, "No such container: "+oldName) cID = container.Run(t, ctx, client, container.WithName(oldName)) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) inspect, err = client.ContainerInspect(ctx, cID) require.NoError(t, err) @@ -101,7 +101,7 @@ func TestRenameInvalidName(t *testing.T) { oldName := "first_name" cID := container.Run(t, ctx, client, container.WithName(oldName)) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) err := client.ContainerRename(ctx, oldName, "new:invalid") testutil.ErrorContains(t, err, "Invalid container name") @@ -136,7 +136,7 @@ func TestRenameAnonymousContainer(t *testing.T) { err = client.ContainerStart(ctx, "container1", types.ContainerStartOptions{}) require.NoError(t, err) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) count := "-c" if testEnv.OSType == "windows" { @@ -148,7 +148,7 @@ func TestRenameAnonymousContainer(t *testing.T) { } c.HostConfig.NetworkMode = "network1" }, container.WithCmd("ping", count, "1", "container1")) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) inspect, err := client.ContainerInspect(ctx, cID) require.NoError(t, err) @@ -162,7 +162,7 @@ func TestRenameContainerWithSameName(t *testing.T) { client := request.NewAPIClient(t) cID := container.Run(t, ctx, client, container.WithName("old")) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) err := client.ContainerRename(ctx, "old", "old") testutil.ErrorContains(t, err, "Renaming a container with the same name") err = client.ContainerRename(ctx, cID, "old") @@ -182,10 +182,10 @@ func TestRenameContainerWithLinkedContainer(t *testing.T) { client := request.NewAPIClient(t) db1ID := container.Run(t, ctx, client, container.WithName("db1")) - poll.WaitOn(t, containerIsInState(ctx, client, db1ID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, db1ID, "running"), poll.WithDelay(100*time.Millisecond)) app1ID := container.Run(t, ctx, client, container.WithName("app1"), container.WithLinks("db1:/mysql")) - poll.WaitOn(t, containerIsInState(ctx, client, app1ID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, app1ID, "running"), poll.WithDelay(100*time.Millisecond)) err := client.ContainerRename(ctx, "app1", "app2") require.NoError(t, err) diff --git a/components/engine/integration/container/resize_test.go b/components/engine/integration/container/resize_test.go index 0ada5c32e4..0a5f26067e 100644 --- a/components/engine/integration/container/resize_test.go +++ b/components/engine/integration/container/resize_test.go @@ -23,7 +23,7 @@ func TestResize(t *testing.T) { cID := container.Run(t, ctx, client) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) err := client.ContainerResize(ctx, cID, types.ResizeOptions{ Height: 40, @@ -39,7 +39,7 @@ func TestResizeWithInvalidSize(t *testing.T) { cID := container.Run(t, ctx, client) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) endpoint := "/containers/" + cID + "/resize?h=foo&w=bar" res, _, err := req.Post(endpoint) @@ -54,7 +54,7 @@ func TestResizeWhenContainerNotStarted(t *testing.T) { cID := container.Run(t, ctx, client, container.WithCmd("echo")) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) err := client.ContainerResize(ctx, cID, types.ResizeOptions{ Height: 40, diff --git a/components/engine/integration/container/stats_test.go b/components/engine/integration/container/stats_test.go index fdf85f44f6..9c0b948498 100644 --- a/components/engine/integration/container/stats_test.go +++ b/components/engine/integration/container/stats_test.go @@ -28,7 +28,7 @@ func TestStats(t *testing.T) { cID := container.Run(t, ctx, client) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) resp, err := client.ContainerStats(ctx, cID, false) require.NoError(t, err) diff --git a/components/engine/integration/container/stop_test.go b/components/engine/integration/container/stop_test.go index 305596ac85..4ecd06dd2c 100644 --- a/components/engine/integration/container/stop_test.go +++ b/components/engine/integration/container/stop_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/docker/docker/api/types" - "github.com/docker/docker/client" "github.com/docker/docker/integration/internal/container" "github.com/docker/docker/integration/internal/request" "github.com/gotestyourself/gotestyourself/icmd" @@ -30,7 +29,7 @@ func TestStopContainerWithRestartPolicyAlways(t *testing.T) { } for _, name := range names { - poll.WaitOn(t, containerIsInState(ctx, client, name, "running", "restarting"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, name, "running", "restarting"), poll.WithDelay(100*time.Millisecond)) } for _, name := range names { @@ -39,7 +38,7 @@ func TestStopContainerWithRestartPolicyAlways(t *testing.T) { } for _, name := range names { - poll.WaitOn(t, containerIsStopped(ctx, client, name), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsStopped(ctx, client, name), poll.WithDelay(100*time.Millisecond)) } } @@ -52,7 +51,7 @@ func TestDeleteDevicemapper(t *testing.T) { id := container.Run(t, ctx, client, container.WithName("foo"), container.WithCmd("echo")) - poll.WaitOn(t, containerIsStopped(ctx, client, id), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsStopped(ctx, client, id), poll.WithDelay(100*time.Millisecond)) inspect, err := client.ContainerInspect(ctx, id) require.NoError(t, err) @@ -70,33 +69,3 @@ func TestDeleteDevicemapper(t *testing.T) { err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{}) require.NoError(t, err) } - -func containerIsStopped(ctx context.Context, client client.APIClient, containerID string) func(log poll.LogT) poll.Result { - return func(log poll.LogT) poll.Result { - inspect, err := client.ContainerInspect(ctx, containerID) - - switch { - case err != nil: - return poll.Error(err) - case !inspect.State.Running: - return poll.Success() - default: - return poll.Continue("waiting for container to be stopped") - } - } -} - -func containerIsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result { - return func(log poll.LogT) poll.Result { - inspect, err := client.ContainerInspect(ctx, containerID) - if err != nil { - return poll.Error(err) - } - for _, v := range state { - if inspect.State.Status == v { - return poll.Success() - } - } - return poll.Continue("waiting for container to be running, currently %s", inspect.State.Status) - } -} diff --git a/components/engine/integration/container/update_linux_test.go b/components/engine/integration/container/update_linux_test.go index 9028f30386..c898dc1d3b 100644 --- a/components/engine/integration/container/update_linux_test.go +++ b/components/engine/integration/container/update_linux_test.go @@ -31,7 +31,7 @@ func TestUpdateMemory(t *testing.T) { } }) - poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) const ( setMemory int64 = 314572800 diff --git a/components/engine/integration/container/update_test.go b/components/engine/integration/container/update_test.go index aee95804a1..651e84cb22 100644 --- a/components/engine/integration/container/update_test.go +++ b/components/engine/integration/container/update_test.go @@ -39,7 +39,7 @@ func TestUpdateRestartPolicy(t *testing.T) { timeout = 180 * time.Second } - poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(timeout)) + poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(timeout)) inspect, err := client.ContainerInspect(ctx, cID) require.NoError(t, err) diff --git a/components/engine/integration/internal/container/states.go b/components/engine/integration/internal/container/states.go new file mode 100644 index 0000000000..27d9973e00 --- /dev/null +++ b/components/engine/integration/internal/container/states.go @@ -0,0 +1,41 @@ +package container + +import ( + "strings" + + "github.com/docker/docker/client" + "github.com/gotestyourself/gotestyourself/poll" + "golang.org/x/net/context" +) + +// IsStopped verifies the container is in stopped state. +func IsStopped(ctx context.Context, client client.APIClient, containerID string) func(log poll.LogT) poll.Result { + return func(log poll.LogT) poll.Result { + inspect, err := client.ContainerInspect(ctx, containerID) + + switch { + case err != nil: + return poll.Error(err) + case !inspect.State.Running: + return poll.Success() + default: + return poll.Continue("waiting for container to be stopped") + } + } +} + +// IsInState verifies the container is in one of the specified state, e.g., "running", "exited", etc. +func IsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result { + return func(log poll.LogT) poll.Result { + inspect, err := client.ContainerInspect(ctx, containerID) + if err != nil { + return poll.Error(err) + } + for _, v := range state { + if inspect.State.Status == v { + return poll.Success() + } + } + return poll.Continue("waiting for container to be one of (%s), currently %s", strings.Join(state, ", "), inspect.State.Status) + } +} From a52b825c7e46c44395fd336762450ac13c410eda Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Fri, 23 Feb 2018 22:24:47 +0000 Subject: [PATCH 2/3] Update docker-py This fix update docker-py so that containers from the tests run could be cleaned up during teardown: ```diff -ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916 +ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f ``` Signed-off-by: Yong Tang Upstream-commit: 66935a0f64f0a72162fb3919c759f4f500b6c372 Component: engine --- components/engine/Dockerfile | 2 +- components/engine/Dockerfile.aarch64 | 2 +- components/engine/Dockerfile.armhf | 2 +- components/engine/Dockerfile.ppc64le | 2 +- components/engine/Dockerfile.s390x | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index bd42d81c46..fab79570c4 100644 --- a/components/engine/Dockerfile +++ b/components/engine/Dockerfile @@ -142,7 +142,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Get the "docker-py" source so we can run their integration tests -ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916 +ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f # To run integration tests docker-pycreds is required. RUN git clone https://github.com/docker/docker-py.git /docker-py \ && cd /docker-py \ diff --git a/components/engine/Dockerfile.aarch64 b/components/engine/Dockerfile.aarch64 index 68df256e74..068d80516f 100644 --- a/components/engine/Dockerfile.aarch64 +++ b/components/engine/Dockerfile.aarch64 @@ -106,7 +106,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Get the "docker-py" source so we can run their integration tests -ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916 +ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f # To run integration tests docker-pycreds is required. RUN git clone https://github.com/docker/docker-py.git /docker-py \ && cd /docker-py \ diff --git a/components/engine/Dockerfile.armhf b/components/engine/Dockerfile.armhf index 1978be7610..0e03fb0a1a 100644 --- a/components/engine/Dockerfile.armhf +++ b/components/engine/Dockerfile.armhf @@ -104,7 +104,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Get the "docker-py" source so we can run their integration tests -ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916 +ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f # To run integration tests docker-pycreds is required. RUN git clone https://github.com/docker/docker-py.git /docker-py \ && cd /docker-py \ diff --git a/components/engine/Dockerfile.ppc64le b/components/engine/Dockerfile.ppc64le index c4aaa831e8..07f4b01492 100644 --- a/components/engine/Dockerfile.ppc64le +++ b/components/engine/Dockerfile.ppc64le @@ -102,7 +102,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Get the "docker-py" source so we can run their integration tests -ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916 +ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f # To run integration tests docker-pycreds is required. RUN git clone https://github.com/docker/docker-py.git /docker-py \ && cd /docker-py \ diff --git a/components/engine/Dockerfile.s390x b/components/engine/Dockerfile.s390x index d6bc28ecf2..dc55d57231 100644 --- a/components/engine/Dockerfile.s390x +++ b/components/engine/Dockerfile.s390x @@ -96,7 +96,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Get the "docker-py" source so we can run their integration tests -ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916 +ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f # To run integration tests docker-pycreds is required. RUN git clone https://github.com/docker/docker-py.git /docker-py \ && cd /docker-py \ From 71ca3e138fcc4357cf43a1e66fa92e3488b449e8 Mon Sep 17 00:00:00 2001 From: Emil Davtyan Date: Wed, 31 Jan 2018 13:39:48 +0100 Subject: [PATCH 3/3] Content encoding negotiation added to archive request. Signed-off-by: Emil Davtyan Upstream-commit: 117cd7ff640d5e58229eb7f6588d01b928a30607 Component: engine --- .../api/server/router/container/copy.go | 30 +- components/engine/vendor.conf | 1 + .../vendor/github.com/golang/gddo/LICENSE | 27 ++ .../github.com/golang/gddo/README.markdown | 44 +++ .../github.com/golang/gddo/httputil/buster.go | 95 ++++++ .../golang/gddo/httputil/header/header.go | 298 ++++++++++++++++++ .../golang/gddo/httputil/httputil.go | 25 ++ .../golang/gddo/httputil/negotiate.go | 79 +++++ .../golang/gddo/httputil/respbuf.go | 58 ++++ .../github.com/golang/gddo/httputil/static.go | 265 ++++++++++++++++ .../golang/gddo/httputil/transport.go | 87 +++++ 11 files changed, 1006 insertions(+), 3 deletions(-) create mode 100644 components/engine/vendor/github.com/golang/gddo/LICENSE create mode 100644 components/engine/vendor/github.com/golang/gddo/README.markdown create mode 100644 components/engine/vendor/github.com/golang/gddo/httputil/buster.go create mode 100644 components/engine/vendor/github.com/golang/gddo/httputil/header/header.go create mode 100644 components/engine/vendor/github.com/golang/gddo/httputil/httputil.go create mode 100644 components/engine/vendor/github.com/golang/gddo/httputil/negotiate.go create mode 100644 components/engine/vendor/github.com/golang/gddo/httputil/respbuf.go create mode 100644 components/engine/vendor/github.com/golang/gddo/httputil/static.go create mode 100644 components/engine/vendor/github.com/golang/gddo/httputil/transport.go diff --git a/components/engine/api/server/router/container/copy.go b/components/engine/api/server/router/container/copy.go index 77ce500419..8851278c05 100644 --- a/components/engine/api/server/router/container/copy.go +++ b/components/engine/api/server/router/container/copy.go @@ -1,6 +1,8 @@ package container // import "github.com/docker/docker/api/server/router/container" import ( + "compress/flate" + "compress/gzip" "encoding/base64" "encoding/json" "io" @@ -9,6 +11,7 @@ import ( "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" + gddohttputil "github.com/golang/gddo/httputil" "golang.org/x/net/context" ) @@ -81,6 +84,29 @@ func (s *containerRouter) headContainersArchive(ctx context.Context, w http.Resp return setContainerPathStatHeader(stat, w.Header()) } +func writeCompressedResponse(w http.ResponseWriter, r *http.Request, body io.Reader) error { + var cw io.Writer + switch gddohttputil.NegotiateContentEncoding(r, []string{"gzip", "deflate"}) { + case "gzip": + gw := gzip.NewWriter(w) + defer gw.Close() + cw = gw + w.Header().Set("Content-Encoding", "gzip") + case "deflate": + fw, err := flate.NewWriter(w, flate.DefaultCompression) + if err != nil { + return err + } + defer fw.Close() + cw = fw + w.Header().Set("Content-Encoding", "deflate") + default: + cw = w + } + _, err := io.Copy(cw, body) + return err +} + func (s *containerRouter) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { v, err := httputils.ArchiveFormValues(r, vars) if err != nil { @@ -98,9 +124,7 @@ func (s *containerRouter) getContainersArchive(ctx context.Context, w http.Respo } w.Header().Set("Content-Type", "application/x-tar") - _, err = io.Copy(w, tarArchive) - - return err + return writeCompressedResponse(w, r, tarArchive) } func (s *containerRouter) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/components/engine/vendor.conf b/components/engine/vendor.conf index c06299e426..dd641112e9 100644 --- a/components/engine/vendor.conf +++ b/components/engine/vendor.conf @@ -5,6 +5,7 @@ github.com/Microsoft/go-winio v0.4.6 github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git +github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a github.com/gorilla/context v1.1 github.com/gorilla/mux v1.1 github.com/Microsoft/opengcs v0.3.6 diff --git a/components/engine/vendor/github.com/golang/gddo/LICENSE b/components/engine/vendor/github.com/golang/gddo/LICENSE new file mode 100644 index 0000000000..65d761bc9f --- /dev/null +++ b/components/engine/vendor/github.com/golang/gddo/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/components/engine/vendor/github.com/golang/gddo/README.markdown b/components/engine/vendor/github.com/golang/gddo/README.markdown new file mode 100644 index 0000000000..7194f4c08c --- /dev/null +++ b/components/engine/vendor/github.com/golang/gddo/README.markdown @@ -0,0 +1,44 @@ +This project is the source for http://godoc.org/ + +[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](http://godoc.org/github.com/golang/gddo) +[![Build +Status](https://travis-ci.org/golang/gddo.svg?branch=master)](https://travis-ci.org/golang/gddo) + +The code in this project is designed to be used by godoc.org. Send mail to +golang-dev@googlegroups.com if you want to discuss other uses of the code. + +## Feedback + +Send ideas and questions to golang-dev@googlegroups.com. Request features and +report bugs using the [GitHub Issue +Tracker](https://github.com/golang/gddo/issues/new). + +## Contributions + +Contributions to this project are welcome, though please [file an +issue](https://github.com/golang/gddo/issues/new). before starting work on +anything major. + +**We do not accept GitHub pull requests** + +Please refer to the [Contribution +Guidelines](https://golang.org/doc/contribute.html) on how to submit changes. + +We use https://go-review.googlesource.com to review change submissions. + +## Getting the Source + +To get started contributing to this project, clone the repository from its +canonical location + +``` +git clone https://go.googlesource.com/gddo $GOPATH/src/github.com/golang/gddo +``` + +Information on how to set up a local environment is available at +https://github.com/golang/gddo/wiki/Development-Environment-Setup. + +## More Documentation + +More documentation about this project is available on the +[wiki](https://github.com/golang/gddo/wiki). diff --git a/components/engine/vendor/github.com/golang/gddo/httputil/buster.go b/components/engine/vendor/github.com/golang/gddo/httputil/buster.go new file mode 100644 index 0000000000..beab151a4b --- /dev/null +++ b/components/engine/vendor/github.com/golang/gddo/httputil/buster.go @@ -0,0 +1,95 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +package httputil + +import ( + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + "sync" +) + +type busterWriter struct { + headerMap http.Header + status int + io.Writer +} + +func (bw *busterWriter) Header() http.Header { + return bw.headerMap +} + +func (bw *busterWriter) WriteHeader(status int) { + bw.status = status +} + +// CacheBusters maintains a cache of cache busting tokens for static resources served by Handler. +type CacheBusters struct { + Handler http.Handler + + mu sync.Mutex + tokens map[string]string +} + +func sanitizeTokenRune(r rune) rune { + if r <= ' ' || r >= 127 { + return -1 + } + // Convert percent encoding reserved characters to '-'. + if strings.ContainsRune("!#$&'()*+,/:;=?@[]", r) { + return '-' + } + return r +} + +// Get returns the cache busting token for path. If the token is not already +// cached, Get issues a HEAD request on handler and uses the response ETag and +// Last-Modified headers to compute a token. +func (cb *CacheBusters) Get(path string) string { + cb.mu.Lock() + if cb.tokens == nil { + cb.tokens = make(map[string]string) + } + token, ok := cb.tokens[path] + cb.mu.Unlock() + if ok { + return token + } + + w := busterWriter{ + Writer: ioutil.Discard, + headerMap: make(http.Header), + } + r := &http.Request{URL: &url.URL{Path: path}, Method: "HEAD"} + cb.Handler.ServeHTTP(&w, r) + + if w.status == 200 { + token = w.headerMap.Get("Etag") + if token == "" { + token = w.headerMap.Get("Last-Modified") + } + token = strings.Trim(token, `" `) + token = strings.Map(sanitizeTokenRune, token) + } + + cb.mu.Lock() + cb.tokens[path] = token + cb.mu.Unlock() + + return token +} + +// AppendQueryParam appends the token as a query parameter to path. +func (cb *CacheBusters) AppendQueryParam(path string, name string) string { + token := cb.Get(path) + if token == "" { + return path + } + return path + "?" + name + "=" + token +} diff --git a/components/engine/vendor/github.com/golang/gddo/httputil/header/header.go b/components/engine/vendor/github.com/golang/gddo/httputil/header/header.go new file mode 100644 index 0000000000..0f1572e3f9 --- /dev/null +++ b/components/engine/vendor/github.com/golang/gddo/httputil/header/header.go @@ -0,0 +1,298 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +// Package header provides functions for parsing HTTP headers. +package header + +import ( + "net/http" + "strings" + "time" +) + +// Octet types from RFC 2616. +var octetTypes [256]octetType + +type octetType byte + +const ( + isToken octetType = 1 << iota + isSpace +) + +func init() { + // OCTET = + // CHAR = + // CTL = + // CR = + // LF = + // SP = + // HT = + // <"> = + // CRLF = CR LF + // LWS = [CRLF] 1*( SP | HT ) + // TEXT = + // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> + // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT + // token = 1* + // qdtext = > + + for c := 0; c < 256; c++ { + var t octetType + isCtl := c <= 31 || c == 127 + isChar := 0 <= c && c <= 127 + isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 + if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { + t |= isSpace + } + if isChar && !isCtl && !isSeparator { + t |= isToken + } + octetTypes[c] = t + } +} + +// Copy returns a shallow copy of the header. +func Copy(header http.Header) http.Header { + h := make(http.Header) + for k, vs := range header { + h[k] = vs + } + return h +} + +var timeLayouts = []string{"Mon, 02 Jan 2006 15:04:05 GMT", time.RFC850, time.ANSIC} + +// ParseTime parses the header as time. The zero value is returned if the +// header is not present or there is an error parsing the +// header. +func ParseTime(header http.Header, key string) time.Time { + if s := header.Get(key); s != "" { + for _, layout := range timeLayouts { + if t, err := time.Parse(layout, s); err == nil { + return t.UTC() + } + } + } + return time.Time{} +} + +// ParseList parses a comma separated list of values. Commas are ignored in +// quoted strings. Quoted values are not unescaped or unquoted. Whitespace is +// trimmed. +func ParseList(header http.Header, key string) []string { + var result []string + for _, s := range header[http.CanonicalHeaderKey(key)] { + begin := 0 + end := 0 + escape := false + quote := false + for i := 0; i < len(s); i++ { + b := s[i] + switch { + case escape: + escape = false + end = i + 1 + case quote: + switch b { + case '\\': + escape = true + case '"': + quote = false + } + end = i + 1 + case b == '"': + quote = true + end = i + 1 + case octetTypes[b]&isSpace != 0: + if begin == end { + begin = i + 1 + end = begin + } + case b == ',': + if begin < end { + result = append(result, s[begin:end]) + } + begin = i + 1 + end = begin + default: + end = i + 1 + } + } + if begin < end { + result = append(result, s[begin:end]) + } + } + return result +} + +// ParseValueAndParams parses a comma separated list of values with optional +// semicolon separated name-value pairs. Content-Type and Content-Disposition +// headers are in this format. +func ParseValueAndParams(header http.Header, key string) (value string, params map[string]string) { + params = make(map[string]string) + s := header.Get(key) + value, s = expectTokenSlash(s) + if value == "" { + return + } + value = strings.ToLower(value) + s = skipSpace(s) + for strings.HasPrefix(s, ";") { + var pkey string + pkey, s = expectToken(skipSpace(s[1:])) + if pkey == "" { + return + } + if !strings.HasPrefix(s, "=") { + return + } + var pvalue string + pvalue, s = expectTokenOrQuoted(s[1:]) + if pvalue == "" { + return + } + pkey = strings.ToLower(pkey) + params[pkey] = pvalue + s = skipSpace(s) + } + return +} + +// AcceptSpec describes an Accept* header. +type AcceptSpec struct { + Value string + Q float64 +} + +// ParseAccept parses Accept* headers. +func ParseAccept(header http.Header, key string) (specs []AcceptSpec) { +loop: + for _, s := range header[key] { + for { + var spec AcceptSpec + spec.Value, s = expectTokenSlash(s) + if spec.Value == "" { + continue loop + } + spec.Q = 1.0 + s = skipSpace(s) + if strings.HasPrefix(s, ";") { + s = skipSpace(s[1:]) + if !strings.HasPrefix(s, "q=") { + continue loop + } + spec.Q, s = expectQuality(s[2:]) + if spec.Q < 0.0 { + continue loop + } + } + specs = append(specs, spec) + s = skipSpace(s) + if !strings.HasPrefix(s, ",") { + continue loop + } + s = skipSpace(s[1:]) + } + } + return +} + +func skipSpace(s string) (rest string) { + i := 0 + for ; i < len(s); i++ { + if octetTypes[s[i]]&isSpace == 0 { + break + } + } + return s[i:] +} + +func expectToken(s string) (token, rest string) { + i := 0 + for ; i < len(s); i++ { + if octetTypes[s[i]]&isToken == 0 { + break + } + } + return s[:i], s[i:] +} + +func expectTokenSlash(s string) (token, rest string) { + i := 0 + for ; i < len(s); i++ { + b := s[i] + if (octetTypes[b]&isToken == 0) && b != '/' { + break + } + } + return s[:i], s[i:] +} + +func expectQuality(s string) (q float64, rest string) { + switch { + case len(s) == 0: + return -1, "" + case s[0] == '0': + q = 0 + case s[0] == '1': + q = 1 + default: + return -1, "" + } + s = s[1:] + if !strings.HasPrefix(s, ".") { + return q, s + } + s = s[1:] + i := 0 + n := 0 + d := 1 + for ; i < len(s); i++ { + b := s[i] + if b < '0' || b > '9' { + break + } + n = n*10 + int(b) - '0' + d *= 10 + } + return q + float64(n)/float64(d), s[i:] +} + +func expectTokenOrQuoted(s string) (value string, rest string) { + if !strings.HasPrefix(s, "\"") { + return expectToken(s) + } + s = s[1:] + for i := 0; i < len(s); i++ { + switch s[i] { + case '"': + return s[:i], s[i+1:] + case '\\': + p := make([]byte, len(s)-1) + j := copy(p, s[:i]) + escape := true + for i = i + 1; i < len(s); i++ { + b := s[i] + switch { + case escape: + escape = false + p[j] = b + j++ + case b == '\\': + escape = true + case b == '"': + return string(p[:j]), s[i+1:] + default: + p[j] = b + j++ + } + } + return "", "" + } + } + return "", "" +} diff --git a/components/engine/vendor/github.com/golang/gddo/httputil/httputil.go b/components/engine/vendor/github.com/golang/gddo/httputil/httputil.go new file mode 100644 index 0000000000..a03717c073 --- /dev/null +++ b/components/engine/vendor/github.com/golang/gddo/httputil/httputil.go @@ -0,0 +1,25 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +// Package httputil is a toolkit for the Go net/http package. +package httputil + +import ( + "net" + "net/http" +) + +// StripPort removes the port specification from an address. +func StripPort(s string) string { + if h, _, err := net.SplitHostPort(s); err == nil { + s = h + } + return s +} + +// Error defines a type for a function that accepts a ResponseWriter for +// a Request with the HTTP status code and error. +type Error func(w http.ResponseWriter, r *http.Request, status int, err error) diff --git a/components/engine/vendor/github.com/golang/gddo/httputil/negotiate.go b/components/engine/vendor/github.com/golang/gddo/httputil/negotiate.go new file mode 100644 index 0000000000..6af3e4ca61 --- /dev/null +++ b/components/engine/vendor/github.com/golang/gddo/httputil/negotiate.go @@ -0,0 +1,79 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +package httputil + +import ( + "github.com/golang/gddo/httputil/header" + "net/http" + "strings" +) + +// NegotiateContentEncoding returns the best offered content encoding for the +// request's Accept-Encoding header. If two offers match with equal weight and +// then the offer earlier in the list is preferred. If no offers are +// acceptable, then "" is returned. +func NegotiateContentEncoding(r *http.Request, offers []string) string { + bestOffer := "identity" + bestQ := -1.0 + specs := header.ParseAccept(r.Header, "Accept-Encoding") + for _, offer := range offers { + for _, spec := range specs { + if spec.Q > bestQ && + (spec.Value == "*" || spec.Value == offer) { + bestQ = spec.Q + bestOffer = offer + } + } + } + if bestQ == 0 { + bestOffer = "" + } + return bestOffer +} + +// NegotiateContentType returns the best offered content type for the request's +// Accept header. If two offers match with equal weight, then the more specific +// offer is preferred. For example, text/* trumps */*. If two offers match +// with equal weight and specificity, then the offer earlier in the list is +// preferred. If no offers match, then defaultOffer is returned. +func NegotiateContentType(r *http.Request, offers []string, defaultOffer string) string { + bestOffer := defaultOffer + bestQ := -1.0 + bestWild := 3 + specs := header.ParseAccept(r.Header, "Accept") + for _, offer := range offers { + for _, spec := range specs { + switch { + case spec.Q == 0.0: + // ignore + case spec.Q < bestQ: + // better match found + case spec.Value == "*/*": + if spec.Q > bestQ || bestWild > 2 { + bestQ = spec.Q + bestWild = 2 + bestOffer = offer + } + case strings.HasSuffix(spec.Value, "/*"): + if strings.HasPrefix(offer, spec.Value[:len(spec.Value)-1]) && + (spec.Q > bestQ || bestWild > 1) { + bestQ = spec.Q + bestWild = 1 + bestOffer = offer + } + default: + if spec.Value == offer && + (spec.Q > bestQ || bestWild > 0) { + bestQ = spec.Q + bestWild = 0 + bestOffer = offer + } + } + } + } + return bestOffer +} diff --git a/components/engine/vendor/github.com/golang/gddo/httputil/respbuf.go b/components/engine/vendor/github.com/golang/gddo/httputil/respbuf.go new file mode 100644 index 0000000000..051af2114b --- /dev/null +++ b/components/engine/vendor/github.com/golang/gddo/httputil/respbuf.go @@ -0,0 +1,58 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +package httputil + +import ( + "bytes" + "net/http" + "strconv" +) + +// ResponseBuffer is the current response being composed by its owner. +// It implements http.ResponseWriter and io.WriterTo. +type ResponseBuffer struct { + buf bytes.Buffer + status int + header http.Header +} + +// Write implements the http.ResponseWriter interface. +func (rb *ResponseBuffer) Write(p []byte) (int, error) { + return rb.buf.Write(p) +} + +// WriteHeader implements the http.ResponseWriter interface. +func (rb *ResponseBuffer) WriteHeader(status int) { + rb.status = status +} + +// Header implements the http.ResponseWriter interface. +func (rb *ResponseBuffer) Header() http.Header { + if rb.header == nil { + rb.header = make(http.Header) + } + return rb.header +} + +// WriteTo implements the io.WriterTo interface. +func (rb *ResponseBuffer) WriteTo(w http.ResponseWriter) error { + for k, v := range rb.header { + w.Header()[k] = v + } + if rb.buf.Len() > 0 { + w.Header().Set("Content-Length", strconv.Itoa(rb.buf.Len())) + } + if rb.status != 0 { + w.WriteHeader(rb.status) + } + if rb.buf.Len() > 0 { + if _, err := w.Write(rb.buf.Bytes()); err != nil { + return err + } + } + return nil +} diff --git a/components/engine/vendor/github.com/golang/gddo/httputil/static.go b/components/engine/vendor/github.com/golang/gddo/httputil/static.go new file mode 100644 index 0000000000..6610dde0da --- /dev/null +++ b/components/engine/vendor/github.com/golang/gddo/httputil/static.go @@ -0,0 +1,265 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +package httputil + +import ( + "bytes" + "crypto/sha1" + "errors" + "fmt" + "github.com/golang/gddo/httputil/header" + "io" + "io/ioutil" + "mime" + "net/http" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "sync" + "time" +) + +// StaticServer serves static files. +type StaticServer struct { + // Dir specifies the location of the directory containing the files to serve. + Dir string + + // MaxAge specifies the maximum age for the cache control and expiration + // headers. + MaxAge time.Duration + + // Error specifies the function used to generate error responses. If Error + // is nil, then http.Error is used to generate error responses. + Error Error + + // MIMETypes is a map from file extensions to MIME types. + MIMETypes map[string]string + + mu sync.Mutex + etags map[string]string +} + +func (ss *StaticServer) resolve(fname string) string { + if path.IsAbs(fname) { + panic("Absolute path not allowed when creating a StaticServer handler") + } + dir := ss.Dir + if dir == "" { + dir = "." + } + fname = filepath.FromSlash(fname) + return filepath.Join(dir, fname) +} + +func (ss *StaticServer) mimeType(fname string) string { + ext := path.Ext(fname) + var mimeType string + if ss.MIMETypes != nil { + mimeType = ss.MIMETypes[ext] + } + if mimeType == "" { + mimeType = mime.TypeByExtension(ext) + } + if mimeType == "" { + mimeType = "application/octet-stream" + } + return mimeType +} + +func (ss *StaticServer) openFile(fname string) (io.ReadCloser, int64, string, error) { + f, err := os.Open(fname) + if err != nil { + return nil, 0, "", err + } + fi, err := f.Stat() + if err != nil { + f.Close() + return nil, 0, "", err + } + const modeType = os.ModeDir | os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice + if fi.Mode()&modeType != 0 { + f.Close() + return nil, 0, "", errors.New("not a regular file") + } + return f, fi.Size(), ss.mimeType(fname), nil +} + +// FileHandler returns a handler that serves a single file. The file is +// specified by a slash separated path relative to the static server's Dir +// field. +func (ss *StaticServer) FileHandler(fileName string) http.Handler { + id := fileName + fileName = ss.resolve(fileName) + return &staticHandler{ + ss: ss, + id: func(_ string) string { return id }, + open: func(_ string) (io.ReadCloser, int64, string, error) { return ss.openFile(fileName) }, + } +} + +// DirectoryHandler returns a handler that serves files from a directory tree. +// The directory is specified by a slash separated path relative to the static +// server's Dir field. +func (ss *StaticServer) DirectoryHandler(prefix, dirName string) http.Handler { + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + idBase := dirName + dirName = ss.resolve(dirName) + return &staticHandler{ + ss: ss, + id: func(p string) string { + if !strings.HasPrefix(p, prefix) { + return "." + } + return path.Join(idBase, p[len(prefix):]) + }, + open: func(p string) (io.ReadCloser, int64, string, error) { + if !strings.HasPrefix(p, prefix) { + return nil, 0, "", errors.New("request url does not match directory prefix") + } + p = p[len(prefix):] + return ss.openFile(filepath.Join(dirName, filepath.FromSlash(p))) + }, + } +} + +// FilesHandler returns a handler that serves the concatentation of the +// specified files. The files are specified by slash separated paths relative +// to the static server's Dir field. +func (ss *StaticServer) FilesHandler(fileNames ...string) http.Handler { + + // todo: cache concatenated files on disk and serve from there. + + mimeType := ss.mimeType(fileNames[0]) + var buf []byte + var openErr error + + for _, fileName := range fileNames { + p, err := ioutil.ReadFile(ss.resolve(fileName)) + if err != nil { + openErr = err + buf = nil + break + } + buf = append(buf, p...) + } + + id := strings.Join(fileNames, " ") + + return &staticHandler{ + ss: ss, + id: func(_ string) string { return id }, + open: func(p string) (io.ReadCloser, int64, string, error) { + return ioutil.NopCloser(bytes.NewReader(buf)), int64(len(buf)), mimeType, openErr + }, + } +} + +type staticHandler struct { + id func(fname string) string + open func(p string) (io.ReadCloser, int64, string, error) + ss *StaticServer +} + +func (h *staticHandler) error(w http.ResponseWriter, r *http.Request, status int, err error) { + http.Error(w, http.StatusText(status), status) +} + +func (h *staticHandler) etag(p string) (string, error) { + id := h.id(p) + + h.ss.mu.Lock() + if h.ss.etags == nil { + h.ss.etags = make(map[string]string) + } + etag := h.ss.etags[id] + h.ss.mu.Unlock() + + if etag != "" { + return etag, nil + } + + // todo: if a concurrent goroutine is calculating the hash, then wait for + // it instead of computing it again here. + + rc, _, _, err := h.open(p) + if err != nil { + return "", err + } + + defer rc.Close() + + w := sha1.New() + _, err = io.Copy(w, rc) + if err != nil { + return "", err + } + + etag = fmt.Sprintf(`"%x"`, w.Sum(nil)) + + h.ss.mu.Lock() + h.ss.etags[id] = etag + h.ss.mu.Unlock() + + return etag, nil +} + +func (h *staticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + p := path.Clean(r.URL.Path) + if p != r.URL.Path { + http.Redirect(w, r, p, 301) + return + } + + etag, err := h.etag(p) + if err != nil { + h.error(w, r, http.StatusNotFound, err) + return + } + + maxAge := h.ss.MaxAge + if maxAge == 0 { + maxAge = 24 * time.Hour + } + if r.FormValue("v") != "" { + maxAge = 365 * 24 * time.Hour + } + + cacheControl := fmt.Sprintf("public, max-age=%d", maxAge/time.Second) + + for _, e := range header.ParseList(r.Header, "If-None-Match") { + if e == etag { + w.Header().Set("Cache-Control", cacheControl) + w.Header().Set("Etag", etag) + w.WriteHeader(http.StatusNotModified) + return + } + } + + rc, cl, ct, err := h.open(p) + if err != nil { + h.error(w, r, http.StatusNotFound, err) + return + } + defer rc.Close() + + w.Header().Set("Cache-Control", cacheControl) + w.Header().Set("Etag", etag) + if ct != "" { + w.Header().Set("Content-Type", ct) + } + if cl != 0 { + w.Header().Set("Content-Length", strconv.FormatInt(cl, 10)) + } + w.WriteHeader(http.StatusOK) + if r.Method != "HEAD" { + io.Copy(w, rc) + } +} diff --git a/components/engine/vendor/github.com/golang/gddo/httputil/transport.go b/components/engine/vendor/github.com/golang/gddo/httputil/transport.go new file mode 100644 index 0000000000..fdad3b4780 --- /dev/null +++ b/components/engine/vendor/github.com/golang/gddo/httputil/transport.go @@ -0,0 +1,87 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +// This file implements a http.RoundTripper that authenticates +// requests issued against api.github.com endpoint. + +package httputil + +import ( + "net/http" + "net/url" +) + +// AuthTransport is an implementation of http.RoundTripper that authenticates +// with the GitHub API. +// +// When both a token and client credentials are set, the latter is preferred. +type AuthTransport struct { + UserAgent string + GithubToken string + GithubClientID string + GithubClientSecret string + Base http.RoundTripper +} + +// RoundTrip implements the http.RoundTripper interface. +func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + var reqCopy *http.Request + if t.UserAgent != "" { + reqCopy = copyRequest(req) + reqCopy.Header.Set("User-Agent", t.UserAgent) + } + if req.URL.Host == "api.github.com" && req.URL.Scheme == "https" { + switch { + case t.GithubClientID != "" && t.GithubClientSecret != "": + if reqCopy == nil { + reqCopy = copyRequest(req) + } + if reqCopy.URL.RawQuery == "" { + reqCopy.URL.RawQuery = "client_id=" + t.GithubClientID + "&client_secret=" + t.GithubClientSecret + } else { + reqCopy.URL.RawQuery += "&client_id=" + t.GithubClientID + "&client_secret=" + t.GithubClientSecret + } + case t.GithubToken != "": + if reqCopy == nil { + reqCopy = copyRequest(req) + } + reqCopy.Header.Set("Authorization", "token "+t.GithubToken) + } + } + if reqCopy != nil { + return t.base().RoundTrip(reqCopy) + } + return t.base().RoundTrip(req) +} + +// CancelRequest cancels an in-flight request by closing its connection. +func (t *AuthTransport) CancelRequest(req *http.Request) { + type canceler interface { + CancelRequest(req *http.Request) + } + if cr, ok := t.base().(canceler); ok { + cr.CancelRequest(req) + } +} + +func (t *AuthTransport) base() http.RoundTripper { + if t.Base != nil { + return t.Base + } + return http.DefaultTransport +} + +func copyRequest(req *http.Request) *http.Request { + req2 := new(http.Request) + *req2 = *req + req2.URL = new(url.URL) + *req2.URL = *req.URL + req2.Header = make(http.Header, len(req.Header)) + for k, s := range req.Header { + req2.Header[k] = append([]string(nil), s...) + } + return req2 +}