From 45ddc4bfcb7d45bf34d8f88390b4d98272caac0a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 6 Sep 2016 11:18:12 -0700 Subject: [PATCH 1/2] Add engine-api types to docker This moves the types for the `engine-api` repo to the existing types package. Signed-off-by: Michael Crosby Upstream-commit: 91e197d614547f0202e6ae9b8a24d88ee131d950 Component: engine --- .../engine/api/client/container/attach.go | 2 +- .../engine/api/client/container/commit.go | 2 +- components/engine/api/client/container/cp.go | 2 +- .../engine/api/client/container/create.go | 6 +- .../engine/api/client/container/exec.go | 2 +- .../engine/api/client/container/exec_test.go | 2 +- .../engine/api/client/container/logs.go | 2 +- components/engine/api/client/container/ps.go | 4 +- components/engine/api/client/container/rm.go | 2 +- components/engine/api/client/container/run.go | 2 +- .../engine/api/client/container/start.go | 2 +- .../engine/api/client/container/stats.go | 6 +- .../api/client/container/stats_helpers.go | 2 +- .../api/client/container/stats_unit_test.go | 2 +- .../engine/api/client/container/update.go | 2 +- .../engine/api/client/container/utils.go | 6 +- components/engine/api/client/credentials.go | 2 +- .../engine/api/client/formatter/container.go | 2 +- .../api/client/formatter/container_test.go | 2 +- .../engine/api/client/formatter/image.go | 2 +- .../engine/api/client/formatter/image_test.go | 2 +- .../engine/api/client/formatter/network.go | 2 +- .../api/client/formatter/network_test.go | 2 +- .../engine/api/client/formatter/volume.go | 2 +- .../api/client/formatter/volume_test.go | 2 +- components/engine/api/client/hijack.go | 2 +- .../api/client/idresolver/idresolver.go | 2 +- components/engine/api/client/image/build.go | 4 +- components/engine/api/client/image/images.go | 4 +- components/engine/api/client/image/import.go | 2 +- components/engine/api/client/image/remove.go | 2 +- components/engine/api/client/image/search.go | 6 +- .../engine/api/client/network/connect.go | 2 +- .../engine/api/client/network/create.go | 4 +- components/engine/api/client/network/list.go | 4 +- components/engine/api/client/node/demote.go | 2 +- components/engine/api/client/node/inspect.go | 2 +- components/engine/api/client/node/list.go | 4 +- components/engine/api/client/node/opts.go | 2 +- components/engine/api/client/node/promote.go | 2 +- components/engine/api/client/node/ps.go | 2 +- components/engine/api/client/node/remove.go | 2 +- components/engine/api/client/node/update.go | 2 +- .../engine/api/client/plugin/install.go | 2 +- components/engine/api/client/plugin/remove.go | 2 +- components/engine/api/client/registry.go | 4 +- .../engine/api/client/service/create.go | 2 +- .../engine/api/client/service/inspect.go | 2 +- .../engine/api/client/service/inspect_test.go | 2 +- components/engine/api/client/service/list.go | 6 +- components/engine/api/client/service/opts.go | 4 +- .../engine/api/client/service/opts_test.go | 2 +- components/engine/api/client/service/ps.go | 2 +- components/engine/api/client/service/scale.go | 2 +- .../engine/api/client/service/update.go | 6 +- .../engine/api/client/service/update_test.go | 4 +- components/engine/api/client/stack/common.go | 6 +- components/engine/api/client/stack/deploy.go | 4 +- components/engine/api/client/stack/ps.go | 4 +- .../engine/api/client/stack/services.go | 4 +- components/engine/api/client/swarm/init.go | 2 +- components/engine/api/client/swarm/join.go | 2 +- .../engine/api/client/swarm/join_token.go | 2 +- components/engine/api/client/swarm/opts.go | 2 +- components/engine/api/client/swarm/update.go | 2 +- components/engine/api/client/system/events.go | 6 +- .../engine/api/client/system/events_utils.go | 2 +- components/engine/api/client/system/info.go | 4 +- .../engine/api/client/system/version.go | 2 +- components/engine/api/client/task/print.go | 2 +- components/engine/api/client/trust.go | 4 +- components/engine/api/client/trust_test.go | 2 +- components/engine/api/client/utils.go | 2 +- components/engine/api/client/volume/create.go | 2 +- components/engine/api/client/volume/list.go | 4 +- components/engine/api/common.go | 2 +- components/engine/api/common_test.go | 2 +- .../engine/api/server/httputils/decoder.go | 4 +- .../engine/api/server/httputils/errors.go | 4 +- .../api/server/middleware/user_agent.go | 2 +- .../engine/api/server/middleware/version.go | 2 +- components/engine/api/types/auth.go | 22 + .../engine/api/types/backend/backend.go | 2 +- components/engine/api/types/blkiodev/blkio.go | 23 + components/engine/api/types/client.go | 300 ++++++++++ components/engine/api/types/configs.go | 61 ++ .../engine/api/types/container/config.go | 62 +++ .../engine/api/types/container/host_config.go | 324 +++++++++++ .../api/types/container/hostconfig_unix.go | 81 +++ .../api/types/container/hostconfig_windows.go | 87 +++ components/engine/api/types/errors.go | 6 + components/engine/api/types/events/events.go | 42 ++ components/engine/api/types/filters/parse.go | 307 +++++++++++ .../engine/api/types/filters/parse_test.go | 417 ++++++++++++++ components/engine/api/types/mount/mount.go | 58 ++ .../engine/api/types/network/network.go | 53 ++ components/engine/api/types/plugin.go | 170 ++++++ .../api/types/reference/image_reference.go | 34 ++ .../types/reference/image_reference_test.go | 72 +++ .../engine/api/types/registry/registry.go | 104 ++++ components/engine/api/types/seccomp.go | 93 ++++ components/engine/api/types/stats.go | 115 ++++ .../engine/api/types/strslice/strslice.go | 30 + .../api/types/strslice/strslice_test.go | 86 +++ components/engine/api/types/swarm/common.go | 21 + .../engine/api/types/swarm/container.go | 22 + components/engine/api/types/swarm/network.go | 100 ++++ components/engine/api/types/swarm/node.go | 107 ++++ components/engine/api/types/swarm/service.go | 77 +++ components/engine/api/types/swarm/swarm.go | 155 ++++++ components/engine/api/types/swarm/task.go | 117 ++++ .../engine/api/types/time/duration_convert.go | 12 + .../api/types/time/duration_convert_test.go | 26 + components/engine/api/types/time/timestamp.go | 124 +++++ .../engine/api/types/time/timestamp_test.go | 93 ++++ components/engine/api/types/types.go | 519 ++++++++++++++++++ .../engine/api/types/versions/README.md | 14 + .../engine/api/types/versions/compare.go | 62 +++ .../engine/api/types/versions/compare_test.go | 26 + .../engine/api/types/versions/v1p19/types.go | 35 ++ .../engine/api/types/versions/v1p20/types.go | 40 ++ components/engine/builder/builder.go | 4 +- .../engine/builder/dockerfile/builder.go | 4 +- .../engine/builder/dockerfile/dispatchers.go | 4 +- .../builder/dockerfile/dispatchers_test.go | 6 +- .../builder/dockerfile/evaluator_test.go | 4 +- .../engine/builder/dockerfile/internals.go | 6 +- .../builder/dockerfile/internals_test.go | 2 +- components/engine/cliconfig/config.go | 2 +- .../engine/cliconfig/configfile/file.go | 2 +- .../engine/cliconfig/configfile/file_test.go | 2 +- .../cliconfig/credentials/credentials.go | 2 +- .../cliconfig/credentials/file_store.go | 4 +- .../cliconfig/credentials/file_store_test.go | 2 +- .../cliconfig/credentials/native_store.go | 2 +- .../credentials/native_store_test.go | 2 +- components/engine/container/archive.go | 2 +- components/engine/container/container.go | 4 +- .../engine/container/container_solaris.go | 2 +- .../engine/container/container_unit_test.go | 2 +- components/engine/container/container_unix.go | 2 +- .../engine/container/container_windows.go | 2 +- components/engine/container/health.go | 2 +- components/engine/daemon/archive.go | 2 +- components/engine/daemon/auth.go | 2 +- components/engine/daemon/cluster/cluster.go | 6 +- .../daemon/cluster/convert/container.go | 4 +- .../engine/daemon/cluster/convert/network.go | 6 +- .../engine/daemon/cluster/convert/node.go | 2 +- .../engine/daemon/cluster/convert/service.go | 2 +- .../engine/daemon/cluster/convert/swarm.go | 2 +- .../engine/daemon/cluster/convert/task.go | 2 +- .../engine/daemon/cluster/executor/backend.go | 10 +- components/engine/daemon/cluster/filters.go | 2 +- .../engine/daemon/cluster/provider/network.go | 2 +- components/engine/daemon/commit.go | 2 +- components/engine/daemon/config_unix.go | 2 +- components/engine/daemon/config_windows.go | 2 +- components/engine/daemon/container.go | 4 +- .../engine/daemon/container_operations.go | 4 +- .../daemon/container_operations_solaris.go | 2 +- .../daemon/container_operations_unix.go | 4 +- .../daemon/container_operations_windows.go | 2 +- components/engine/daemon/create.go | 6 +- components/engine/daemon/create_unix.go | 2 +- components/engine/daemon/create_windows.go | 2 +- components/engine/daemon/daemon.go | 4 +- .../engine/daemon/daemon_experimental.go | 2 +- components/engine/daemon/daemon_solaris.go | 4 +- components/engine/daemon/daemon_stub.go | 2 +- components/engine/daemon/daemon_test.go | 2 +- components/engine/daemon/daemon_unix.go | 8 +- components/engine/daemon/daemon_unix_test.go | 2 +- components/engine/daemon/daemon_windows.go | 6 +- components/engine/daemon/delete.go | 2 +- components/engine/daemon/delete_test.go | 4 +- components/engine/daemon/events.go | 4 +- components/engine/daemon/events/events.go | 2 +- .../engine/daemon/events/events_test.go | 4 +- components/engine/daemon/events/filter.go | 4 +- .../daemon/events/testutils/testutils.go | 4 +- components/engine/daemon/events_test.go | 4 +- components/engine/daemon/exec.go | 4 +- components/engine/daemon/health.go | 4 +- components/engine/daemon/health_test.go | 6 +- components/engine/daemon/image.go | 2 +- components/engine/daemon/image_delete.go | 2 +- components/engine/daemon/image_history.go | 2 +- components/engine/daemon/image_inspect.go | 2 +- components/engine/daemon/image_pull.go | 2 +- components/engine/daemon/image_push.go | 2 +- components/engine/daemon/images.go | 4 +- components/engine/daemon/import.go | 2 +- components/engine/daemon/info.go | 2 +- components/engine/daemon/inspect.go | 8 +- components/engine/daemon/inspect_solaris.go | 2 +- components/engine/daemon/inspect_unix.go | 4 +- components/engine/daemon/inspect_windows.go | 2 +- components/engine/daemon/links_test.go | 2 +- components/engine/daemon/list.go | 6 +- components/engine/daemon/logs.go | 4 +- components/engine/daemon/logs_test.go | 2 +- components/engine/daemon/monitor.go | 2 +- components/engine/daemon/network.go | 4 +- components/engine/daemon/network/settings.go | 2 +- components/engine/daemon/oci_linux.go | 2 +- components/engine/daemon/search.go | 6 +- components/engine/daemon/search_test.go | 4 +- components/engine/daemon/start.go | 4 +- components/engine/daemon/stats.go | 6 +- .../engine/daemon/stats_collector_unix.go | 2 +- components/engine/daemon/top_unix.go | 2 +- components/engine/daemon/top_windows.go | 2 +- components/engine/daemon/update.go | 4 +- components/engine/daemon/update_linux.go | 2 +- components/engine/daemon/update_solaris.go | 2 +- components/engine/daemon/update_windows.go | 2 +- components/engine/daemon/volumes.go | 4 +- components/engine/distribution/pull.go | 2 +- components/engine/distribution/push.go | 2 +- components/engine/distribution/registry.go | 2 +- .../engine/distribution/registry_unit_test.go | 4 +- components/engine/image/image.go | 2 +- components/engine/image/v1/imagev1.go | 2 +- .../engine/integration-cli/check_test.go | 2 +- components/engine/integration-cli/daemon.go | 2 +- .../engine/integration-cli/daemon_swarm.go | 6 +- .../integration-cli/docker_api_auth_test.go | 2 +- .../docker_api_containers_test.go | 6 +- .../integration-cli/docker_api_images_test.go | 2 +- .../docker_api_inspect_test.go | 4 +- .../docker_api_network_test.go | 6 +- .../docker_api_service_update_test.go | 2 +- .../integration-cli/docker_api_stats_test.go | 4 +- .../docker_api_stats_unix_test.go | 2 +- .../integration-cli/docker_api_swarm_test.go | 2 +- .../docker_api_version_test.go | 2 +- .../docker_api_volumes_test.go | 2 +- .../docker_cli_by_digest_test.go | 2 +- ...er_cli_external_volume_driver_unix_test.go | 2 +- .../integration-cli/docker_cli_health_test.go | 2 +- .../docker_cli_inspect_test.go | 4 +- .../docker_cli_network_unix_test.go | 4 +- .../docker_cli_service_create_test.go | 4 +- .../docker_cli_service_health_test.go | 2 +- .../docker_cli_service_update_test.go | 2 +- .../integration-cli/docker_cli_swarm_test.go | 2 +- .../docker_cli_update_unix_test.go | 2 +- .../engine/integration-cli/docker_utils.go | 2 +- components/engine/opts/opts.go | 2 +- components/engine/plugin/backend.go | 2 +- components/engine/plugin/distribution/pull.go | 2 +- components/engine/plugin/distribution/push.go | 2 +- components/engine/plugin/manager_linux.go | 2 +- components/engine/plugin/v2/plugin.go | 2 +- components/engine/profiles/seccomp/seccomp.go | 2 +- .../profiles/seccomp/seccomp_default.go | 2 +- .../profiles/seccomp/seccomp_unsupported.go | 2 +- components/engine/registry/auth.go | 4 +- components/engine/registry/auth_test.go | 4 +- components/engine/registry/config.go | 2 +- components/engine/registry/endpoint_v1.go | 2 +- .../engine/registry/registry_mock_test.go | 2 +- components/engine/registry/registry_test.go | 4 +- components/engine/registry/service.go | 4 +- components/engine/registry/session.go | 4 +- components/engine/registry/types.go | 2 +- .../engine/restartmanager/restartmanager.go | 2 +- .../restartmanager/restartmanager_test.go | 2 +- components/engine/runconfig/compare.go | 2 +- components/engine/runconfig/compare_test.go | 4 +- components/engine/runconfig/config.go | 4 +- components/engine/runconfig/config_test.go | 6 +- components/engine/runconfig/config_unix.go | 4 +- components/engine/runconfig/config_windows.go | 4 +- components/engine/runconfig/hostconfig.go | 2 +- .../engine/runconfig/hostconfig_solaris.go | 2 +- .../engine/runconfig/hostconfig_test.go | 2 +- .../engine/runconfig/hostconfig_unix.go | 2 +- .../engine/runconfig/hostconfig_windows.go | 2 +- components/engine/runconfig/opts/parse.go | 6 +- .../engine/runconfig/opts/parse_test.go | 4 +- components/engine/runconfig/opts/runtime.go | 2 +- .../engine/runconfig/opts/throttledevice.go | 2 +- .../engine/runconfig/opts/weightdevice.go | 2 +- components/engine/volume/volume.go | 2 +- .../engine/volume/volume_propagation_linux.go | 2 +- .../volume/volume_propagation_unsupported.go | 2 +- components/engine/volume/volume_unix.go | 2 +- 289 files changed, 4465 insertions(+), 366 deletions(-) create mode 100644 components/engine/api/types/auth.go create mode 100644 components/engine/api/types/blkiodev/blkio.go create mode 100644 components/engine/api/types/client.go create mode 100644 components/engine/api/types/configs.go create mode 100644 components/engine/api/types/container/config.go create mode 100644 components/engine/api/types/container/host_config.go create mode 100644 components/engine/api/types/container/hostconfig_unix.go create mode 100644 components/engine/api/types/container/hostconfig_windows.go create mode 100644 components/engine/api/types/errors.go create mode 100644 components/engine/api/types/events/events.go create mode 100644 components/engine/api/types/filters/parse.go create mode 100644 components/engine/api/types/filters/parse_test.go create mode 100644 components/engine/api/types/mount/mount.go create mode 100644 components/engine/api/types/network/network.go create mode 100644 components/engine/api/types/plugin.go create mode 100644 components/engine/api/types/reference/image_reference.go create mode 100644 components/engine/api/types/reference/image_reference_test.go create mode 100644 components/engine/api/types/registry/registry.go create mode 100644 components/engine/api/types/seccomp.go create mode 100644 components/engine/api/types/stats.go create mode 100644 components/engine/api/types/strslice/strslice.go create mode 100644 components/engine/api/types/strslice/strslice_test.go create mode 100644 components/engine/api/types/swarm/common.go create mode 100644 components/engine/api/types/swarm/container.go create mode 100644 components/engine/api/types/swarm/network.go create mode 100644 components/engine/api/types/swarm/node.go create mode 100644 components/engine/api/types/swarm/service.go create mode 100644 components/engine/api/types/swarm/swarm.go create mode 100644 components/engine/api/types/swarm/task.go create mode 100644 components/engine/api/types/time/duration_convert.go create mode 100644 components/engine/api/types/time/duration_convert_test.go create mode 100644 components/engine/api/types/time/timestamp.go create mode 100644 components/engine/api/types/time/timestamp_test.go create mode 100644 components/engine/api/types/types.go create mode 100644 components/engine/api/types/versions/README.md create mode 100644 components/engine/api/types/versions/compare.go create mode 100644 components/engine/api/types/versions/compare_test.go create mode 100644 components/engine/api/types/versions/v1p19/types.go create mode 100644 components/engine/api/types/versions/v1p20/types.go diff --git a/components/engine/api/client/container/attach.go b/components/engine/api/client/container/attach.go index a08065cc5c..fb3a715506 100644 --- a/components/engine/api/client/container/attach.go +++ b/components/engine/api/client/container/attach.go @@ -9,9 +9,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/signal" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/container/commit.go b/components/engine/api/client/container/commit.go index c6e5c1b582..1fad18cac9 100644 --- a/components/engine/api/client/container/commit.go +++ b/components/engine/api/client/container/commit.go @@ -6,9 +6,9 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" dockeropts "github.com/docker/docker/opts" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/container/cp.go b/components/engine/api/client/container/cp.go index a0031c8b00..653b828f16 100644 --- a/components/engine/api/client/container/cp.go +++ b/components/engine/api/client/container/cp.go @@ -10,10 +10,10 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/system" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/container/create.go b/components/engine/api/client/container/create.go index ef9b8cd7f6..cbfe36b5e2 100644 --- a/components/engine/api/client/container/create.go +++ b/components/engine/api/client/container/create.go @@ -11,13 +11,13 @@ import ( "github.com/docker/docker/cli" "github.com/docker/docker/pkg/jsonmessage" // FIXME migrate to docker/distribution/reference + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/reference" "github.com/docker/docker/registry" runconfigopts "github.com/docker/docker/runconfig/opts" apiclient "github.com/docker/engine-api/client" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" "github.com/spf13/cobra" "github.com/spf13/pflag" ) diff --git a/components/engine/api/client/container/exec.go b/components/engine/api/client/container/exec.go index d74ae4ac38..f20b9f366e 100644 --- a/components/engine/api/client/container/exec.go +++ b/components/engine/api/client/container/exec.go @@ -8,9 +8,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/promise" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/container/exec_test.go b/components/engine/api/client/container/exec_test.go index 00d4e503c8..2e122e7386 100644 --- a/components/engine/api/client/container/exec_test.go +++ b/components/engine/api/client/container/exec_test.go @@ -3,7 +3,7 @@ package container import ( "testing" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) type arguments struct { diff --git a/components/engine/api/client/container/logs.go b/components/engine/api/client/container/logs.go index 593ec60d29..a7b6241c4b 100644 --- a/components/engine/api/client/container/logs.go +++ b/components/engine/api/client/container/logs.go @@ -7,9 +7,9 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/container/ps.go b/components/engine/api/client/container/ps.go index a4bd5310bb..700ba365b6 100644 --- a/components/engine/api/client/container/ps.go +++ b/components/engine/api/client/container/ps.go @@ -5,9 +5,9 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/formatter" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" "io/ioutil" diff --git a/components/engine/api/client/container/rm.go b/components/engine/api/client/container/rm.go index 2ccc6c21ff..e45994f9ef 100644 --- a/components/engine/api/client/container/rm.go +++ b/components/engine/api/client/container/rm.go @@ -7,8 +7,8 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/container/run.go b/components/engine/api/client/container/run.go index 652739f5df..154683819d 100644 --- a/components/engine/api/client/container/run.go +++ b/components/engine/api/client/container/run.go @@ -13,12 +13,12 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" opttypes "github.com/docker/docker/opts" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types" "github.com/docker/libnetwork/resolvconf/dns" "github.com/spf13/cobra" "github.com/spf13/pflag" diff --git a/components/engine/api/client/container/start.go b/components/engine/api/client/container/start.go index 8ff2ab27c5..db0e948128 100644 --- a/components/engine/api/client/container/start.go +++ b/components/engine/api/client/container/start.go @@ -9,10 +9,10 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/container/stats.go b/components/engine/api/client/container/stats.go index 404ddfb349..093b81fcad 100644 --- a/components/engine/api/client/container/stats.go +++ b/components/engine/api/client/container/stats.go @@ -13,10 +13,10 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/system" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/events" - "github.com/docker/engine-api/types/filters" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/container/stats_helpers.go b/components/engine/api/client/container/stats_helpers.go index 336ad14760..8c2fa56e37 100644 --- a/components/engine/api/client/container/stats_helpers.go +++ b/components/engine/api/client/container/stats_helpers.go @@ -10,8 +10,8 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/engine-api/client" - "github.com/docker/engine-api/types" "github.com/docker/go-units" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/container/stats_unit_test.go b/components/engine/api/client/container/stats_unit_test.go index 83f24bb295..6f6a468068 100644 --- a/components/engine/api/client/container/stats_unit_test.go +++ b/components/engine/api/client/container/stats_unit_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) func TestDisplay(t *testing.T) { diff --git a/components/engine/api/client/container/update.go b/components/engine/api/client/container/update.go index 64c1210147..ee3426db2c 100644 --- a/components/engine/api/client/container/update.go +++ b/components/engine/api/client/container/update.go @@ -7,9 +7,9 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/cli" runconfigopts "github.com/docker/docker/runconfig/opts" - containertypes "github.com/docker/engine-api/types/container" "github.com/docker/go-units" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/container/utils.go b/components/engine/api/client/container/utils.go index 5a589ff680..4b061f7338 100644 --- a/components/engine/api/client/container/utils.go +++ b/components/engine/api/client/container/utils.go @@ -9,10 +9,10 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/system" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" clientapi "github.com/docker/engine-api/client" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/events" - "github.com/docker/engine-api/types/filters" ) func waitExitOrRemoved(dockerCli *client.DockerCli, ctx context.Context, containerID string, waitRemove bool) (chan int, error) { diff --git a/components/engine/api/client/credentials.go b/components/engine/api/client/credentials.go index 9f5e80c7a9..b9aa3ebda4 100644 --- a/components/engine/api/client/credentials.go +++ b/components/engine/api/client/credentials.go @@ -1,9 +1,9 @@ package client import ( + "github.com/docker/docker/api/types" "github.com/docker/docker/cliconfig/configfile" "github.com/docker/docker/cliconfig/credentials" - "github.com/docker/engine-api/types" ) // GetCredentials loads the user credentials from a credentials store. diff --git a/components/engine/api/client/formatter/container.go b/components/engine/api/client/formatter/container.go index 5b96f607b3..f1c985791b 100644 --- a/components/engine/api/client/formatter/container.go +++ b/components/engine/api/client/formatter/container.go @@ -8,9 +8,9 @@ import ( "time" "github.com/docker/docker/api" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringutils" - "github.com/docker/engine-api/types" "github.com/docker/go-units" ) diff --git a/components/engine/api/client/formatter/container_test.go b/components/engine/api/client/formatter/container_test.go index 4776123be4..cc7b916242 100644 --- a/components/engine/api/client/formatter/container_test.go +++ b/components/engine/api/client/formatter/container_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" - "github.com/docker/engine-api/types" ) func TestContainerPsContext(t *testing.T) { diff --git a/components/engine/api/client/formatter/image.go b/components/engine/api/client/formatter/image.go index 04cd4b75fd..0ffcfaf728 100644 --- a/components/engine/api/client/formatter/image.go +++ b/components/engine/api/client/formatter/image.go @@ -5,9 +5,9 @@ import ( "strings" "time" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" "github.com/docker/go-units" ) diff --git a/components/engine/api/client/formatter/image_test.go b/components/engine/api/client/formatter/image_test.go index 64fda18bf5..7c87f393fc 100644 --- a/components/engine/api/client/formatter/image_test.go +++ b/components/engine/api/client/formatter/image_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" - "github.com/docker/engine-api/types" ) func TestImageContext(t *testing.T) { diff --git a/components/engine/api/client/formatter/network.go b/components/engine/api/client/formatter/network.go index 9bd3d3b180..6eb820879e 100644 --- a/components/engine/api/client/formatter/network.go +++ b/components/engine/api/client/formatter/network.go @@ -5,8 +5,8 @@ import ( "fmt" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" - "github.com/docker/engine-api/types" ) const ( diff --git a/components/engine/api/client/formatter/network_test.go b/components/engine/api/client/formatter/network_test.go index b760bfab1d..b5f826af6d 100644 --- a/components/engine/api/client/formatter/network_test.go +++ b/components/engine/api/client/formatter/network_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" - "github.com/docker/engine-api/types" ) func TestNetworkContext(t *testing.T) { diff --git a/components/engine/api/client/formatter/volume.go b/components/engine/api/client/formatter/volume.go index 00ebada364..ba24b06a4f 100644 --- a/components/engine/api/client/formatter/volume.go +++ b/components/engine/api/client/formatter/volume.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) const ( diff --git a/components/engine/api/client/formatter/volume_test.go b/components/engine/api/client/formatter/volume_test.go index 5562446a22..2295eff3ef 100644 --- a/components/engine/api/client/formatter/volume_test.go +++ b/components/engine/api/client/formatter/volume_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" - "github.com/docker/engine-api/types" ) func TestVolumeContext(t *testing.T) { diff --git a/components/engine/api/client/hijack.go b/components/engine/api/client/hijack.go index 294078e44c..c7f7c3a0c0 100644 --- a/components/engine/api/client/hijack.go +++ b/components/engine/api/client/hijack.go @@ -7,8 +7,8 @@ import ( "golang.org/x/net/context" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/engine-api/types" ) // HoldHijackedConnection handles copying input to and output from streams to the diff --git a/components/engine/api/client/idresolver/idresolver.go b/components/engine/api/client/idresolver/idresolver.go index 9b38d151bb..6d39252002 100644 --- a/components/engine/api/client/idresolver/idresolver.go +++ b/components/engine/api/client/idresolver/idresolver.go @@ -5,8 +5,8 @@ import ( "golang.org/x/net/context" + "github.com/docker/docker/api/types/swarm" "github.com/docker/engine-api/client" - "github.com/docker/engine-api/types/swarm" ) // IDResolver provides ID to Name resolution. diff --git a/components/engine/api/client/image/build.go b/components/engine/api/client/image/build.go index b1a227c099..df89e7df1c 100644 --- a/components/engine/api/client/image/build.go +++ b/components/engine/api/client/image/build.go @@ -15,6 +15,8 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder" "github.com/docker/docker/builder/dockerignore" "github.com/docker/docker/cli" @@ -27,8 +29,6 @@ import ( "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/reference" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" "github.com/docker/go-units" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/image/images.go b/components/engine/api/client/image/images.go index a357123386..6b864a8acc 100644 --- a/components/engine/api/client/image/images.go +++ b/components/engine/api/client/image/images.go @@ -5,9 +5,9 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/formatter" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/image/import.go b/components/engine/api/client/image/import.go index 07ff49a9d2..2ca775a4ff 100644 --- a/components/engine/api/client/image/import.go +++ b/components/engine/api/client/image/import.go @@ -7,11 +7,11 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" dockeropts "github.com/docker/docker/opts" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/urlutil" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/image/remove.go b/components/engine/api/client/image/remove.go index bb2ba8a9be..04826a2d64 100644 --- a/components/engine/api/client/image/remove.go +++ b/components/engine/api/client/image/remove.go @@ -7,8 +7,8 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/image/search.go b/components/engine/api/client/image/search.go index 22825b50af..b4b800ef0f 100644 --- a/components/engine/api/client/image/search.go +++ b/components/engine/api/client/image/search.go @@ -9,12 +9,12 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" - registrytypes "github.com/docker/engine-api/types/registry" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/network/connect.go b/components/engine/api/client/network/connect.go index 57a7299c14..66f761e80d 100644 --- a/components/engine/api/client/network/connect.go +++ b/components/engine/api/client/network/connect.go @@ -4,10 +4,10 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/cli" "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types/network" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/network/create.go b/components/engine/api/client/network/create.go index 29def8e66c..9bec170a7c 100644 --- a/components/engine/api/client/network/create.go +++ b/components/engine/api/client/network/create.go @@ -8,11 +8,11 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/cli" "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/network" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/network/list.go b/components/engine/api/client/network/list.go index c332a801d8..54ed879e45 100644 --- a/components/engine/api/client/network/list.go +++ b/components/engine/api/client/network/list.go @@ -7,9 +7,9 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/formatter" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/node/demote.go b/components/engine/api/client/node/demote.go index 6acf5c3eb2..900648cfef 100644 --- a/components/engine/api/client/node/demote.go +++ b/components/engine/api/client/node/demote.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/node/inspect.go b/components/engine/api/client/node/inspect.go index 6c2c82410b..2eed901ce2 100644 --- a/components/engine/api/client/node/inspect.go +++ b/components/engine/api/client/node/inspect.go @@ -8,9 +8,9 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/inspect" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/ioutils" - "github.com/docker/engine-api/types/swarm" "github.com/docker/go-units" "github.com/spf13/cobra" "golang.org/x/net/context" diff --git a/components/engine/api/client/node/list.go b/components/engine/api/client/node/list.go index 55929adabe..02c1727606 100644 --- a/components/engine/api/client/node/list.go +++ b/components/engine/api/client/node/list.go @@ -8,10 +8,10 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/opts" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/node/opts.go b/components/engine/api/client/node/opts.go index f387bafc47..7e6c55d487 100644 --- a/components/engine/api/client/node/opts.go +++ b/components/engine/api/client/node/opts.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types/swarm" ) type nodeOptions struct { diff --git a/components/engine/api/client/node/promote.go b/components/engine/api/client/node/promote.go index 9bfc4a277f..19c35e4c64 100644 --- a/components/engine/api/client/node/promote.go +++ b/components/engine/api/client/node/promote.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/node/ps.go b/components/engine/api/client/node/ps.go index 1913cbcf6d..4565ce70d7 100644 --- a/components/engine/api/client/node/ps.go +++ b/components/engine/api/client/node/ps.go @@ -4,9 +4,9 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/idresolver" "github.com/docker/docker/api/client/task" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/opts" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/node/remove.go b/components/engine/api/client/node/remove.go index bb54a831e8..18af6a6154 100644 --- a/components/engine/api/client/node/remove.go +++ b/components/engine/api/client/node/remove.go @@ -6,8 +6,8 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/node/update.go b/components/engine/api/client/node/update.go index 9437853ca4..9032250089 100644 --- a/components/engine/api/client/node/update.go +++ b/components/engine/api/client/node/update.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" "github.com/spf13/pflag" "golang.org/x/net/context" diff --git a/components/engine/api/client/plugin/install.go b/components/engine/api/client/plugin/install.go index bfa3f1f50d..05dc8e8268 100644 --- a/components/engine/api/client/plugin/install.go +++ b/components/engine/api/client/plugin/install.go @@ -8,10 +8,10 @@ import ( "strings" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/reference" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/plugin/remove.go b/components/engine/api/client/plugin/remove.go index 4a5d4785ab..3b61374009 100644 --- a/components/engine/api/client/plugin/remove.go +++ b/components/engine/api/client/plugin/remove.go @@ -6,9 +6,9 @@ import ( "fmt" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/registry.go b/components/engine/api/client/registry.go index 104d68278d..2fcdf77c53 100644 --- a/components/engine/api/client/registry.go +++ b/components/engine/api/client/registry.go @@ -12,11 +12,11 @@ import ( "golang.org/x/net/context" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/pkg/term" "github.com/docker/docker/reference" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" ) // ElectAuthServer returns the default registry to use (by asking the daemon) diff --git a/components/engine/api/client/service/create.go b/components/engine/api/client/service/create.go index e55b2a4723..740d6dfdd0 100644 --- a/components/engine/api/client/service/create.go +++ b/components/engine/api/client/service/create.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/service/inspect.go b/components/engine/api/client/service/inspect.go index 0abdf5adb3..5511417337 100644 --- a/components/engine/api/client/service/inspect.go +++ b/components/engine/api/client/service/inspect.go @@ -10,10 +10,10 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/inspect" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/ioutils" apiclient "github.com/docker/engine-api/client" - "github.com/docker/engine-api/types/swarm" "github.com/docker/go-units" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/service/inspect_test.go b/components/engine/api/client/service/inspect_test.go index 7d7f03ffc9..0e0f2ae74f 100644 --- a/components/engine/api/client/service/inspect_test.go +++ b/components/engine/api/client/service/inspect_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/docker/engine-api/types/swarm" + "github.com/docker/docker/api/types/swarm" ) func TestPrettyPrintWithNoUpdateConfig(t *testing.T) { diff --git a/components/engine/api/client/service/list.go b/components/engine/api/client/service/list.go index 95ab067055..625dfc1df0 100644 --- a/components/engine/api/client/service/list.go +++ b/components/engine/api/client/service/list.go @@ -7,12 +7,12 @@ import ( "text/tabwriter" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/stringid" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" - "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/service/opts.go b/components/engine/api/client/service/opts.go index 9ea334fe97..7151bca4b0 100644 --- a/components/engine/api/client/service/opts.go +++ b/components/engine/api/client/service/opts.go @@ -8,10 +8,10 @@ import ( "strings" "time" + mounttypes "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" - mounttypes "github.com/docker/engine-api/types/mount" - "github.com/docker/engine-api/types/swarm" "github.com/docker/go-connections/nat" units "github.com/docker/go-units" "github.com/spf13/cobra" diff --git a/components/engine/api/client/service/opts_test.go b/components/engine/api/client/service/opts_test.go index d5e2babb68..30e261b8de 100644 --- a/components/engine/api/client/service/opts_test.go +++ b/components/engine/api/client/service/opts_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" + mounttypes "github.com/docker/docker/api/types/mount" "github.com/docker/docker/pkg/testutil/assert" - mounttypes "github.com/docker/engine-api/types/mount" ) func TestMemBytesString(t *testing.T) { diff --git a/components/engine/api/client/service/ps.go b/components/engine/api/client/service/ps.go index cb93e7e871..e9f3f0ba67 100644 --- a/components/engine/api/client/service/ps.go +++ b/components/engine/api/client/service/ps.go @@ -5,9 +5,9 @@ import ( "github.com/docker/docker/api/client/idresolver" "github.com/docker/docker/api/client/node" "github.com/docker/docker/api/client/task" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/opts" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/service/scale.go b/components/engine/api/client/service/scale.go index 1a3ea80eaf..edd43123a3 100644 --- a/components/engine/api/client/service/scale.go +++ b/components/engine/api/client/service/scale.go @@ -8,8 +8,8 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/service/update.go b/components/engine/api/client/service/update.go index e843b69427..0bc9c25f04 100644 --- a/components/engine/api/client/service/update.go +++ b/components/engine/api/client/service/update.go @@ -9,12 +9,12 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" + mounttypes "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types" - mounttypes "github.com/docker/engine-api/types/mount" - "github.com/docker/engine-api/types/swarm" "github.com/docker/go-connections/nat" shlex "github.com/flynn-archive/go-shlex" "github.com/spf13/cobra" diff --git a/components/engine/api/client/service/update_test.go b/components/engine/api/client/service/update_test.go index ff3a211111..6e68e977ac 100644 --- a/components/engine/api/client/service/update_test.go +++ b/components/engine/api/client/service/update_test.go @@ -4,9 +4,9 @@ import ( "sort" "testing" + mounttypes "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/testutil/assert" - mounttypes "github.com/docker/engine-api/types/mount" - "github.com/docker/engine-api/types/swarm" ) func TestUpdateServiceArgs(t *testing.T) { diff --git a/components/engine/api/client/stack/common.go b/components/engine/api/client/stack/common.go index 46c9957250..b55c4214fe 100644 --- a/components/engine/api/client/stack/common.go +++ b/components/engine/api/client/stack/common.go @@ -5,10 +5,10 @@ package stack import ( "golang.org/x/net/context" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" "github.com/docker/engine-api/client" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" - "github.com/docker/engine-api/types/swarm" ) const ( diff --git a/components/engine/api/client/stack/deploy.go b/components/engine/api/client/stack/deploy.go index 5c3ae0dc84..5c03dc3d31 100644 --- a/components/engine/api/client/stack/deploy.go +++ b/components/engine/api/client/stack/deploy.go @@ -10,9 +10,9 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/bundlefile" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/swarm" ) const ( diff --git a/components/engine/api/client/stack/ps.go b/components/engine/api/client/stack/ps.go index f7f66427ef..9d9458d85f 100644 --- a/components/engine/api/client/stack/ps.go +++ b/components/engine/api/client/stack/ps.go @@ -10,10 +10,10 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/idresolver" "github.com/docker/docker/api/client/task" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/opts" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/stack/services.go b/components/engine/api/client/stack/services.go index 3da75460c3..819b1c6759 100644 --- a/components/engine/api/client/stack/services.go +++ b/components/engine/api/client/stack/services.go @@ -9,10 +9,10 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/service" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" "github.com/docker/docker/opts" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/swarm/init.go b/components/engine/api/client/swarm/init.go index 6a59019067..cb23fb6824 100644 --- a/components/engine/api/client/swarm/init.go +++ b/components/engine/api/client/swarm/init.go @@ -8,8 +8,8 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" "github.com/spf13/pflag" ) diff --git a/components/engine/api/client/swarm/join.go b/components/engine/api/client/swarm/join.go index de2e15ab23..fc8fb10b6c 100644 --- a/components/engine/api/client/swarm/join.go +++ b/components/engine/api/client/swarm/join.go @@ -5,8 +5,8 @@ import ( "strings" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/swarm/join_token.go b/components/engine/api/client/swarm/join_token.go index a1e381e1b1..803f360fa2 100644 --- a/components/engine/api/client/swarm/join_token.go +++ b/components/engine/api/client/swarm/join_token.go @@ -7,8 +7,8 @@ import ( "github.com/spf13/cobra" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/swarm/opts.go b/components/engine/api/client/swarm/opts.go index 9fcfa89bbf..7fcf25d136 100644 --- a/components/engine/api/client/swarm/opts.go +++ b/components/engine/api/client/swarm/opts.go @@ -7,8 +7,8 @@ import ( "strings" "time" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/opts" - "github.com/docker/engine-api/types/swarm" "github.com/spf13/pflag" ) diff --git a/components/engine/api/client/swarm/update.go b/components/engine/api/client/swarm/update.go index b9d6a6b521..ce9f34df37 100644 --- a/components/engine/api/client/swarm/update.go +++ b/components/engine/api/client/swarm/update.go @@ -6,8 +6,8 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" "github.com/spf13/pflag" ) diff --git a/components/engine/api/client/system/events.go b/components/engine/api/client/system/events.go index c29692ccd1..9258995174 100644 --- a/components/engine/api/client/system/events.go +++ b/components/engine/api/client/system/events.go @@ -10,11 +10,11 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" + eventtypes "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/jsonlog" - "github.com/docker/engine-api/types" - eventtypes "github.com/docker/engine-api/types/events" - "github.com/docker/engine-api/types/filters" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/system/events_utils.go b/components/engine/api/client/system/events_utils.go index 685225daa6..71c1b0476b 100644 --- a/components/engine/api/client/system/events_utils.go +++ b/components/engine/api/client/system/events_utils.go @@ -6,7 +6,7 @@ import ( "sync" "github.com/Sirupsen/logrus" - eventtypes "github.com/docker/engine-api/types/events" + eventtypes "github.com/docker/docker/api/types/events" ) // EventHandler is abstract interface for user to customize diff --git a/components/engine/api/client/system/info.go b/components/engine/api/client/system/info.go index c2e314aea8..b08d14ce63 100644 --- a/components/engine/api/client/system/info.go +++ b/components/engine/api/client/system/info.go @@ -8,12 +8,12 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/utils" "github.com/docker/docker/utils/templates" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/swarm" "github.com/docker/go-units" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/system/version.go b/components/engine/api/client/system/version.go index cdfe1d466d..a4cabd6b76 100644 --- a/components/engine/api/client/system/version.go +++ b/components/engine/api/client/system/version.go @@ -7,11 +7,11 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/dockerversion" "github.com/docker/docker/utils" "github.com/docker/docker/utils/templates" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/task/print.go b/components/engine/api/client/task/print.go index 94e7fb050c..ef7732ccc6 100644 --- a/components/engine/api/client/task/print.go +++ b/components/engine/api/client/task/print.go @@ -11,7 +11,7 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/idresolver" - "github.com/docker/engine-api/types/swarm" + "github.com/docker/docker/api/types/swarm" "github.com/docker/go-units" ) diff --git a/components/engine/api/client/trust.go b/components/engine/api/client/trust.go index 12d9c5ce12..ae33528c2b 100644 --- a/components/engine/api/client/trust.go +++ b/components/engine/api/client/trust.go @@ -22,13 +22,13 @@ import ( "github.com/docker/distribution/digest" "github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/transport" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/cliconfig" "github.com/docker/docker/distribution" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/reference" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" "github.com/docker/go-connections/tlsconfig" "github.com/docker/notary/client" "github.com/docker/notary/passphrase" diff --git a/components/engine/api/client/trust_test.go b/components/engine/api/client/trust_test.go index ec95bd9d35..86f9e61e4d 100644 --- a/components/engine/api/client/trust_test.go +++ b/components/engine/api/client/trust_test.go @@ -4,8 +4,8 @@ import ( "os" "testing" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/registry" - registrytypes "github.com/docker/engine-api/types/registry" ) func unsetENV() { diff --git a/components/engine/api/client/utils.go b/components/engine/api/client/utils.go index a3dd3dc2e3..390a673a21 100644 --- a/components/engine/api/client/utils.go +++ b/components/engine/api/client/utils.go @@ -14,10 +14,10 @@ import ( "golang.org/x/net/context" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/term" "github.com/docker/engine-api/client" - "github.com/docker/engine-api/types" ) func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) { diff --git a/components/engine/api/client/volume/create.go b/components/engine/api/client/volume/create.go index 8d17a8006f..b40f51ace3 100644 --- a/components/engine/api/client/volume/create.go +++ b/components/engine/api/client/volume/create.go @@ -6,10 +6,10 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/volume/list.go b/components/engine/api/client/volume/list.go index f5fceec77e..e6f1fb3ae4 100644 --- a/components/engine/api/client/volume/list.go +++ b/components/engine/api/client/volume/list.go @@ -7,9 +7,9 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/formatter" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" "github.com/spf13/cobra" ) diff --git a/components/engine/api/common.go b/components/engine/api/common.go index f0a7d4834c..2cb538e3a2 100644 --- a/components/engine/api/common.go +++ b/components/engine/api/common.go @@ -12,9 +12,9 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/system" - "github.com/docker/engine-api/types" "github.com/docker/libtrust" ) diff --git a/components/engine/api/common_test.go b/components/engine/api/common_test.go index c214660cc4..31d6f58253 100644 --- a/components/engine/api/common_test.go +++ b/components/engine/api/common_test.go @@ -7,7 +7,7 @@ import ( "os" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) type ports struct { diff --git a/components/engine/api/server/httputils/decoder.go b/components/engine/api/server/httputils/decoder.go index dbe469cca6..458eac5600 100644 --- a/components/engine/api/server/httputils/decoder.go +++ b/components/engine/api/server/httputils/decoder.go @@ -3,8 +3,8 @@ package httputils import ( "io" - "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/network" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" ) // ContainerDecoder specifies how diff --git a/components/engine/api/server/httputils/errors.go b/components/engine/api/server/httputils/errors.go index da4db97915..acfae85f98 100644 --- a/components/engine/api/server/httputils/errors.go +++ b/components/engine/api/server/httputils/errors.go @@ -5,8 +5,8 @@ import ( "strings" "github.com/Sirupsen/logrus" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/versions" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions" "github.com/gorilla/mux" "google.golang.org/grpc" ) diff --git a/components/engine/api/server/middleware/user_agent.go b/components/engine/api/server/middleware/user_agent.go index 87c8cb83e1..be1c3fb4c4 100644 --- a/components/engine/api/server/middleware/user_agent.go +++ b/components/engine/api/server/middleware/user_agent.go @@ -6,7 +6,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/server/httputils" - "github.com/docker/engine-api/types/versions" + "github.com/docker/docker/api/types/versions" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/middleware/version.go b/components/engine/api/server/middleware/version.go index eb7bbf3a3e..e0a4fc5f46 100644 --- a/components/engine/api/server/middleware/version.go +++ b/components/engine/api/server/middleware/version.go @@ -5,7 +5,7 @@ import ( "net/http" "runtime" - "github.com/docker/engine-api/types/versions" + "github.com/docker/docker/api/types/versions" "golang.org/x/net/context" ) diff --git a/components/engine/api/types/auth.go b/components/engine/api/types/auth.go new file mode 100644 index 0000000000..056af6b842 --- /dev/null +++ b/components/engine/api/types/auth.go @@ -0,0 +1,22 @@ +package types + +// AuthConfig contains authorization information for connecting to a Registry +type AuthConfig struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth,omitempty"` + + // Email is an optional value associated with the username. + // This field is deprecated and will be removed in a later + // version of docker. + Email string `json:"email,omitempty"` + + ServerAddress string `json:"serveraddress,omitempty"` + + // IdentityToken is used to authenticate the user and get + // an access token for the registry. + IdentityToken string `json:"identitytoken,omitempty"` + + // RegistryToken is a bearer token to be sent to a registry + RegistryToken string `json:"registrytoken,omitempty"` +} diff --git a/components/engine/api/types/backend/backend.go b/components/engine/api/types/backend/backend.go index c7b4f01757..2da92a2902 100644 --- a/components/engine/api/types/backend/backend.go +++ b/components/engine/api/types/backend/backend.go @@ -6,8 +6,8 @@ package backend import ( "io" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/streamformatter" - "github.com/docker/engine-api/types" ) // ContainerAttachConfig holds the streams to use when connecting to a container to view logs. diff --git a/components/engine/api/types/blkiodev/blkio.go b/components/engine/api/types/blkiodev/blkio.go new file mode 100644 index 0000000000..931ae10ab1 --- /dev/null +++ b/components/engine/api/types/blkiodev/blkio.go @@ -0,0 +1,23 @@ +package blkiodev + +import "fmt" + +// WeightDevice is a structure that holds device:weight pair +type WeightDevice struct { + Path string + Weight uint16 +} + +func (w *WeightDevice) String() string { + return fmt.Sprintf("%s:%d", w.Path, w.Weight) +} + +// ThrottleDevice is a structure that holds device:rate_per_second pair +type ThrottleDevice struct { + Path string + Rate uint64 +} + +func (t *ThrottleDevice) String() string { + return fmt.Sprintf("%s:%d", t.Path, t.Rate) +} diff --git a/components/engine/api/types/client.go b/components/engine/api/types/client.go new file mode 100644 index 0000000000..e19d16e5c4 --- /dev/null +++ b/components/engine/api/types/client.go @@ -0,0 +1,300 @@ +package types + +import ( + "bufio" + "io" + "net" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/go-units" +) + +// CheckpointCreateOptions holds parameters to create a checkpoint from a container +type CheckpointCreateOptions struct { + CheckpointID string + Exit bool +} + +// ContainerAttachOptions holds parameters to attach to a container. +type ContainerAttachOptions struct { + Stream bool + Stdin bool + Stdout bool + Stderr bool + DetachKeys string +} + +// ContainerCommitOptions holds parameters to commit changes into a container. +type ContainerCommitOptions struct { + Reference string + Comment string + Author string + Changes []string + Pause bool + Config *container.Config +} + +// ContainerExecInspect holds information returned by exec inspect. +type ContainerExecInspect struct { + ExecID string + ContainerID string + Running bool + ExitCode int +} + +// ContainerListOptions holds parameters to list containers with. +type ContainerListOptions struct { + Quiet bool + Size bool + All bool + Latest bool + Since string + Before string + Limit int + Filter filters.Args +} + +// ContainerLogsOptions holds parameters to filter logs with. +type ContainerLogsOptions struct { + ShowStdout bool + ShowStderr bool + Since string + Timestamps bool + Follow bool + Tail string + Details bool +} + +// ContainerRemoveOptions holds parameters to remove containers. +type ContainerRemoveOptions struct { + RemoveVolumes bool + RemoveLinks bool + Force bool +} + +// ContainerStartOptions holds parameters to start containers. +type ContainerStartOptions struct { + CheckpointID string +} + +// CopyToContainerOptions holds information +// about files to copy into a container +type CopyToContainerOptions struct { + AllowOverwriteDirWithFile bool +} + +// EventsOptions hold parameters to filter events with. +type EventsOptions struct { + Since string + Until string + Filters filters.Args +} + +// NetworkListOptions holds parameters to filter the list of networks with. +type NetworkListOptions struct { + Filters filters.Args +} + +// HijackedResponse holds connection information for a hijacked request. +type HijackedResponse struct { + Conn net.Conn + Reader *bufio.Reader +} + +// Close closes the hijacked connection and reader. +func (h *HijackedResponse) Close() { + h.Conn.Close() +} + +// CloseWriter is an interface that implements structs +// that close input streams to prevent from writing. +type CloseWriter interface { + CloseWrite() error +} + +// CloseWrite closes a readWriter for writing. +func (h *HijackedResponse) CloseWrite() error { + if conn, ok := h.Conn.(CloseWriter); ok { + return conn.CloseWrite() + } + return nil +} + +// ImageBuildOptions holds the information +// necessary to build images. +type ImageBuildOptions struct { + Tags []string + SuppressOutput bool + RemoteContext string + NoCache bool + Remove bool + ForceRemove bool + PullParent bool + Isolation container.Isolation + CPUSetCPUs string + CPUSetMems string + CPUShares int64 + CPUQuota int64 + CPUPeriod int64 + Memory int64 + MemorySwap int64 + CgroupParent string + ShmSize int64 + Dockerfile string + Ulimits []*units.Ulimit + BuildArgs map[string]string + AuthConfigs map[string]AuthConfig + Context io.Reader + Labels map[string]string + // squash the resulting image's layers to the parent + // preserves the original image and creates a new one from the parent with all + // the changes applied to a single layer + Squash bool +} + +// ImageBuildResponse holds information +// returned by a server after building +// an image. +type ImageBuildResponse struct { + Body io.ReadCloser + OSType string +} + +// ImageCreateOptions holds information to create images. +type ImageCreateOptions struct { + RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry +} + +// ImageImportSource holds source information for ImageImport +type ImageImportSource struct { + Source io.Reader // Source is the data to send to the server to create this image from (mutually exclusive with SourceName) + SourceName string // SourceName is the name of the image to pull (mutually exclusive with Source) +} + +// ImageImportOptions holds information to import images from the client host. +type ImageImportOptions struct { + Tag string // Tag is the name to tag this image with. This attribute is deprecated. + Message string // Message is the message to tag the image with + Changes []string // Changes are the raw changes to apply to this image +} + +// ImageListOptions holds parameters to filter the list of images with. +type ImageListOptions struct { + MatchName string + All bool + Filters filters.Args +} + +// ImageLoadResponse returns information to the client about a load process. +type ImageLoadResponse struct { + // Body must be closed to avoid a resource leak + Body io.ReadCloser + JSON bool +} + +// ImagePullOptions holds information to pull images. +type ImagePullOptions struct { + All bool + RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry + PrivilegeFunc RequestPrivilegeFunc +} + +// RequestPrivilegeFunc is a function interface that +// clients can supply to retry operations after +// getting an authorization error. +// This function returns the registry authentication +// header value in base 64 format, or an error +// if the privilege request fails. +type RequestPrivilegeFunc func() (string, error) + +//ImagePushOptions holds information to push images. +type ImagePushOptions ImagePullOptions + +// ImageRemoveOptions holds parameters to remove images. +type ImageRemoveOptions struct { + Force bool + PruneChildren bool +} + +// ImageSearchOptions holds parameters to search images with. +type ImageSearchOptions struct { + RegistryAuth string + PrivilegeFunc RequestPrivilegeFunc + Filters filters.Args + Limit int +} + +// ResizeOptions holds parameters to resize a tty. +// It can be used to resize container ttys and +// exec process ttys too. +type ResizeOptions struct { + Height int + Width int +} + +// VersionResponse holds version information for the client and the server +type VersionResponse struct { + Client *Version + Server *Version +} + +// ServerOK returns true when the client could connect to the docker server +// and parse the information received. It returns false otherwise. +func (v VersionResponse) ServerOK() bool { + return v.Server != nil +} + +// NodeListOptions holds parameters to list nodes with. +type NodeListOptions struct { + Filter filters.Args +} + +// NodeRemoveOptions holds parameters to remove nodes with. +type NodeRemoveOptions struct { + Force bool +} + +// ServiceCreateOptions contains the options to use when creating a service. +type ServiceCreateOptions struct { + // EncodedRegistryAuth is the encoded registry authorization credentials to + // use when updating the service. + // + // This field follows the format of the X-Registry-Auth header. + EncodedRegistryAuth string +} + +// ServiceCreateResponse contains the information returned to a client +// on the creation of a new service. +type ServiceCreateResponse struct { + // ID is the ID of the created service. + ID string +} + +// ServiceUpdateOptions contains the options to be used for updating services. +type ServiceUpdateOptions struct { + // EncodedRegistryAuth is the encoded registry authorization credentials to + // use when updating the service. + // + // This field follows the format of the X-Registry-Auth header. + EncodedRegistryAuth string + + // TODO(stevvooe): Consider moving the version parameter of ServiceUpdate + // into this field. While it does open API users up to racy writes, most + // users may not need that level of consistency in practice. +} + +// ServiceListOptions holds parameters to list services with. +type ServiceListOptions struct { + Filter filters.Args +} + +// TaskListOptions holds parameters to list tasks with. +type TaskListOptions struct { + Filter filters.Args +} + +// PluginRemoveOptions holds parameters to remove plugins. +type PluginRemoveOptions struct { + Force bool +} diff --git a/components/engine/api/types/configs.go b/components/engine/api/types/configs.go new file mode 100644 index 0000000000..177a62bb4f --- /dev/null +++ b/components/engine/api/types/configs.go @@ -0,0 +1,61 @@ +package types + +import ( + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" +) + +// configs holds structs used for internal communication between the +// frontend (such as an http server) and the backend (such as the +// docker daemon). + +// ContainerCreateConfig is the parameter set to ContainerCreate() +type ContainerCreateConfig struct { + Name string + Config *container.Config + HostConfig *container.HostConfig + NetworkingConfig *network.NetworkingConfig + AdjustCPUShares bool +} + +// ContainerRmConfig holds arguments for the container remove +// operation. This struct is used to tell the backend what operations +// to perform. +type ContainerRmConfig struct { + ForceRemove, RemoveVolume, RemoveLink bool +} + +// ContainerCommitConfig contains build configs for commit operation, +// and is used when making a commit with the current state of the container. +type ContainerCommitConfig struct { + Pause bool + Repo string + Tag string + Author string + Comment string + // merge container config into commit config before commit + MergeConfigs bool + Config *container.Config +} + +// ExecConfig is a small subset of the Config struct that holds the configuration +// for the exec feature of docker. +type ExecConfig struct { + User string // User that will run the command + Privileged bool // Is the container in privileged mode + Tty bool // Attach standard streams to a tty. + AttachStdin bool // Attach the standard input, makes possible user interaction + AttachStderr bool // Attach the standard error + AttachStdout bool // Attach the standard output + Detach bool // Execute in detach mode + DetachKeys string // Escape keys for detach + Env []string // Environment variables + Cmd []string // Execution commands and args +} + +// PluginRmConfig holds arguments for the plugin remove +// operation. This struct is used to tell the backend what operations +// to perform. +type PluginRmConfig struct { + ForceRemove bool +} diff --git a/components/engine/api/types/container/config.go b/components/engine/api/types/container/config.go new file mode 100644 index 0000000000..e72b065b4b --- /dev/null +++ b/components/engine/api/types/container/config.go @@ -0,0 +1,62 @@ +package container + +import ( + "time" + + "github.com/docker/docker/api/types/strslice" + "github.com/docker/go-connections/nat" +) + +// HealthConfig holds configuration settings for the HEALTHCHECK feature. +type HealthConfig struct { + // Test is the test to perform to check that the container is healthy. + // An empty slice means to inherit the default. + // The options are: + // {} : inherit healthcheck + // {"NONE"} : disable healthcheck + // {"CMD", args...} : exec arguments directly + // {"CMD-SHELL", command} : run command with system's default shell + Test []string `json:",omitempty"` + + // Zero means to inherit. Durations are expressed as integer nanoseconds. + Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks. + Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung. + + // Retries is the number of consecutive failures needed to consider a container as unhealthy. + // Zero means inherit. + Retries int `json:",omitempty"` +} + +// Config contains the configuration data about a container. +// It should hold only portable information about the container. +// Here, "portable" means "independent from the host we are running on". +// Non-portable information *should* appear in HostConfig. +// All fields added to this struct must be marked `omitempty` to keep getting +// predictable hashes from the old `v1Compatibility` configuration. +type Config struct { + Hostname string // Hostname + Domainname string // Domainname + User string // User that will run the command(s) inside the container, also support user:group + AttachStdin bool // Attach the standard input, makes possible user interaction + AttachStdout bool // Attach the standard output + AttachStderr bool // Attach the standard error + ExposedPorts map[nat.Port]struct{} `json:",omitempty"` // List of exposed ports + Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + OpenStdin bool // Open stdin + StdinOnce bool // If true, close stdin after the 1 attached client disconnects. + Env []string // List of environment variable to set in the container + Cmd strslice.StrSlice // Command to run when starting the container + Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy + ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific) + Image string // Name of the image as it was passed by the operator (eg. could be symbolic) + Volumes map[string]struct{} // List of volumes (mounts) used for the container + WorkingDir string // Current directory (PWD) in the command will be launched + Entrypoint strslice.StrSlice // Entrypoint to run when starting the container + NetworkDisabled bool `json:",omitempty"` // Is network disabled + MacAddress string `json:",omitempty"` // Mac Address of the container + OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile + Labels map[string]string // List of labels set to this container + StopSignal string `json:",omitempty"` // Signal to stop a container + StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container + Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT +} diff --git a/components/engine/api/types/container/host_config.go b/components/engine/api/types/container/host_config.go new file mode 100644 index 0000000000..c57470cd41 --- /dev/null +++ b/components/engine/api/types/container/host_config.go @@ -0,0 +1,324 @@ +package container + +import ( + "strings" + + "github.com/docker/docker/api/types/blkiodev" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/strslice" + "github.com/docker/go-connections/nat" + "github.com/docker/go-units" +) + +// NetworkMode represents the container network stack. +type NetworkMode string + +// Isolation represents the isolation technology of a container. The supported +// values are platform specific +type Isolation string + +// IsDefault indicates the default isolation technology of a container. On Linux this +// is the native driver. On Windows, this is a Windows Server Container. +func (i Isolation) IsDefault() bool { + return strings.ToLower(string(i)) == "default" || string(i) == "" +} + +// IpcMode represents the container ipc stack. +type IpcMode string + +// IsPrivate indicates whether the container uses its private ipc stack. +func (n IpcMode) IsPrivate() bool { + return !(n.IsHost() || n.IsContainer()) +} + +// IsHost indicates whether the container uses the host's ipc stack. +func (n IpcMode) IsHost() bool { + return n == "host" +} + +// IsContainer indicates whether the container uses a container's ipc stack. +func (n IpcMode) IsContainer() bool { + parts := strings.SplitN(string(n), ":", 2) + return len(parts) > 1 && parts[0] == "container" +} + +// Valid indicates whether the ipc stack is valid. +func (n IpcMode) Valid() bool { + parts := strings.Split(string(n), ":") + switch mode := parts[0]; mode { + case "", "host": + case "container": + if len(parts) != 2 || parts[1] == "" { + return false + } + default: + return false + } + return true +} + +// Container returns the name of the container ipc stack is going to be used. +func (n IpcMode) Container() string { + parts := strings.SplitN(string(n), ":", 2) + if len(parts) > 1 { + return parts[1] + } + return "" +} + +// UsernsMode represents userns mode in the container. +type UsernsMode string + +// IsHost indicates whether the container uses the host's userns. +func (n UsernsMode) IsHost() bool { + return n == "host" +} + +// IsPrivate indicates whether the container uses the a private userns. +func (n UsernsMode) IsPrivate() bool { + return !(n.IsHost()) +} + +// Valid indicates whether the userns is valid. +func (n UsernsMode) Valid() bool { + parts := strings.Split(string(n), ":") + switch mode := parts[0]; mode { + case "", "host": + default: + return false + } + return true +} + +// CgroupSpec represents the cgroup to use for the container. +type CgroupSpec string + +// IsContainer indicates whether the container is using another container cgroup +func (c CgroupSpec) IsContainer() bool { + parts := strings.SplitN(string(c), ":", 2) + return len(parts) > 1 && parts[0] == "container" +} + +// Valid indicates whether the cgroup spec is valid. +func (c CgroupSpec) Valid() bool { + return c.IsContainer() || c == "" +} + +// Container returns the name of the container whose cgroup will be used. +func (c CgroupSpec) Container() string { + parts := strings.SplitN(string(c), ":", 2) + if len(parts) > 1 { + return parts[1] + } + return "" +} + +// UTSMode represents the UTS namespace of the container. +type UTSMode string + +// IsPrivate indicates whether the container uses its private UTS namespace. +func (n UTSMode) IsPrivate() bool { + return !(n.IsHost()) +} + +// IsHost indicates whether the container uses the host's UTS namespace. +func (n UTSMode) IsHost() bool { + return n == "host" +} + +// Valid indicates whether the UTS namespace is valid. +func (n UTSMode) Valid() bool { + parts := strings.Split(string(n), ":") + switch mode := parts[0]; mode { + case "", "host": + default: + return false + } + return true +} + +// PidMode represents the pid namespace of the container. +type PidMode string + +// IsPrivate indicates whether the container uses its own new pid namespace. +func (n PidMode) IsPrivate() bool { + return !(n.IsHost() || n.IsContainer()) +} + +// IsHost indicates whether the container uses the host's pid namespace. +func (n PidMode) IsHost() bool { + return n == "host" +} + +// IsContainer indicates whether the container uses a container's pid namespace. +func (n PidMode) IsContainer() bool { + parts := strings.SplitN(string(n), ":", 2) + return len(parts) > 1 && parts[0] == "container" +} + +// Valid indicates whether the pid namespace is valid. +func (n PidMode) Valid() bool { + parts := strings.Split(string(n), ":") + switch mode := parts[0]; mode { + case "", "host": + case "container": + if len(parts) != 2 || parts[1] == "" { + return false + } + default: + return false + } + return true +} + +// Container returns the name of the container whose pid namespace is going to be used. +func (n PidMode) Container() string { + parts := strings.SplitN(string(n), ":", 2) + if len(parts) > 1 { + return parts[1] + } + return "" +} + +// DeviceMapping represents the device mapping between the host and the container. +type DeviceMapping struct { + PathOnHost string + PathInContainer string + CgroupPermissions string +} + +// RestartPolicy represents the restart policies of the container. +type RestartPolicy struct { + Name string + MaximumRetryCount int +} + +// IsNone indicates whether the container has the "no" restart policy. +// This means the container will not automatically restart when exiting. +func (rp *RestartPolicy) IsNone() bool { + return rp.Name == "no" || rp.Name == "" +} + +// IsAlways indicates whether the container has the "always" restart policy. +// This means the container will automatically restart regardless of the exit status. +func (rp *RestartPolicy) IsAlways() bool { + return rp.Name == "always" +} + +// IsOnFailure indicates whether the container has the "on-failure" restart policy. +// This means the container will automatically restart of exiting with a non-zero exit status. +func (rp *RestartPolicy) IsOnFailure() bool { + return rp.Name == "on-failure" +} + +// IsUnlessStopped indicates whether the container has the +// "unless-stopped" restart policy. This means the container will +// automatically restart unless user has put it to stopped state. +func (rp *RestartPolicy) IsUnlessStopped() bool { + return rp.Name == "unless-stopped" +} + +// IsSame compares two RestartPolicy to see if they are the same +func (rp *RestartPolicy) IsSame(tp *RestartPolicy) bool { + return rp.Name == tp.Name && rp.MaximumRetryCount == tp.MaximumRetryCount +} + +// LogConfig represents the logging configuration of the container. +type LogConfig struct { + Type string + Config map[string]string +} + +// Resources contains container's resources (cgroups config, ulimits...) +type Resources struct { + // Applicable to all platforms + CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers) + Memory int64 // Memory limit (in bytes) + + // Applicable to UNIX platforms + CgroupParent string // Parent cgroup. + BlkioWeight uint16 // Block IO weight (relative weight vs. other containers) + BlkioWeightDevice []*blkiodev.WeightDevice + BlkioDeviceReadBps []*blkiodev.ThrottleDevice + BlkioDeviceWriteBps []*blkiodev.ThrottleDevice + BlkioDeviceReadIOps []*blkiodev.ThrottleDevice + BlkioDeviceWriteIOps []*blkiodev.ThrottleDevice + CPUPeriod int64 `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period + CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota + CpusetCpus string // CpusetCpus 0-2, 0,1 + CpusetMems string // CpusetMems 0-2, 0,1 + Devices []DeviceMapping // List of devices to map inside the container + DiskQuota int64 // Disk limit (in bytes) + KernelMemory int64 // Kernel memory limit (in bytes) + MemoryReservation int64 // Memory soft limit (in bytes) + MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap + MemorySwappiness *int64 // Tuning container memory swappiness behaviour + OomKillDisable *bool // Whether to disable OOM Killer or not + PidsLimit int64 // Setting pids limit for a container + Ulimits []*units.Ulimit // List of ulimits to be set in the container + + // Applicable to Windows + CPUCount int64 `json:"CpuCount"` // CPU count + CPUPercent int64 `json:"CpuPercent"` // CPU percent + IOMaximumIOps uint64 // Maximum IOps for the container system drive + IOMaximumBandwidth uint64 // Maximum IO in bytes per second for the container system drive +} + +// UpdateConfig holds the mutable attributes of a Container. +// Those attributes can be updated at runtime. +type UpdateConfig struct { + // Contains container's resources (cgroups, ulimits) + Resources + RestartPolicy RestartPolicy +} + +// HostConfig the non-portable Config structure of a container. +// Here, "non-portable" means "dependent of the host we are running on". +// Portable information *should* appear in Config. +type HostConfig struct { + // Applicable to all platforms + Binds []string // List of volume bindings for this container + ContainerIDFile string // File (path) where the containerId is written + LogConfig LogConfig // Configuration of the logs for this container + NetworkMode NetworkMode // Network mode to use for the container + PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host + RestartPolicy RestartPolicy // Restart policy to be used for the container + AutoRemove bool // Automatically remove container when it exits + VolumeDriver string // Name of the volume driver used to mount volumes + VolumesFrom []string // List of volumes to take from other container + + // Applicable to UNIX platforms + CapAdd strslice.StrSlice // List of kernel capabilities to add to the container + CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container + DNS []string `json:"Dns"` // List of DNS server to lookup + DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for + DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for + ExtraHosts []string // List of extra hosts + GroupAdd []string // List of additional groups that the container process will run as + IpcMode IpcMode // IPC namespace to use for the container + Cgroup CgroupSpec // Cgroup to use for the container + Links []string // List of links (in the name:alias form) + OomScoreAdj int // Container preference for OOM-killing + PidMode PidMode // PID namespace to use for the container + Privileged bool // Is the container in privileged mode + PublishAllPorts bool // Should docker publish all exposed port for the container + ReadonlyRootfs bool // Is the container root filesystem in read-only + SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. + StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container. + Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container + UTSMode UTSMode // UTS namespace to use for the container + UsernsMode UsernsMode // The user namespace to use for the container + ShmSize int64 // Total shm memory usage + Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container + Runtime string `json:",omitempty"` // Runtime to use with this container + + // Applicable to Windows + ConsoleSize [2]int // Initial console size + Isolation Isolation // Isolation technology of the container (eg default, hyperv) + + // Contains container's resources (cgroups, ulimits) + Resources + + // Mounts specs used by the container + Mounts []mount.Mount `json:",omitempty"` +} diff --git a/components/engine/api/types/container/hostconfig_unix.go b/components/engine/api/types/container/hostconfig_unix.go new file mode 100644 index 0000000000..4171059a47 --- /dev/null +++ b/components/engine/api/types/container/hostconfig_unix.go @@ -0,0 +1,81 @@ +// +build !windows + +package container + +import "strings" + +// IsValid indicates if an isolation technology is valid +func (i Isolation) IsValid() bool { + return i.IsDefault() +} + +// IsPrivate indicates whether container uses it's private network stack. +func (n NetworkMode) IsPrivate() bool { + return !(n.IsHost() || n.IsContainer()) +} + +// IsDefault indicates whether container uses the default network stack. +func (n NetworkMode) IsDefault() bool { + return n == "default" +} + +// NetworkName returns the name of the network stack. +func (n NetworkMode) NetworkName() string { + if n.IsBridge() { + return "bridge" + } else if n.IsHost() { + return "host" + } else if n.IsContainer() { + return "container" + } else if n.IsNone() { + return "none" + } else if n.IsDefault() { + return "default" + } else if n.IsUserDefined() { + return n.UserDefined() + } + return "" +} + +// IsBridge indicates whether container uses the bridge network stack +func (n NetworkMode) IsBridge() bool { + return n == "bridge" +} + +// IsHost indicates whether container uses the host network stack. +func (n NetworkMode) IsHost() bool { + return n == "host" +} + +// IsContainer indicates whether container uses a container network stack. +func (n NetworkMode) IsContainer() bool { + parts := strings.SplitN(string(n), ":", 2) + return len(parts) > 1 && parts[0] == "container" +} + +// IsNone indicates whether container isn't using a network stack. +func (n NetworkMode) IsNone() bool { + return n == "none" +} + +// ConnectedContainer is the id of the container which network this container is connected to. +func (n NetworkMode) ConnectedContainer() string { + parts := strings.SplitN(string(n), ":", 2) + if len(parts) > 1 { + return parts[1] + } + return "" +} + +// IsUserDefined indicates user-created network +func (n NetworkMode) IsUserDefined() bool { + return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() +} + +//UserDefined indicates user-created network +func (n NetworkMode) UserDefined() string { + if n.IsUserDefined() { + return string(n) + } + return "" +} diff --git a/components/engine/api/types/container/hostconfig_windows.go b/components/engine/api/types/container/hostconfig_windows.go new file mode 100644 index 0000000000..0ee332ba68 --- /dev/null +++ b/components/engine/api/types/container/hostconfig_windows.go @@ -0,0 +1,87 @@ +package container + +import ( + "strings" +) + +// IsDefault indicates whether container uses the default network stack. +func (n NetworkMode) IsDefault() bool { + return n == "default" +} + +// IsNone indicates whether container isn't using a network stack. +func (n NetworkMode) IsNone() bool { + return n == "none" +} + +// IsContainer indicates whether container uses a container network stack. +// Returns false as windows doesn't support this mode +func (n NetworkMode) IsContainer() bool { + return false +} + +// IsBridge indicates whether container uses the bridge network stack +// in windows it is given the name NAT +func (n NetworkMode) IsBridge() bool { + return n == "nat" +} + +// IsHost indicates whether container uses the host network stack. +// returns false as this is not supported by windows +func (n NetworkMode) IsHost() bool { + return false +} + +// IsPrivate indicates whether container uses its private network stack. +func (n NetworkMode) IsPrivate() bool { + return !(n.IsHost() || n.IsContainer()) +} + +// ConnectedContainer is the id of the container which network this container is connected to. +// Returns blank string on windows +func (n NetworkMode) ConnectedContainer() string { + return "" +} + +// IsUserDefined indicates user-created network +func (n NetworkMode) IsUserDefined() bool { + return !n.IsDefault() && !n.IsNone() && !n.IsBridge() +} + +// IsHyperV indicates the use of a Hyper-V partition for isolation +func (i Isolation) IsHyperV() bool { + return strings.ToLower(string(i)) == "hyperv" +} + +// IsProcess indicates the use of process isolation +func (i Isolation) IsProcess() bool { + return strings.ToLower(string(i)) == "process" +} + +// IsValid indicates if an isolation technology is valid +func (i Isolation) IsValid() bool { + return i.IsDefault() || i.IsHyperV() || i.IsProcess() +} + +// NetworkName returns the name of the network stack. +func (n NetworkMode) NetworkName() string { + if n.IsDefault() { + return "default" + } else if n.IsBridge() { + return "nat" + } else if n.IsNone() { + return "none" + } else if n.IsUserDefined() { + return n.UserDefined() + } + + return "" +} + +//UserDefined indicates user-created network +func (n NetworkMode) UserDefined() string { + if n.IsUserDefined() { + return string(n) + } + return "" +} diff --git a/components/engine/api/types/errors.go b/components/engine/api/types/errors.go new file mode 100644 index 0000000000..649ab95131 --- /dev/null +++ b/components/engine/api/types/errors.go @@ -0,0 +1,6 @@ +package types + +// ErrorResponse is the response body of API errors. +type ErrorResponse struct { + Message string `json:"message"` +} diff --git a/components/engine/api/types/events/events.go b/components/engine/api/types/events/events.go new file mode 100644 index 0000000000..7129a65acf --- /dev/null +++ b/components/engine/api/types/events/events.go @@ -0,0 +1,42 @@ +package events + +const ( + // ContainerEventType is the event type that containers generate + ContainerEventType = "container" + // DaemonEventType is the event type that daemon generate + DaemonEventType = "daemon" + // ImageEventType is the event type that images generate + ImageEventType = "image" + // NetworkEventType is the event type that networks generate + NetworkEventType = "network" + // PluginEventType is the event type that plugins generate + PluginEventType = "plugin" + // VolumeEventType is the event type that volumes generate + VolumeEventType = "volume" +) + +// Actor describes something that generates events, +// like a container, or a network, or a volume. +// It has a defined name and a set or attributes. +// The container attributes are its labels, other actors +// can generate these attributes from other properties. +type Actor struct { + ID string + Attributes map[string]string +} + +// Message represents the information an event contains +type Message struct { + // Deprecated information from JSONMessage. + // With data only in container events. + Status string `json:"status,omitempty"` + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` + + Type string + Action string + Actor Actor + + Time int64 `json:"time,omitempty"` + TimeNano int64 `json:"timeNano,omitempty"` +} diff --git a/components/engine/api/types/filters/parse.go b/components/engine/api/types/filters/parse.go new file mode 100644 index 0000000000..12e5a3b0d5 --- /dev/null +++ b/components/engine/api/types/filters/parse.go @@ -0,0 +1,307 @@ +// Package filters provides helper function to parse and handle command line +// filter, used for example in docker ps or docker images commands. +package filters + +import ( + "encoding/json" + "errors" + "fmt" + "regexp" + "strings" + + "github.com/docker/docker/api/types/versions" +) + +// Args stores filter arguments as map key:{map key: bool}. +// It contains an aggregation of the map of arguments (which are in the form +// of -f 'key=value') based on the key, and stores values for the same key +// in a map with string keys and boolean values. +// e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu' +// the args will be {"image.name":{"ubuntu":true},"label":{"label1=1":true,"label2=2":true}} +type Args struct { + fields map[string]map[string]bool +} + +// NewArgs initializes a new Args struct. +func NewArgs() Args { + return Args{fields: map[string]map[string]bool{}} +} + +// ParseFlag parses the argument to the filter flag. Like +// +// `docker ps -f 'created=today' -f 'image.name=ubuntu*'` +// +// If prev map is provided, then it is appended to, and returned. By default a new +// map is created. +func ParseFlag(arg string, prev Args) (Args, error) { + filters := prev + if len(arg) == 0 { + return filters, nil + } + + if !strings.Contains(arg, "=") { + return filters, ErrBadFormat + } + + f := strings.SplitN(arg, "=", 2) + + name := strings.ToLower(strings.TrimSpace(f[0])) + value := strings.TrimSpace(f[1]) + + filters.Add(name, value) + + return filters, nil +} + +// ErrBadFormat is an error returned in case of bad format for a filter. +var ErrBadFormat = errors.New("bad format of filter (expected name=value)") + +// ToParam packs the Args into a string for easy transport from client to server. +func ToParam(a Args) (string, error) { + // this way we don't URL encode {}, just empty space + if a.Len() == 0 { + return "", nil + } + + buf, err := json.Marshal(a.fields) + if err != nil { + return "", err + } + return string(buf), nil +} + +// ToParamWithVersion packs the Args into a string for easy transport from client to server. +// The generated string will depend on the specified version (corresponding to the API version). +func ToParamWithVersion(version string, a Args) (string, error) { + // this way we don't URL encode {}, just empty space + if a.Len() == 0 { + return "", nil + } + + // for daemons older than v1.10, filter must be of the form map[string][]string + buf := []byte{} + err := errors.New("") + if version != "" && versions.LessThan(version, "1.22") { + buf, err = json.Marshal(convertArgsToSlice(a.fields)) + } else { + buf, err = json.Marshal(a.fields) + } + if err != nil { + return "", err + } + return string(buf), nil +} + +// FromParam unpacks the filter Args. +func FromParam(p string) (Args, error) { + if len(p) == 0 { + return NewArgs(), nil + } + + r := strings.NewReader(p) + d := json.NewDecoder(r) + + m := map[string]map[string]bool{} + if err := d.Decode(&m); err != nil { + r.Seek(0, 0) + + // Allow parsing old arguments in slice format. + // Because other libraries might be sending them in this format. + deprecated := map[string][]string{} + if deprecatedErr := d.Decode(&deprecated); deprecatedErr == nil { + m = deprecatedArgs(deprecated) + } else { + return NewArgs(), err + } + } + return Args{m}, nil +} + +// Get returns the list of values associates with a field. +// It returns a slice of strings to keep backwards compatibility with old code. +func (filters Args) Get(field string) []string { + values := filters.fields[field] + if values == nil { + return make([]string, 0) + } + slice := make([]string, 0, len(values)) + for key := range values { + slice = append(slice, key) + } + return slice +} + +// Add adds a new value to a filter field. +func (filters Args) Add(name, value string) { + if _, ok := filters.fields[name]; ok { + filters.fields[name][value] = true + } else { + filters.fields[name] = map[string]bool{value: true} + } +} + +// Del removes a value from a filter field. +func (filters Args) Del(name, value string) { + if _, ok := filters.fields[name]; ok { + delete(filters.fields[name], value) + } +} + +// Len returns the number of fields in the arguments. +func (filters Args) Len() int { + return len(filters.fields) +} + +// MatchKVList returns true if the values for the specified field matches the ones +// from the sources. +// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}}, +// field is 'label' and sources are {'label1': '1', 'label2': '2'} +// it returns true. +func (filters Args) MatchKVList(field string, sources map[string]string) bool { + fieldValues := filters.fields[field] + + //do not filter if there is no filter set or cannot determine filter + if len(fieldValues) == 0 { + return true + } + + if sources == nil || len(sources) == 0 { + return false + } + + for name2match := range fieldValues { + testKV := strings.SplitN(name2match, "=", 2) + + v, ok := sources[testKV[0]] + if !ok { + return false + } + if len(testKV) == 2 && testKV[1] != v { + return false + } + } + + return true +} + +// Match returns true if the values for the specified field matches the source string +// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}}, +// field is 'image.name' and source is 'ubuntu' +// it returns true. +func (filters Args) Match(field, source string) bool { + if filters.ExactMatch(field, source) { + return true + } + + fieldValues := filters.fields[field] + for name2match := range fieldValues { + match, err := regexp.MatchString(name2match, source) + if err != nil { + continue + } + if match { + return true + } + } + return false +} + +// ExactMatch returns true if the source matches exactly one of the filters. +func (filters Args) ExactMatch(field, source string) bool { + fieldValues, ok := filters.fields[field] + //do not filter if there is no filter set or cannot determine filter + if !ok || len(fieldValues) == 0 { + return true + } + + // try to match full name value to avoid O(N) regular expression matching + return fieldValues[source] +} + +// UniqueExactMatch returns true if there is only one filter and the source matches exactly this one. +func (filters Args) UniqueExactMatch(field, source string) bool { + fieldValues := filters.fields[field] + //do not filter if there is no filter set or cannot determine filter + if len(fieldValues) == 0 { + return true + } + if len(filters.fields[field]) != 1 { + return false + } + + // try to match full name value to avoid O(N) regular expression matching + return fieldValues[source] +} + +// FuzzyMatch returns true if the source matches exactly one of the filters, +// or the source has one of the filters as a prefix. +func (filters Args) FuzzyMatch(field, source string) bool { + if filters.ExactMatch(field, source) { + return true + } + + fieldValues := filters.fields[field] + for prefix := range fieldValues { + if strings.HasPrefix(source, prefix) { + return true + } + } + return false +} + +// Include returns true if the name of the field to filter is in the filters. +func (filters Args) Include(field string) bool { + _, ok := filters.fields[field] + return ok +} + +// Validate ensures that all the fields in the filter are valid. +// It returns an error as soon as it finds an invalid field. +func (filters Args) Validate(accepted map[string]bool) error { + for name := range filters.fields { + if !accepted[name] { + return fmt.Errorf("Invalid filter '%s'", name) + } + } + return nil +} + +// WalkValues iterates over the list of filtered values for a field. +// It stops the iteration if it finds an error and it returns that error. +func (filters Args) WalkValues(field string, op func(value string) error) error { + if _, ok := filters.fields[field]; !ok { + return nil + } + for v := range filters.fields[field] { + if err := op(v); err != nil { + return err + } + } + return nil +} + +func deprecatedArgs(d map[string][]string) map[string]map[string]bool { + m := map[string]map[string]bool{} + for k, v := range d { + values := map[string]bool{} + for _, vv := range v { + values[vv] = true + } + m[k] = values + } + return m +} + +func convertArgsToSlice(f map[string]map[string]bool) map[string][]string { + m := map[string][]string{} + for k, v := range f { + values := []string{} + for kk := range v { + if v[kk] { + values = append(values, kk) + } + } + m[k] = values + } + return m +} diff --git a/components/engine/api/types/filters/parse_test.go b/components/engine/api/types/filters/parse_test.go new file mode 100644 index 0000000000..b2ed27b9ce --- /dev/null +++ b/components/engine/api/types/filters/parse_test.go @@ -0,0 +1,417 @@ +package filters + +import ( + "fmt" + "testing" +) + +func TestParseArgs(t *testing.T) { + // equivalent of `docker ps -f 'created=today' -f 'image.name=ubuntu*' -f 'image.name=*untu'` + flagArgs := []string{ + "created=today", + "image.name=ubuntu*", + "image.name=*untu", + } + var ( + args = NewArgs() + err error + ) + for i := range flagArgs { + args, err = ParseFlag(flagArgs[i], args) + if err != nil { + t.Errorf("failed to parse %s: %s", flagArgs[i], err) + } + } + if len(args.Get("created")) != 1 { + t.Errorf("failed to set this arg") + } + if len(args.Get("image.name")) != 2 { + t.Errorf("the args should have collapsed") + } +} + +func TestParseArgsEdgeCase(t *testing.T) { + var filters Args + args, err := ParseFlag("", filters) + if err != nil { + t.Fatal(err) + } + if args.Len() != 0 { + t.Fatalf("Expected an empty Args (map), got %v", args) + } + if args, err = ParseFlag("anything", args); err == nil || err != ErrBadFormat { + t.Fatalf("Expected ErrBadFormat, got %v", err) + } +} + +func TestToParam(t *testing.T) { + fields := map[string]map[string]bool{ + "created": {"today": true}, + "image.name": {"ubuntu*": true, "*untu": true}, + } + a := Args{fields: fields} + + _, err := ToParam(a) + if err != nil { + t.Errorf("failed to marshal the filters: %s", err) + } +} + +func TestToParamWithVersion(t *testing.T) { + fields := map[string]map[string]bool{ + "created": {"today": true}, + "image.name": {"ubuntu*": true, "*untu": true}, + } + a := Args{fields: fields} + + str1, err := ToParamWithVersion("1.21", a) + if err != nil { + t.Errorf("failed to marshal the filters with version < 1.22: %s", err) + } + str2, err := ToParamWithVersion("1.22", a) + if err != nil { + t.Errorf("failed to marshal the filters with version >= 1.22: %s", err) + } + if str1 != `{"created":["today"],"image.name":["*untu","ubuntu*"]}` && + str1 != `{"created":["today"],"image.name":["ubuntu*","*untu"]}` { + t.Errorf("incorrectly marshaled the filters: %s", str1) + } + if str2 != `{"created":{"today":true},"image.name":{"*untu":true,"ubuntu*":true}}` && + str2 != `{"created":{"today":true},"image.name":{"ubuntu*":true,"*untu":true}}` { + t.Errorf("incorrectly marshaled the filters: %s", str2) + } +} + +func TestFromParam(t *testing.T) { + invalids := []string{ + "anything", + "['a','list']", + "{'key': 'value'}", + `{"key": "value"}`, + } + valid := map[*Args][]string{ + &Args{fields: map[string]map[string]bool{"key": {"value": true}}}: { + `{"key": ["value"]}`, + `{"key": {"value": true}}`, + }, + &Args{fields: map[string]map[string]bool{"key": {"value1": true, "value2": true}}}: { + `{"key": ["value1", "value2"]}`, + `{"key": {"value1": true, "value2": true}}`, + }, + &Args{fields: map[string]map[string]bool{"key1": {"value1": true}, "key2": {"value2": true}}}: { + `{"key1": ["value1"], "key2": ["value2"]}`, + `{"key1": {"value1": true}, "key2": {"value2": true}}`, + }, + } + + for _, invalid := range invalids { + if _, err := FromParam(invalid); err == nil { + t.Fatalf("Expected an error with %v, got nothing", invalid) + } + } + + for expectedArgs, matchers := range valid { + for _, json := range matchers { + args, err := FromParam(json) + if err != nil { + t.Fatal(err) + } + if args.Len() != expectedArgs.Len() { + t.Fatalf("Expected %v, go %v", expectedArgs, args) + } + for key, expectedValues := range expectedArgs.fields { + values := args.Get(key) + + if len(values) != len(expectedValues) { + t.Fatalf("Expected %v, go %v", expectedArgs, args) + } + + for _, v := range values { + if !expectedValues[v] { + t.Fatalf("Expected %v, go %v", expectedArgs, args) + } + } + } + } + } +} + +func TestEmpty(t *testing.T) { + a := Args{} + v, err := ToParam(a) + if err != nil { + t.Errorf("failed to marshal the filters: %s", err) + } + v1, err := FromParam(v) + if err != nil { + t.Errorf("%s", err) + } + if a.Len() != v1.Len() { + t.Errorf("these should both be empty sets") + } +} + +func TestArgsMatchKVListEmptySources(t *testing.T) { + args := NewArgs() + if !args.MatchKVList("created", map[string]string{}) { + t.Fatalf("Expected true for (%v,created), got true", args) + } + + args = Args{map[string]map[string]bool{"created": {"today": true}}} + if args.MatchKVList("created", map[string]string{}) { + t.Fatalf("Expected false for (%v,created), got true", args) + } +} + +func TestArgsMatchKVList(t *testing.T) { + // Not empty sources + sources := map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + } + + matches := map[*Args]string{ + &Args{}: "field", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"today": true}, + "labels": map[string]bool{"key1": true}}, + }: "labels", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"today": true}, + "labels": map[string]bool{"key1=value1": true}}, + }: "labels", + } + + for args, field := range matches { + if args.MatchKVList(field, sources) != true { + t.Fatalf("Expected true for %v on %v, got false", sources, args) + } + } + + differs := map[*Args]string{ + &Args{map[string]map[string]bool{ + "created": map[string]bool{"today": true}}, + }: "created", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"today": true}, + "labels": map[string]bool{"key4": true}}, + }: "labels", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"today": true}, + "labels": map[string]bool{"key1=value3": true}}, + }: "labels", + } + + for args, field := range differs { + if args.MatchKVList(field, sources) != false { + t.Fatalf("Expected false for %v on %v, got true", sources, args) + } + } +} + +func TestArgsMatch(t *testing.T) { + source := "today" + + matches := map[*Args]string{ + &Args{}: "field", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"today": true}}, + }: "today", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"to*": true}}, + }: "created", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"to(.*)": true}}, + }: "created", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"tod": true}}, + }: "created", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"anyting": true, "to*": true}}, + }: "created", + } + + for args, field := range matches { + if args.Match(field, source) != true { + t.Fatalf("Expected true for %v on %v, got false", source, args) + } + } + + differs := map[*Args]string{ + &Args{map[string]map[string]bool{ + "created": map[string]bool{"tomorrow": true}}, + }: "created", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"to(day": true}}, + }: "created", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"tom(.*)": true}}, + }: "created", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"tom": true}}, + }: "created", + &Args{map[string]map[string]bool{ + "created": map[string]bool{"today1": true}, + "labels": map[string]bool{"today": true}}, + }: "created", + } + + for args, field := range differs { + if args.Match(field, source) != false { + t.Fatalf("Expected false for %v on %v, got true", source, args) + } + } +} + +func TestAdd(t *testing.T) { + f := NewArgs() + f.Add("status", "running") + v := f.fields["status"] + if len(v) != 1 || !v["running"] { + t.Fatalf("Expected to include a running status, got %v", v) + } + + f.Add("status", "paused") + if len(v) != 2 || !v["paused"] { + t.Fatalf("Expected to include a paused status, got %v", v) + } +} + +func TestDel(t *testing.T) { + f := NewArgs() + f.Add("status", "running") + f.Del("status", "running") + v := f.fields["status"] + if v["running"] { + t.Fatalf("Expected to not include a running status filter, got true") + } +} + +func TestLen(t *testing.T) { + f := NewArgs() + if f.Len() != 0 { + t.Fatalf("Expected to not include any field") + } + f.Add("status", "running") + if f.Len() != 1 { + t.Fatalf("Expected to include one field") + } +} + +func TestExactMatch(t *testing.T) { + f := NewArgs() + + if !f.ExactMatch("status", "running") { + t.Fatalf("Expected to match `running` when there are no filters, got false") + } + + f.Add("status", "running") + f.Add("status", "pause*") + + if !f.ExactMatch("status", "running") { + t.Fatalf("Expected to match `running` with one of the filters, got false") + } + + if f.ExactMatch("status", "paused") { + t.Fatalf("Expected to not match `paused` with one of the filters, got true") + } +} + +func TestOnlyOneExactMatch(t *testing.T) { + f := NewArgs() + + if !f.UniqueExactMatch("status", "running") { + t.Fatalf("Expected to match `running` when there are no filters, got false") + } + + f.Add("status", "running") + + if !f.UniqueExactMatch("status", "running") { + t.Fatalf("Expected to match `running` with one of the filters, got false") + } + + if f.UniqueExactMatch("status", "paused") { + t.Fatalf("Expected to not match `paused` with one of the filters, got true") + } + + f.Add("status", "pause") + if f.UniqueExactMatch("status", "running") { + t.Fatalf("Expected to not match only `running` with two filters, got true") + } +} + +func TestInclude(t *testing.T) { + f := NewArgs() + if f.Include("status") { + t.Fatalf("Expected to not include a status key, got true") + } + f.Add("status", "running") + if !f.Include("status") { + t.Fatalf("Expected to include a status key, got false") + } +} + +func TestValidate(t *testing.T) { + f := NewArgs() + f.Add("status", "running") + + valid := map[string]bool{ + "status": true, + "dangling": true, + } + + if err := f.Validate(valid); err != nil { + t.Fatal(err) + } + + f.Add("bogus", "running") + if err := f.Validate(valid); err == nil { + t.Fatalf("Expected to return an error, got nil") + } +} + +func TestWalkValues(t *testing.T) { + f := NewArgs() + f.Add("status", "running") + f.Add("status", "paused") + + f.WalkValues("status", func(value string) error { + if value != "running" && value != "paused" { + t.Fatalf("Unexpected value %s", value) + } + return nil + }) + + err := f.WalkValues("status", func(value string) error { + return fmt.Errorf("return") + }) + if err == nil { + t.Fatalf("Expected to get an error, got nil") + } + + err = f.WalkValues("foo", func(value string) error { + return fmt.Errorf("return") + }) + if err != nil { + t.Fatalf("Expected to not iterate when the field doesn't exist, got %v", err) + } +} + +func TestFuzzyMatch(t *testing.T) { + f := NewArgs() + f.Add("container", "foo") + + cases := map[string]bool{ + "foo": true, + "foobar": true, + "barfoo": false, + "bar": false, + } + for source, match := range cases { + got := f.FuzzyMatch("container", source) + if got != match { + t.Fatalf("Expected %v, got %v: %s", match, got, source) + } + } +} diff --git a/components/engine/api/types/mount/mount.go b/components/engine/api/types/mount/mount.go new file mode 100644 index 0000000000..5516ed09db --- /dev/null +++ b/components/engine/api/types/mount/mount.go @@ -0,0 +1,58 @@ +package mount + +// Type represents the type of a mount. +type Type string + +const ( + // TypeBind BIND + TypeBind Type = "bind" + // TypeVolume VOLUME + TypeVolume Type = "volume" +) + +// Mount represents a mount (volume). +type Mount struct { + Type Type `json:",omitempty"` + Source string `json:",omitempty"` + Target string `json:",omitempty"` + ReadOnly bool `json:",omitempty"` + + BindOptions *BindOptions `json:",omitempty"` + VolumeOptions *VolumeOptions `json:",omitempty"` +} + +// Propagation represents the propagation of a mount. +type Propagation string + +const ( + // PropagationRPrivate RPRIVATE + PropagationRPrivate Propagation = "rprivate" + // PropagationPrivate PRIVATE + PropagationPrivate Propagation = "private" + // PropagationRShared RSHARED + PropagationRShared Propagation = "rshared" + // PropagationShared SHARED + PropagationShared Propagation = "shared" + // PropagationRSlave RSLAVE + PropagationRSlave Propagation = "rslave" + // PropagationSlave SLAVE + PropagationSlave Propagation = "slave" +) + +// BindOptions defines options specific to mounts of type "bind". +type BindOptions struct { + Propagation Propagation `json:",omitempty"` +} + +// VolumeOptions represents the options for a mount of type volume. +type VolumeOptions struct { + NoCopy bool `json:",omitempty"` + Labels map[string]string `json:",omitempty"` + DriverConfig *Driver `json:",omitempty"` +} + +// Driver represents a volume driver. +type Driver struct { + Name string `json:",omitempty"` + Options map[string]string `json:",omitempty"` +} diff --git a/components/engine/api/types/network/network.go b/components/engine/api/types/network/network.go new file mode 100644 index 0000000000..47080b652e --- /dev/null +++ b/components/engine/api/types/network/network.go @@ -0,0 +1,53 @@ +package network + +// Address represents an IP address +type Address struct { + Addr string + PrefixLen int +} + +// IPAM represents IP Address Management +type IPAM struct { + Driver string + Options map[string]string //Per network IPAM driver options + Config []IPAMConfig +} + +// IPAMConfig represents IPAM configurations +type IPAMConfig struct { + Subnet string `json:",omitempty"` + IPRange string `json:",omitempty"` + Gateway string `json:",omitempty"` + AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` +} + +// EndpointIPAMConfig represents IPAM configurations for the endpoint +type EndpointIPAMConfig struct { + IPv4Address string `json:",omitempty"` + IPv6Address string `json:",omitempty"` + LinkLocalIPs []string `json:",omitempty"` +} + +// EndpointSettings stores the network endpoint details +type EndpointSettings struct { + // Configurations + IPAMConfig *EndpointIPAMConfig + Links []string + Aliases []string + // Operational data + NetworkID string + EndpointID string + Gateway string + IPAddress string + IPPrefixLen int + IPv6Gateway string + GlobalIPv6Address string + GlobalIPv6PrefixLen int + MacAddress string +} + +// NetworkingConfig represents the container's networking configuration for each of its interfaces +// Carries the networking configs specified in the `docker run` and `docker network connect` commands +type NetworkingConfig struct { + EndpointsConfig map[string]*EndpointSettings // Endpoint configs for each connecting network +} diff --git a/components/engine/api/types/plugin.go b/components/engine/api/types/plugin.go new file mode 100644 index 0000000000..601c0ac12a --- /dev/null +++ b/components/engine/api/types/plugin.go @@ -0,0 +1,170 @@ +// +build experimental + +package types + +import ( + "encoding/json" + "fmt" +) + +// PluginInstallOptions holds parameters to install a plugin. +type PluginInstallOptions struct { + Disabled bool + AcceptAllPermissions bool + RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry + PrivilegeFunc RequestPrivilegeFunc + AcceptPermissionsFunc func(PluginPrivileges) (bool, error) +} + +// PluginConfig represents the values of settings potentially modifiable by a user +type PluginConfig struct { + Mounts []PluginMount + Env []string + Args []string + Devices []PluginDevice +} + +// Plugin represents a Docker plugin for the remote API +type Plugin struct { + ID string `json:"Id,omitempty"` + Name string + Tag string + // Enabled is true when the plugin is running, is false when the plugin is not running, only installed. + Enabled bool + Config PluginConfig + Manifest PluginManifest +} + +// PluginsListResponse contains the response for the remote API +type PluginsListResponse []*Plugin + +const ( + authzDriver = "AuthzDriver" + graphDriver = "GraphDriver" + ipamDriver = "IpamDriver" + networkDriver = "NetworkDriver" + volumeDriver = "VolumeDriver" +) + +// PluginInterfaceType represents a type that a plugin implements. +type PluginInterfaceType struct { + Prefix string // This is always "docker" + Capability string // Capability should be validated against the above list. + Version string // Plugin API version. Depends on the capability +} + +// UnmarshalJSON implements json.Unmarshaler for PluginInterfaceType +func (t *PluginInterfaceType) UnmarshalJSON(p []byte) error { + versionIndex := len(p) + prefixIndex := 0 + if len(p) < 2 || p[0] != '"' || p[len(p)-1] != '"' { + return fmt.Errorf("%q is not a plugin interface type", p) + } + p = p[1 : len(p)-1] +loop: + for i, b := range p { + switch b { + case '.': + prefixIndex = i + case '/': + versionIndex = i + break loop + } + } + t.Prefix = string(p[:prefixIndex]) + t.Capability = string(p[prefixIndex+1 : versionIndex]) + if versionIndex < len(p) { + t.Version = string(p[versionIndex+1:]) + } + return nil +} + +// MarshalJSON implements json.Marshaler for PluginInterfaceType +func (t *PluginInterfaceType) MarshalJSON() ([]byte, error) { + return json.Marshal(t.String()) +} + +// String implements fmt.Stringer for PluginInterfaceType +func (t PluginInterfaceType) String() string { + return fmt.Sprintf("%s.%s/%s", t.Prefix, t.Capability, t.Version) +} + +// PluginInterface describes the interface between Docker and plugin +type PluginInterface struct { + Types []PluginInterfaceType + Socket string +} + +// PluginSetting is to be embedded in other structs, if they are supposed to be +// modifiable by the user. +type PluginSetting struct { + Name string + Description string + Settable []string +} + +// PluginNetwork represents the network configuration for a plugin +type PluginNetwork struct { + Type string +} + +// PluginMount represents the mount configuration for a plugin +type PluginMount struct { + PluginSetting + Source *string + Destination string + Type string + Options []string +} + +// PluginEnv represents an environment variable for a plugin +type PluginEnv struct { + PluginSetting + Value *string +} + +// PluginArgs represents the command line arguments for a plugin +type PluginArgs struct { + PluginSetting + Value []string +} + +// PluginDevice represents a device for a plugin +type PluginDevice struct { + PluginSetting + Path *string +} + +// PluginUser represents the user for the plugin's process +type PluginUser struct { + UID uint32 `json:"Uid,omitempty"` + GID uint32 `json:"Gid,omitempty"` +} + +// PluginManifest represents the manifest of a plugin +type PluginManifest struct { + ManifestVersion string + Description string + Documentation string + Interface PluginInterface + Entrypoint []string + Workdir string + User PluginUser `json:",omitempty"` + Network PluginNetwork + Capabilities []string + Mounts []PluginMount + Devices []PluginDevice + Env []PluginEnv + Args PluginArgs +} + +// PluginPrivilege describes a permission the user has to accept +// upon installing a plugin. +type PluginPrivilege struct { + Name string + Description string + Value []string +} + +// PluginPrivileges is a list of PluginPrivilege +type PluginPrivileges []PluginPrivilege diff --git a/components/engine/api/types/reference/image_reference.go b/components/engine/api/types/reference/image_reference.go new file mode 100644 index 0000000000..be9cf8ebed --- /dev/null +++ b/components/engine/api/types/reference/image_reference.go @@ -0,0 +1,34 @@ +package reference + +import ( + distreference "github.com/docker/distribution/reference" +) + +// Parse parses the given references and returns the repository and +// tag (if present) from it. If there is an error during parsing, it will +// return an error. +func Parse(ref string) (string, string, error) { + distributionRef, err := distreference.ParseNamed(ref) + if err != nil { + return "", "", err + } + + tag := GetTagFromNamedRef(distributionRef) + return distributionRef.Name(), tag, nil +} + +// GetTagFromNamedRef returns a tag from the specified reference. +// This function is necessary as long as the docker "server" api makes the distinction between repository +// and tags. +func GetTagFromNamedRef(ref distreference.Named) string { + var tag string + switch x := ref.(type) { + case distreference.Digested: + tag = x.Digest().String() + case distreference.NamedTagged: + tag = x.Tag() + default: + tag = "latest" + } + return tag +} diff --git a/components/engine/api/types/reference/image_reference_test.go b/components/engine/api/types/reference/image_reference_test.go new file mode 100644 index 0000000000..61fb676b6c --- /dev/null +++ b/components/engine/api/types/reference/image_reference_test.go @@ -0,0 +1,72 @@ +package reference + +import ( + "testing" +) + +func TestParse(t *testing.T) { + testCases := []struct { + ref string + expectedName string + expectedTag string + expectedError bool + }{ + { + ref: "", + expectedName: "", + expectedTag: "", + expectedError: true, + }, + { + ref: "repository", + expectedName: "repository", + expectedTag: "latest", + expectedError: false, + }, + { + ref: "repository:tag", + expectedName: "repository", + expectedTag: "tag", + expectedError: false, + }, + { + ref: "test.com/repository", + expectedName: "test.com/repository", + expectedTag: "latest", + expectedError: false, + }, + { + ref: "test.com:5000/test/repository", + expectedName: "test.com:5000/test/repository", + expectedTag: "latest", + expectedError: false, + }, + { + ref: "test.com:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + expectedName: "test.com:5000/repo", + expectedTag: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + expectedError: false, + }, + { + ref: "test.com:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + expectedName: "test.com:5000/repo", + expectedTag: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + expectedError: false, + }, + } + + for _, c := range testCases { + name, tag, err := Parse(c.ref) + if err != nil && c.expectedError { + continue + } else if err != nil { + t.Fatalf("error with %s: %s", c.ref, err.Error()) + } + if name != c.expectedName { + t.Fatalf("expected name %s, got %s", c.expectedName, name) + } + if tag != c.expectedTag { + t.Fatalf("expected tag %s, got %s", c.expectedTag, tag) + } + } +} diff --git a/components/engine/api/types/registry/registry.go b/components/engine/api/types/registry/registry.go new file mode 100644 index 0000000000..28fafab901 --- /dev/null +++ b/components/engine/api/types/registry/registry.go @@ -0,0 +1,104 @@ +package registry + +import ( + "encoding/json" + "net" +) + +// ServiceConfig stores daemon registry services configuration. +type ServiceConfig struct { + InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"` + IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"` + Mirrors []string +} + +// NetIPNet is the net.IPNet type, which can be marshalled and +// unmarshalled to JSON +type NetIPNet net.IPNet + +// String returns the CIDR notation of ipnet +func (ipnet *NetIPNet) String() string { + return (*net.IPNet)(ipnet).String() +} + +// MarshalJSON returns the JSON representation of the IPNet +func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) { + return json.Marshal((*net.IPNet)(ipnet).String()) +} + +// UnmarshalJSON sets the IPNet from a byte array of JSON +func (ipnet *NetIPNet) UnmarshalJSON(b []byte) (err error) { + var ipnetStr string + if err = json.Unmarshal(b, &ipnetStr); err == nil { + var cidr *net.IPNet + if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil { + *ipnet = NetIPNet(*cidr) + } + } + return +} + +// IndexInfo contains information about a registry +// +// RepositoryInfo Examples: +// { +// "Index" : { +// "Name" : "docker.io", +// "Mirrors" : ["https://registry-2.docker.io/v1/", "https://registry-3.docker.io/v1/"], +// "Secure" : true, +// "Official" : true, +// }, +// "RemoteName" : "library/debian", +// "LocalName" : "debian", +// "CanonicalName" : "docker.io/debian" +// "Official" : true, +// } +// +// { +// "Index" : { +// "Name" : "127.0.0.1:5000", +// "Mirrors" : [], +// "Secure" : false, +// "Official" : false, +// }, +// "RemoteName" : "user/repo", +// "LocalName" : "127.0.0.1:5000/user/repo", +// "CanonicalName" : "127.0.0.1:5000/user/repo", +// "Official" : false, +// } +type IndexInfo struct { + // Name is the name of the registry, such as "docker.io" + Name string + // Mirrors is a list of mirrors, expressed as URIs + Mirrors []string + // Secure is set to false if the registry is part of the list of + // insecure registries. Insecure registries accept HTTP and/or accept + // HTTPS with certificates from unknown CAs. + Secure bool + // Official indicates whether this is an official registry + Official bool +} + +// SearchResult describes a search result returned from a registry +type SearchResult struct { + // StarCount indicates the number of stars this repository has + StarCount int `json:"star_count"` + // IsOfficial is true if the result is from an official repository. + IsOfficial bool `json:"is_official"` + // Name is the name of the repository + Name string `json:"name"` + // IsAutomated indicates whether the result is automated + IsAutomated bool `json:"is_automated"` + // Description is a textual description of the repository + Description string `json:"description"` +} + +// SearchResults lists a collection search results returned from a registry +type SearchResults struct { + // Query contains the query string that generated the search results + Query string `json:"query"` + // NumResults indicates the number of results the query returned + NumResults int `json:"num_results"` + // Results is a slice containing the actual results for the search + Results []SearchResult `json:"results"` +} diff --git a/components/engine/api/types/seccomp.go b/components/engine/api/types/seccomp.go new file mode 100644 index 0000000000..4f02ef36b8 --- /dev/null +++ b/components/engine/api/types/seccomp.go @@ -0,0 +1,93 @@ +package types + +// Seccomp represents the config for a seccomp profile for syscall restriction. +type Seccomp struct { + DefaultAction Action `json:"defaultAction"` + // Architectures is kept to maintain backward compatibility with the old + // seccomp profile. + Architectures []Arch `json:"architectures,omitempty"` + ArchMap []Architecture `json:"archMap,omitempty"` + Syscalls []*Syscall `json:"syscalls"` +} + +// Architecture is used to represent an specific architecture +// and its sub-architectures +type Architecture struct { + Arch Arch `json:"architecture"` + SubArches []Arch `json:"subArchitectures"` +} + +// Arch used for architectures +type Arch string + +// Additional architectures permitted to be used for system calls +// By default only the native architecture of the kernel is permitted +const ( + ArchX86 Arch = "SCMP_ARCH_X86" + ArchX86_64 Arch = "SCMP_ARCH_X86_64" + ArchX32 Arch = "SCMP_ARCH_X32" + ArchARM Arch = "SCMP_ARCH_ARM" + ArchAARCH64 Arch = "SCMP_ARCH_AARCH64" + ArchMIPS Arch = "SCMP_ARCH_MIPS" + ArchMIPS64 Arch = "SCMP_ARCH_MIPS64" + ArchMIPS64N32 Arch = "SCMP_ARCH_MIPS64N32" + ArchMIPSEL Arch = "SCMP_ARCH_MIPSEL" + ArchMIPSEL64 Arch = "SCMP_ARCH_MIPSEL64" + ArchMIPSEL64N32 Arch = "SCMP_ARCH_MIPSEL64N32" + ArchPPC Arch = "SCMP_ARCH_PPC" + ArchPPC64 Arch = "SCMP_ARCH_PPC64" + ArchPPC64LE Arch = "SCMP_ARCH_PPC64LE" + ArchS390 Arch = "SCMP_ARCH_S390" + ArchS390X Arch = "SCMP_ARCH_S390X" +) + +// Action taken upon Seccomp rule match +type Action string + +// Define actions for Seccomp rules +const ( + ActKill Action = "SCMP_ACT_KILL" + ActTrap Action = "SCMP_ACT_TRAP" + ActErrno Action = "SCMP_ACT_ERRNO" + ActTrace Action = "SCMP_ACT_TRACE" + ActAllow Action = "SCMP_ACT_ALLOW" +) + +// Operator used to match syscall arguments in Seccomp +type Operator string + +// Define operators for syscall arguments in Seccomp +const ( + OpNotEqual Operator = "SCMP_CMP_NE" + OpLessThan Operator = "SCMP_CMP_LT" + OpLessEqual Operator = "SCMP_CMP_LE" + OpEqualTo Operator = "SCMP_CMP_EQ" + OpGreaterEqual Operator = "SCMP_CMP_GE" + OpGreaterThan Operator = "SCMP_CMP_GT" + OpMaskedEqual Operator = "SCMP_CMP_MASKED_EQ" +) + +// Arg used for matching specific syscall arguments in Seccomp +type Arg struct { + Index uint `json:"index"` + Value uint64 `json:"value"` + ValueTwo uint64 `json:"valueTwo"` + Op Operator `json:"op"` +} + +// Filter is used to conditionally apply Seccomp rules +type Filter struct { + Caps []string `json:"caps,omitempty"` + Arches []string `json:"arches,omitempty"` +} + +// Syscall is used to match a group of syscalls in Seccomp +type Syscall struct { + Name string `json:"name,omitempty"` + Names []string `json:"names,omitempty"` + Action Action `json:"action"` + Args []*Arg `json:"args"` + Comment string `json:"comment"` + Includes Filter `json:"includes"` + Excludes Filter `json:"excludes"` +} diff --git a/components/engine/api/types/stats.go b/components/engine/api/types/stats.go new file mode 100644 index 0000000000..b420ebe7f6 --- /dev/null +++ b/components/engine/api/types/stats.go @@ -0,0 +1,115 @@ +// Package types is used for API stability in the types and response to the +// consumers of the API stats endpoint. +package types + +import "time" + +// ThrottlingData stores CPU throttling stats of one running container +type ThrottlingData struct { + // Number of periods with throttling active + Periods uint64 `json:"periods"` + // Number of periods when the container hits its throttling limit. + ThrottledPeriods uint64 `json:"throttled_periods"` + // Aggregate time the container was throttled for in nanoseconds. + ThrottledTime uint64 `json:"throttled_time"` +} + +// CPUUsage stores All CPU stats aggregated since container inception. +type CPUUsage struct { + // Total CPU time consumed. + // Units: nanoseconds. + TotalUsage uint64 `json:"total_usage"` + // Total CPU time consumed per core. + // Units: nanoseconds. + PercpuUsage []uint64 `json:"percpu_usage"` + // Time spent by tasks of the cgroup in kernel mode. + // Units: nanoseconds. + UsageInKernelmode uint64 `json:"usage_in_kernelmode"` + // Time spent by tasks of the cgroup in user mode. + // Units: nanoseconds. + UsageInUsermode uint64 `json:"usage_in_usermode"` +} + +// CPUStats aggregates and wraps all CPU related info of container +type CPUStats struct { + CPUUsage CPUUsage `json:"cpu_usage"` + SystemUsage uint64 `json:"system_cpu_usage"` + ThrottlingData ThrottlingData `json:"throttling_data,omitempty"` +} + +// MemoryStats aggregates All memory stats since container inception +type MemoryStats struct { + // current res_counter usage for memory + Usage uint64 `json:"usage"` + // maximum usage ever recorded. + MaxUsage uint64 `json:"max_usage"` + // TODO(vishh): Export these as stronger types. + // all the stats exported via memory.stat. + Stats map[string]uint64 `json:"stats"` + // number of times memory usage hits limits. + Failcnt uint64 `json:"failcnt"` + Limit uint64 `json:"limit"` +} + +// BlkioStatEntry is one small entity to store a piece of Blkio stats +// TODO Windows: This can be factored out +type BlkioStatEntry struct { + Major uint64 `json:"major"` + Minor uint64 `json:"minor"` + Op string `json:"op"` + Value uint64 `json:"value"` +} + +// BlkioStats stores All IO service stats for data read and write +// TODO Windows: This can be factored out +type BlkioStats struct { + // number of bytes transferred to and from the block device + IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"` + IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive"` + IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive"` + IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive"` + IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive"` + IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive"` + IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive"` + SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"` +} + +// NetworkStats aggregates All network stats of one container +// TODO Windows: This will require refactoring +type NetworkStats struct { + RxBytes uint64 `json:"rx_bytes"` + RxPackets uint64 `json:"rx_packets"` + RxErrors uint64 `json:"rx_errors"` + RxDropped uint64 `json:"rx_dropped"` + TxBytes uint64 `json:"tx_bytes"` + TxPackets uint64 `json:"tx_packets"` + TxErrors uint64 `json:"tx_errors"` + TxDropped uint64 `json:"tx_dropped"` +} + +// PidsStats contains the stats of a container's pids +type PidsStats struct { + // Current is the number of pids in the cgroup + Current uint64 `json:"current,omitempty"` + // Limit is the hard limit on the number of pids in the cgroup. + // A "Limit" of 0 means that there is no limit. + Limit uint64 `json:"limit,omitempty"` +} + +// Stats is Ultimate struct aggregating all types of stats of one container +type Stats struct { + Read time.Time `json:"read"` + PreCPUStats CPUStats `json:"precpu_stats,omitempty"` + CPUStats CPUStats `json:"cpu_stats,omitempty"` + MemoryStats MemoryStats `json:"memory_stats,omitempty"` + BlkioStats BlkioStats `json:"blkio_stats,omitempty"` + PidsStats PidsStats `json:"pids_stats,omitempty"` +} + +// StatsJSON is newly used Networks +type StatsJSON struct { + Stats + + // Networks request version >=1.21 + Networks map[string]NetworkStats `json:"networks,omitempty"` +} diff --git a/components/engine/api/types/strslice/strslice.go b/components/engine/api/types/strslice/strslice.go new file mode 100644 index 0000000000..bad493fb89 --- /dev/null +++ b/components/engine/api/types/strslice/strslice.go @@ -0,0 +1,30 @@ +package strslice + +import "encoding/json" + +// StrSlice represents a string or an array of strings. +// We need to override the json decoder to accept both options. +type StrSlice []string + +// UnmarshalJSON decodes the byte slice whether it's a string or an array of +// strings. This method is needed to implement json.Unmarshaler. +func (e *StrSlice) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + // With no input, we preserve the existing value by returning nil and + // leaving the target alone. This allows defining default values for + // the type. + return nil + } + + p := make([]string, 0, 1) + if err := json.Unmarshal(b, &p); err != nil { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + p = append(p, s) + } + + *e = p + return nil +} diff --git a/components/engine/api/types/strslice/strslice_test.go b/components/engine/api/types/strslice/strslice_test.go new file mode 100644 index 0000000000..1163b3652c --- /dev/null +++ b/components/engine/api/types/strslice/strslice_test.go @@ -0,0 +1,86 @@ +package strslice + +import ( + "encoding/json" + "reflect" + "testing" +) + +func TestStrSliceMarshalJSON(t *testing.T) { + for _, testcase := range []struct { + input StrSlice + expected string + }{ + // MADNESS(stevvooe): No clue why nil would be "" but empty would be + // "null". Had to make a change here that may affect compatibility. + {input: nil, expected: "null"}, + {StrSlice{}, "[]"}, + {StrSlice{"/bin/sh", "-c", "echo"}, `["/bin/sh","-c","echo"]`}, + } { + data, err := json.Marshal(testcase.input) + if err != nil { + t.Fatal(err) + } + if string(data) != testcase.expected { + t.Fatalf("%#v: expected %v, got %v", testcase.input, testcase.expected, string(data)) + } + } +} + +func TestStrSliceUnmarshalJSON(t *testing.T) { + parts := map[string][]string{ + "": {"default", "values"}, + "[]": {}, + `["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"}, + } + for json, expectedParts := range parts { + strs := StrSlice{"default", "values"} + if err := strs.UnmarshalJSON([]byte(json)); err != nil { + t.Fatal(err) + } + + actualParts := []string(strs) + if !reflect.DeepEqual(actualParts, expectedParts) { + t.Fatalf("%#v: expected %v, got %v", json, expectedParts, actualParts) + } + + } +} + +func TestStrSliceUnmarshalString(t *testing.T) { + var e StrSlice + echo, err := json.Marshal("echo") + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(echo, &e); err != nil { + t.Fatal(err) + } + + if len(e) != 1 { + t.Fatalf("expected 1 element after unmarshal: %q", e) + } + + if e[0] != "echo" { + t.Fatalf("expected `echo`, got: %q", e[0]) + } +} + +func TestStrSliceUnmarshalSlice(t *testing.T) { + var e StrSlice + echo, err := json.Marshal([]string{"echo"}) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(echo, &e); err != nil { + t.Fatal(err) + } + + if len(e) != 1 { + t.Fatalf("expected 1 element after unmarshal: %q", e) + } + + if e[0] != "echo" { + t.Fatalf("expected `echo`, got: %q", e[0]) + } +} diff --git a/components/engine/api/types/swarm/common.go b/components/engine/api/types/swarm/common.go new file mode 100644 index 0000000000..b87f545369 --- /dev/null +++ b/components/engine/api/types/swarm/common.go @@ -0,0 +1,21 @@ +package swarm + +import "time" + +// Version represent the internal object version. +type Version struct { + Index uint64 `json:",omitempty"` +} + +// Meta is base object inherited by most of the other once. +type Meta struct { + Version Version `json:",omitempty"` + CreatedAt time.Time `json:",omitempty"` + UpdatedAt time.Time `json:",omitempty"` +} + +// Annotations represents how to describe an object. +type Annotations struct { + Name string `json:",omitempty"` + Labels map[string]string `json:",omitempty"` +} diff --git a/components/engine/api/types/swarm/container.go b/components/engine/api/types/swarm/container.go new file mode 100644 index 0000000000..4a84f2e53f --- /dev/null +++ b/components/engine/api/types/swarm/container.go @@ -0,0 +1,22 @@ +package swarm + +import ( + "time" + + "github.com/docker/docker/api/types/mount" +) + +// ContainerSpec represents the spec of a container. +type ContainerSpec struct { + Image string `json:",omitempty"` + Labels map[string]string `json:",omitempty"` + Command []string `json:",omitempty"` + Args []string `json:",omitempty"` + Env []string `json:",omitempty"` + Dir string `json:",omitempty"` + User string `json:",omitempty"` + Groups []string `json:",omitempty"` + TTY bool `json:",omitempty"` + Mounts []mount.Mount `json:",omitempty"` + StopGracePeriod *time.Duration `json:",omitempty"` +} diff --git a/components/engine/api/types/swarm/network.go b/components/engine/api/types/swarm/network.go new file mode 100644 index 0000000000..0af0ce1daa --- /dev/null +++ b/components/engine/api/types/swarm/network.go @@ -0,0 +1,100 @@ +package swarm + +// Endpoint represents an endpoint. +type Endpoint struct { + Spec EndpointSpec `json:",omitempty"` + Ports []PortConfig `json:",omitempty"` + VirtualIPs []EndpointVirtualIP `json:",omitempty"` +} + +// EndpointSpec represents the spec of an endpoint. +type EndpointSpec struct { + Mode ResolutionMode `json:",omitempty"` + Ports []PortConfig `json:",omitempty"` +} + +// ResolutionMode represents a resolution mode. +type ResolutionMode string + +const ( + // ResolutionModeVIP VIP + ResolutionModeVIP ResolutionMode = "vip" + // ResolutionModeDNSRR DNSRR + ResolutionModeDNSRR ResolutionMode = "dnsrr" +) + +// PortConfig represents the config of a port. +type PortConfig struct { + Name string `json:",omitempty"` + Protocol PortConfigProtocol `json:",omitempty"` + TargetPort uint32 `json:",omitempty"` + PublishedPort uint32 `json:",omitempty"` +} + +// PortConfigProtocol represents the protocol of a port. +type PortConfigProtocol string + +const ( + // TODO(stevvooe): These should be used generally, not just for PortConfig. + + // PortConfigProtocolTCP TCP + PortConfigProtocolTCP PortConfigProtocol = "tcp" + // PortConfigProtocolUDP UDP + PortConfigProtocolUDP PortConfigProtocol = "udp" +) + +// EndpointVirtualIP represents the virtual ip of a port. +type EndpointVirtualIP struct { + NetworkID string `json:",omitempty"` + Addr string `json:",omitempty"` +} + +// Network represents a network. +type Network struct { + ID string + Meta + Spec NetworkSpec `json:",omitempty"` + DriverState Driver `json:",omitempty"` + IPAMOptions *IPAMOptions `json:",omitempty"` +} + +// NetworkSpec represents the spec of a network. +type NetworkSpec struct { + Annotations + DriverConfiguration *Driver `json:",omitempty"` + IPv6Enabled bool `json:",omitempty"` + Internal bool `json:",omitempty"` + Attachable bool `json:",omitempty"` + IPAMOptions *IPAMOptions `json:",omitempty"` +} + +// NetworkAttachmentConfig represents the configuration of a network attachment. +type NetworkAttachmentConfig struct { + Target string `json:",omitempty"` + Aliases []string `json:",omitempty"` +} + +// NetworkAttachment represents a network attachment. +type NetworkAttachment struct { + Network Network `json:",omitempty"` + Addresses []string `json:",omitempty"` +} + +// IPAMOptions represents ipam options. +type IPAMOptions struct { + Driver Driver `json:",omitempty"` + Configs []IPAMConfig `json:",omitempty"` +} + +// IPAMConfig represents ipam configuration. +type IPAMConfig struct { + Subnet string `json:",omitempty"` + Range string `json:",omitempty"` + Gateway string `json:",omitempty"` +} + +// Driver represents a network driver. +type Driver struct { + Name string `json:",omitempty"` + Options map[string]string `json:",omitempty"` +} diff --git a/components/engine/api/types/swarm/node.go b/components/engine/api/types/swarm/node.go new file mode 100644 index 0000000000..9987662a58 --- /dev/null +++ b/components/engine/api/types/swarm/node.go @@ -0,0 +1,107 @@ +package swarm + +// Node represents a node. +type Node struct { + ID string + Meta + + Spec NodeSpec `json:",omitempty"` + Description NodeDescription `json:",omitempty"` + Status NodeStatus `json:",omitempty"` + ManagerStatus *ManagerStatus `json:",omitempty"` +} + +// NodeSpec represents the spec of a node. +type NodeSpec struct { + Annotations + Role NodeRole `json:",omitempty"` + Availability NodeAvailability `json:",omitempty"` +} + +// NodeRole represents the role of a node. +type NodeRole string + +const ( + // NodeRoleWorker WORKER + NodeRoleWorker NodeRole = "worker" + // NodeRoleManager MANAGER + NodeRoleManager NodeRole = "manager" +) + +// NodeAvailability represents the availability of a node. +type NodeAvailability string + +const ( + // NodeAvailabilityActive ACTIVE + NodeAvailabilityActive NodeAvailability = "active" + // NodeAvailabilityPause PAUSE + NodeAvailabilityPause NodeAvailability = "pause" + // NodeAvailabilityDrain DRAIN + NodeAvailabilityDrain NodeAvailability = "drain" +) + +// NodeDescription represents the description of a node. +type NodeDescription struct { + Hostname string `json:",omitempty"` + Platform Platform `json:",omitempty"` + Resources Resources `json:",omitempty"` + Engine EngineDescription `json:",omitempty"` +} + +// Platform represents the platfrom (Arch/OS). +type Platform struct { + Architecture string `json:",omitempty"` + OS string `json:",omitempty"` +} + +// EngineDescription represents the description of an engine. +type EngineDescription struct { + EngineVersion string `json:",omitempty"` + Labels map[string]string `json:",omitempty"` + Plugins []PluginDescription `json:",omitempty"` +} + +// PluginDescription represents the description of an engine plugin. +type PluginDescription struct { + Type string `json:",omitempty"` + Name string `json:",omitempty"` +} + +// NodeStatus represents the status of a node. +type NodeStatus struct { + State NodeState `json:",omitempty"` + Message string `json:",omitempty"` +} + +// Reachability represents the reachability of a node. +type Reachability string + +const ( + // ReachabilityUnknown UNKNOWN + ReachabilityUnknown Reachability = "unknown" + // ReachabilityUnreachable UNREACHABLE + ReachabilityUnreachable Reachability = "unreachable" + // ReachabilityReachable REACHABLE + ReachabilityReachable Reachability = "reachable" +) + +// ManagerStatus represents the status of a manager. +type ManagerStatus struct { + Leader bool `json:",omitempty"` + Reachability Reachability `json:",omitempty"` + Addr string `json:",omitempty"` +} + +// NodeState represents the state of a node. +type NodeState string + +const ( + // NodeStateUnknown UNKNOWN + NodeStateUnknown NodeState = "unknown" + // NodeStateDown DOWN + NodeStateDown NodeState = "down" + // NodeStateReady READY + NodeStateReady NodeState = "ready" + // NodeStateDisconnected DISCONNECTED + NodeStateDisconnected NodeState = "disconnected" +) diff --git a/components/engine/api/types/swarm/service.go b/components/engine/api/types/swarm/service.go new file mode 100644 index 0000000000..d679c45406 --- /dev/null +++ b/components/engine/api/types/swarm/service.go @@ -0,0 +1,77 @@ +package swarm + +import "time" + +// Service represents a service. +type Service struct { + ID string + Meta + Spec ServiceSpec `json:",omitempty"` + Endpoint Endpoint `json:",omitempty"` + UpdateStatus UpdateStatus `json:",omitempty"` +} + +// ServiceSpec represents the spec of a service. +type ServiceSpec struct { + Annotations + + // TaskTemplate defines how the service should construct new tasks when + // orchestrating this service. + TaskTemplate TaskSpec `json:",omitempty"` + Mode ServiceMode `json:",omitempty"` + UpdateConfig *UpdateConfig `json:",omitempty"` + + // Networks field in ServiceSpec is being deprecated. Users of + // engine-api should start using the same field in + // TaskSpec. This field will be removed in future releases. + Networks []NetworkAttachmentConfig `json:",omitempty"` + EndpointSpec *EndpointSpec `json:",omitempty"` +} + +// ServiceMode represents the mode of a service. +type ServiceMode struct { + Replicated *ReplicatedService `json:",omitempty"` + Global *GlobalService `json:",omitempty"` +} + +// UpdateState is the state of a service update. +type UpdateState string + +const ( + // UpdateStateUpdating is the updating state. + UpdateStateUpdating UpdateState = "updating" + // UpdateStatePaused is the paused state. + UpdateStatePaused UpdateState = "paused" + // UpdateStateCompleted is the completed state. + UpdateStateCompleted UpdateState = "completed" +) + +// UpdateStatus reports the status of a service update. +type UpdateStatus struct { + State UpdateState `json:",omitempty"` + StartedAt time.Time `json:",omitempty"` + CompletedAt time.Time `json:",omitempty"` + Message string `json:",omitempty"` +} + +// ReplicatedService is a kind of ServiceMode. +type ReplicatedService struct { + Replicas *uint64 `json:",omitempty"` +} + +// GlobalService is a kind of ServiceMode. +type GlobalService struct{} + +const ( + // UpdateFailureActionPause PAUSE + UpdateFailureActionPause = "pause" + // UpdateFailureActionContinue CONTINUE + UpdateFailureActionContinue = "continue" +) + +// UpdateConfig represents the update configuration. +type UpdateConfig struct { + Parallelism uint64 `json:",omitempty"` + Delay time.Duration `json:",omitempty"` + FailureAction string `json:",omitempty"` +} diff --git a/components/engine/api/types/swarm/swarm.go b/components/engine/api/types/swarm/swarm.go new file mode 100644 index 0000000000..23b2e6affe --- /dev/null +++ b/components/engine/api/types/swarm/swarm.go @@ -0,0 +1,155 @@ +package swarm + +import "time" + +// ClusterInfo represents info about the cluster for outputing in "info" +// it contains the same information as "Swarm", but without the JoinTokens +type ClusterInfo struct { + ID string + Meta + Spec Spec +} + +// Swarm represents a swarm. +type Swarm struct { + ClusterInfo + JoinTokens JoinTokens +} + +// JoinTokens contains the tokens workers and managers need to join the swarm. +type JoinTokens struct { + Worker string + Manager string +} + +// Spec represents the spec of a swarm. +type Spec struct { + Annotations + + Orchestration OrchestrationConfig `json:",omitempty"` + Raft RaftConfig `json:",omitempty"` + Dispatcher DispatcherConfig `json:",omitempty"` + CAConfig CAConfig `json:",omitempty"` + TaskDefaults TaskDefaults `json:",omitempty"` +} + +// OrchestrationConfig represents orchestration configuration. +type OrchestrationConfig struct { + TaskHistoryRetentionLimit int64 `json:",omitempty"` +} + +// TaskDefaults parameterizes cluster-level task creation with default values. +type TaskDefaults struct { + // LogDriver selects the log driver to use for tasks created in the + // orchestrator if unspecified by a service. + // + // Updating this value will only have an affect on new tasks. Old tasks + // will continue use their previously configured log driver until + // recreated. + LogDriver *Driver `json:",omitempty"` +} + +// RaftConfig represents raft configuration. +type RaftConfig struct { + SnapshotInterval uint64 `json:",omitempty"` + KeepOldSnapshots uint64 `json:",omitempty"` + LogEntriesForSlowFollowers uint64 `json:",omitempty"` + + // ElectionTick is the number of ticks that a follower will wait for a message + // from the leader before becoming a candidate and starting an election. + // ElectionTick must be greater than HeartbeatTick. + // + // A tick currently defaults to one second, so these translate directly to + // seconds currently, but this is NOT guaranteed. + ElectionTick int + + // HeartbeatTick is the number of ticks between heartbeats. Every + // HeartbeatTick ticks, the leader will send a heartbeat to the + // followers. + // + // A tick currently defaults to one second, so these translate directly to + // seconds currently, but this is NOT guaranteed. + HeartbeatTick int +} + +// DispatcherConfig represents dispatcher configuration. +type DispatcherConfig struct { + HeartbeatPeriod time.Duration `json:",omitempty"` +} + +// CAConfig represents CA configuration. +type CAConfig struct { + NodeCertExpiry time.Duration `json:",omitempty"` + ExternalCAs []*ExternalCA `json:",omitempty"` +} + +// ExternalCAProtocol represents type of external CA. +type ExternalCAProtocol string + +// ExternalCAProtocolCFSSL CFSSL +const ExternalCAProtocolCFSSL ExternalCAProtocol = "cfssl" + +// ExternalCA defines external CA to be used by the cluster. +type ExternalCA struct { + Protocol ExternalCAProtocol + URL string + Options map[string]string `json:",omitempty"` +} + +// InitRequest is the request used to init a swarm. +type InitRequest struct { + ListenAddr string + AdvertiseAddr string + ForceNewCluster bool + Spec Spec +} + +// JoinRequest is the request used to join a swarm. +type JoinRequest struct { + ListenAddr string + AdvertiseAddr string + RemoteAddrs []string + JoinToken string // accept by secret +} + +// LocalNodeState represents the state of the local node. +type LocalNodeState string + +const ( + // LocalNodeStateInactive INACTIVE + LocalNodeStateInactive LocalNodeState = "inactive" + // LocalNodeStatePending PENDING + LocalNodeStatePending LocalNodeState = "pending" + // LocalNodeStateActive ACTIVE + LocalNodeStateActive LocalNodeState = "active" + // LocalNodeStateError ERROR + LocalNodeStateError LocalNodeState = "error" +) + +// Info represents generic information about swarm. +type Info struct { + NodeID string + NodeAddr string + + LocalNodeState LocalNodeState + ControlAvailable bool + Error string + + RemoteManagers []Peer + Nodes int + Managers int + + Cluster ClusterInfo +} + +// Peer represents a peer. +type Peer struct { + NodeID string + Addr string +} + +// UpdateFlags contains flags for SwarmUpdate. +type UpdateFlags struct { + RotateWorkerToken bool + RotateManagerToken bool +} diff --git a/components/engine/api/types/swarm/task.go b/components/engine/api/types/swarm/task.go new file mode 100644 index 0000000000..591e07ba41 --- /dev/null +++ b/components/engine/api/types/swarm/task.go @@ -0,0 +1,117 @@ +package swarm + +import "time" + +// TaskState represents the state of a task. +type TaskState string + +const ( + // TaskStateNew NEW + TaskStateNew TaskState = "new" + // TaskStateAllocated ALLOCATED + TaskStateAllocated TaskState = "allocated" + // TaskStatePending PENDING + TaskStatePending TaskState = "pending" + // TaskStateAssigned ASSIGNED + TaskStateAssigned TaskState = "assigned" + // TaskStateAccepted ACCEPTED + TaskStateAccepted TaskState = "accepted" + // TaskStatePreparing PREPARING + TaskStatePreparing TaskState = "preparing" + // TaskStateReady READY + TaskStateReady TaskState = "ready" + // TaskStateStarting STARTING + TaskStateStarting TaskState = "starting" + // TaskStateRunning RUNNING + TaskStateRunning TaskState = "running" + // TaskStateComplete COMPLETE + TaskStateComplete TaskState = "complete" + // TaskStateShutdown SHUTDOWN + TaskStateShutdown TaskState = "shutdown" + // TaskStateFailed FAILED + TaskStateFailed TaskState = "failed" + // TaskStateRejected REJECTED + TaskStateRejected TaskState = "rejected" +) + +// Task represents a task. +type Task struct { + ID string + Meta + Annotations + + Spec TaskSpec `json:",omitempty"` + ServiceID string `json:",omitempty"` + Slot int `json:",omitempty"` + NodeID string `json:",omitempty"` + Status TaskStatus `json:",omitempty"` + DesiredState TaskState `json:",omitempty"` + NetworksAttachments []NetworkAttachment `json:",omitempty"` +} + +// TaskSpec represents the spec of a task. +type TaskSpec struct { + ContainerSpec ContainerSpec `json:",omitempty"` + Resources *ResourceRequirements `json:",omitempty"` + RestartPolicy *RestartPolicy `json:",omitempty"` + Placement *Placement `json:",omitempty"` + Networks []NetworkAttachmentConfig `json:",omitempty"` + + // LogDriver specifies the LogDriver to use for tasks created from this + // spec. If not present, the one on cluster default on swarm.Spec will be + // used, finally falling back to the engine default if not specified. + LogDriver *Driver `json:",omitempty"` +} + +// Resources represents resources (CPU/Memory). +type Resources struct { + NanoCPUs int64 `json:",omitempty"` + MemoryBytes int64 `json:",omitempty"` +} + +// ResourceRequirements represents resources requirements. +type ResourceRequirements struct { + Limits *Resources `json:",omitempty"` + Reservations *Resources `json:",omitempty"` +} + +// Placement represents orchestration parameters. +type Placement struct { + Constraints []string `json:",omitempty"` +} + +// RestartPolicy represents the restart policy. +type RestartPolicy struct { + Condition RestartPolicyCondition `json:",omitempty"` + Delay *time.Duration `json:",omitempty"` + MaxAttempts *uint64 `json:",omitempty"` + Window *time.Duration `json:",omitempty"` +} + +// RestartPolicyCondition represents when to restart. +type RestartPolicyCondition string + +const ( + // RestartPolicyConditionNone NONE + RestartPolicyConditionNone RestartPolicyCondition = "none" + // RestartPolicyConditionOnFailure ON_FAILURE + RestartPolicyConditionOnFailure RestartPolicyCondition = "on-failure" + // RestartPolicyConditionAny ANY + RestartPolicyConditionAny RestartPolicyCondition = "any" +) + +// TaskStatus represents the status of a task. +type TaskStatus struct { + Timestamp time.Time `json:",omitempty"` + State TaskState `json:",omitempty"` + Message string `json:",omitempty"` + Err string `json:",omitempty"` + ContainerStatus ContainerStatus `json:",omitempty"` +} + +// ContainerStatus represents the status of a container. +type ContainerStatus struct { + ContainerID string `json:",omitempty"` + PID int `json:",omitempty"` + ExitCode int `json:",omitempty"` +} diff --git a/components/engine/api/types/time/duration_convert.go b/components/engine/api/types/time/duration_convert.go new file mode 100644 index 0000000000..63e1eec19e --- /dev/null +++ b/components/engine/api/types/time/duration_convert.go @@ -0,0 +1,12 @@ +package time + +import ( + "strconv" + "time" +) + +// DurationToSecondsString converts the specified duration to the number +// seconds it represents, formatted as a string. +func DurationToSecondsString(duration time.Duration) string { + return strconv.FormatFloat(duration.Seconds(), 'f', 0, 64) +} diff --git a/components/engine/api/types/time/duration_convert_test.go b/components/engine/api/types/time/duration_convert_test.go new file mode 100644 index 0000000000..869c08f863 --- /dev/null +++ b/components/engine/api/types/time/duration_convert_test.go @@ -0,0 +1,26 @@ +package time + +import ( + "testing" + "time" +) + +func TestDurationToSecondsString(t *testing.T) { + cases := []struct { + in time.Duration + expected string + }{ + {0 * time.Second, "0"}, + {1 * time.Second, "1"}, + {1 * time.Minute, "60"}, + {24 * time.Hour, "86400"}, + } + + for _, c := range cases { + s := DurationToSecondsString(c.in) + if s != c.expected { + t.Errorf("wrong value for input `%v`: expected `%s`, got `%s`", c.in, c.expected, s) + t.Fail() + } + } +} diff --git a/components/engine/api/types/time/timestamp.go b/components/engine/api/types/time/timestamp.go new file mode 100644 index 0000000000..d3695ba723 --- /dev/null +++ b/components/engine/api/types/time/timestamp.go @@ -0,0 +1,124 @@ +package time + +import ( + "fmt" + "math" + "strconv" + "strings" + "time" +) + +// These are additional predefined layouts for use in Time.Format and Time.Parse +// with --since and --until parameters for `docker logs` and `docker events` +const ( + rFC3339Local = "2006-01-02T15:04:05" // RFC3339 with local timezone + rFC3339NanoLocal = "2006-01-02T15:04:05.999999999" // RFC3339Nano with local timezone + dateWithZone = "2006-01-02Z07:00" // RFC3339 with time at 00:00:00 + dateLocal = "2006-01-02" // RFC3339 with local timezone and time at 00:00:00 +) + +// GetTimestamp tries to parse given string as golang duration, +// then RFC3339 time and finally as a Unix timestamp. If +// any of these were successful, it returns a Unix timestamp +// as string otherwise returns the given value back. +// In case of duration input, the returned timestamp is computed +// as the given reference time minus the amount of the duration. +func GetTimestamp(value string, reference time.Time) (string, error) { + if d, err := time.ParseDuration(value); value != "0" && err == nil { + return strconv.FormatInt(reference.Add(-d).Unix(), 10), nil + } + + var format string + var parseInLocation bool + + // if the string has a Z or a + or three dashes use parse otherwise use parseinlocation + parseInLocation = !(strings.ContainsAny(value, "zZ+") || strings.Count(value, "-") == 3) + + if strings.Contains(value, ".") { + if parseInLocation { + format = rFC3339NanoLocal + } else { + format = time.RFC3339Nano + } + } else if strings.Contains(value, "T") { + // we want the number of colons in the T portion of the timestamp + tcolons := strings.Count(value, ":") + // if parseInLocation is off and we have a +/- zone offset (not Z) then + // there will be an extra colon in the input for the tz offset subtract that + // colon from the tcolons count + if !parseInLocation && !strings.ContainsAny(value, "zZ") && tcolons > 0 { + tcolons-- + } + if parseInLocation { + switch tcolons { + case 0: + format = "2006-01-02T15" + case 1: + format = "2006-01-02T15:04" + default: + format = rFC3339Local + } + } else { + switch tcolons { + case 0: + format = "2006-01-02T15Z07:00" + case 1: + format = "2006-01-02T15:04Z07:00" + default: + format = time.RFC3339 + } + } + } else if parseInLocation { + format = dateLocal + } else { + format = dateWithZone + } + + var t time.Time + var err error + + if parseInLocation { + t, err = time.ParseInLocation(format, value, time.FixedZone(reference.Zone())) + } else { + t, err = time.Parse(format, value) + } + + if err != nil { + // if there is a `-` then its an RFC3339 like timestamp otherwise assume unixtimestamp + if strings.Contains(value, "-") { + return "", err // was probably an RFC3339 like timestamp but the parser failed with an error + } + return value, nil // unixtimestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server) + } + + return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil +} + +// ParseTimestamps returns seconds and nanoseconds from a timestamp that has the +// format "%d.%09d", time.Unix(), int64(time.Nanosecond())) +// if the incoming nanosecond portion is longer or shorter than 9 digits it is +// converted to nanoseconds. The expectation is that the seconds and +// seconds will be used to create a time variable. For example: +// seconds, nanoseconds, err := ParseTimestamp("1136073600.000000001",0) +// if err == nil since := time.Unix(seconds, nanoseconds) +// returns seconds as def(aultSeconds) if value == "" +func ParseTimestamps(value string, def int64) (int64, int64, error) { + if value == "" { + return def, 0, nil + } + sa := strings.SplitN(value, ".", 2) + s, err := strconv.ParseInt(sa[0], 10, 64) + if err != nil { + return s, 0, err + } + if len(sa) != 2 { + return s, 0, nil + } + n, err := strconv.ParseInt(sa[1], 10, 64) + if err != nil { + return s, n, err + } + // should already be in nanoseconds but just in case convert n to nanoseonds + n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1])))) + return s, n, nil +} diff --git a/components/engine/api/types/time/timestamp_test.go b/components/engine/api/types/time/timestamp_test.go new file mode 100644 index 0000000000..a1651309d7 --- /dev/null +++ b/components/engine/api/types/time/timestamp_test.go @@ -0,0 +1,93 @@ +package time + +import ( + "fmt" + "testing" + "time" +) + +func TestGetTimestamp(t *testing.T) { + now := time.Now().In(time.UTC) + cases := []struct { + in, expected string + expectedErr bool + }{ + // Partial RFC3339 strings get parsed with second precision + {"2006-01-02T15:04:05.999999999+07:00", "1136189045.999999999", false}, + {"2006-01-02T15:04:05.999999999Z", "1136214245.999999999", false}, + {"2006-01-02T15:04:05.999999999", "1136214245.999999999", false}, + {"2006-01-02T15:04:05Z", "1136214245.000000000", false}, + {"2006-01-02T15:04:05", "1136214245.000000000", false}, + {"2006-01-02T15:04:0Z", "", true}, + {"2006-01-02T15:04:0", "", true}, + {"2006-01-02T15:04Z", "1136214240.000000000", false}, + {"2006-01-02T15:04+00:00", "1136214240.000000000", false}, + {"2006-01-02T15:04-00:00", "1136214240.000000000", false}, + {"2006-01-02T15:04", "1136214240.000000000", false}, + {"2006-01-02T15:0Z", "", true}, + {"2006-01-02T15:0", "", true}, + {"2006-01-02T15Z", "1136214000.000000000", false}, + {"2006-01-02T15+00:00", "1136214000.000000000", false}, + {"2006-01-02T15-00:00", "1136214000.000000000", false}, + {"2006-01-02T15", "1136214000.000000000", false}, + {"2006-01-02T1Z", "1136163600.000000000", false}, + {"2006-01-02T1", "1136163600.000000000", false}, + {"2006-01-02TZ", "", true}, + {"2006-01-02T", "", true}, + {"2006-01-02+00:00", "1136160000.000000000", false}, + {"2006-01-02-00:00", "1136160000.000000000", false}, + {"2006-01-02-00:01", "1136160060.000000000", false}, + {"2006-01-02Z", "1136160000.000000000", false}, + {"2006-01-02", "1136160000.000000000", false}, + {"2015-05-13T20:39:09Z", "1431549549.000000000", false}, + + // unix timestamps returned as is + {"1136073600", "1136073600", false}, + {"1136073600.000000001", "1136073600.000000001", false}, + // Durations + {"1m", fmt.Sprintf("%d", now.Add(-1*time.Minute).Unix()), false}, + {"1.5h", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false}, + {"1h30m", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false}, + + // String fallback + {"invalid", "invalid", false}, + } + + for _, c := range cases { + o, err := GetTimestamp(c.in, now) + if o != c.expected || + (err == nil && c.expectedErr) || + (err != nil && !c.expectedErr) { + t.Errorf("wrong value for '%s'. expected:'%s' got:'%s' with error: `%s`", c.in, c.expected, o, err) + t.Fail() + } + } +} + +func TestParseTimestamps(t *testing.T) { + cases := []struct { + in string + def, expectedS, expectedN int64 + expectedErr bool + }{ + // unix timestamps + {"1136073600", 0, 1136073600, 0, false}, + {"1136073600.000000001", 0, 1136073600, 1, false}, + {"1136073600.0000000010", 0, 1136073600, 1, false}, + {"1136073600.00000001", 0, 1136073600, 10, false}, + {"foo.bar", 0, 0, 0, true}, + {"1136073600.bar", 0, 1136073600, 0, true}, + {"", -1, -1, 0, false}, + } + + for _, c := range cases { + s, n, err := ParseTimestamps(c.in, c.def) + if s != c.expectedS || + n != c.expectedN || + (err == nil && c.expectedErr) || + (err != nil && !c.expectedErr) { + t.Errorf("wrong values for input `%s` with default `%d` expected:'%d'seconds and `%d`nanosecond got:'%d'seconds and `%d`nanoseconds with error: `%s`", c.in, c.def, c.expectedS, c.expectedN, s, n, err) + t.Fail() + } + } +} diff --git a/components/engine/api/types/types.go b/components/engine/api/types/types.go new file mode 100644 index 0000000000..ea3582ebcf --- /dev/null +++ b/components/engine/api/types/types.go @@ -0,0 +1,519 @@ +package types + +import ( + "os" + "time" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/go-connections/nat" +) + +// ContainerCreateResponse contains the information returned to a client on the +// creation of a new container. +type ContainerCreateResponse struct { + // ID is the ID of the created container. + ID string `json:"Id"` + + // Warnings are any warnings encountered during the creation of the container. + Warnings []string `json:"Warnings"` +} + +// ContainerExecCreateResponse contains response of Remote API: +// POST "/containers/{name:.*}/exec" +type ContainerExecCreateResponse struct { + // ID is the exec ID. + ID string `json:"Id"` +} + +// ContainerUpdateResponse contains response of Remote API: +// POST "/containers/{name:.*}/update" +type ContainerUpdateResponse struct { + // Warnings are any warnings encountered during the updating of the container. + Warnings []string `json:"Warnings"` +} + +// AuthResponse contains response of Remote API: +// POST "/auth" +type AuthResponse struct { + // Status is the authentication status + Status string `json:"Status"` + + // IdentityToken is an opaque token used for authenticating + // a user after a successful login. + IdentityToken string `json:"IdentityToken,omitempty"` +} + +// ContainerWaitResponse contains response of Remote API: +// POST "/containers/"+containerID+"/wait" +type ContainerWaitResponse struct { + // StatusCode is the status code of the wait job + StatusCode int `json:"StatusCode"` +} + +// ContainerCommitResponse contains response of Remote API: +// POST "/commit?container="+containerID +type ContainerCommitResponse struct { + ID string `json:"Id"` +} + +// ContainerChange contains response of Remote API: +// GET "/containers/{name:.*}/changes" +type ContainerChange struct { + Kind int + Path string +} + +// ImageHistory contains response of Remote API: +// GET "/images/{name:.*}/history" +type ImageHistory struct { + ID string `json:"Id"` + Created int64 + CreatedBy string + Tags []string + Size int64 + Comment string +} + +// ImageDelete contains response of Remote API: +// DELETE "/images/{name:.*}" +type ImageDelete struct { + Untagged string `json:",omitempty"` + Deleted string `json:",omitempty"` +} + +// Image contains response of Remote API: +// GET "/images/json" +type Image struct { + ID string `json:"Id"` + ParentID string `json:"ParentId"` + RepoTags []string + RepoDigests []string + Created int64 + Size int64 + VirtualSize int64 + Labels map[string]string +} + +// GraphDriverData returns Image's graph driver config info +// when calling inspect command +type GraphDriverData struct { + Name string + Data map[string]string +} + +// RootFS returns Image's RootFS description including the layer IDs. +type RootFS struct { + Type string + Layers []string `json:",omitempty"` + BaseLayer string `json:",omitempty"` +} + +// ImageInspect contains response of Remote API: +// GET "/images/{name:.*}/json" +type ImageInspect struct { + ID string `json:"Id"` + RepoTags []string + RepoDigests []string + Parent string + Comment string + Created string + Container string + ContainerConfig *container.Config + DockerVersion string + Author string + Config *container.Config + Architecture string + Os string + Size int64 + VirtualSize int64 + GraphDriver GraphDriverData + RootFS RootFS +} + +// Port stores open ports info of container +// e.g. {"PrivatePort": 8080, "PublicPort": 80, "Type": "tcp"} +type Port struct { + IP string `json:",omitempty"` + PrivatePort int + PublicPort int `json:",omitempty"` + Type string +} + +// Container contains response of Remote API: +// GET "/containers/json" +type Container struct { + ID string `json:"Id"` + Names []string + Image string + ImageID string + Command string + Created int64 + Ports []Port + SizeRw int64 `json:",omitempty"` + SizeRootFs int64 `json:",omitempty"` + Labels map[string]string + State string + Status string + HostConfig struct { + NetworkMode string `json:",omitempty"` + } + NetworkSettings *SummaryNetworkSettings + Mounts []MountPoint +} + +// CopyConfig contains request body of Remote API: +// POST "/containers/"+containerID+"/copy" +type CopyConfig struct { + Resource string +} + +// ContainerPathStat is used to encode the header from +// GET "/containers/{name:.*}/archive" +// "Name" is the file or directory name. +type ContainerPathStat struct { + Name string `json:"name"` + Size int64 `json:"size"` + Mode os.FileMode `json:"mode"` + Mtime time.Time `json:"mtime"` + LinkTarget string `json:"linkTarget"` +} + +// ContainerProcessList contains response of Remote API: +// GET "/containers/{name:.*}/top" +type ContainerProcessList struct { + Processes [][]string + Titles []string +} + +// Version contains response of Remote API: +// GET "/version" +type Version struct { + Version string + APIVersion string `json:"ApiVersion"` + GitCommit string + GoVersion string + Os string + Arch string + KernelVersion string `json:",omitempty"` + Experimental bool `json:",omitempty"` + BuildTime string `json:",omitempty"` +} + +// Info contains response of Remote API: +// GET "/info" +type Info struct { + ID string + Containers int + ContainersRunning int + ContainersPaused int + ContainersStopped int + Images int + Driver string + DriverStatus [][2]string + SystemStatus [][2]string + Plugins PluginsInfo + MemoryLimit bool + SwapLimit bool + KernelMemory bool + CPUCfsPeriod bool `json:"CpuCfsPeriod"` + CPUCfsQuota bool `json:"CpuCfsQuota"` + CPUShares bool + CPUSet bool + IPv4Forwarding bool + BridgeNfIptables bool + BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` + Debug bool + NFd int + OomKillDisable bool + NGoroutines int + SystemTime string + LoggingDriver string + CgroupDriver string + NEventsListener int + KernelVersion string + OperatingSystem string + OSType string + Architecture string + IndexServerAddress string + RegistryConfig *registry.ServiceConfig + NCPU int + MemTotal int64 + DockerRootDir string + HTTPProxy string `json:"HttpProxy"` + HTTPSProxy string `json:"HttpsProxy"` + NoProxy string + Name string + Labels []string + ExperimentalBuild bool + ServerVersion string + ClusterStore string + ClusterAdvertise string + SecurityOptions []string + Runtimes map[string]Runtime + DefaultRuntime string + Swarm swarm.Info + // LiveRestoreEnabled determines whether containers should be kept + // running when the daemon is shutdown or upon daemon start if + // running containers are detected + LiveRestoreEnabled bool +} + +// PluginsInfo is a temp struct holding Plugins name +// registered with docker daemon. It is used by Info struct +type PluginsInfo struct { + // List of Volume plugins registered + Volume []string + // List of Network plugins registered + Network []string + // List of Authorization plugins registered + Authorization []string +} + +// ExecStartCheck is a temp struct used by execStart +// Config fields is part of ExecConfig in runconfig package +type ExecStartCheck struct { + // ExecStart will first check if it's detached + Detach bool + // Check if there's a tty + Tty bool +} + +// HealthcheckResult stores information about a single run of a healthcheck probe +type HealthcheckResult struct { + Start time.Time // Start is the time this check started + End time.Time // End is the time this check ended + ExitCode int // ExitCode meanings: 0=healthy, 1=unhealthy, 2=reserved (considered unhealthy), else=error running probe + Output string // Output from last check +} + +// Health states +const ( + Starting = "starting" // Starting indicates that the container is not yet ready + Healthy = "healthy" // Healthy indicates that the container is running correctly + Unhealthy = "unhealthy" // Unhealthy indicates that the container has a problem +) + +// Health stores information about the container's healthcheck results +type Health struct { + Status string // Status is one of Starting, Healthy or Unhealthy + FailingStreak int // FailingStreak is the number of consecutive failures + Log []*HealthcheckResult // Log contains the last few results (oldest first) +} + +// ContainerState stores container's running state +// it's part of ContainerJSONBase and will return by "inspect" command +type ContainerState struct { + Status string + Running bool + Paused bool + Restarting bool + OOMKilled bool + Dead bool + Pid int + ExitCode int + Error string + StartedAt string + FinishedAt string + Health *Health `json:",omitempty"` +} + +// ContainerNode stores information about the node that a container +// is running on. It's only available in Docker Swarm +type ContainerNode struct { + ID string + IPAddress string `json:"IP"` + Addr string + Name string + Cpus int + Memory int64 + Labels map[string]string +} + +// ContainerJSONBase contains response of Remote API: +// GET "/containers/{name:.*}/json" +type ContainerJSONBase struct { + ID string `json:"Id"` + Created string + Path string + Args []string + State *ContainerState + Image string + ResolvConfPath string + HostnamePath string + HostsPath string + LogPath string + Node *ContainerNode `json:",omitempty"` + Name string + RestartCount int + Driver string + MountLabel string + ProcessLabel string + AppArmorProfile string + ExecIDs []string + HostConfig *container.HostConfig + GraphDriver GraphDriverData + SizeRw *int64 `json:",omitempty"` + SizeRootFs *int64 `json:",omitempty"` +} + +// ContainerJSON is newly used struct along with MountPoint +type ContainerJSON struct { + *ContainerJSONBase + Mounts []MountPoint + Config *container.Config + NetworkSettings *NetworkSettings +} + +// NetworkSettings exposes the network settings in the api +type NetworkSettings struct { + NetworkSettingsBase + DefaultNetworkSettings + Networks map[string]*network.EndpointSettings +} + +// SummaryNetworkSettings provides a summary of container's networks +// in /containers/json +type SummaryNetworkSettings struct { + Networks map[string]*network.EndpointSettings +} + +// NetworkSettingsBase holds basic information about networks +type NetworkSettingsBase struct { + Bridge string // Bridge is the Bridge name the network uses(e.g. `docker0`) + SandboxID string // SandboxID uniquely represents a container's network stack + HairpinMode bool // HairpinMode specifies if hairpin NAT should be enabled on the virtual interface + LinkLocalIPv6Address string // LinkLocalIPv6Address is an IPv6 unicast address using the link-local prefix + LinkLocalIPv6PrefixLen int // LinkLocalIPv6PrefixLen is the prefix length of an IPv6 unicast address + Ports nat.PortMap // Ports is a collection of PortBinding indexed by Port + SandboxKey string // SandboxKey identifies the sandbox + SecondaryIPAddresses []network.Address + SecondaryIPv6Addresses []network.Address +} + +// DefaultNetworkSettings holds network information +// during the 2 release deprecation period. +// It will be removed in Docker 1.11. +type DefaultNetworkSettings struct { + EndpointID string // EndpointID uniquely represents a service endpoint in a Sandbox + Gateway string // Gateway holds the gateway address for the network + GlobalIPv6Address string // GlobalIPv6Address holds network's global IPv6 address + GlobalIPv6PrefixLen int // GlobalIPv6PrefixLen represents mask length of network's global IPv6 address + IPAddress string // IPAddress holds the IPv4 address for the network + IPPrefixLen int // IPPrefixLen represents mask length of network's IPv4 address + IPv6Gateway string // IPv6Gateway holds gateway address specific for IPv6 + MacAddress string // MacAddress holds the MAC address for the network +} + +// MountPoint represents a mount point configuration inside the container. +// This is used for reporting the mountpoints in use by a container. +type MountPoint struct { + Type mount.Type `json:",omitempty"` + Name string `json:",omitempty"` + Source string + Destination string + Driver string `json:",omitempty"` + Mode string + RW bool + Propagation mount.Propagation +} + +// Volume represents the configuration of a volume for the remote API +type Volume struct { + Name string // Name is the name of the volume + Driver string // Driver is the Driver name used to create the volume + Mountpoint string // Mountpoint is the location on disk of the volume + Status map[string]interface{} `json:",omitempty"` // Status provides low-level status information about the volume + Labels map[string]string // Labels is metadata specific to the volume + Scope string // Scope describes the level at which the volume exists (e.g. `global` for cluster-wide or `local` for machine level) +} + +// VolumesListResponse contains the response for the remote API: +// GET "/volumes" +type VolumesListResponse struct { + Volumes []*Volume // Volumes is the list of volumes being returned + Warnings []string // Warnings is a list of warnings that occurred when getting the list from the volume drivers +} + +// VolumeCreateRequest contains the response for the remote API: +// POST "/volumes/create" +type VolumeCreateRequest struct { + Name string // Name is the requested name of the volume + Driver string // Driver is the name of the driver that should be used to create the volume + DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume. + Labels map[string]string // Labels holds metadata specific to the volume being created. +} + +// NetworkResource is the body of the "get network" http response message +type NetworkResource struct { + Name string // Name is the requested name of the network + ID string `json:"Id"` // ID uniquely identifies a network on a single machine + Scope string // Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level) + Driver string // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`) + EnableIPv6 bool // EnableIPv6 represents whether to enable IPv6 + IPAM network.IPAM // IPAM is the network's IP Address Management + Internal bool // Internal represents if the network is used internal only + Attachable bool // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode. + Containers map[string]EndpointResource // Containers contains endpoints belonging to the network + Options map[string]string // Options holds the network specific options to use for when creating the network + Labels map[string]string // Labels holds metadata specific to the network being created +} + +// EndpointResource contains network resources allocated and used for a container in a network +type EndpointResource struct { + Name string + EndpointID string + MacAddress string + IPv4Address string + IPv6Address string +} + +// NetworkCreate is the expected body of the "create network" http request message +type NetworkCreate struct { + CheckDuplicate bool + Driver string + EnableIPv6 bool + IPAM *network.IPAM + Internal bool + Attachable bool + Options map[string]string + Labels map[string]string +} + +// NetworkCreateRequest is the request message sent to the server for network create call. +type NetworkCreateRequest struct { + NetworkCreate + Name string +} + +// NetworkCreateResponse is the response message sent by the server for network create call +type NetworkCreateResponse struct { + ID string `json:"Id"` + Warning string +} + +// NetworkConnect represents the data to be used to connect a container to the network +type NetworkConnect struct { + Container string + EndpointConfig *network.EndpointSettings `json:",omitempty"` +} + +// NetworkDisconnect represents the data to be used to disconnect a container from the network +type NetworkDisconnect struct { + Container string + Force bool +} + +// Checkpoint represents the details of a checkpoint +type Checkpoint struct { + Name string // Name is the name of the checkpoint +} + +// Runtime describes an OCI runtime +type Runtime struct { + Path string `json:"path"` + Args []string `json:"runtimeArgs,omitempty"` +} diff --git a/components/engine/api/types/versions/README.md b/components/engine/api/types/versions/README.md new file mode 100644 index 0000000000..cdac50a53c --- /dev/null +++ b/components/engine/api/types/versions/README.md @@ -0,0 +1,14 @@ +## Legacy API type versions + +This package includes types for legacy API versions. The stable version of the API types live in `api/types/*.go`. + +Consider moving a type here when you need to keep backwards compatibility in the API. This legacy types are organized by the latest API version they appear in. For instance, types in the `v1p19` package are valid for API versions below or equal `1.19`. Types in the `v1p20` package are valid for the API version `1.20`, since the versions below that will use the legacy types in `v1p19`. + +### Package name conventions + +The package name convention is to use `v` as a prefix for the version number and `p`(patch) as a separator. We use this nomenclature due to a few restrictions in the Go package name convention: + +1. We cannot use `.` because it's interpreted by the language, think of `v1.20.CallFunction`. +2. We cannot use `_` because golint complains about it. The code is actually valid, but it looks probably more weird: `v1_20.CallFunction`. + +For instance, if you want to modify a type that was available in the version `1.21` of the API but it will have different fields in the version `1.22`, you want to create a new package under `api/types/versions/v1p21`. diff --git a/components/engine/api/types/versions/compare.go b/components/engine/api/types/versions/compare.go new file mode 100644 index 0000000000..611d4fed66 --- /dev/null +++ b/components/engine/api/types/versions/compare.go @@ -0,0 +1,62 @@ +package versions + +import ( + "strconv" + "strings" +) + +// compare compares two version strings +// returns -1 if v1 < v2, 1 if v1 > v2, 0 otherwise. +func compare(v1, v2 string) int { + var ( + currTab = strings.Split(v1, ".") + otherTab = strings.Split(v2, ".") + ) + + max := len(currTab) + if len(otherTab) > max { + max = len(otherTab) + } + for i := 0; i < max; i++ { + var currInt, otherInt int + + if len(currTab) > i { + currInt, _ = strconv.Atoi(currTab[i]) + } + if len(otherTab) > i { + otherInt, _ = strconv.Atoi(otherTab[i]) + } + if currInt > otherInt { + return 1 + } + if otherInt > currInt { + return -1 + } + } + return 0 +} + +// LessThan checks if a version is less than another +func LessThan(v, other string) bool { + return compare(v, other) == -1 +} + +// LessThanOrEqualTo checks if a version is less than or equal to another +func LessThanOrEqualTo(v, other string) bool { + return compare(v, other) <= 0 +} + +// GreaterThan checks if a version is greater than another +func GreaterThan(v, other string) bool { + return compare(v, other) == 1 +} + +// GreaterThanOrEqualTo checks if a version is greater than or equal to another +func GreaterThanOrEqualTo(v, other string) bool { + return compare(v, other) >= 0 +} + +// Equal checks if a version is equal to another +func Equal(v, other string) bool { + return compare(v, other) == 0 +} diff --git a/components/engine/api/types/versions/compare_test.go b/components/engine/api/types/versions/compare_test.go new file mode 100644 index 0000000000..c2b96869f7 --- /dev/null +++ b/components/engine/api/types/versions/compare_test.go @@ -0,0 +1,26 @@ +package versions + +import ( + "testing" +) + +func assertVersion(t *testing.T, a, b string, result int) { + if r := compare(a, b); r != result { + t.Fatalf("Unexpected version comparison result. Found %d, expected %d", r, result) + } +} + +func TestCompareVersion(t *testing.T) { + assertVersion(t, "1.12", "1.12", 0) + assertVersion(t, "1.0.0", "1", 0) + assertVersion(t, "1", "1.0.0", 0) + assertVersion(t, "1.05.00.0156", "1.0.221.9289", 1) + assertVersion(t, "1", "1.0.1", -1) + assertVersion(t, "1.0.1", "1", 1) + assertVersion(t, "1.0.1", "1.0.2", -1) + assertVersion(t, "1.0.2", "1.0.3", -1) + assertVersion(t, "1.0.3", "1.1", -1) + assertVersion(t, "1.1", "1.1.1", -1) + assertVersion(t, "1.1.1", "1.1.2", -1) + assertVersion(t, "1.1.2", "1.2", -1) +} diff --git a/components/engine/api/types/versions/v1p19/types.go b/components/engine/api/types/versions/v1p19/types.go new file mode 100644 index 0000000000..4ed4335881 --- /dev/null +++ b/components/engine/api/types/versions/v1p19/types.go @@ -0,0 +1,35 @@ +// Package v1p19 provides specific API types for the API version 1, patch 19. +package v1p19 + +import ( + "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/container" + "github.com/docker/engine-api/types/versions/v1p20" + "github.com/docker/go-connections/nat" +) + +// ContainerJSON is a backcompatibility struct for APIs prior to 1.20. +// Note this is not used by the Windows daemon. +type ContainerJSON struct { + *types.ContainerJSONBase + Volumes map[string]string + VolumesRW map[string]bool + Config *ContainerConfig + NetworkSettings *v1p20.NetworkSettings +} + +// ContainerConfig is a backcompatibility struct for APIs prior to 1.20. +type ContainerConfig struct { + *container.Config + + MacAddress string + NetworkDisabled bool + ExposedPorts map[nat.Port]struct{} + + // backward compatibility, they now live in HostConfig + VolumeDriver string + Memory int64 + MemorySwap int64 + CPUShares int64 `json:"CpuShares"` + CPUSet string `json:"Cpuset"` +} diff --git a/components/engine/api/types/versions/v1p20/types.go b/components/engine/api/types/versions/v1p20/types.go new file mode 100644 index 0000000000..5736efad00 --- /dev/null +++ b/components/engine/api/types/versions/v1p20/types.go @@ -0,0 +1,40 @@ +// Package v1p20 provides specific API types for the API version 1, patch 20. +package v1p20 + +import ( + "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/container" + "github.com/docker/go-connections/nat" +) + +// ContainerJSON is a backcompatibility struct for the API 1.20 +type ContainerJSON struct { + *types.ContainerJSONBase + Mounts []types.MountPoint + Config *ContainerConfig + NetworkSettings *NetworkSettings +} + +// ContainerConfig is a backcompatibility struct used in ContainerJSON for the API 1.20 +type ContainerConfig struct { + *container.Config + + MacAddress string + NetworkDisabled bool + ExposedPorts map[nat.Port]struct{} + + // backward compatibility, they now live in HostConfig + VolumeDriver string +} + +// StatsJSON is a backcompatibility struct used in Stats for APIs prior to 1.21 +type StatsJSON struct { + types.Stats + Network types.NetworkStats `json:"network,omitempty"` +} + +// NetworkSettings is a backward compatible struct for APIs prior to 1.21 +type NetworkSettings struct { + types.NetworkSettingsBase + types.DefaultNetworkSettings +} diff --git a/components/engine/builder/builder.go b/components/engine/builder/builder.go index 125e56ab22..e592877e7d 100644 --- a/components/engine/builder/builder.go +++ b/components/engine/builder/builder.go @@ -9,11 +9,11 @@ import ( "os" "time" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/image" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" "golang.org/x/net/context" ) diff --git a/components/engine/builder/dockerfile/builder.go b/components/engine/builder/dockerfile/builder.go index c48c67d046..c206aa90c2 100644 --- a/components/engine/builder/dockerfile/builder.go +++ b/components/engine/builder/dockerfile/builder.go @@ -10,14 +10,14 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder" "github.com/docker/docker/builder/dockerfile/parser" "github.com/docker/docker/image" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" "golang.org/x/net/context" ) diff --git a/components/engine/builder/dockerfile/dispatchers.go b/components/engine/builder/dockerfile/dispatchers.go index d54299ff40..06dc622682 100644 --- a/components/engine/builder/dockerfile/dispatchers.go +++ b/components/engine/builder/dockerfile/dispatchers.go @@ -18,11 +18,11 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/strslice" "github.com/docker/docker/builder" "github.com/docker/docker/pkg/signal" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/strslice" "github.com/docker/go-connections/nat" ) diff --git a/components/engine/builder/dockerfile/dispatchers_test.go b/components/engine/builder/dockerfile/dispatchers_test.go index e5746f7454..a57f78a0e1 100644 --- a/components/engine/builder/dockerfile/dispatchers_test.go +++ b/components/engine/builder/dockerfile/dispatchers_test.go @@ -6,9 +6,9 @@ import ( "strings" "testing" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/strslice" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/strslice" "github.com/docker/go-connections/nat" ) diff --git a/components/engine/builder/dockerfile/evaluator_test.go b/components/engine/builder/dockerfile/evaluator_test.go index ac066d7f64..4340a2f8ac 100644 --- a/components/engine/builder/dockerfile/evaluator_test.go +++ b/components/engine/builder/dockerfile/evaluator_test.go @@ -5,12 +5,12 @@ import ( "strings" "testing" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder" "github.com/docker/docker/builder/dockerfile/parser" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/reexec" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" ) type dispatchTestCase struct { diff --git a/components/engine/builder/dockerfile/internals.go b/components/engine/builder/dockerfile/internals.go index 73e80b1a1d..267d8e4e72 100644 --- a/components/engine/builder/dockerfile/internals.go +++ b/components/engine/builder/dockerfile/internals.go @@ -20,7 +20,10 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/strslice" "github.com/docker/docker/builder" "github.com/docker/docker/builder/dockerfile/parser" "github.com/docker/docker/pkg/archive" @@ -34,9 +37,6 @@ import ( "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/strslice" ) func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) error { diff --git a/components/engine/builder/dockerfile/internals_test.go b/components/engine/builder/dockerfile/internals_test.go index 279a6f8912..d170d8e25a 100644 --- a/components/engine/builder/dockerfile/internals_test.go +++ b/components/engine/builder/dockerfile/internals_test.go @@ -5,9 +5,9 @@ import ( "strings" "testing" + "github.com/docker/docker/api/types" "github.com/docker/docker/builder" "github.com/docker/docker/pkg/archive" - "github.com/docker/engine-api/types" ) func TestEmptyDockerfile(t *testing.T) { diff --git a/components/engine/cliconfig/config.go b/components/engine/cliconfig/config.go index 9d5df0ac4b..d81bf86b7a 100644 --- a/components/engine/cliconfig/config.go +++ b/components/engine/cliconfig/config.go @@ -6,9 +6,9 @@ import ( "os" "path/filepath" + "github.com/docker/docker/api/types" "github.com/docker/docker/cliconfig/configfile" "github.com/docker/docker/pkg/homedir" - "github.com/docker/engine-api/types" ) const ( diff --git a/components/engine/cliconfig/configfile/file.go b/components/engine/cliconfig/configfile/file.go index 9ac952042a..c0f5ce2d9a 100644 --- a/components/engine/cliconfig/configfile/file.go +++ b/components/engine/cliconfig/configfile/file.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) const ( diff --git a/components/engine/cliconfig/configfile/file_test.go b/components/engine/cliconfig/configfile/file_test.go index 15eecb73e2..435797f681 100644 --- a/components/engine/cliconfig/configfile/file_test.go +++ b/components/engine/cliconfig/configfile/file_test.go @@ -3,7 +3,7 @@ package configfile import ( "testing" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) func TestEncodeAuth(t *testing.T) { diff --git a/components/engine/cliconfig/credentials/credentials.go b/components/engine/cliconfig/credentials/credentials.go index 510cf8cf0e..ca874cac51 100644 --- a/components/engine/cliconfig/credentials/credentials.go +++ b/components/engine/cliconfig/credentials/credentials.go @@ -1,7 +1,7 @@ package credentials import ( - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) // Store is the interface that any credentials store must implement. diff --git a/components/engine/cliconfig/credentials/file_store.go b/components/engine/cliconfig/credentials/file_store.go index 55fa4f8dac..8da8c91434 100644 --- a/components/engine/cliconfig/credentials/file_store.go +++ b/components/engine/cliconfig/credentials/file_store.go @@ -3,7 +3,9 @@ package credentials import ( "github.com/docker/docker/cliconfig/configfile" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/cliconfig/configfile" ) // fileStore implements a credentials store using diff --git a/components/engine/cliconfig/credentials/file_store_test.go b/components/engine/cliconfig/credentials/file_store_test.go index f087f04e75..efed4e9040 100644 --- a/components/engine/cliconfig/credentials/file_store_test.go +++ b/components/engine/cliconfig/credentials/file_store_test.go @@ -4,9 +4,9 @@ import ( "io/ioutil" "testing" + "github.com/docker/docker/api/types" "github.com/docker/docker/cliconfig" "github.com/docker/docker/cliconfig/configfile" - "github.com/docker/engine-api/types" ) func newConfigFile(auths map[string]types.AuthConfig) *configfile.ConfigFile { diff --git a/components/engine/cliconfig/credentials/native_store.go b/components/engine/cliconfig/credentials/native_store.go index 1717ce7b39..6539497bef 100644 --- a/components/engine/cliconfig/credentials/native_store.go +++ b/components/engine/cliconfig/credentials/native_store.go @@ -3,8 +3,8 @@ package credentials import ( "github.com/docker/docker-credential-helpers/client" "github.com/docker/docker-credential-helpers/credentials" + "github.com/docker/docker/api/types" "github.com/docker/docker/cliconfig/configfile" - "github.com/docker/engine-api/types" ) const ( diff --git a/components/engine/cliconfig/credentials/native_store_test.go b/components/engine/cliconfig/credentials/native_store_test.go index 952e447dfe..9da06f3d68 100644 --- a/components/engine/cliconfig/credentials/native_store_test.go +++ b/components/engine/cliconfig/credentials/native_store_test.go @@ -10,7 +10,7 @@ import ( "github.com/docker/docker-credential-helpers/client" "github.com/docker/docker-credential-helpers/credentials" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) const ( diff --git a/components/engine/container/archive.go b/components/engine/container/archive.go index e22a001c59..56e6598b9c 100644 --- a/components/engine/container/archive.go +++ b/components/engine/container/archive.go @@ -4,9 +4,9 @@ import ( "os" "path/filepath" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/system" - "github.com/docker/engine-api/types" ) // ResolvePath resolves the given path in the container to a resource on the diff --git a/components/engine/container/container.go b/components/engine/container/container.go index d34226922a..b4c59f4ab7 100644 --- a/components/engine/container/container.go +++ b/components/engine/container/container.go @@ -16,6 +16,8 @@ import ( "golang.org/x/net/context" "github.com/Sirupsen/logrus" + containertypes "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/daemon/exec" "github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/logger/jsonfilelog" @@ -31,8 +33,6 @@ import ( "github.com/docker/docker/runconfig" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/docker/volume" - containertypes "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" "github.com/docker/go-connections/nat" "github.com/docker/libnetwork" "github.com/docker/libnetwork/netlabel" diff --git a/components/engine/container/container_solaris.go b/components/engine/container/container_solaris.go index ca02d8ea89..5c5b66e022 100644 --- a/components/engine/container/container_solaris.go +++ b/components/engine/container/container_solaris.go @@ -6,8 +6,8 @@ import ( "os" "path/filepath" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/volume" - "github.com/docker/engine-api/types/container" ) // Container holds fields specific to the Solaris implementation. See diff --git a/components/engine/container/container_unit_test.go b/components/engine/container/container_unit_test.go index 67b829f9f9..f14dc12e97 100644 --- a/components/engine/container/container_unit_test.go +++ b/components/engine/container/container_unit_test.go @@ -3,8 +3,8 @@ package container import ( "testing" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/signal" - "github.com/docker/engine-api/types/container" ) func TestContainerStopSignal(t *testing.T) { diff --git a/components/engine/container/container_unix.go b/components/engine/container/container_unix.go index 63f2ff9a65..22c07c2652 100644 --- a/components/engine/container/container_unix.go +++ b/components/engine/container/container_unix.go @@ -11,13 +11,13 @@ import ( "syscall" "github.com/Sirupsen/logrus" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" "github.com/docker/docker/utils" "github.com/docker/docker/volume" - containertypes "github.com/docker/engine-api/types/container" "github.com/opencontainers/runc/libcontainer/label" ) diff --git a/components/engine/container/container_windows.go b/components/engine/container/container_windows.go index 14f90c5f68..c775809fe2 100644 --- a/components/engine/container/container_windows.go +++ b/components/engine/container/container_windows.go @@ -7,9 +7,9 @@ import ( "os" "path/filepath" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/utils" "github.com/docker/docker/volume" - containertypes "github.com/docker/engine-api/types/container" ) // Container holds fields specific to the Windows implementation. See diff --git a/components/engine/container/health.go b/components/engine/container/health.go index 7a459d8182..81fae50456 100644 --- a/components/engine/container/health.go +++ b/components/engine/container/health.go @@ -2,7 +2,7 @@ package container import ( "github.com/Sirupsen/logrus" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) // Health holds the current container health-check state diff --git a/components/engine/daemon/archive.go b/components/engine/daemon/archive.go index b2221560a3..955e7b9b36 100644 --- a/components/engine/daemon/archive.go +++ b/components/engine/daemon/archive.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/builder" "github.com/docker/docker/container" "github.com/docker/docker/pkg/archive" @@ -14,7 +15,6 @@ import ( "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/system" - "github.com/docker/engine-api/types" ) // ErrExtractPointNotDirectory is used to convey that the operation to extract diff --git a/components/engine/daemon/auth.go b/components/engine/daemon/auth.go index a1400d88ae..f5f4d7bf24 100644 --- a/components/engine/daemon/auth.go +++ b/components/engine/daemon/auth.go @@ -3,8 +3,8 @@ package daemon import ( "golang.org/x/net/context" + "github.com/docker/docker/api/types" "github.com/docker/docker/dockerversion" - "github.com/docker/engine-api/types" ) // AuthenticateToRegistry checks the validity of credentials in authConfig diff --git a/components/engine/daemon/cluster/cluster.go b/components/engine/daemon/cluster/cluster.go index b63d8050ce..5b84cef1d1 100644 --- a/components/engine/daemon/cluster/cluster.go +++ b/components/engine/daemon/cluster/cluster.go @@ -14,6 +14,9 @@ import ( "google.golang.org/grpc" "github.com/Sirupsen/logrus" + apitypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + types "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/daemon/cluster/convert" executorpkg "github.com/docker/docker/daemon/cluster/executor" "github.com/docker/docker/daemon/cluster/executor/container" @@ -22,9 +25,6 @@ import ( "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/runconfig" - apitypes "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" - types "github.com/docker/engine-api/types/swarm" swarmagent "github.com/docker/swarmkit/agent" swarmapi "github.com/docker/swarmkit/api" "golang.org/x/net/context" diff --git a/components/engine/daemon/cluster/convert/container.go b/components/engine/daemon/cluster/convert/container.go index 1c44f99ae1..7cac7960b6 100644 --- a/components/engine/daemon/cluster/convert/container.go +++ b/components/engine/daemon/cluster/convert/container.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - mounttypes "github.com/docker/engine-api/types/mount" - types "github.com/docker/engine-api/types/swarm" + mounttypes "github.com/docker/docker/api/types/mount" + types "github.com/docker/docker/api/types/swarm" swarmapi "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/protobuf/ptypes" ) diff --git a/components/engine/daemon/cluster/convert/network.go b/components/engine/daemon/cluster/convert/network.go index bd060b363d..decc320151 100644 --- a/components/engine/daemon/cluster/convert/network.go +++ b/components/engine/daemon/cluster/convert/network.go @@ -3,9 +3,9 @@ package convert import ( "strings" - basictypes "github.com/docker/engine-api/types" - networktypes "github.com/docker/engine-api/types/network" - types "github.com/docker/engine-api/types/swarm" + basictypes "github.com/docker/docker/api/types" + networktypes "github.com/docker/docker/api/types/network" + types "github.com/docker/docker/api/types/swarm" swarmapi "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/protobuf/ptypes" ) diff --git a/components/engine/daemon/cluster/convert/node.go b/components/engine/daemon/cluster/convert/node.go index 53d7efa428..8422b7a36c 100644 --- a/components/engine/daemon/cluster/convert/node.go +++ b/components/engine/daemon/cluster/convert/node.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - types "github.com/docker/engine-api/types/swarm" + types "github.com/docker/docker/api/types/swarm" swarmapi "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/protobuf/ptypes" ) diff --git a/components/engine/daemon/cluster/convert/service.go b/components/engine/daemon/cluster/convert/service.go index 75e7c3bcfa..5c20bf6c4f 100644 --- a/components/engine/daemon/cluster/convert/service.go +++ b/components/engine/daemon/cluster/convert/service.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" + types "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/namesgenerator" - types "github.com/docker/engine-api/types/swarm" swarmapi "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/protobuf/ptypes" ) diff --git a/components/engine/daemon/cluster/convert/swarm.go b/components/engine/daemon/cluster/convert/swarm.go index bd92c7eeba..54dde3efa5 100644 --- a/components/engine/daemon/cluster/convert/swarm.go +++ b/components/engine/daemon/cluster/convert/swarm.go @@ -5,7 +5,7 @@ import ( "strings" "time" - types "github.com/docker/engine-api/types/swarm" + types "github.com/docker/docker/api/types/swarm" swarmapi "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/protobuf/ptypes" ) diff --git a/components/engine/daemon/cluster/convert/task.go b/components/engine/daemon/cluster/convert/task.go index 4ba85b726a..f23bccb545 100644 --- a/components/engine/daemon/cluster/convert/task.go +++ b/components/engine/daemon/cluster/convert/task.go @@ -3,7 +3,7 @@ package convert import ( "strings" - types "github.com/docker/engine-api/types/swarm" + types "github.com/docker/docker/api/types/swarm" swarmapi "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/protobuf/ptypes" ) diff --git a/components/engine/daemon/cluster/executor/backend.go b/components/engine/daemon/cluster/executor/backend.go index 4576b106bf..af09be55d4 100644 --- a/components/engine/daemon/cluster/executor/backend.go +++ b/components/engine/daemon/cluster/executor/backend.go @@ -4,12 +4,12 @@ import ( "io" "time" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" clustertypes "github.com/docker/docker/daemon/cluster/provider" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/events" - "github.com/docker/engine-api/types/filters" - "github.com/docker/engine-api/types/network" "github.com/docker/libnetwork" "github.com/docker/libnetwork/cluster" networktypes "github.com/docker/libnetwork/types" diff --git a/components/engine/daemon/cluster/filters.go b/components/engine/daemon/cluster/filters.go index c49ea7b39b..a1d800e56c 100644 --- a/components/engine/daemon/cluster/filters.go +++ b/components/engine/daemon/cluster/filters.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" + "github.com/docker/docker/api/types/filters" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types/filters" swarmapi "github.com/docker/swarmkit/api" ) diff --git a/components/engine/daemon/cluster/provider/network.go b/components/engine/daemon/cluster/provider/network.go index d99c2f7294..f4c72ae13b 100644 --- a/components/engine/daemon/cluster/provider/network.go +++ b/components/engine/daemon/cluster/provider/network.go @@ -1,6 +1,6 @@ package provider -import "github.com/docker/engine-api/types" +import "github.com/docker/docker/api/types" // NetworkCreateRequest is a request when creating a network. type NetworkCreateRequest struct { diff --git a/components/engine/daemon/commit.go b/components/engine/daemon/commit.go index 507a311233..3db3ea0070 100644 --- a/components/engine/daemon/commit.go +++ b/components/engine/daemon/commit.go @@ -8,6 +8,7 @@ import ( "time" "github.com/docker/docker/api/types/backend" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder/dockerfile" "github.com/docker/docker/container" "github.com/docker/docker/dockerversion" @@ -16,7 +17,6 @@ import ( "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/reference" - containertypes "github.com/docker/engine-api/types/container" "github.com/docker/go-connections/nat" ) diff --git a/components/engine/daemon/config_unix.go b/components/engine/daemon/config_unix.go index 10b5a0da28..74c3ae4f8e 100644 --- a/components/engine/daemon/config_unix.go +++ b/components/engine/daemon/config_unix.go @@ -6,9 +6,9 @@ import ( "fmt" "net" + "github.com/docker/docker/api/types" "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types" units "github.com/docker/go-units" "github.com/spf13/pflag" ) diff --git a/components/engine/daemon/config_windows.go b/components/engine/daemon/config_windows.go index 6740743a59..90c2a1dc0a 100644 --- a/components/engine/daemon/config_windows.go +++ b/components/engine/daemon/config_windows.go @@ -3,7 +3,7 @@ package daemon import ( "os" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" "github.com/spf13/pflag" ) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index fd68add7fc..bd9a307c12 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/daemon/container.go @@ -6,6 +6,8 @@ import ( "regexp" "time" + containertypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/strslice" "github.com/docker/docker/container" "github.com/docker/docker/daemon/network" "github.com/docker/docker/errors" @@ -13,8 +15,6 @@ import ( "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/truncindex" - containertypes "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/strslice" "github.com/docker/go-connections/nat" ) diff --git a/components/engine/daemon/container_operations.go b/components/engine/daemon/container_operations.go index 83a6001cf2..68bd1b458a 100644 --- a/components/engine/daemon/container_operations.go +++ b/components/engine/daemon/container_operations.go @@ -9,13 +9,13 @@ import ( "strings" "github.com/Sirupsen/logrus" + containertypes "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/container" "github.com/docker/docker/daemon/network" derr "github.com/docker/docker/errors" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" - containertypes "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" "github.com/docker/go-connections/nat" "github.com/docker/libnetwork" "github.com/docker/libnetwork/netlabel" diff --git a/components/engine/daemon/container_operations_solaris.go b/components/engine/daemon/container_operations_solaris.go index 511fc3a39e..4294cd7b4f 100644 --- a/components/engine/daemon/container_operations_solaris.go +++ b/components/engine/daemon/container_operations_solaris.go @@ -5,8 +5,8 @@ package daemon import ( "fmt" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/container" - networktypes "github.com/docker/engine-api/types/network" ) func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) { diff --git a/components/engine/daemon/container_operations_unix.go b/components/engine/daemon/container_operations_unix.go index 5c048d1281..fc44932d4e 100644 --- a/components/engine/daemon/container_operations_unix.go +++ b/components/engine/daemon/container_operations_unix.go @@ -12,6 +12,8 @@ import ( "time" "github.com/Sirupsen/logrus" + containertypes "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/container" "github.com/docker/docker/daemon/links" "github.com/docker/docker/pkg/fileutils" @@ -19,8 +21,6 @@ import ( "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" - containertypes "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/label" diff --git a/components/engine/daemon/container_operations_windows.go b/components/engine/daemon/container_operations_windows.go index f85caa8d2c..817f1a07f9 100644 --- a/components/engine/daemon/container_operations_windows.go +++ b/components/engine/daemon/container_operations_windows.go @@ -5,8 +5,8 @@ package daemon import ( "fmt" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/container" - networktypes "github.com/docker/engine-api/types/network" ) func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) { diff --git a/components/engine/daemon/create.go b/components/engine/daemon/create.go index 7bebcb77a1..b9dbc8e83d 100644 --- a/components/engine/daemon/create.go +++ b/components/engine/daemon/create.go @@ -6,6 +6,9 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/container" "github.com/docker/docker/errors" "github.com/docker/docker/image" @@ -14,9 +17,6 @@ import ( "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" volumestore "github.com/docker/docker/volume/store" - "github.com/docker/engine-api/types" - containertypes "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" "github.com/opencontainers/runc/libcontainer/label" ) diff --git a/components/engine/daemon/create_unix.go b/components/engine/daemon/create_unix.go index 37c4a911f0..eecf27e1f4 100644 --- a/components/engine/daemon/create_unix.go +++ b/components/engine/daemon/create_unix.go @@ -8,9 +8,9 @@ import ( "path/filepath" "github.com/Sirupsen/logrus" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/pkg/stringid" - containertypes "github.com/docker/engine-api/types/container" "github.com/opencontainers/runc/libcontainer/label" ) diff --git a/components/engine/daemon/create_windows.go b/components/engine/daemon/create_windows.go index d4da759fd3..06e284feb0 100644 --- a/components/engine/daemon/create_windows.go +++ b/components/engine/daemon/create_windows.go @@ -3,10 +3,10 @@ package daemon import ( "fmt" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/volume" - containertypes "github.com/docker/engine-api/types/container" ) // createContainerPlatformSpecificSettings performs platform specific container create functionality diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 04aeeb2c66..52e587eb43 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/daemon/daemon.go @@ -23,11 +23,11 @@ import ( "github.com/Sirupsen/logrus" containerd "github.com/docker/containerd/api/grpc/types" "github.com/docker/docker/api" + "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/daemon/events" "github.com/docker/docker/daemon/exec" - "github.com/docker/engine-api/types" - containertypes "github.com/docker/engine-api/types/container" "github.com/docker/libnetwork/cluster" // register graph drivers _ "github.com/docker/docker/daemon/graphdriver/register" diff --git a/components/engine/daemon/daemon_experimental.go b/components/engine/daemon/daemon_experimental.go index a09aeb7062..149e4c71bc 100644 --- a/components/engine/daemon/daemon_experimental.go +++ b/components/engine/daemon/daemon_experimental.go @@ -3,9 +3,9 @@ package daemon import ( + "github.com/docker/docker/api/types/container" "github.com/docker/docker/libcontainerd" "github.com/docker/docker/plugin" - "github.com/docker/engine-api/types/container" ) func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) { diff --git a/components/engine/daemon/daemon_solaris.go b/components/engine/daemon/daemon_solaris.go index 8eb7872635..7578bc5b5b 100644 --- a/components/engine/daemon/daemon_solaris.go +++ b/components/engine/daemon/daemon_solaris.go @@ -5,14 +5,14 @@ package daemon import ( "fmt" + "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/image" "github.com/docker/docker/layer" "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - containertypes "github.com/docker/engine-api/types/container" "github.com/docker/libnetwork" nwconfig "github.com/docker/libnetwork/config" ) diff --git a/components/engine/daemon/daemon_stub.go b/components/engine/daemon/daemon_stub.go index a5f534964d..5c72f82b3c 100644 --- a/components/engine/daemon/daemon_stub.go +++ b/components/engine/daemon/daemon_stub.go @@ -3,8 +3,8 @@ package daemon import ( + "github.com/docker/docker/api/types/container" "github.com/docker/docker/libcontainerd" - "github.com/docker/engine-api/types/container" ) func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) { diff --git a/components/engine/daemon/daemon_test.go b/components/engine/daemon/daemon_test.go index d799823970..223b3cd6ac 100644 --- a/components/engine/daemon/daemon_test.go +++ b/components/engine/daemon/daemon_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/pkg/discovery" _ "github.com/docker/docker/pkg/discovery/memory" @@ -17,7 +18,6 @@ import ( volumedrivers "github.com/docker/docker/volume/drivers" "github.com/docker/docker/volume/local" "github.com/docker/docker/volume/store" - containertypes "github.com/docker/engine-api/types/container" "github.com/docker/go-connections/nat" ) diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go index ee64089dfb..ecd9a8b0f8 100644 --- a/components/engine/daemon/daemon_unix.go +++ b/components/engine/daemon/daemon_unix.go @@ -16,6 +16,10 @@ import ( "syscall" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/blkiodev" + pblkiodev "github.com/docker/docker/api/types/blkiodev" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/image" "github.com/docker/docker/pkg/idtools" @@ -24,10 +28,6 @@ import ( "github.com/docker/docker/pkg/sysinfo" "github.com/docker/docker/runconfig" runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/blkiodev" - pblkiodev "github.com/docker/engine-api/types/blkiodev" - containertypes "github.com/docker/engine-api/types/container" "github.com/docker/libnetwork" nwconfig "github.com/docker/libnetwork/config" "github.com/docker/libnetwork/drivers/bridge" diff --git a/components/engine/daemon/daemon_unix_test.go b/components/engine/daemon/daemon_unix_test.go index 98d94ff23d..3b15547b9a 100644 --- a/components/engine/daemon/daemon_unix_test.go +++ b/components/engine/daemon/daemon_unix_test.go @@ -8,12 +8,12 @@ import ( "path/filepath" "testing" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/volume" "github.com/docker/docker/volume/drivers" "github.com/docker/docker/volume/local" "github.com/docker/docker/volume/store" - containertypes "github.com/docker/engine-api/types/container" ) // Unix test as uses settings which are not available on Windows diff --git a/components/engine/daemon/daemon_windows.go b/components/engine/daemon/daemon_windows.go index 8151c42c59..ad65fab4ce 100644 --- a/components/engine/daemon/daemon_windows.go +++ b/components/engine/daemon/daemon_windows.go @@ -7,6 +7,9 @@ import ( "github.com/Microsoft/hcsshim" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + pblkiodev "github.com/docker/docker/api/types/blkiodev" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/image" "github.com/docker/docker/pkg/idtools" @@ -14,9 +17,6 @@ import ( "github.com/docker/docker/pkg/sysinfo" "github.com/docker/docker/pkg/system" "github.com/docker/docker/runconfig" - "github.com/docker/engine-api/types" - pblkiodev "github.com/docker/engine-api/types/blkiodev" - containertypes "github.com/docker/engine-api/types/container" "github.com/docker/libnetwork" nwconfig "github.com/docker/libnetwork/config" winlibnetwork "github.com/docker/libnetwork/drivers/windows" diff --git a/components/engine/daemon/delete.go b/components/engine/daemon/delete.go index 4aa1e2cc08..9658a40325 100644 --- a/components/engine/daemon/delete.go +++ b/components/engine/daemon/delete.go @@ -7,11 +7,11 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/container" "github.com/docker/docker/errors" "github.com/docker/docker/layer" volumestore "github.com/docker/docker/volume/store" - "github.com/docker/engine-api/types" ) // ContainerRm removes the container id from the filesystem. An error diff --git a/components/engine/daemon/delete_test.go b/components/engine/daemon/delete_test.go index 9db83b0685..2e3c0a26ec 100644 --- a/components/engine/daemon/delete_test.go +++ b/components/engine/daemon/delete_test.go @@ -5,9 +5,9 @@ import ( "os" "testing" + "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" - "github.com/docker/engine-api/types" - containertypes "github.com/docker/engine-api/types/container" ) func TestContainerDoubleDelete(t *testing.T) { diff --git a/components/engine/daemon/events.go b/components/engine/daemon/events.go index e01015777e..8fe8e1b640 100644 --- a/components/engine/daemon/events.go +++ b/components/engine/daemon/events.go @@ -4,10 +4,10 @@ import ( "strings" "time" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/container" daemonevents "github.com/docker/docker/daemon/events" - "github.com/docker/engine-api/types/events" - "github.com/docker/engine-api/types/filters" "github.com/docker/libnetwork" ) diff --git a/components/engine/daemon/events/events.go b/components/engine/daemon/events/events.go index df2181fb09..1c08fb05b5 100644 --- a/components/engine/daemon/events/events.go +++ b/components/engine/daemon/events/events.go @@ -4,8 +4,8 @@ import ( "sync" "time" + eventtypes "github.com/docker/docker/api/types/events" "github.com/docker/docker/pkg/pubsub" - eventtypes "github.com/docker/engine-api/types/events" ) const ( diff --git a/components/engine/daemon/events/events_test.go b/components/engine/daemon/events/events_test.go index 0c8ee6b920..2c19f1832a 100644 --- a/components/engine/daemon/events/events_test.go +++ b/components/engine/daemon/events/events_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" + "github.com/docker/docker/api/types/events" + timetypes "github.com/docker/docker/api/types/time" "github.com/docker/docker/daemon/events/testutils" - "github.com/docker/engine-api/types/events" - timetypes "github.com/docker/engine-api/types/time" ) func TestEventsLog(t *testing.T) { diff --git a/components/engine/daemon/events/filter.go b/components/engine/daemon/events/filter.go index 525431c915..78da87de30 100644 --- a/components/engine/daemon/events/filter.go +++ b/components/engine/daemon/events/filter.go @@ -1,9 +1,9 @@ package events import ( + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types/events" - "github.com/docker/engine-api/types/filters" ) // Filter can filter out docker events from a stream diff --git a/components/engine/daemon/events/testutils/testutils.go b/components/engine/daemon/events/testutils/testutils.go index c84418a9e7..56db396bbf 100644 --- a/components/engine/daemon/events/testutils/testutils.go +++ b/components/engine/daemon/events/testutils/testutils.go @@ -6,8 +6,8 @@ import ( "strings" "time" - "github.com/docker/engine-api/types/events" - timetypes "github.com/docker/engine-api/types/time" + "github.com/docker/docker/api/types/events" + timetypes "github.com/docker/docker/api/types/time" ) var ( diff --git a/components/engine/daemon/events_test.go b/components/engine/daemon/events_test.go index 8ee14a3144..2dbcc27dfc 100644 --- a/components/engine/daemon/events_test.go +++ b/components/engine/daemon/events_test.go @@ -4,10 +4,10 @@ import ( "testing" "time" + containertypes "github.com/docker/docker/api/types/container" + eventtypes "github.com/docker/docker/api/types/events" "github.com/docker/docker/container" "github.com/docker/docker/daemon/events" - containertypes "github.com/docker/engine-api/types/container" - eventtypes "github.com/docker/engine-api/types/events" ) func TestLogContainerEventCopyLabels(t *testing.T) { diff --git a/components/engine/daemon/exec.go b/components/engine/daemon/exec.go index d57b6875d8..b535cf851b 100644 --- a/components/engine/daemon/exec.go +++ b/components/engine/daemon/exec.go @@ -9,6 +9,8 @@ import ( "golang.org/x/net/context" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/strslice" "github.com/docker/docker/container" "github.com/docker/docker/daemon/exec" "github.com/docker/docker/errors" @@ -16,8 +18,6 @@ import ( "github.com/docker/docker/pkg/pools" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/term" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/strslice" ) // Seconds to wait after sending TERM before trying KILL diff --git a/components/engine/daemon/health.go b/components/engine/daemon/health.go index db4ee6f6c8..9f7c76ee14 100644 --- a/components/engine/daemon/health.go +++ b/components/engine/daemon/health.go @@ -10,10 +10,10 @@ import ( "golang.org/x/net/context" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/strslice" "github.com/docker/docker/container" "github.com/docker/docker/daemon/exec" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/strslice" ) const ( diff --git a/components/engine/daemon/health_test.go b/components/engine/daemon/health_test.go index caa91da335..d4f0348a89 100644 --- a/components/engine/daemon/health_test.go +++ b/components/engine/daemon/health_test.go @@ -4,11 +4,11 @@ import ( "testing" "time" + "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" + eventtypes "github.com/docker/docker/api/types/events" "github.com/docker/docker/container" "github.com/docker/docker/daemon/events" - "github.com/docker/engine-api/types" - containertypes "github.com/docker/engine-api/types/container" - eventtypes "github.com/docker/engine-api/types/events" ) func reset(c *container.Container) { diff --git a/components/engine/daemon/image.go b/components/engine/daemon/image.go index 9a3fa1aeaa..6faeea954a 100644 --- a/components/engine/daemon/image.go +++ b/components/engine/daemon/image.go @@ -3,11 +3,11 @@ package daemon import ( "fmt" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder" "github.com/docker/docker/image" "github.com/docker/docker/reference" "github.com/docker/docker/runconfig" - containertypes "github.com/docker/engine-api/types/container" ) // ErrImageDoesNotExist is error returned when no image can be found for a reference. diff --git a/components/engine/daemon/image_delete.go b/components/engine/daemon/image_delete.go index 7c61f4e016..b071fca769 100644 --- a/components/engine/daemon/image_delete.go +++ b/components/engine/daemon/image_delete.go @@ -4,12 +4,12 @@ import ( "fmt" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/container" "github.com/docker/docker/errors" "github.com/docker/docker/image" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" ) type conflictType int diff --git a/components/engine/daemon/image_history.go b/components/engine/daemon/image_history.go index 05140d3685..3696146ea0 100644 --- a/components/engine/daemon/image_history.go +++ b/components/engine/daemon/image_history.go @@ -3,9 +3,9 @@ package daemon import ( "fmt" + "github.com/docker/docker/api/types" "github.com/docker/docker/layer" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" ) // ImageHistory returns a slice of ImageHistory structures for the specified image diff --git a/components/engine/daemon/image_inspect.go b/components/engine/daemon/image_inspect.go index 5b0022688e..5831471d3c 100644 --- a/components/engine/daemon/image_inspect.go +++ b/components/engine/daemon/image_inspect.go @@ -4,9 +4,9 @@ import ( "fmt" "time" + "github.com/docker/docker/api/types" "github.com/docker/docker/layer" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" ) // LookupImage looks up an image by name and returns it as an ImageInspect diff --git a/components/engine/daemon/image_pull.go b/components/engine/daemon/image_pull.go index 06ffa06fbe..984bdf63e6 100644 --- a/components/engine/daemon/image_pull.go +++ b/components/engine/daemon/image_pull.go @@ -5,12 +5,12 @@ import ( "strings" "github.com/docker/distribution/digest" + "github.com/docker/docker/api/types" "github.com/docker/docker/builder" "github.com/docker/docker/distribution" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/reference" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" "golang.org/x/net/context" ) diff --git a/components/engine/daemon/image_push.go b/components/engine/daemon/image_push.go index 11c89709f1..42d27e02ad 100644 --- a/components/engine/daemon/image_push.go +++ b/components/engine/daemon/image_push.go @@ -3,10 +3,10 @@ package daemon import ( "io" + "github.com/docker/docker/api/types" "github.com/docker/docker/distribution" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" "golang.org/x/net/context" ) diff --git a/components/engine/daemon/images.go b/components/engine/daemon/images.go index 0060cdafb2..089b3997fd 100644 --- a/components/engine/daemon/images.go +++ b/components/engine/daemon/images.go @@ -5,11 +5,11 @@ import ( "path" "sort" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/image" "github.com/docker/docker/layer" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" ) var acceptedImageFilterTags = map[string]bool{ diff --git a/components/engine/daemon/import.go b/components/engine/daemon/import.go index b980f210f5..c93322b92e 100644 --- a/components/engine/daemon/import.go +++ b/components/engine/daemon/import.go @@ -9,6 +9,7 @@ import ( "runtime" "time" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder/dockerfile" "github.com/docker/docker/dockerversion" "github.com/docker/docker/image" @@ -18,7 +19,6 @@ import ( "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types/container" ) // ImportImage imports an image, getting the archived layer data either from diff --git a/components/engine/daemon/info.go b/components/engine/daemon/info.go index 517d33c5d4..a7a918f3a0 100644 --- a/components/engine/daemon/info.go +++ b/components/engine/daemon/info.go @@ -7,6 +7,7 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/container" "github.com/docker/docker/dockerversion" "github.com/docker/docker/pkg/fileutils" @@ -18,7 +19,6 @@ import ( "github.com/docker/docker/registry" "github.com/docker/docker/utils" "github.com/docker/docker/volume/drivers" - "github.com/docker/engine-api/types" "github.com/docker/go-connections/sockets" ) diff --git a/components/engine/daemon/inspect.go b/components/engine/daemon/inspect.go index 75ca253a85..3577beaaea 100644 --- a/components/engine/daemon/inspect.go +++ b/components/engine/daemon/inspect.go @@ -4,13 +4,13 @@ import ( "fmt" "time" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" + networktypes "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/versions" + "github.com/docker/docker/api/types/versions/v1p20" "github.com/docker/docker/container" "github.com/docker/docker/daemon/network" - "github.com/docker/engine-api/types" - networktypes "github.com/docker/engine-api/types/network" - "github.com/docker/engine-api/types/versions" - "github.com/docker/engine-api/types/versions/v1p20" ) // ContainerInspect returns low-level information about a diff --git a/components/engine/daemon/inspect_solaris.go b/components/engine/daemon/inspect_solaris.go index 2e49bef3a3..72e61d92c4 100644 --- a/components/engine/daemon/inspect_solaris.go +++ b/components/engine/daemon/inspect_solaris.go @@ -1,10 +1,10 @@ package daemon import ( + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" "github.com/docker/docker/container" "github.com/docker/docker/daemon/exec" - "github.com/docker/engine-api/types" ) // This sets platform-specific fields diff --git a/components/engine/daemon/inspect_unix.go b/components/engine/daemon/inspect_unix.go index 9d72d145df..532ebbad2e 100644 --- a/components/engine/daemon/inspect_unix.go +++ b/components/engine/daemon/inspect_unix.go @@ -3,11 +3,11 @@ package daemon import ( + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/versions/v1p19" "github.com/docker/docker/container" "github.com/docker/docker/daemon/exec" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/versions/v1p19" ) // This sets platform-specific fields diff --git a/components/engine/daemon/inspect_windows.go b/components/engine/daemon/inspect_windows.go index a23f703e09..c8d8c795f8 100644 --- a/components/engine/daemon/inspect_windows.go +++ b/components/engine/daemon/inspect_windows.go @@ -1,10 +1,10 @@ package daemon import ( + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" "github.com/docker/docker/container" "github.com/docker/docker/daemon/exec" - "github.com/docker/engine-api/types" ) // This sets platform-specific fields diff --git a/components/engine/daemon/links_test.go b/components/engine/daemon/links_test.go index d7a3c2aea9..e2dbff2d25 100644 --- a/components/engine/daemon/links_test.go +++ b/components/engine/daemon/links_test.go @@ -8,10 +8,10 @@ import ( "path/filepath" "testing" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/stringid" - containertypes "github.com/docker/engine-api/types/container" ) func TestMigrateLegacySqliteLinks(t *testing.T) { diff --git a/components/engine/daemon/list.go b/components/engine/daemon/list.go index 59524d7545..91ccdd9adc 100644 --- a/components/engine/daemon/list.go +++ b/components/engine/daemon/list.go @@ -8,12 +8,12 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/container" "github.com/docker/docker/image" "github.com/docker/docker/volume" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" - networktypes "github.com/docker/engine-api/types/network" "github.com/docker/go-connections/nat" ) diff --git a/components/engine/daemon/logs.go b/components/engine/daemon/logs.go index 1b285c691d..55ef1bac48 100644 --- a/components/engine/daemon/logs.go +++ b/components/engine/daemon/logs.go @@ -10,13 +10,13 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types/backend" + containertypes "github.com/docker/docker/api/types/container" + timetypes "github.com/docker/docker/api/types/time" "github.com/docker/docker/container" "github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/logger/jsonfilelog" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stdcopy" - containertypes "github.com/docker/engine-api/types/container" - timetypes "github.com/docker/engine-api/types/time" ) // ContainerLogs hooks up a container's stdout and stderr streams diff --git a/components/engine/daemon/logs_test.go b/components/engine/daemon/logs_test.go index f0c6f6e1e9..0c36299e09 100644 --- a/components/engine/daemon/logs_test.go +++ b/components/engine/daemon/logs_test.go @@ -3,7 +3,7 @@ package daemon import ( "testing" - containertypes "github.com/docker/engine-api/types/container" + containertypes "github.com/docker/docker/api/types/container" ) func TestMergeAndVerifyLogConfigNilConfig(t *testing.T) { diff --git a/components/engine/daemon/monitor.go b/components/engine/daemon/monitor.go index 60cd11ec2c..b88dff6890 100644 --- a/components/engine/daemon/monitor.go +++ b/components/engine/daemon/monitor.go @@ -8,10 +8,10 @@ import ( "strconv" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/daemon/exec" "github.com/docker/docker/libcontainerd" "github.com/docker/docker/runconfig" - "github.com/docker/engine-api/types" ) // StateChanged updates daemon state changes from containerd diff --git a/components/engine/daemon/network.go b/components/engine/daemon/network.go index 06f2af5df4..48015c00a8 100644 --- a/components/engine/daemon/network.go +++ b/components/engine/daemon/network.go @@ -7,11 +7,11 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" clustertypes "github.com/docker/docker/daemon/cluster/provider" "github.com/docker/docker/errors" "github.com/docker/docker/runconfig" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/network" "github.com/docker/libnetwork" networktypes "github.com/docker/libnetwork/types" ) diff --git a/components/engine/daemon/network/settings.go b/components/engine/daemon/network/settings.go index ff27cb0bbc..4320573c9a 100644 --- a/components/engine/daemon/network/settings.go +++ b/components/engine/daemon/network/settings.go @@ -1,8 +1,8 @@ package network import ( + networktypes "github.com/docker/docker/api/types/network" clustertypes "github.com/docker/docker/daemon/cluster/provider" - networktypes "github.com/docker/engine-api/types/network" "github.com/docker/go-connections/nat" ) diff --git a/components/engine/daemon/oci_linux.go b/components/engine/daemon/oci_linux.go index 8e161af72c..49d9e1c8aa 100644 --- a/components/engine/daemon/oci_linux.go +++ b/components/engine/daemon/oci_linux.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/Sirupsen/logrus" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/daemon/caps" "github.com/docker/docker/libcontainerd" @@ -19,7 +20,6 @@ import ( "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/volume" - containertypes "github.com/docker/engine-api/types/container" "github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/user" diff --git a/components/engine/daemon/search.go b/components/engine/daemon/search.go index 09c6ae49b7..5d2ac5d222 100644 --- a/components/engine/daemon/search.go +++ b/components/engine/daemon/search.go @@ -6,10 +6,10 @@ import ( "golang.org/x/net/context" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/dockerversion" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" - registrytypes "github.com/docker/engine-api/types/registry" ) var acceptedSearchFilterTags = map[string]bool{ diff --git a/components/engine/daemon/search_test.go b/components/engine/daemon/search_test.go index 8a98a52623..f5aa85a61e 100644 --- a/components/engine/daemon/search_test.go +++ b/components/engine/daemon/search_test.go @@ -7,9 +7,9 @@ import ( "golang.org/x/net/context" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" ) type FakeService struct { diff --git a/components/engine/daemon/start.go b/components/engine/daemon/start.go index c1e6c7983e..561f0a09c3 100644 --- a/components/engine/daemon/start.go +++ b/components/engine/daemon/start.go @@ -10,12 +10,12 @@ import ( "google.golang.org/grpc" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/errors" "github.com/docker/docker/libcontainerd" "github.com/docker/docker/runconfig" - "github.com/docker/engine-api/types" - containertypes "github.com/docker/engine-api/types/container" ) // ContainerStart starts a container. diff --git a/components/engine/daemon/stats.go b/components/engine/daemon/stats.go index 3eafecc256..f7d4afbb95 100644 --- a/components/engine/daemon/stats.go +++ b/components/engine/daemon/stats.go @@ -8,12 +8,12 @@ import ( "golang.org/x/net/context" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/versions" + "github.com/docker/docker/api/types/versions/v1p20" "github.com/docker/docker/container" "github.com/docker/docker/pkg/ioutils" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/versions" - "github.com/docker/engine-api/types/versions/v1p20" ) // ContainerStats writes information about the container to the stream diff --git a/components/engine/daemon/stats_collector_unix.go b/components/engine/daemon/stats_collector_unix.go index f66dc2c3c7..fa34d97ce0 100644 --- a/components/engine/daemon/stats_collector_unix.go +++ b/components/engine/daemon/stats_collector_unix.go @@ -12,10 +12,10 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/container" "github.com/docker/docker/pkg/pubsub" sysinfo "github.com/docker/docker/pkg/system" - "github.com/docker/engine-api/types" "github.com/opencontainers/runc/libcontainer/system" ) diff --git a/components/engine/daemon/top_unix.go b/components/engine/daemon/top_unix.go index 935f38f29e..7fb81d0148 100644 --- a/components/engine/daemon/top_unix.go +++ b/components/engine/daemon/top_unix.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) func validatePSArgs(psArgs string) error { diff --git a/components/engine/daemon/top_windows.go b/components/engine/daemon/top_windows.go index ea79ac86ac..123eb29862 100644 --- a/components/engine/daemon/top_windows.go +++ b/components/engine/daemon/top_windows.go @@ -4,7 +4,7 @@ import ( "errors" "strconv" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) // ContainerTop is a minimal implementation on Windows currently. diff --git a/components/engine/daemon/update.go b/components/engine/daemon/update.go index e79dfa9a61..46a3d35e5f 100644 --- a/components/engine/daemon/update.go +++ b/components/engine/daemon/update.go @@ -3,8 +3,8 @@ package daemon import ( "fmt" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" ) // ContainerUpdate updates configuration of the container diff --git a/components/engine/daemon/update_linux.go b/components/engine/daemon/update_linux.go index 69cc0840ab..f422325272 100644 --- a/components/engine/daemon/update_linux.go +++ b/components/engine/daemon/update_linux.go @@ -3,8 +3,8 @@ package daemon import ( + "github.com/docker/docker/api/types/container" "github.com/docker/docker/libcontainerd" - "github.com/docker/engine-api/types/container" ) func toContainerdResources(resources container.Resources) libcontainerd.Resources { diff --git a/components/engine/daemon/update_solaris.go b/components/engine/daemon/update_solaris.go index 848adae9d2..f3b545c5f0 100644 --- a/components/engine/daemon/update_solaris.go +++ b/components/engine/daemon/update_solaris.go @@ -1,8 +1,8 @@ package daemon import ( + "github.com/docker/docker/api/types/container" "github.com/docker/docker/libcontainerd" - "github.com/docker/engine-api/types/container" ) func toContainerdResources(resources container.Resources) libcontainerd.Resources { diff --git a/components/engine/daemon/update_windows.go b/components/engine/daemon/update_windows.go index 2cd0ff2618..01466260bb 100644 --- a/components/engine/daemon/update_windows.go +++ b/components/engine/daemon/update_windows.go @@ -3,8 +3,8 @@ package daemon import ( + "github.com/docker/docker/api/types/container" "github.com/docker/docker/libcontainerd" - "github.com/docker/engine-api/types/container" ) func toContainerdResources(resources container.Resources) libcontainerd.Resources { diff --git a/components/engine/daemon/volumes.go b/components/engine/daemon/volumes.go index d178d410ab..4166b09d95 100644 --- a/components/engine/daemon/volumes.go +++ b/components/engine/daemon/volumes.go @@ -7,10 +7,10 @@ import ( "path/filepath" "strings" + "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/volume" - "github.com/docker/engine-api/types" - containertypes "github.com/docker/engine-api/types/container" ) var ( diff --git a/components/engine/distribution/pull.go b/components/engine/distribution/pull.go index dad93b656d..f1d362e5cc 100644 --- a/components/engine/distribution/pull.go +++ b/components/engine/distribution/pull.go @@ -6,13 +6,13 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/docker/api" + "github.com/docker/docker/api/types" "github.com/docker/docker/distribution/metadata" "github.com/docker/docker/distribution/xfer" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/reference" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" "golang.org/x/net/context" ) diff --git a/components/engine/distribution/push.go b/components/engine/distribution/push.go index f3b54b7cf7..e696a4e109 100644 --- a/components/engine/distribution/push.go +++ b/components/engine/distribution/push.go @@ -7,6 +7,7 @@ import ( "io" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/distribution/metadata" "github.com/docker/docker/distribution/xfer" "github.com/docker/docker/image" @@ -14,7 +15,6 @@ import ( "github.com/docker/docker/pkg/progress" "github.com/docker/docker/reference" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" "github.com/docker/libtrust" "golang.org/x/net/context" ) diff --git a/components/engine/distribution/registry.go b/components/engine/distribution/registry.go index 98b82fbaeb..d3c991e875 100644 --- a/components/engine/distribution/registry.go +++ b/components/engine/distribution/registry.go @@ -11,9 +11,9 @@ import ( "github.com/docker/distribution/registry/client" "github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/transport" + "github.com/docker/docker/api/types" "github.com/docker/docker/dockerversion" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" "github.com/docker/go-connections/sockets" "golang.org/x/net/context" ) diff --git a/components/engine/distribution/registry_unit_test.go b/components/engine/distribution/registry_unit_test.go index b60a465d78..23edc095e1 100644 --- a/components/engine/distribution/registry_unit_test.go +++ b/components/engine/distribution/registry_unit_test.go @@ -9,11 +9,11 @@ import ( "testing" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/reference" "github.com/docker/docker/registry" "github.com/docker/docker/utils" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" "golang.org/x/net/context" ) diff --git a/components/engine/image/image.go b/components/engine/image/image.go index 7a05e649fb..d3034cc10d 100644 --- a/components/engine/image/image.go +++ b/components/engine/image/image.go @@ -7,7 +7,7 @@ import ( "time" "github.com/docker/distribution/digest" - "github.com/docker/engine-api/types/container" + "github.com/docker/docker/api/types/container" ) // ID is the content-addressable ID of an image. diff --git a/components/engine/image/v1/imagev1.go b/components/engine/image/v1/imagev1.go index b7a9529ed8..d498ddbc00 100644 --- a/components/engine/image/v1/imagev1.go +++ b/components/engine/image/v1/imagev1.go @@ -9,9 +9,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/image" "github.com/docker/docker/layer" - "github.com/docker/engine-api/types/versions" ) var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`) diff --git a/components/engine/integration-cli/check_test.go b/components/engine/integration-cli/check_test.go index 5b79054e91..3b5965e571 100644 --- a/components/engine/integration-cli/check_test.go +++ b/components/engine/integration-cli/check_test.go @@ -8,9 +8,9 @@ import ( "syscall" "testing" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/reexec" - "github.com/docker/engine-api/types/swarm" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/daemon.go b/components/engine/integration-cli/daemon.go index a16798c0ee..38c96684d5 100644 --- a/components/engine/integration-cli/daemon.go +++ b/components/engine/integration-cli/daemon.go @@ -14,11 +14,11 @@ import ( "strings" "time" + "github.com/docker/docker/api/types/events" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringid" - "github.com/docker/engine-api/types/events" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" "github.com/go-check/check" diff --git a/components/engine/integration-cli/daemon_swarm.go b/components/engine/integration-cli/daemon_swarm.go index efb59991f7..f7f2424e5e 100644 --- a/components/engine/integration-cli/daemon_swarm.go +++ b/components/engine/integration-cli/daemon_swarm.go @@ -7,10 +7,10 @@ import ( "strings" "time" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" - "github.com/docker/engine-api/types/swarm" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_auth_test.go b/components/engine/integration-cli/docker_api_auth_test.go index d73c61d411..1002b06998 100644 --- a/components/engine/integration-cli/docker_api_auth_test.go +++ b/components/engine/integration-cli/docker_api_auth_test.go @@ -3,8 +3,8 @@ package main import ( "net/http" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_containers_test.go b/components/engine/integration-cli/docker_api_containers_test.go index c72d5556b9..567413e1bd 100644 --- a/components/engine/integration-cli/docker_api_containers_test.go +++ b/components/engine/integration-cli/docker_api_containers_test.go @@ -15,12 +15,12 @@ import ( "strings" "time" + "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/pkg/integration" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/pkg/stringid" - "github.com/docker/engine-api/types" - containertypes "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_images_test.go b/components/engine/integration-cli/docker_api_images_test.go index 9d35b0c9ed..2edf8589a1 100644 --- a/components/engine/integration-cli/docker_api_images_test.go +++ b/components/engine/integration-cli/docker_api_images_test.go @@ -6,8 +6,8 @@ import ( "net/url" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_inspect_test.go b/components/engine/integration-cli/docker_api_inspect_test.go index 2477d5a49e..8507b94b2d 100644 --- a/components/engine/integration-cli/docker_api_inspect_test.go +++ b/components/engine/integration-cli/docker_api_inspect_test.go @@ -5,10 +5,10 @@ import ( "net/http" "strings" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions/v1p20" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/pkg/stringutils" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/versions/v1p20" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_network_test.go b/components/engine/integration-cli/docker_api_network_test.go index 40c77cbd9e..dd83579e07 100644 --- a/components/engine/integration-cli/docker_api_network_test.go +++ b/components/engine/integration-cli/docker_api_network_test.go @@ -8,10 +8,10 @@ import ( "net/url" "strings" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" - "github.com/docker/engine-api/types/network" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_service_update_test.go b/components/engine/integration-cli/docker_api_service_update_test.go index 7fdef97121..a0c795b248 100644 --- a/components/engine/integration-cli/docker_api_service_update_test.go +++ b/components/engine/integration-cli/docker_api_service_update_test.go @@ -3,8 +3,8 @@ package main import ( + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types/swarm" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_stats_test.go b/components/engine/integration-cli/docker_api_stats_test.go index 93d18f70df..4ed2aacaa3 100644 --- a/components/engine/integration-cli/docker_api_stats_test.go +++ b/components/engine/integration-cli/docker_api_stats_test.go @@ -11,9 +11,9 @@ import ( "sync" "time" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/versions" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_stats_unix_test.go b/components/engine/integration-cli/docker_api_stats_unix_test.go index 5409a0dbca..31547a29c6 100644 --- a/components/engine/integration-cli/docker_api_stats_unix_test.go +++ b/components/engine/integration-cli/docker_api_stats_unix_test.go @@ -7,8 +7,8 @@ import ( "fmt" "net/http" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_swarm_test.go b/components/engine/integration-cli/docker_api_swarm_test.go index d8d01d10fe..e49c79a193 100644 --- a/components/engine/integration-cli/docker_api_swarm_test.go +++ b/components/engine/integration-cli/docker_api_swarm_test.go @@ -13,8 +13,8 @@ import ( "syscall" "time" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types/swarm" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_version_test.go b/components/engine/integration-cli/docker_api_version_test.go index ccb1484190..eb2de5904a 100644 --- a/components/engine/integration-cli/docker_api_version_test.go +++ b/components/engine/integration-cli/docker_api_version_test.go @@ -4,9 +4,9 @@ import ( "encoding/json" "net/http" + "github.com/docker/docker/api/types" "github.com/docker/docker/dockerversion" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_api_volumes_test.go b/components/engine/integration-cli/docker_api_volumes_test.go index 732271d02d..26fb864c21 100644 --- a/components/engine/integration-cli/docker_api_volumes_test.go +++ b/components/engine/integration-cli/docker_api_volumes_test.go @@ -5,8 +5,8 @@ import ( "net/http" "path/filepath" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_cli_by_digest_test.go b/components/engine/integration-cli/docker_cli_by_digest_test.go index edfed8e04d..8cce0c8ec2 100644 --- a/components/engine/integration-cli/docker_cli_by_digest_test.go +++ b/components/engine/integration-cli/docker_cli_by_digest_test.go @@ -11,9 +11,9 @@ import ( "github.com/docker/distribution/digest" "github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema2" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/pkg/stringutils" - "github.com/docker/engine-api/types" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_cli_external_volume_driver_unix_test.go b/components/engine/integration-cli/docker_cli_external_volume_driver_unix_test.go index 2be42775b5..375b73a77b 100644 --- a/components/engine/integration-cli/docker_cli_external_volume_driver_unix_test.go +++ b/components/engine/integration-cli/docker_cli_external_volume_driver_unix_test.go @@ -15,9 +15,9 @@ import ( "strings" "time" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/volume" - "github.com/docker/engine-api/types" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_cli_health_test.go b/components/engine/integration-cli/docker_cli_health_test.go index 6894efba6c..5cfbfdf32b 100644 --- a/components/engine/integration-cli/docker_cli_health_test.go +++ b/components/engine/integration-cli/docker_cli_health_test.go @@ -2,8 +2,8 @@ package main import ( "encoding/json" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" "github.com/go-check/check" "strconv" "strings" diff --git a/components/engine/integration-cli/docker_cli_inspect_test.go b/components/engine/integration-cli/docker_cli_inspect_test.go index 21117878c4..d28bb074ba 100644 --- a/components/engine/integration-cli/docker_cli_inspect_test.go +++ b/components/engine/integration-cli/docker_cli_inspect_test.go @@ -9,9 +9,9 @@ import ( "strings" "time" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_cli_network_unix_test.go b/components/engine/integration-cli/docker_cli_network_unix_test.go index 7797ee57ad..b86e086b62 100644 --- a/components/engine/integration-cli/docker_cli_network_unix_test.go +++ b/components/engine/integration-cli/docker_cli_network_unix_test.go @@ -14,12 +14,12 @@ import ( "strings" "time" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions/v1p20" "github.com/docker/docker/pkg/integration/checker" icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/versions/v1p20" "github.com/docker/libnetwork/driverapi" remoteapi "github.com/docker/libnetwork/drivers/remote/api" "github.com/docker/libnetwork/ipamapi" diff --git a/components/engine/integration-cli/docker_cli_service_create_test.go b/components/engine/integration-cli/docker_cli_service_create_test.go index 4814ddee59..4b71a18b8d 100644 --- a/components/engine/integration-cli/docker_cli_service_create_test.go +++ b/components/engine/integration-cli/docker_cli_service_create_test.go @@ -6,9 +6,9 @@ import ( "encoding/json" "strings" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/swarm" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_cli_service_health_test.go b/components/engine/integration-cli/docker_cli_service_health_test.go index fb32070827..4782317765 100644 --- a/components/engine/integration-cli/docker_cli_service_health_test.go +++ b/components/engine/integration-cli/docker_cli_service_health_test.go @@ -6,9 +6,9 @@ import ( "strconv" "strings" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/daemon/cluster/executor/container" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types/swarm" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_cli_service_update_test.go b/components/engine/integration-cli/docker_cli_service_update_test.go index cf017dcaa8..e99a176bcf 100644 --- a/components/engine/integration-cli/docker_cli_service_update_test.go +++ b/components/engine/integration-cli/docker_cli_service_update_test.go @@ -5,8 +5,8 @@ package main import ( "encoding/json" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types/swarm" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_cli_swarm_test.go b/components/engine/integration-cli/docker_cli_swarm_test.go index 9fc3f40069..a1ffcd9027 100644 --- a/components/engine/integration-cli/docker_cli_swarm_test.go +++ b/components/engine/integration-cli/docker_cli_swarm_test.go @@ -7,8 +7,8 @@ import ( "strings" "time" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/engine-api/types/swarm" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_cli_update_unix_test.go b/components/engine/integration-cli/docker_cli_update_unix_test.go index 21fa9991bb..fb695372f1 100644 --- a/components/engine/integration-cli/docker_cli_update_unix_test.go +++ b/components/engine/integration-cli/docker_cli_update_unix_test.go @@ -7,9 +7,9 @@ import ( "fmt" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/pkg/parsers/kernel" - "github.com/docker/engine-api/types" "github.com/go-check/check" ) diff --git a/components/engine/integration-cli/docker_utils.go b/components/engine/integration-cli/docker_utils.go index edcfc88a3b..e56b9f2949 100644 --- a/components/engine/integration-cli/docker_utils.go +++ b/components/engine/integration-cli/docker_utils.go @@ -22,12 +22,12 @@ import ( "strings" "time" + "github.com/docker/docker/api/types" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/httputils" icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringutils" - "github.com/docker/engine-api/types" "github.com/docker/go-connections/tlsconfig" "github.com/docker/go-units" "github.com/go-check/check" diff --git a/components/engine/opts/opts.go b/components/engine/opts/opts.go index 20f4c655a1..f8bb3ba745 100644 --- a/components/engine/opts/opts.go +++ b/components/engine/opts/opts.go @@ -6,7 +6,7 @@ import ( "regexp" "strings" - "github.com/docker/engine-api/types/filters" + "github.com/docker/docker/api/types/filters" ) var ( diff --git a/components/engine/plugin/backend.go b/components/engine/plugin/backend.go index 089efdb4e6..4c57325412 100644 --- a/components/engine/plugin/backend.go +++ b/components/engine/plugin/backend.go @@ -12,12 +12,12 @@ import ( "path/filepath" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/plugin/distribution" "github.com/docker/docker/plugin/v2" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" ) // Disable deactivates a plugin, which implies that they cannot be used by containers. diff --git a/components/engine/plugin/distribution/pull.go b/components/engine/plugin/distribution/pull.go index c60865340c..d061744558 100644 --- a/components/engine/plugin/distribution/pull.go +++ b/components/engine/plugin/distribution/pull.go @@ -14,11 +14,11 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution" "github.com/docker/distribution/manifest/schema2" + "github.com/docker/docker/api/types" dockerdist "github.com/docker/docker/distribution" archive "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/reference" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" "golang.org/x/net/context" ) diff --git a/components/engine/plugin/distribution/push.go b/components/engine/plugin/distribution/push.go index 5a9f689492..131738976f 100644 --- a/components/engine/plugin/distribution/push.go +++ b/components/engine/plugin/distribution/push.go @@ -11,10 +11,10 @@ import ( "github.com/docker/distribution" "github.com/docker/distribution/digest" "github.com/docker/distribution/manifest/schema2" + "github.com/docker/docker/api/types" dockerdist "github.com/docker/docker/distribution" "github.com/docker/docker/reference" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" "golang.org/x/net/context" ) diff --git a/components/engine/plugin/manager_linux.go b/components/engine/plugin/manager_linux.go index 4535212283..301b522149 100644 --- a/components/engine/plugin/manager_linux.go +++ b/components/engine/plugin/manager_linux.go @@ -9,12 +9,12 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/libcontainerd" "github.com/docker/docker/oci" "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/plugin/v2" "github.com/docker/docker/restartmanager" - "github.com/docker/engine-api/types/container" ) func (pm *Manager) enable(p *v2.Plugin, force bool) error { diff --git a/components/engine/plugin/v2/plugin.go b/components/engine/plugin/v2/plugin.go index 5e6d643136..48e1be576c 100644 --- a/components/engine/plugin/v2/plugin.go +++ b/components/engine/plugin/v2/plugin.go @@ -11,10 +11,10 @@ import ( "strings" "sync" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/pkg/system" "github.com/docker/docker/restartmanager" - "github.com/docker/engine-api/types" "github.com/opencontainers/runtime-spec/specs-go" ) diff --git a/components/engine/profiles/seccomp/seccomp.go b/components/engine/profiles/seccomp/seccomp.go index ac7496ced9..a54ef50a8b 100644 --- a/components/engine/profiles/seccomp/seccomp.go +++ b/components/engine/profiles/seccomp/seccomp.go @@ -7,8 +7,8 @@ import ( "errors" "fmt" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringutils" - "github.com/docker/engine-api/types" "github.com/opencontainers/runtime-spec/specs-go" libseccomp "github.com/seccomp/libseccomp-golang" ) diff --git a/components/engine/profiles/seccomp/seccomp_default.go b/components/engine/profiles/seccomp/seccomp_default.go index f6f84daf94..b392640c92 100644 --- a/components/engine/profiles/seccomp/seccomp_default.go +++ b/components/engine/profiles/seccomp/seccomp_default.go @@ -5,7 +5,7 @@ package seccomp import ( "syscall" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) func arches() []types.Architecture { diff --git a/components/engine/profiles/seccomp/seccomp_unsupported.go b/components/engine/profiles/seccomp/seccomp_unsupported.go index b3906f916f..f84b20b6d9 100644 --- a/components/engine/profiles/seccomp/seccomp_unsupported.go +++ b/components/engine/profiles/seccomp/seccomp_unsupported.go @@ -3,7 +3,7 @@ package seccomp import ( - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" "github.com/opencontainers/runtime-spec/specs-go" ) diff --git a/components/engine/registry/auth.go b/components/engine/registry/auth.go index 045c8deac1..0bf0450b2c 100644 --- a/components/engine/registry/auth.go +++ b/components/engine/registry/auth.go @@ -11,8 +11,8 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/transport" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" ) const ( diff --git a/components/engine/registry/auth_test.go b/components/engine/registry/auth_test.go index eedee44ef7..f5f213bf94 100644 --- a/components/engine/registry/auth_test.go +++ b/components/engine/registry/auth_test.go @@ -3,8 +3,8 @@ package registry import ( "testing" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" ) func buildAuthConfigs() map[string]types.AuthConfig { diff --git a/components/engine/registry/config.go b/components/engine/registry/config.go index 0adcc9c122..588720475c 100644 --- a/components/engine/registry/config.go +++ b/components/engine/registry/config.go @@ -7,9 +7,9 @@ import ( "net/url" "strings" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/opts" "github.com/docker/docker/reference" - registrytypes "github.com/docker/engine-api/types/registry" "github.com/spf13/pflag" ) diff --git a/components/engine/registry/endpoint_v1.go b/components/engine/registry/endpoint_v1.go index fd81972c72..6bcf8c935d 100644 --- a/components/engine/registry/endpoint_v1.go +++ b/components/engine/registry/endpoint_v1.go @@ -11,7 +11,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/registry/client/transport" - registrytypes "github.com/docker/engine-api/types/registry" + registrytypes "github.com/docker/docker/api/types/registry" ) // V1Endpoint stores basic information about a V1 registry endpoint. diff --git a/components/engine/registry/registry_mock_test.go b/components/engine/registry/registry_mock_test.go index 828f48fc92..ea2719fe55 100644 --- a/components/engine/registry/registry_mock_test.go +++ b/components/engine/registry/registry_mock_test.go @@ -15,8 +15,8 @@ import ( "testing" "time" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/reference" - registrytypes "github.com/docker/engine-api/types/registry" "github.com/gorilla/mux" "github.com/Sirupsen/logrus" diff --git a/components/engine/registry/registry_test.go b/components/engine/registry/registry_test.go index 9927af32d8..4eb312a998 100644 --- a/components/engine/registry/registry_test.go +++ b/components/engine/registry/registry_test.go @@ -9,9 +9,9 @@ import ( "testing" "github.com/docker/distribution/registry/client/transport" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" ) var ( diff --git a/components/engine/registry/service.go b/components/engine/registry/service.go index dbc16284f0..808d4555cf 100644 --- a/components/engine/registry/service.go +++ b/components/engine/registry/service.go @@ -11,9 +11,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/registry/client/auth" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" ) const ( diff --git a/components/engine/registry/session.go b/components/engine/registry/session.go index d48b9e8d20..72e286ab44 100644 --- a/components/engine/registry/session.go +++ b/components/engine/registry/session.go @@ -20,13 +20,13 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/registry/api/errcode" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" ) var ( diff --git a/components/engine/registry/types.go b/components/engine/registry/types.go index 601fa09ed7..479d22a7a6 100644 --- a/components/engine/registry/types.go +++ b/components/engine/registry/types.go @@ -1,8 +1,8 @@ package registry import ( + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/reference" - registrytypes "github.com/docker/engine-api/types/registry" ) // RepositoryData tracks the image list, list of endpoints, and list of tokens diff --git a/components/engine/restartmanager/restartmanager.go b/components/engine/restartmanager/restartmanager.go index 9893183a2a..570fc93802 100644 --- a/components/engine/restartmanager/restartmanager.go +++ b/components/engine/restartmanager/restartmanager.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/docker/engine-api/types/container" + "github.com/docker/docker/api/types/container" ) const ( diff --git a/components/engine/restartmanager/restartmanager_test.go b/components/engine/restartmanager/restartmanager_test.go index 95a36b426b..20eced54d3 100644 --- a/components/engine/restartmanager/restartmanager_test.go +++ b/components/engine/restartmanager/restartmanager_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/docker/engine-api/types/container" + "github.com/docker/docker/api/types/container" ) func TestRestartManagerTimeout(t *testing.T) { diff --git a/components/engine/runconfig/compare.go b/components/engine/runconfig/compare.go index 61346aabf4..708922f986 100644 --- a/components/engine/runconfig/compare.go +++ b/components/engine/runconfig/compare.go @@ -1,6 +1,6 @@ package runconfig -import "github.com/docker/engine-api/types/container" +import "github.com/docker/docker/api/types/container" // Compare two Config struct. Do not compare the "Image" nor "Hostname" fields // If OpenStdin is set, then it differs diff --git a/components/engine/runconfig/compare_test.go b/components/engine/runconfig/compare_test.go index 9c17c553f3..6370d7a887 100644 --- a/components/engine/runconfig/compare_test.go +++ b/components/engine/runconfig/compare_test.go @@ -3,8 +3,8 @@ package runconfig import ( "testing" - "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/strslice" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/strslice" "github.com/docker/go-connections/nat" ) diff --git a/components/engine/runconfig/config.go b/components/engine/runconfig/config.go index 8145e4b1da..e55b9589c7 100644 --- a/components/engine/runconfig/config.go +++ b/components/engine/runconfig/config.go @@ -5,9 +5,9 @@ import ( "fmt" "io" + "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/volume" - "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" ) // ContainerDecoder implements httputils.ContainerDecoder diff --git a/components/engine/runconfig/config_test.go b/components/engine/runconfig/config_test.go index 5804b12d0e..47a58f4f3e 100644 --- a/components/engine/runconfig/config_test.go +++ b/components/engine/runconfig/config_test.go @@ -9,9 +9,9 @@ import ( "strings" "testing" - "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" - "github.com/docker/engine-api/types/strslice" + "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/strslice" ) type f struct { diff --git a/components/engine/runconfig/config_unix.go b/components/engine/runconfig/config_unix.go index e5902fb024..4ccfc73be2 100644 --- a/components/engine/runconfig/config_unix.go +++ b/components/engine/runconfig/config_unix.go @@ -3,8 +3,8 @@ package runconfig import ( - "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" + "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" ) // ContainerConfigWrapper is a Config wrapper that holds the container Config (portable) diff --git a/components/engine/runconfig/config_windows.go b/components/engine/runconfig/config_windows.go index 50a5238000..f2361b554b 100644 --- a/components/engine/runconfig/config_windows.go +++ b/components/engine/runconfig/config_windows.go @@ -1,8 +1,8 @@ package runconfig import ( - "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" + "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" ) // ContainerConfigWrapper is a Config wrapper that holds the container Config (portable) diff --git a/components/engine/runconfig/hostconfig.go b/components/engine/runconfig/hostconfig.go index 769cc9f5da..2b81d02c20 100644 --- a/components/engine/runconfig/hostconfig.go +++ b/components/engine/runconfig/hostconfig.go @@ -4,7 +4,7 @@ import ( "encoding/json" "io" - "github.com/docker/engine-api/types/container" + "github.com/docker/docker/api/types/container" ) // DecodeHostConfig creates a HostConfig based on the specified Reader. diff --git a/components/engine/runconfig/hostconfig_solaris.go b/components/engine/runconfig/hostconfig_solaris.go index 5c2e861202..97b66480c7 100644 --- a/components/engine/runconfig/hostconfig_solaris.go +++ b/components/engine/runconfig/hostconfig_solaris.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/docker/engine-api/types/container" + "github.com/docker/docker/api/types/container" ) // DefaultDaemonNetworkMode returns the default network stack the daemon should diff --git a/components/engine/runconfig/hostconfig_test.go b/components/engine/runconfig/hostconfig_test.go index 1fbc90a4cf..8fdc567636 100644 --- a/components/engine/runconfig/hostconfig_test.go +++ b/components/engine/runconfig/hostconfig_test.go @@ -8,7 +8,7 @@ import ( "io/ioutil" "testing" - "github.com/docker/engine-api/types/container" + "github.com/docker/docker/api/types/container" ) // TODO Windows: This will need addressing for a Windows daemon. diff --git a/components/engine/runconfig/hostconfig_unix.go b/components/engine/runconfig/hostconfig_unix.go index c06b6ebfa1..c43718fc28 100644 --- a/components/engine/runconfig/hostconfig_unix.go +++ b/components/engine/runconfig/hostconfig_unix.go @@ -7,7 +7,7 @@ import ( "runtime" "strings" - "github.com/docker/engine-api/types/container" + "github.com/docker/docker/api/types/container" ) // DefaultDaemonNetworkMode returns the default network stack the daemon should diff --git a/components/engine/runconfig/hostconfig_windows.go b/components/engine/runconfig/hostconfig_windows.go index d06452db18..edfd0b1e79 100644 --- a/components/engine/runconfig/hostconfig_windows.go +++ b/components/engine/runconfig/hostconfig_windows.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/docker/engine-api/types/container" + "github.com/docker/docker/api/types/container" ) // DefaultDaemonNetworkMode returns the default network stack the daemon should diff --git a/components/engine/runconfig/opts/parse.go b/components/engine/runconfig/opts/parse.go index 83ad27f8ed..106109202f 100644 --- a/components/engine/runconfig/opts/parse.go +++ b/components/engine/runconfig/opts/parse.go @@ -10,12 +10,12 @@ import ( "strings" "time" + "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/strslice" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/signal" - "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" - "github.com/docker/engine-api/types/strslice" "github.com/docker/go-connections/nat" units "github.com/docker/go-units" "github.com/spf13/pflag" diff --git a/components/engine/runconfig/opts/parse_test.go b/components/engine/runconfig/opts/parse_test.go index 100b5d93a1..67f4c40e37 100644 --- a/components/engine/runconfig/opts/parse_test.go +++ b/components/engine/runconfig/opts/parse_test.go @@ -11,9 +11,9 @@ import ( "testing" "time" + "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/runconfig" - "github.com/docker/engine-api/types/container" - networktypes "github.com/docker/engine-api/types/network" "github.com/docker/go-connections/nat" "github.com/spf13/pflag" ) diff --git a/components/engine/runconfig/opts/runtime.go b/components/engine/runconfig/opts/runtime.go index 541fda017a..4361b3ce09 100644 --- a/components/engine/runconfig/opts/runtime.go +++ b/components/engine/runconfig/opts/runtime.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) // RuntimeOpt defines a map of Runtimes diff --git a/components/engine/runconfig/opts/throttledevice.go b/components/engine/runconfig/opts/throttledevice.go index f69e74ecc7..3a104b86f8 100644 --- a/components/engine/runconfig/opts/throttledevice.go +++ b/components/engine/runconfig/opts/throttledevice.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "github.com/docker/engine-api/types/blkiodev" + "github.com/docker/docker/api/types/blkiodev" "github.com/docker/go-units" ) diff --git a/components/engine/runconfig/opts/weightdevice.go b/components/engine/runconfig/opts/weightdevice.go index b3afd2213c..2a5da6da08 100644 --- a/components/engine/runconfig/opts/weightdevice.go +++ b/components/engine/runconfig/opts/weightdevice.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "github.com/docker/engine-api/types/blkiodev" + "github.com/docker/docker/api/types/blkiodev" ) // ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error. diff --git a/components/engine/volume/volume.go b/components/engine/volume/volume.go index f6e28a4840..5385a94545 100644 --- a/components/engine/volume/volume.go +++ b/components/engine/volume/volume.go @@ -6,9 +6,9 @@ import ( "strings" "syscall" + mounttypes "github.com/docker/docker/api/types/mount" "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/stringid" - mounttypes "github.com/docker/engine-api/types/mount" "github.com/opencontainers/runc/libcontainer/label" ) diff --git a/components/engine/volume/volume_propagation_linux.go b/components/engine/volume/volume_propagation_linux.go index 47dd357fca..77e863555f 100644 --- a/components/engine/volume/volume_propagation_linux.go +++ b/components/engine/volume/volume_propagation_linux.go @@ -5,7 +5,7 @@ package volume import ( "strings" - mounttypes "github.com/docker/engine-api/types/mount" + mounttypes "github.com/docker/docker/api/types/mount" ) // DefaultPropagationMode defines what propagation mode should be used by diff --git a/components/engine/volume/volume_propagation_unsupported.go b/components/engine/volume/volume_propagation_unsupported.go index b58f3d5a11..3eb61f2226 100644 --- a/components/engine/volume/volume_propagation_unsupported.go +++ b/components/engine/volume/volume_propagation_unsupported.go @@ -2,7 +2,7 @@ package volume -import mounttypes "github.com/docker/engine-api/types/mount" +import mounttypes "github.com/docker/docker/api/types/mount" // DefaultPropagationMode is used only in linux. In other cases it returns // empty string. diff --git a/components/engine/volume/volume_unix.go b/components/engine/volume/volume_unix.go index 5fe1fdd596..0803ef9cf6 100644 --- a/components/engine/volume/volume_unix.go +++ b/components/engine/volume/volume_unix.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strings" - mounttypes "github.com/docker/engine-api/types/mount" + mounttypes "github.com/docker/docker/api/types/mount" ) // read-write modes From 60a22c207c8de5424123a1e85ff74b58f082c588 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 6 Sep 2016 11:46:37 -0700 Subject: [PATCH 2/2] Move engine-api client package This moves the engine-api client package to `/docker/docker/client`. Signed-off-by: Michael Crosby Upstream-commit: 7c36a1af031b510cd990cf488ee5998a3efb450f Component: engine --- components/engine/api/client/cli.go | 2 +- .../engine/api/client/container/create.go | 2 +- .../api/client/container/stats_helpers.go | 2 +- .../engine/api/client/container/utils.go | 2 +- .../api/client/idresolver/idresolver.go | 2 +- components/engine/api/client/node/cmd.go | 2 +- .../engine/api/client/service/inspect.go | 2 +- components/engine/api/client/stack/common.go | 2 +- .../engine/api/client/system/inspect.go | 2 +- components/engine/api/client/utils.go | 2 +- .../engine/api/server/router/build/backend.go | 2 +- .../api/server/router/build/build_routes.go | 6 +- .../api/server/router/container/backend.go | 4 +- .../router/container/container_routes.go | 8 +- .../api/server/router/container/copy.go | 4 +- .../api/server/router/container/exec.go | 4 +- .../engine/api/server/router/image/backend.go | 4 +- .../api/server/router/image/image_routes.go | 6 +- .../api/server/router/network/backend.go | 4 +- .../api/server/router/network/filter.go | 4 +- .../server/router/network/network_routes.go | 6 +- .../api/server/router/plugin/backend.go | 2 +- .../api/server/router/plugin/plugin_routes.go | 2 +- .../engine/api/server/router/swarm/backend.go | 4 +- .../api/server/router/swarm/cluster_routes.go | 6 +- .../api/server/router/system/backend.go | 6 +- .../api/server/router/system/system_routes.go | 10 +- .../api/server/router/volume/backend.go | 2 +- .../api/server/router/volume/volume_routes.go | 2 +- .../engine/api/types/versions/v1p19/types.go | 6 +- .../engine/api/types/versions/v1p20/types.go | 4 +- .../cliconfig/credentials/file_store.go | 4 +- components/engine/client/checkpoint_create.go | 13 + .../engine/client/checkpoint_create_test.go | 73 +++++ components/engine/client/checkpoint_delete.go | 12 + .../engine/client/checkpoint_delete_test.go | 47 ++++ components/engine/client/checkpoint_list.go | 22 ++ .../engine/client/checkpoint_list_test.go | 57 ++++ components/engine/client/client.go | 156 +++++++++++ components/engine/client/client_mock_test.go | 76 ++++++ components/engine/client/client_test.go | 249 ++++++++++++++++++ components/engine/client/client_unix.go | 6 + components/engine/client/client_windows.go | 4 + components/engine/client/container_attach.go | 34 +++ components/engine/client/container_commit.go | 53 ++++ .../engine/client/container_commit_test.go | 96 +++++++ components/engine/client/container_copy.go | 97 +++++++ .../engine/client/container_copy_test.go | 244 +++++++++++++++++ components/engine/client/container_create.go | 46 ++++ .../engine/client/container_create_test.go | 77 ++++++ components/engine/client/container_diff.go | 23 ++ .../engine/client/container_diff_test.go | 61 +++++ components/engine/client/container_exec.go | 49 ++++ .../engine/client/container_exec_test.go | 157 +++++++++++ components/engine/client/container_export.go | 20 ++ .../engine/client/container_export_test.go | 50 ++++ components/engine/client/container_inspect.go | 54 ++++ .../engine/client/container_inspect_test.go | 125 +++++++++ components/engine/client/container_kill.go | 17 ++ .../engine/client/container_kill_test.go | 46 ++++ components/engine/client/container_list.go | 56 ++++ .../engine/client/container_list_test.go | 96 +++++++ components/engine/client/container_logs.go | 52 ++++ .../engine/client/container_logs_test.go | 133 ++++++++++ components/engine/client/container_pause.go | 10 + .../engine/client/container_pause_test.go | 41 +++ components/engine/client/container_remove.go | 27 ++ .../engine/client/container_remove_test.go | 59 +++++ components/engine/client/container_rename.go | 16 ++ .../engine/client/container_rename_test.go | 46 ++++ components/engine/client/container_resize.go | 29 ++ .../engine/client/container_resize_test.go | 82 ++++++ components/engine/client/container_restart.go | 22 ++ .../engine/client/container_restart_test.go | 48 ++++ components/engine/client/container_start.go | 21 ++ .../engine/client/container_start_test.go | 58 ++++ components/engine/client/container_stats.go | 24 ++ .../engine/client/container_stats_test.go | 70 +++++ components/engine/client/container_stop.go | 21 ++ .../engine/client/container_stop_test.go | 48 ++++ components/engine/client/container_top.go | 28 ++ .../engine/client/container_top_test.go | 74 ++++++ components/engine/client/container_unpause.go | 10 + .../engine/client/container_unpause_test.go | 41 +++ components/engine/client/container_update.go | 23 ++ .../engine/client/container_update_test.go | 59 +++++ components/engine/client/container_wait.go | 26 ++ .../engine/client/container_wait_test.go | 70 +++++ components/engine/client/errors.go | 208 +++++++++++++++ components/engine/client/events.go | 48 ++++ components/engine/client/events_test.go | 126 +++++++++ components/engine/client/hijack.go | 174 ++++++++++++ components/engine/client/image_build.go | 123 +++++++++ components/engine/client/image_build_test.go | 230 ++++++++++++++++ components/engine/client/image_create.go | 34 +++ components/engine/client/image_create_test.go | 76 ++++++ components/engine/client/image_history.go | 22 ++ .../engine/client/image_history_test.go | 60 +++++ components/engine/client/image_import.go | 37 +++ components/engine/client/image_import_test.go | 81 ++++++ components/engine/client/image_inspect.go | 33 +++ .../engine/client/image_inspect_test.go | 71 +++++ components/engine/client/image_list.go | 40 +++ components/engine/client/image_list_test.go | 122 +++++++++ components/engine/client/image_load.go | 30 +++ components/engine/client/image_load_test.go | 95 +++++++ components/engine/client/image_pull.go | 46 ++++ components/engine/client/image_pull_test.go | 199 ++++++++++++++ components/engine/client/image_push.go | 54 ++++ components/engine/client/image_push_test.go | 180 +++++++++++++ components/engine/client/image_remove.go | 31 +++ components/engine/client/image_remove_test.go | 95 +++++++ components/engine/client/image_save.go | 22 ++ components/engine/client/image_save_test.go | 58 ++++ components/engine/client/image_search.go | 51 ++++ components/engine/client/image_search_test.go | 165 ++++++++++++ components/engine/client/image_tag.go | 34 +++ components/engine/client/image_tag_test.go | 121 +++++++++ components/engine/client/info.go | 26 ++ components/engine/client/info_test.go | 76 ++++++ components/engine/client/interface.go | 135 ++++++++++ .../engine/client/interface_experimental.go | 37 +++ components/engine/client/interface_stable.go | 11 + components/engine/client/login.go | 28 ++ components/engine/client/network_connect.go | 18 ++ .../engine/client/network_connect_test.go | 107 ++++++++ components/engine/client/network_create.go | 25 ++ .../engine/client/network_create_test.go | 72 +++++ .../engine/client/network_disconnect.go | 14 + .../engine/client/network_disconnect_test.go | 64 +++++ components/engine/client/network_inspect.go | 38 +++ .../engine/client/network_inspect_test.go | 69 +++++ components/engine/client/network_list.go | 31 +++ components/engine/client/network_list_test.go | 108 ++++++++ components/engine/client/network_remove.go | 10 + .../engine/client/network_remove_test.go | 47 ++++ components/engine/client/node_inspect.go | 33 +++ components/engine/client/node_inspect_test.go | 65 +++++ components/engine/client/node_list.go | 36 +++ components/engine/client/node_list_test.go | 94 +++++++ components/engine/client/node_remove.go | 21 ++ components/engine/client/node_remove_test.go | 69 +++++ components/engine/client/node_update.go | 18 ++ components/engine/client/node_update_test.go | 49 ++++ components/engine/client/plugin_disable.go | 14 + .../engine/client/plugin_disable_test.go | 49 ++++ components/engine/client/plugin_enable.go | 14 + .../engine/client/plugin_enable_test.go | 49 ++++ components/engine/client/plugin_inspect.go | 30 +++ .../engine/client/plugin_inspect_test.go | 56 ++++ components/engine/client/plugin_install.go | 59 +++++ components/engine/client/plugin_list.go | 23 ++ components/engine/client/plugin_list_test.go | 61 +++++ components/engine/client/plugin_push.go | 15 ++ components/engine/client/plugin_push_test.go | 53 ++++ components/engine/client/plugin_remove.go | 22 ++ .../engine/client/plugin_remove_test.go | 51 ++++ components/engine/client/plugin_set.go | 14 + components/engine/client/plugin_set_test.go | 49 ++++ components/engine/client/request.go | 208 +++++++++++++++ components/engine/client/request_test.go | 91 +++++++ components/engine/client/service_create.go | 30 +++ .../engine/client/service_create_test.go | 57 ++++ components/engine/client/service_inspect.go | 33 +++ .../engine/client/service_inspect_test.go | 65 +++++ components/engine/client/service_list.go | 35 +++ components/engine/client/service_list_test.go | 94 +++++++ components/engine/client/service_remove.go | 10 + .../engine/client/service_remove_test.go | 47 ++++ components/engine/client/service_update.go | 30 +++ .../engine/client/service_update_test.go | 77 ++++++ components/engine/client/swarm_init.go | 21 ++ components/engine/client/swarm_init_test.go | 54 ++++ components/engine/client/swarm_inspect.go | 21 ++ .../engine/client/swarm_inspect_test.go | 56 ++++ components/engine/client/swarm_join.go | 13 + components/engine/client/swarm_join_test.go | 51 ++++ components/engine/client/swarm_leave.go | 18 ++ components/engine/client/swarm_leave_test.go | 66 +++++ components/engine/client/swarm_update.go | 21 ++ components/engine/client/swarm_update_test.go | 49 ++++ components/engine/client/task_inspect.go | 34 +++ components/engine/client/task_inspect_test.go | 54 ++++ components/engine/client/task_list.go | 35 +++ components/engine/client/task_list_test.go | 94 +++++++ components/engine/client/testdata/ca.pem | 18 ++ components/engine/client/testdata/cert.pem | 18 ++ components/engine/client/testdata/key.pem | 27 ++ .../client/transport/cancellable/LICENSE | 27 ++ .../client/transport/cancellable/canceler.go | 23 ++ .../transport/cancellable/canceler_go14.go | 27 ++ .../transport/cancellable/cancellable.go | 115 ++++++++ components/engine/client/transport/client.go | 47 ++++ .../client/transport/tlsconfig_clone.go | 11 + .../client/transport/tlsconfig_clone_go17.go | 33 +++ .../engine/client/transport/transport.go | 57 ++++ components/engine/client/version.go | 21 ++ components/engine/client/volume_create.go | 20 ++ .../engine/client/volume_create_test.go | 74 ++++++ components/engine/client/volume_inspect.go | 38 +++ .../engine/client/volume_inspect_test.go | 76 ++++++ components/engine/client/volume_list.go | 32 +++ components/engine/client/volume_list_test.go | 97 +++++++ components/engine/client/volume_remove.go | 18 ++ .../engine/client/volume_remove_test.go | 47 ++++ .../cluster/executor/container/adapter.go | 6 +- .../cluster/executor/container/container.go | 10 +- .../cluster/executor/container/controller.go | 4 +- .../cluster/executor/container/executor.go | 4 +- .../cluster/executor/container/health_test.go | 2 +- components/engine/daemon/logs.go | 3 +- 211 files changed, 10044 insertions(+), 75 deletions(-) create mode 100644 components/engine/client/checkpoint_create.go create mode 100644 components/engine/client/checkpoint_create_test.go create mode 100644 components/engine/client/checkpoint_delete.go create mode 100644 components/engine/client/checkpoint_delete_test.go create mode 100644 components/engine/client/checkpoint_list.go create mode 100644 components/engine/client/checkpoint_list_test.go create mode 100644 components/engine/client/client.go create mode 100644 components/engine/client/client_mock_test.go create mode 100644 components/engine/client/client_test.go create mode 100644 components/engine/client/client_unix.go create mode 100644 components/engine/client/client_windows.go create mode 100644 components/engine/client/container_attach.go create mode 100644 components/engine/client/container_commit.go create mode 100644 components/engine/client/container_commit_test.go create mode 100644 components/engine/client/container_copy.go create mode 100644 components/engine/client/container_copy_test.go create mode 100644 components/engine/client/container_create.go create mode 100644 components/engine/client/container_create_test.go create mode 100644 components/engine/client/container_diff.go create mode 100644 components/engine/client/container_diff_test.go create mode 100644 components/engine/client/container_exec.go create mode 100644 components/engine/client/container_exec_test.go create mode 100644 components/engine/client/container_export.go create mode 100644 components/engine/client/container_export_test.go create mode 100644 components/engine/client/container_inspect.go create mode 100644 components/engine/client/container_inspect_test.go create mode 100644 components/engine/client/container_kill.go create mode 100644 components/engine/client/container_kill_test.go create mode 100644 components/engine/client/container_list.go create mode 100644 components/engine/client/container_list_test.go create mode 100644 components/engine/client/container_logs.go create mode 100644 components/engine/client/container_logs_test.go create mode 100644 components/engine/client/container_pause.go create mode 100644 components/engine/client/container_pause_test.go create mode 100644 components/engine/client/container_remove.go create mode 100644 components/engine/client/container_remove_test.go create mode 100644 components/engine/client/container_rename.go create mode 100644 components/engine/client/container_rename_test.go create mode 100644 components/engine/client/container_resize.go create mode 100644 components/engine/client/container_resize_test.go create mode 100644 components/engine/client/container_restart.go create mode 100644 components/engine/client/container_restart_test.go create mode 100644 components/engine/client/container_start.go create mode 100644 components/engine/client/container_start_test.go create mode 100644 components/engine/client/container_stats.go create mode 100644 components/engine/client/container_stats_test.go create mode 100644 components/engine/client/container_stop.go create mode 100644 components/engine/client/container_stop_test.go create mode 100644 components/engine/client/container_top.go create mode 100644 components/engine/client/container_top_test.go create mode 100644 components/engine/client/container_unpause.go create mode 100644 components/engine/client/container_unpause_test.go create mode 100644 components/engine/client/container_update.go create mode 100644 components/engine/client/container_update_test.go create mode 100644 components/engine/client/container_wait.go create mode 100644 components/engine/client/container_wait_test.go create mode 100644 components/engine/client/errors.go create mode 100644 components/engine/client/events.go create mode 100644 components/engine/client/events_test.go create mode 100644 components/engine/client/hijack.go create mode 100644 components/engine/client/image_build.go create mode 100644 components/engine/client/image_build_test.go create mode 100644 components/engine/client/image_create.go create mode 100644 components/engine/client/image_create_test.go create mode 100644 components/engine/client/image_history.go create mode 100644 components/engine/client/image_history_test.go create mode 100644 components/engine/client/image_import.go create mode 100644 components/engine/client/image_import_test.go create mode 100644 components/engine/client/image_inspect.go create mode 100644 components/engine/client/image_inspect_test.go create mode 100644 components/engine/client/image_list.go create mode 100644 components/engine/client/image_list_test.go create mode 100644 components/engine/client/image_load.go create mode 100644 components/engine/client/image_load_test.go create mode 100644 components/engine/client/image_pull.go create mode 100644 components/engine/client/image_pull_test.go create mode 100644 components/engine/client/image_push.go create mode 100644 components/engine/client/image_push_test.go create mode 100644 components/engine/client/image_remove.go create mode 100644 components/engine/client/image_remove_test.go create mode 100644 components/engine/client/image_save.go create mode 100644 components/engine/client/image_save_test.go create mode 100644 components/engine/client/image_search.go create mode 100644 components/engine/client/image_search_test.go create mode 100644 components/engine/client/image_tag.go create mode 100644 components/engine/client/image_tag_test.go create mode 100644 components/engine/client/info.go create mode 100644 components/engine/client/info_test.go create mode 100644 components/engine/client/interface.go create mode 100644 components/engine/client/interface_experimental.go create mode 100644 components/engine/client/interface_stable.go create mode 100644 components/engine/client/login.go create mode 100644 components/engine/client/network_connect.go create mode 100644 components/engine/client/network_connect_test.go create mode 100644 components/engine/client/network_create.go create mode 100644 components/engine/client/network_create_test.go create mode 100644 components/engine/client/network_disconnect.go create mode 100644 components/engine/client/network_disconnect_test.go create mode 100644 components/engine/client/network_inspect.go create mode 100644 components/engine/client/network_inspect_test.go create mode 100644 components/engine/client/network_list.go create mode 100644 components/engine/client/network_list_test.go create mode 100644 components/engine/client/network_remove.go create mode 100644 components/engine/client/network_remove_test.go create mode 100644 components/engine/client/node_inspect.go create mode 100644 components/engine/client/node_inspect_test.go create mode 100644 components/engine/client/node_list.go create mode 100644 components/engine/client/node_list_test.go create mode 100644 components/engine/client/node_remove.go create mode 100644 components/engine/client/node_remove_test.go create mode 100644 components/engine/client/node_update.go create mode 100644 components/engine/client/node_update_test.go create mode 100644 components/engine/client/plugin_disable.go create mode 100644 components/engine/client/plugin_disable_test.go create mode 100644 components/engine/client/plugin_enable.go create mode 100644 components/engine/client/plugin_enable_test.go create mode 100644 components/engine/client/plugin_inspect.go create mode 100644 components/engine/client/plugin_inspect_test.go create mode 100644 components/engine/client/plugin_install.go create mode 100644 components/engine/client/plugin_list.go create mode 100644 components/engine/client/plugin_list_test.go create mode 100644 components/engine/client/plugin_push.go create mode 100644 components/engine/client/plugin_push_test.go create mode 100644 components/engine/client/plugin_remove.go create mode 100644 components/engine/client/plugin_remove_test.go create mode 100644 components/engine/client/plugin_set.go create mode 100644 components/engine/client/plugin_set_test.go create mode 100644 components/engine/client/request.go create mode 100644 components/engine/client/request_test.go create mode 100644 components/engine/client/service_create.go create mode 100644 components/engine/client/service_create_test.go create mode 100644 components/engine/client/service_inspect.go create mode 100644 components/engine/client/service_inspect_test.go create mode 100644 components/engine/client/service_list.go create mode 100644 components/engine/client/service_list_test.go create mode 100644 components/engine/client/service_remove.go create mode 100644 components/engine/client/service_remove_test.go create mode 100644 components/engine/client/service_update.go create mode 100644 components/engine/client/service_update_test.go create mode 100644 components/engine/client/swarm_init.go create mode 100644 components/engine/client/swarm_init_test.go create mode 100644 components/engine/client/swarm_inspect.go create mode 100644 components/engine/client/swarm_inspect_test.go create mode 100644 components/engine/client/swarm_join.go create mode 100644 components/engine/client/swarm_join_test.go create mode 100644 components/engine/client/swarm_leave.go create mode 100644 components/engine/client/swarm_leave_test.go create mode 100644 components/engine/client/swarm_update.go create mode 100644 components/engine/client/swarm_update_test.go create mode 100644 components/engine/client/task_inspect.go create mode 100644 components/engine/client/task_inspect_test.go create mode 100644 components/engine/client/task_list.go create mode 100644 components/engine/client/task_list_test.go create mode 100644 components/engine/client/testdata/ca.pem create mode 100644 components/engine/client/testdata/cert.pem create mode 100644 components/engine/client/testdata/key.pem create mode 100644 components/engine/client/transport/cancellable/LICENSE create mode 100644 components/engine/client/transport/cancellable/canceler.go create mode 100644 components/engine/client/transport/cancellable/canceler_go14.go create mode 100644 components/engine/client/transport/cancellable/cancellable.go create mode 100644 components/engine/client/transport/client.go create mode 100644 components/engine/client/transport/tlsconfig_clone.go create mode 100644 components/engine/client/transport/tlsconfig_clone_go17.go create mode 100644 components/engine/client/transport/transport.go create mode 100644 components/engine/client/version.go create mode 100644 components/engine/client/volume_create.go create mode 100644 components/engine/client/volume_create_test.go create mode 100644 components/engine/client/volume_inspect.go create mode 100644 components/engine/client/volume_inspect_test.go create mode 100644 components/engine/client/volume_list.go create mode 100644 components/engine/client/volume_list_test.go create mode 100644 components/engine/client/volume_remove.go create mode 100644 components/engine/client/volume_remove_test.go diff --git a/components/engine/api/client/cli.go b/components/engine/api/client/cli.go index 0ce807bde5..077d1650b9 100644 --- a/components/engine/api/client/cli.go +++ b/components/engine/api/client/cli.go @@ -14,10 +14,10 @@ import ( "github.com/docker/docker/cliconfig" "github.com/docker/docker/cliconfig/configfile" "github.com/docker/docker/cliconfig/credentials" + "github.com/docker/docker/client" "github.com/docker/docker/dockerversion" dopts "github.com/docker/docker/opts" "github.com/docker/docker/pkg/term" - "github.com/docker/engine-api/client" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" ) diff --git a/components/engine/api/client/container/create.go b/components/engine/api/client/container/create.go index cbfe36b5e2..82c14ada67 100644 --- a/components/engine/api/client/container/create.go +++ b/components/engine/api/client/container/create.go @@ -14,10 +14,10 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" networktypes "github.com/docker/docker/api/types/network" + apiclient "github.com/docker/docker/client" "github.com/docker/docker/reference" "github.com/docker/docker/registry" runconfigopts "github.com/docker/docker/runconfig/opts" - apiclient "github.com/docker/engine-api/client" "github.com/spf13/cobra" "github.com/spf13/pflag" ) diff --git a/components/engine/api/client/container/stats_helpers.go b/components/engine/api/client/container/stats_helpers.go index 8c2fa56e37..b5e8e0472f 100644 --- a/components/engine/api/client/container/stats_helpers.go +++ b/components/engine/api/client/container/stats_helpers.go @@ -11,7 +11,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" - "github.com/docker/engine-api/client" + "github.com/docker/docker/client" "github.com/docker/go-units" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/container/utils.go b/components/engine/api/client/container/utils.go index 4b061f7338..38fb0a5a4b 100644 --- a/components/engine/api/client/container/utils.go +++ b/components/engine/api/client/container/utils.go @@ -12,7 +12,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" - clientapi "github.com/docker/engine-api/client" + clientapi "github.com/docker/docker/client" ) func waitExitOrRemoved(dockerCli *client.DockerCli, ctx context.Context, containerID string, waitRemove bool) (chan int, error) { diff --git a/components/engine/api/client/idresolver/idresolver.go b/components/engine/api/client/idresolver/idresolver.go index 6d39252002..ad0d96735d 100644 --- a/components/engine/api/client/idresolver/idresolver.go +++ b/components/engine/api/client/idresolver/idresolver.go @@ -6,7 +6,7 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/types/swarm" - "github.com/docker/engine-api/client" + "github.com/docker/docker/client" ) // IDResolver provides ID to Name resolution. diff --git a/components/engine/api/client/node/cmd.go b/components/engine/api/client/node/cmd.go index b24c1bdc52..961460727b 100644 --- a/components/engine/api/client/node/cmd.go +++ b/components/engine/api/client/node/cmd.go @@ -5,7 +5,7 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/cli" - apiclient "github.com/docker/engine-api/client" + apiclient "github.com/docker/docker/client" "github.com/spf13/cobra" "golang.org/x/net/context" ) diff --git a/components/engine/api/client/service/inspect.go b/components/engine/api/client/service/inspect.go index 5511417337..4a3e709c40 100644 --- a/components/engine/api/client/service/inspect.go +++ b/components/engine/api/client/service/inspect.go @@ -12,8 +12,8 @@ import ( "github.com/docker/docker/api/client/inspect" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" + apiclient "github.com/docker/docker/client" "github.com/docker/docker/pkg/ioutils" - apiclient "github.com/docker/engine-api/client" "github.com/docker/go-units" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/stack/common.go b/components/engine/api/client/stack/common.go index b55c4214fe..2afdb5147d 100644 --- a/components/engine/api/client/stack/common.go +++ b/components/engine/api/client/stack/common.go @@ -8,7 +8,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" - "github.com/docker/engine-api/client" + "github.com/docker/docker/client" ) const ( diff --git a/components/engine/api/client/system/inspect.go b/components/engine/api/client/system/inspect.go index 7d8065d57d..cf5b60db99 100644 --- a/components/engine/api/client/system/inspect.go +++ b/components/engine/api/client/system/inspect.go @@ -9,7 +9,7 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/client/inspect" "github.com/docker/docker/cli" - apiclient "github.com/docker/engine-api/client" + apiclient "github.com/docker/docker/client" "github.com/spf13/cobra" ) diff --git a/components/engine/api/client/utils.go b/components/engine/api/client/utils.go index 390a673a21..28fbe85156 100644 --- a/components/engine/api/client/utils.go +++ b/components/engine/api/client/utils.go @@ -15,9 +15,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" + "github.com/docker/docker/client" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/term" - "github.com/docker/engine-api/client" ) func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) { diff --git a/components/engine/api/server/router/build/backend.go b/components/engine/api/server/router/build/backend.go index 18ba1b2769..0f01c11af4 100644 --- a/components/engine/api/server/router/build/backend.go +++ b/components/engine/api/server/router/build/backend.go @@ -3,8 +3,8 @@ package build import ( "io" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" - "github.com/docker/engine-api/types" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/router/build/build_routes.go b/components/engine/api/server/router/build/build_routes.go index 7c95087be1..7f66fdd35a 100644 --- a/components/engine/api/server/router/build/build_routes.go +++ b/components/engine/api/server/router/build/build_routes.go @@ -13,13 +13,13 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/server/httputils" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/versions" "github.com/docker/go-units" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/router/container/backend.go b/components/engine/api/server/router/container/backend.go index 3a66d6c2cb..d6e8268ce7 100644 --- a/components/engine/api/server/router/container/backend.go +++ b/components/engine/api/server/router/container/backend.go @@ -6,10 +6,10 @@ import ( "golang.org/x/net/context" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/archive" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" ) // execBackend includes functions to implement to provide exec functionality. diff --git a/components/engine/api/server/router/container/container_routes.go b/components/engine/api/server/router/container/container_routes.go index eaff9e4266..bb53b13873 100644 --- a/components/engine/api/server/router/container/container_routes.go +++ b/components/engine/api/server/router/container/container_routes.go @@ -12,13 +12,13 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/server/httputils" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/signal" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/filters" - "github.com/docker/engine-api/types/versions" "golang.org/x/net/context" "golang.org/x/net/websocket" ) diff --git a/components/engine/api/server/router/container/copy.go b/components/engine/api/server/router/container/copy.go index 554b40e8be..ede6dff92c 100644 --- a/components/engine/api/server/router/container/copy.go +++ b/components/engine/api/server/router/container/copy.go @@ -10,8 +10,8 @@ import ( "strings" "github.com/docker/docker/api/server/httputils" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/versions" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/router/container/exec.go b/components/engine/api/server/router/container/exec.go index 21f5dc8300..f840e54d70 100644 --- a/components/engine/api/server/router/container/exec.go +++ b/components/engine/api/server/router/container/exec.go @@ -9,9 +9,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/server/httputils" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/versions" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/router/image/backend.go b/components/engine/api/server/router/image/backend.go index 08101736e0..f77dbedb06 100644 --- a/components/engine/api/server/router/image/backend.go +++ b/components/engine/api/server/router/image/backend.go @@ -3,9 +3,9 @@ package image import ( "io" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/registry" + "github.com/docker/docker/api/types/registry" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/router/image/image_routes.go b/components/engine/api/server/router/image/image_routes.go index 97f350b1a9..c1fdc06709 100644 --- a/components/engine/api/server/router/image/image_routes.go +++ b/components/engine/api/server/router/image/image_routes.go @@ -10,13 +10,13 @@ import ( "strings" "github.com/docker/docker/api/server/httputils" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/registry" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/versions" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/router/network/backend.go b/components/engine/api/server/router/network/backend.go index 067b033b8f..280ca07efd 100644 --- a/components/engine/api/server/router/network/backend.go +++ b/components/engine/api/server/router/network/backend.go @@ -1,8 +1,8 @@ package network import ( - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/network" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" "github.com/docker/libnetwork" ) diff --git a/components/engine/api/server/router/network/filter.go b/components/engine/api/server/router/network/filter.go index 8168413d1a..94affb83cd 100644 --- a/components/engine/api/server/router/network/filter.go +++ b/components/engine/api/server/router/network/filter.go @@ -3,9 +3,9 @@ package network import ( "fmt" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/runconfig" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" ) var ( diff --git a/components/engine/api/server/router/network/network_routes.go b/components/engine/api/server/router/network/network_routes.go index 8a42edfb6d..1e508ab302 100644 --- a/components/engine/api/server/router/network/network_routes.go +++ b/components/engine/api/server/router/network/network_routes.go @@ -8,10 +8,10 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/server/httputils" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/errors" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" - "github.com/docker/engine-api/types/network" "github.com/docker/libnetwork" ) diff --git a/components/engine/api/server/router/plugin/backend.go b/components/engine/api/server/router/plugin/backend.go index 42392fec3c..2f28f992b9 100644 --- a/components/engine/api/server/router/plugin/backend.go +++ b/components/engine/api/server/router/plugin/backend.go @@ -5,7 +5,7 @@ package plugin import ( "net/http" - enginetypes "github.com/docker/engine-api/types" + enginetypes "github.com/docker/docker/api/types" ) // Backend for Plugin diff --git a/components/engine/api/server/router/plugin/plugin_routes.go b/components/engine/api/server/router/plugin/plugin_routes.go index 42fa32983a..0fd1f915b4 100644 --- a/components/engine/api/server/router/plugin/plugin_routes.go +++ b/components/engine/api/server/router/plugin/plugin_routes.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/docker/docker/api/server/httputils" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/router/swarm/backend.go b/components/engine/api/server/router/swarm/backend.go index e51ce7ba6b..e86ca1261f 100644 --- a/components/engine/api/server/router/swarm/backend.go +++ b/components/engine/api/server/router/swarm/backend.go @@ -1,8 +1,8 @@ package swarm import ( - basictypes "github.com/docker/engine-api/types" - types "github.com/docker/engine-api/types/swarm" + basictypes "github.com/docker/docker/api/types" + types "github.com/docker/docker/api/types/swarm" ) // Backend abstracts an swarm commands manager. diff --git a/components/engine/api/server/router/swarm/cluster_routes.go b/components/engine/api/server/router/swarm/cluster_routes.go index 53b49b7d59..c40d22229b 100644 --- a/components/engine/api/server/router/swarm/cluster_routes.go +++ b/components/engine/api/server/router/swarm/cluster_routes.go @@ -8,9 +8,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/server/httputils" - basictypes "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/filters" - types "github.com/docker/engine-api/types/swarm" + basictypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + types "github.com/docker/docker/api/types/swarm" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/router/system/backend.go b/components/engine/api/server/router/system/backend.go index 83609ab55a..56e98e05d7 100644 --- a/components/engine/api/server/router/system/backend.go +++ b/components/engine/api/server/router/system/backend.go @@ -3,9 +3,9 @@ package system import ( "time" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/events" - "github.com/docker/engine-api/types/filters" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/router/system/system_routes.go b/components/engine/api/server/router/system/system_routes.go index 5e83ca7430..69e0e924cf 100644 --- a/components/engine/api/server/router/system/system_routes.go +++ b/components/engine/api/server/router/system/system_routes.go @@ -9,13 +9,13 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/api/server/httputils" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" + timetypes "github.com/docker/docker/api/types/time" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/errors" "github.com/docker/docker/pkg/ioutils" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/events" - "github.com/docker/engine-api/types/filters" - timetypes "github.com/docker/engine-api/types/time" - "github.com/docker/engine-api/types/versions" "golang.org/x/net/context" ) diff --git a/components/engine/api/server/router/volume/backend.go b/components/engine/api/server/router/volume/backend.go index f9d1c64f17..80aebee7c4 100644 --- a/components/engine/api/server/router/volume/backend.go +++ b/components/engine/api/server/router/volume/backend.go @@ -2,7 +2,7 @@ package volume import ( // TODO return types need to be refactored into pkg - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" ) // Backend is the methods that need to be implemented to provide diff --git a/components/engine/api/server/router/volume/volume_routes.go b/components/engine/api/server/router/volume/volume_routes.go index 737554c802..1ab3fc253a 100644 --- a/components/engine/api/server/router/volume/volume_routes.go +++ b/components/engine/api/server/router/volume/volume_routes.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/docker/docker/api/server/httputils" - "github.com/docker/engine-api/types" + "github.com/docker/docker/api/types" "golang.org/x/net/context" ) diff --git a/components/engine/api/types/versions/v1p19/types.go b/components/engine/api/types/versions/v1p19/types.go index 4ed4335881..dc13150545 100644 --- a/components/engine/api/types/versions/v1p19/types.go +++ b/components/engine/api/types/versions/v1p19/types.go @@ -2,9 +2,9 @@ package v1p19 import ( - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/versions/v1p20" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/versions/v1p20" "github.com/docker/go-connections/nat" ) diff --git a/components/engine/api/types/versions/v1p20/types.go b/components/engine/api/types/versions/v1p20/types.go index 5736efad00..94a06d7452 100644 --- a/components/engine/api/types/versions/v1p20/types.go +++ b/components/engine/api/types/versions/v1p20/types.go @@ -2,8 +2,8 @@ package v1p20 import ( - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/container" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" ) diff --git a/components/engine/cliconfig/credentials/file_store.go b/components/engine/cliconfig/credentials/file_store.go index 8da8c91434..ca73a384d4 100644 --- a/components/engine/cliconfig/credentials/file_store.go +++ b/components/engine/cliconfig/credentials/file_store.go @@ -1,11 +1,9 @@ package credentials import ( - "github.com/docker/docker/cliconfig/configfile" - "github.com/docker/docker/registry" - "github.com/docker/docker/api/types" "github.com/docker/docker/cliconfig/configfile" + "github.com/docker/docker/registry" ) // fileStore implements a credentials store using diff --git a/components/engine/client/checkpoint_create.go b/components/engine/client/checkpoint_create.go new file mode 100644 index 0000000000..0effe498be --- /dev/null +++ b/components/engine/client/checkpoint_create.go @@ -0,0 +1,13 @@ +package client + +import ( + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// CheckpointCreate creates a checkpoint from the given container with the given name +func (cli *Client) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error { + resp, err := cli.post(ctx, "/containers/"+container+"/checkpoints", nil, options, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/checkpoint_create_test.go b/components/engine/client/checkpoint_create_test.go new file mode 100644 index 0000000000..e2ae36e1e0 --- /dev/null +++ b/components/engine/client/checkpoint_create_test.go @@ -0,0 +1,73 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestCheckpointCreateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.CheckpointCreate(context.Background(), "nothing", types.CheckpointCreateOptions{ + CheckpointID: "noting", + Exit: true, + }) + + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestCheckpointCreate(t *testing.T) { + expectedContainerID := "container_id" + expectedCheckpointID := "checkpoint_id" + expectedURL := "/containers/container_id/checkpoints" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + + createOptions := &types.CheckpointCreateOptions{} + if err := json.NewDecoder(req.Body).Decode(createOptions); err != nil { + return nil, err + } + + if createOptions.CheckpointID != expectedCheckpointID { + return nil, fmt.Errorf("expected CheckpointID to be 'checkpoint_id', got %v", createOptions.CheckpointID) + } + + if !createOptions.Exit { + return nil, fmt.Errorf("expected Exit to be true") + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.CheckpointCreate(context.Background(), expectedContainerID, types.CheckpointCreateOptions{ + CheckpointID: expectedCheckpointID, + Exit: true, + }) + + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/checkpoint_delete.go b/components/engine/client/checkpoint_delete.go new file mode 100644 index 0000000000..a4e9ed0c06 --- /dev/null +++ b/components/engine/client/checkpoint_delete.go @@ -0,0 +1,12 @@ +package client + +import ( + "golang.org/x/net/context" +) + +// CheckpointDelete deletes the checkpoint with the given name from the given container +func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, checkpointID string) error { + resp, err := cli.delete(ctx, "/containers/"+containerID+"/checkpoints/"+checkpointID, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/checkpoint_delete_test.go b/components/engine/client/checkpoint_delete_test.go new file mode 100644 index 0000000000..097ab37693 --- /dev/null +++ b/components/engine/client/checkpoint_delete_test.go @@ -0,0 +1,47 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestCheckpointDeleteError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.CheckpointDelete(context.Background(), "container_id", "checkpoint_id") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestCheckpointDelete(t *testing.T) { + expectedURL := "/containers/container_id/checkpoints/checkpoint_id" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "DELETE" { + return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.CheckpointDelete(context.Background(), "container_id", "checkpoint_id") + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/checkpoint_list.go b/components/engine/client/checkpoint_list.go new file mode 100644 index 0000000000..bb471e0056 --- /dev/null +++ b/components/engine/client/checkpoint_list.go @@ -0,0 +1,22 @@ +package client + +import ( + "encoding/json" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// CheckpointList returns the volumes configured in the docker host. +func (cli *Client) CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error) { + var checkpoints []types.Checkpoint + + resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", nil, nil) + if err != nil { + return checkpoints, err + } + + err = json.NewDecoder(resp.body).Decode(&checkpoints) + ensureReaderClosed(resp) + return checkpoints, err +} diff --git a/components/engine/client/checkpoint_list_test.go b/components/engine/client/checkpoint_list_test.go new file mode 100644 index 0000000000..5960436eb1 --- /dev/null +++ b/components/engine/client/checkpoint_list_test.go @@ -0,0 +1,57 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestCheckpointListError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.CheckpointList(context.Background(), "container_id") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestCheckpointList(t *testing.T) { + expectedURL := "/containers/container_id/checkpoints" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + content, err := json.Marshal([]types.Checkpoint{ + { + Name: "checkpoint", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + checkpoints, err := client.CheckpointList(context.Background(), "container_id") + if err != nil { + t.Fatal(err) + } + if len(checkpoints) != 1 { + t.Fatalf("expected 1 checkpoint, got %v", checkpoints) + } +} diff --git a/components/engine/client/client.go b/components/engine/client/client.go new file mode 100644 index 0000000000..6a85121c6d --- /dev/null +++ b/components/engine/client/client.go @@ -0,0 +1,156 @@ +package client + +import ( + "fmt" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/client/transport" + "github.com/docker/go-connections/tlsconfig" +) + +// DefaultVersion is the version of the current stable API +const DefaultVersion string = "1.23" + +// Client is the API client that performs all operations +// against a docker server. +type Client struct { + // host holds the server address to connect to + host string + // proto holds the client protocol i.e. unix. + proto string + // addr holds the client address. + addr string + // basePath holds the path to prepend to the requests. + basePath string + // transport is the interface to send request with, it implements transport.Client. + transport transport.Client + // version of the server to talk to. + version string + // custom http headers configured by users. + customHTTPHeaders map[string]string +} + +// NewEnvClient initializes a new API client based on environment variables. +// Use DOCKER_HOST to set the url to the docker server. +// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest. +// Use DOCKER_CERT_PATH to load the tls certificates from. +// Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default. +func NewEnvClient() (*Client, error) { + var client *http.Client + if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" { + options := tlsconfig.Options{ + CAFile: filepath.Join(dockerCertPath, "ca.pem"), + CertFile: filepath.Join(dockerCertPath, "cert.pem"), + KeyFile: filepath.Join(dockerCertPath, "key.pem"), + InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "", + } + tlsc, err := tlsconfig.Client(options) + if err != nil { + return nil, err + } + + client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsc, + }, + } + } + + host := os.Getenv("DOCKER_HOST") + if host == "" { + host = DefaultDockerHost + } + + version := os.Getenv("DOCKER_API_VERSION") + if version == "" { + version = DefaultVersion + } + + return NewClient(host, version, client, nil) +} + +// NewClient initializes a new API client for the given host and API version. +// It uses the given http client as transport. +// It also initializes the custom http headers to add to each request. +// +// It won't send any version information if the version number is empty. It is +// highly recommended that you set a version or your client may break if the +// server is upgraded. +func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) { + proto, addr, basePath, err := ParseHost(host) + if err != nil { + return nil, err + } + + transport, err := transport.NewTransportWithHTTP(proto, addr, client) + if err != nil { + return nil, err + } + + return &Client{ + host: host, + proto: proto, + addr: addr, + basePath: basePath, + transport: transport, + version: version, + customHTTPHeaders: httpHeaders, + }, nil +} + +// getAPIPath returns the versioned request path to call the api. +// It appends the query parameters to the path if they are not empty. +func (cli *Client) getAPIPath(p string, query url.Values) string { + var apiPath string + if cli.version != "" { + v := strings.TrimPrefix(cli.version, "v") + apiPath = fmt.Sprintf("%s/v%s%s", cli.basePath, v, p) + } else { + apiPath = fmt.Sprintf("%s%s", cli.basePath, p) + } + + u := &url.URL{ + Path: apiPath, + } + if len(query) > 0 { + u.RawQuery = query.Encode() + } + return u.String() +} + +// ClientVersion returns the version string associated with this +// instance of the Client. Note that this value can be changed +// via the DOCKER_API_VERSION env var. +func (cli *Client) ClientVersion() string { + return cli.version +} + +// UpdateClientVersion updates the version string associated with this +// instance of the Client. +func (cli *Client) UpdateClientVersion(v string) { + cli.version = v +} + +// ParseHost verifies that the given host strings is valid. +func ParseHost(host string) (string, string, string, error) { + protoAddrParts := strings.SplitN(host, "://", 2) + if len(protoAddrParts) == 1 { + return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host) + } + + var basePath string + proto, addr := protoAddrParts[0], protoAddrParts[1] + if proto == "tcp" { + parsed, err := url.Parse("tcp://" + addr) + if err != nil { + return "", "", "", err + } + addr = parsed.Host + basePath = parsed.Path + } + return proto, addr, basePath, nil +} diff --git a/components/engine/client/client_mock_test.go b/components/engine/client/client_mock_test.go new file mode 100644 index 0000000000..33c247266c --- /dev/null +++ b/components/engine/client/client_mock_test.go @@ -0,0 +1,76 @@ +package client + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client/transport" +) + +type mockClient struct { + do func(*http.Request) (*http.Response, error) +} + +// TLSConfig returns the TLS configuration. +func (m *mockClient) TLSConfig() *tls.Config { + return &tls.Config{} +} + +// Scheme returns protocol scheme to use. +func (m *mockClient) Scheme() string { + return "http" +} + +// Secure returns true if there is a TLS configuration. +func (m *mockClient) Secure() bool { + return false +} + +// NewMockClient returns a mocked client that runs the function supplied as `client.Do` call +func newMockClient(tlsConfig *tls.Config, doer func(*http.Request) (*http.Response, error)) transport.Client { + if tlsConfig != nil { + panic("this actually gets set!") + } + + return &mockClient{ + do: doer, + } +} + +// Do executes the supplied function for the mock. +func (m mockClient) Do(req *http.Request) (*http.Response, error) { + return m.do(req) +} + +func errorMock(statusCode int, message string) func(req *http.Request) (*http.Response, error) { + return func(req *http.Request) (*http.Response, error) { + header := http.Header{} + header.Set("Content-Type", "application/json") + + body, err := json.Marshal(&types.ErrorResponse{ + Message: message, + }) + if err != nil { + return nil, err + } + + return &http.Response{ + StatusCode: statusCode, + Body: ioutil.NopCloser(bytes.NewReader(body)), + Header: header, + }, nil + } +} + +func plainTextErrorMock(statusCode int, message string) func(req *http.Request) (*http.Response, error) { + return func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: statusCode, + Body: ioutil.NopCloser(bytes.NewReader([]byte(message))), + }, nil + } +} diff --git a/components/engine/client/client_test.go b/components/engine/client/client_test.go new file mode 100644 index 0000000000..60af3db029 --- /dev/null +++ b/components/engine/client/client_test.go @@ -0,0 +1,249 @@ +package client + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + "os" + "runtime" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestNewEnvClient(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping unix only test for windows") + } + cases := []struct { + envs map[string]string + expectedError string + expectedVersion string + }{ + { + envs: map[string]string{}, + expectedVersion: DefaultVersion, + }, + { + envs: map[string]string{ + "DOCKER_CERT_PATH": "invalid/path", + }, + expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory. Make sure the key is not encrypted", + }, + { + envs: map[string]string{ + "DOCKER_CERT_PATH": "testdata/", + }, + expectedVersion: DefaultVersion, + }, + { + envs: map[string]string{ + "DOCKER_HOST": "host", + }, + expectedError: "unable to parse docker host `host`", + }, + { + envs: map[string]string{ + "DOCKER_HOST": "invalid://url", + }, + expectedVersion: DefaultVersion, + }, + { + envs: map[string]string{ + "DOCKER_API_VERSION": "anything", + }, + expectedVersion: "anything", + }, + { + envs: map[string]string{ + "DOCKER_API_VERSION": "1.22", + }, + expectedVersion: "1.22", + }, + } + for _, c := range cases { + recoverEnvs := setupEnvs(t, c.envs) + apiclient, err := NewEnvClient() + if c.expectedError != "" { + if err == nil || err.Error() != c.expectedError { + t.Errorf("expected an error %s, got %s, for %v", c.expectedError, err.Error(), c) + } + } else { + if err != nil { + t.Error(err) + } + version := apiclient.ClientVersion() + if version != c.expectedVersion { + t.Errorf("expected %s, got %s, for %v", c.expectedVersion, version, c) + } + } + recoverEnvs(t) + } +} + +func setupEnvs(t *testing.T, envs map[string]string) func(*testing.T) { + oldEnvs := map[string]string{} + for key, value := range envs { + oldEnv := os.Getenv(key) + oldEnvs[key] = oldEnv + err := os.Setenv(key, value) + if err != nil { + t.Error(err) + } + } + return func(t *testing.T) { + for key, value := range oldEnvs { + err := os.Setenv(key, value) + if err != nil { + t.Error(err) + } + } + } +} + +func TestGetAPIPath(t *testing.T) { + cases := []struct { + v string + p string + q url.Values + e string + }{ + {"", "/containers/json", nil, "/containers/json"}, + {"", "/containers/json", url.Values{}, "/containers/json"}, + {"", "/containers/json", url.Values{"s": []string{"c"}}, "/containers/json?s=c"}, + {"1.22", "/containers/json", nil, "/v1.22/containers/json"}, + {"1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"}, + {"1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"}, + {"v1.22", "/containers/json", nil, "/v1.22/containers/json"}, + {"v1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"}, + {"v1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"}, + {"v1.22", "/networks/kiwl$%^", nil, "/v1.22/networks/kiwl$%25%5E"}, + } + + for _, cs := range cases { + c, err := NewClient("unix:///var/run/docker.sock", cs.v, nil, nil) + if err != nil { + t.Fatal(err) + } + g := c.getAPIPath(cs.p, cs.q) + if g != cs.e { + t.Fatalf("Expected %s, got %s", cs.e, g) + } + } +} + +func TestParseHost(t *testing.T) { + cases := []struct { + host string + proto string + addr string + base string + err bool + }{ + {"", "", "", "", true}, + {"foobar", "", "", "", true}, + {"foo://bar", "foo", "bar", "", false}, + {"tcp://localhost:2476", "tcp", "localhost:2476", "", false}, + {"tcp://localhost:2476/path", "tcp", "localhost:2476", "/path", false}, + } + + for _, cs := range cases { + p, a, b, e := ParseHost(cs.host) + if cs.err && e == nil { + t.Fatalf("expected error, got nil") + } + if !cs.err && e != nil { + t.Fatal(e) + } + if cs.proto != p { + t.Fatalf("expected proto %s, got %s", cs.proto, p) + } + if cs.addr != a { + t.Fatalf("expected addr %s, got %s", cs.addr, a) + } + if cs.base != b { + t.Fatalf("expected base %s, got %s", cs.base, b) + } + } +} + +func TestUpdateClientVersion(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + splitQuery := strings.Split(req.URL.Path, "/") + queryVersion := splitQuery[1] + b, err := json.Marshal(types.Version{ + APIVersion: queryVersion, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + cases := []struct { + v string + }{ + {"1.20"}, + {"v1.21"}, + {"1.22"}, + {"v1.22"}, + } + + for _, cs := range cases { + client.UpdateClientVersion(cs.v) + r, err := client.ServerVersion(context.Background()) + if err != nil { + t.Fatal(err) + } + if strings.TrimPrefix(r.APIVersion, "v") != strings.TrimPrefix(cs.v, "v") { + t.Fatalf("Expected %s, got %s", cs.v, r.APIVersion) + } + } +} + +func TestNewEnvClientSetsDefaultVersion(t *testing.T) { + // Unset environment variables + envVarKeys := []string{ + "DOCKER_HOST", + "DOCKER_API_VERSION", + "DOCKER_TLS_VERIFY", + "DOCKER_CERT_PATH", + } + envVarValues := make(map[string]string) + for _, key := range envVarKeys { + envVarValues[key] = os.Getenv(key) + os.Setenv(key, "") + } + + client, err := NewEnvClient() + if err != nil { + t.Fatal(err) + } + if client.version != DefaultVersion { + t.Fatalf("Expected %s, got %s", DefaultVersion, client.version) + } + + expected := "1.22" + os.Setenv("DOCKER_API_VERSION", expected) + client, err = NewEnvClient() + if err != nil { + t.Fatal(err) + } + if client.version != expected { + t.Fatalf("Expected %s, got %s", expected, client.version) + } + + // Restore environment variables + for _, key := range envVarKeys { + os.Setenv(key, envVarValues[key]) + } +} diff --git a/components/engine/client/client_unix.go b/components/engine/client/client_unix.go new file mode 100644 index 0000000000..89de892c85 --- /dev/null +++ b/components/engine/client/client_unix.go @@ -0,0 +1,6 @@ +// +build linux freebsd solaris openbsd darwin + +package client + +// DefaultDockerHost defines os specific default if DOCKER_HOST is unset +const DefaultDockerHost = "unix:///var/run/docker.sock" diff --git a/components/engine/client/client_windows.go b/components/engine/client/client_windows.go new file mode 100644 index 0000000000..07c0c7a774 --- /dev/null +++ b/components/engine/client/client_windows.go @@ -0,0 +1,4 @@ +package client + +// DefaultDockerHost defines os specific default if DOCKER_HOST is unset +const DefaultDockerHost = "npipe:////./pipe/docker_engine" diff --git a/components/engine/client/container_attach.go b/components/engine/client/container_attach.go new file mode 100644 index 0000000000..7cfc860fcc --- /dev/null +++ b/components/engine/client/container_attach.go @@ -0,0 +1,34 @@ +package client + +import ( + "net/url" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ContainerAttach attaches a connection to a container in the server. +// It returns a types.HijackedConnection with the hijacked connection +// and the a reader to get output. It's up to the called to close +// the hijacked connection by calling types.HijackedResponse.Close. +func (cli *Client) ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) { + query := url.Values{} + if options.Stream { + query.Set("stream", "1") + } + if options.Stdin { + query.Set("stdin", "1") + } + if options.Stdout { + query.Set("stdout", "1") + } + if options.Stderr { + query.Set("stderr", "1") + } + if options.DetachKeys != "" { + query.Set("detachKeys", options.DetachKeys) + } + + headers := map[string][]string{"Content-Type": {"text/plain"}} + return cli.postHijacked(ctx, "/containers/"+container+"/attach", query, nil, headers) +} diff --git a/components/engine/client/container_commit.go b/components/engine/client/container_commit.go new file mode 100644 index 0000000000..363950cc24 --- /dev/null +++ b/components/engine/client/container_commit.go @@ -0,0 +1,53 @@ +package client + +import ( + "encoding/json" + "errors" + "net/url" + + distreference "github.com/docker/distribution/reference" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/reference" + "golang.org/x/net/context" +) + +// ContainerCommit applies changes into a container and creates a new tagged image. +func (cli *Client) ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) { + var repository, tag string + if options.Reference != "" { + distributionRef, err := distreference.ParseNamed(options.Reference) + if err != nil { + return types.ContainerCommitResponse{}, err + } + + if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { + return types.ContainerCommitResponse{}, errors.New("refusing to create a tag with a digest reference") + } + + tag = reference.GetTagFromNamedRef(distributionRef) + repository = distributionRef.Name() + } + + query := url.Values{} + query.Set("container", container) + query.Set("repo", repository) + query.Set("tag", tag) + query.Set("comment", options.Comment) + query.Set("author", options.Author) + for _, change := range options.Changes { + query.Add("changes", change) + } + if options.Pause != true { + query.Set("pause", "0") + } + + var response types.ContainerCommitResponse + resp, err := cli.post(ctx, "/commit", query, options.Config, nil) + if err != nil { + return response, err + } + + err = json.NewDecoder(resp.body).Decode(&response) + ensureReaderClosed(resp) + return response, err +} diff --git a/components/engine/client/container_commit_test.go b/components/engine/client/container_commit_test.go new file mode 100644 index 0000000000..3fc3e5cfd0 --- /dev/null +++ b/components/engine/client/container_commit_test.go @@ -0,0 +1,96 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestContainerCommitError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerCommit(context.Background(), "nothing", types.ContainerCommitOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerCommit(t *testing.T) { + expectedURL := "/commit" + expectedContainerID := "container_id" + specifiedReference := "repository_name:tag" + expectedRepositoryName := "repository_name" + expectedTag := "tag" + expectedComment := "comment" + expectedAuthor := "author" + expectedChanges := []string{"change1", "change2"} + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + containerID := query.Get("container") + if containerID != expectedContainerID { + return nil, fmt.Errorf("container id not set in URL query properly. Expected '%s', got %s", expectedContainerID, containerID) + } + repo := query.Get("repo") + if repo != expectedRepositoryName { + return nil, fmt.Errorf("container repo not set in URL query properly. Expected '%s', got %s", expectedRepositoryName, repo) + } + tag := query.Get("tag") + if tag != expectedTag { + return nil, fmt.Errorf("container tag not set in URL query properly. Expected '%s', got %s'", expectedTag, tag) + } + comment := query.Get("comment") + if comment != expectedComment { + return nil, fmt.Errorf("container comment not set in URL query properly. Expected '%s', got %s'", expectedComment, comment) + } + author := query.Get("author") + if author != expectedAuthor { + return nil, fmt.Errorf("container author not set in URL query properly. Expected '%s', got %s'", expectedAuthor, author) + } + pause := query.Get("pause") + if pause != "0" { + return nil, fmt.Errorf("container pause not set in URL query properly. Expected 'true', got %v'", pause) + } + changes := query["changes"] + if len(changes) != len(expectedChanges) { + return nil, fmt.Errorf("expected container changes size to be '%d', got %d", len(expectedChanges), len(changes)) + } + b, err := json.Marshal(types.ContainerCommitResponse{ + ID: "new_container_id", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + r, err := client.ContainerCommit(context.Background(), expectedContainerID, types.ContainerCommitOptions{ + Reference: specifiedReference, + Comment: expectedComment, + Author: expectedAuthor, + Changes: expectedChanges, + Pause: false, + }) + if err != nil { + t.Fatal(err) + } + if r.ID != "new_container_id" { + t.Fatalf("expected `container_id`, got %s", r.ID) + } +} diff --git a/components/engine/client/container_copy.go b/components/engine/client/container_copy.go new file mode 100644 index 0000000000..8380eeabc9 --- /dev/null +++ b/components/engine/client/container_copy.go @@ -0,0 +1,97 @@ +package client + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path/filepath" + "strings" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" +) + +// ContainerStatPath returns Stat information about a path inside the container filesystem. +func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (types.ContainerPathStat, error) { + query := url.Values{} + query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. + + urlStr := fmt.Sprintf("/containers/%s/archive", containerID) + response, err := cli.head(ctx, urlStr, query, nil) + if err != nil { + return types.ContainerPathStat{}, err + } + defer ensureReaderClosed(response) + return getContainerPathStatFromHeader(response.header) +} + +// CopyToContainer copies content into the container filesystem. +func (cli *Client) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error { + query := url.Values{} + query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. + // Do not allow for an existing directory to be overwritten by a non-directory and vice versa. + if !options.AllowOverwriteDirWithFile { + query.Set("noOverwriteDirNonDir", "true") + } + + apiPath := fmt.Sprintf("/containers/%s/archive", container) + + response, err := cli.putRaw(ctx, apiPath, query, content, nil) + if err != nil { + return err + } + defer ensureReaderClosed(response) + + if response.statusCode != http.StatusOK { + return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode) + } + + return nil +} + +// CopyFromContainer gets the content from the container and returns it as a Reader +// to manipulate it in the host. It's up to the caller to close the reader. +func (cli *Client) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { + query := make(url.Values, 1) + query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. + + apiPath := fmt.Sprintf("/containers/%s/archive", container) + response, err := cli.get(ctx, apiPath, query, nil) + if err != nil { + return nil, types.ContainerPathStat{}, err + } + + if response.statusCode != http.StatusOK { + return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode) + } + + // In order to get the copy behavior right, we need to know information + // about both the source and the destination. The response headers include + // stat info about the source that we can use in deciding exactly how to + // copy it locally. Along with the stat info about the local destination, + // we have everything we need to handle the multiple possibilities there + // can be when copying a file/dir from one location to another file/dir. + stat, err := getContainerPathStatFromHeader(response.header) + if err != nil { + return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err) + } + return response.body, stat, err +} + +func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) { + var stat types.ContainerPathStat + + encodedStat := header.Get("X-Docker-Container-Path-Stat") + statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat)) + + err := json.NewDecoder(statDecoder).Decode(&stat) + if err != nil { + err = fmt.Errorf("unable to decode container path stat header: %s", err) + } + + return stat, err +} diff --git a/components/engine/client/container_copy_test.go b/components/engine/client/container_copy_test.go new file mode 100644 index 0000000000..39cd05ac2d --- /dev/null +++ b/components/engine/client/container_copy_test.go @@ -0,0 +1,244 @@ +package client + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" +) + +func TestContainerStatPathError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerStatPath(context.Background(), "container_id", "path") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server error, got %v", err) + } +} + +func TestContainerStatPathNoHeaderError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + _, err := client.ContainerStatPath(context.Background(), "container_id", "path/to/file") + if err == nil { + t.Fatalf("expected an error, got nothing") + } +} + +func TestContainerStatPath(t *testing.T) { + expectedURL := "/containers/container_id/archive" + expectedPath := "path/to/file" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "HEAD" { + return nil, fmt.Errorf("expected HEAD method, got %s", req.Method) + } + query := req.URL.Query() + path := query.Get("path") + if path != expectedPath { + return nil, fmt.Errorf("path not set in URL query properly") + } + content, err := json.Marshal(types.ContainerPathStat{ + Name: "name", + Mode: 0700, + }) + if err != nil { + return nil, err + } + base64PathStat := base64.StdEncoding.EncodeToString(content) + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + Header: http.Header{ + "X-Docker-Container-Path-Stat": []string{base64PathStat}, + }, + }, nil + }), + } + stat, err := client.ContainerStatPath(context.Background(), "container_id", expectedPath) + if err != nil { + t.Fatal(err) + } + if stat.Name != "name" { + t.Fatalf("expected container path stat name to be 'name', was '%s'", stat.Name) + } + if stat.Mode != 0700 { + t.Fatalf("expected container path stat mode to be 0700, was '%v'", stat.Mode) + } +} + +func TestCopyToContainerError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), types.CopyToContainerOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server error, got %v", err) + } +} + +func TestCopyToContainerNotStatusOKError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusNoContent, "No content")), + } + err := client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), types.CopyToContainerOptions{}) + if err == nil || err.Error() != "unexpected status code from daemon: 204" { + t.Fatalf("expected an unexpected status code error, got %v", err) + } +} + +func TestCopyToContainer(t *testing.T) { + expectedURL := "/containers/container_id/archive" + expectedPath := "path/to/file" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "PUT" { + return nil, fmt.Errorf("expected PUT method, got %s", req.Method) + } + query := req.URL.Query() + path := query.Get("path") + if path != expectedPath { + return nil, fmt.Errorf("path not set in URL query properly, expected '%s', got %s", expectedPath, path) + } + noOverwriteDirNonDir := query.Get("noOverwriteDirNonDir") + if noOverwriteDirNonDir != "true" { + return nil, fmt.Errorf("noOverwriteDirNonDir not set in URL query properly, expected true, got %s", noOverwriteDirNonDir) + } + + content, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, err + } + if err := req.Body.Close(); err != nil { + return nil, err + } + if string(content) != "content" { + return nil, fmt.Errorf("expected content to be 'content', got %s", string(content)) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + err := client.CopyToContainer(context.Background(), "container_id", expectedPath, bytes.NewReader([]byte("content")), types.CopyToContainerOptions{ + AllowOverwriteDirWithFile: false, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestCopyFromContainerError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, _, err := client.CopyFromContainer(context.Background(), "container_id", "path/to/file") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server error, got %v", err) + } +} + +func TestCopyFromContainerNotStatusOKError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusNoContent, "No content")), + } + _, _, err := client.CopyFromContainer(context.Background(), "container_id", "path/to/file") + if err == nil || err.Error() != "unexpected status code from daemon: 204" { + t.Fatalf("expected an unexpected status code error, got %v", err) + } +} + +func TestCopyFromContainerNoHeaderError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + _, _, err := client.CopyFromContainer(context.Background(), "container_id", "path/to/file") + if err == nil { + t.Fatalf("expected an error, got nothing") + } +} + +func TestCopyFromContainer(t *testing.T) { + expectedURL := "/containers/container_id/archive" + expectedPath := "path/to/file" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "GET" { + return nil, fmt.Errorf("expected PUT method, got %s", req.Method) + } + query := req.URL.Query() + path := query.Get("path") + if path != expectedPath { + return nil, fmt.Errorf("path not set in URL query properly, expected '%s', got %s", expectedPath, path) + } + + headercontent, err := json.Marshal(types.ContainerPathStat{ + Name: "name", + Mode: 0700, + }) + if err != nil { + return nil, err + } + base64PathStat := base64.StdEncoding.EncodeToString(headercontent) + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("content"))), + Header: http.Header{ + "X-Docker-Container-Path-Stat": []string{base64PathStat}, + }, + }, nil + }), + } + r, stat, err := client.CopyFromContainer(context.Background(), "container_id", expectedPath) + if err != nil { + t.Fatal(err) + } + if stat.Name != "name" { + t.Fatalf("expected container path stat name to be 'name', was '%s'", stat.Name) + } + if stat.Mode != 0700 { + t.Fatalf("expected container path stat mode to be 0700, was '%v'", stat.Mode) + } + content, err := ioutil.ReadAll(r) + if err != nil { + t.Fatal(err) + } + if err := r.Close(); err != nil { + t.Fatal(err) + } + if string(content) != "content" { + t.Fatalf("expected content to be 'content', got %s", string(content)) + } +} diff --git a/components/engine/client/container_create.go b/components/engine/client/container_create.go new file mode 100644 index 0000000000..a862172956 --- /dev/null +++ b/components/engine/client/container_create.go @@ -0,0 +1,46 @@ +package client + +import ( + "encoding/json" + "net/url" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "golang.org/x/net/context" +) + +type configWrapper struct { + *container.Config + HostConfig *container.HostConfig + NetworkingConfig *network.NetworkingConfig +} + +// ContainerCreate creates a new container based in the given configuration. +// It can be associated with a name, but it's not mandatory. +func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) { + var response types.ContainerCreateResponse + query := url.Values{} + if containerName != "" { + query.Set("name", containerName) + } + + body := configWrapper{ + Config: config, + HostConfig: hostConfig, + NetworkingConfig: networkingConfig, + } + + serverResp, err := cli.post(ctx, "/containers/create", query, body, nil) + if err != nil { + if serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") { + return response, imageNotFoundError{config.Image} + } + return response, err + } + + err = json.NewDecoder(serverResp.body).Decode(&response) + ensureReaderClosed(serverResp) + return response, err +} diff --git a/components/engine/client/container_create_test.go b/components/engine/client/container_create_test.go new file mode 100644 index 0000000000..4c14cdc5d1 --- /dev/null +++ b/components/engine/client/container_create_test.go @@ -0,0 +1,77 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "golang.org/x/net/context" +) + +func TestContainerCreateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerCreate(context.Background(), nil, nil, nil, "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } + + // 404 doesn't automagitally means an unknown image + client = &Client{ + transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), + } + _, err = client.ContainerCreate(context.Background(), nil, nil, nil, "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerCreateImageNotFound(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusNotFound, "No such image")), + } + _, err := client.ContainerCreate(context.Background(), &container.Config{Image: "unknown_image"}, nil, nil, "unknown") + if err == nil || !IsErrImageNotFound(err) { + t.Fatalf("expected an imageNotFound error, got %v", err) + } +} + +func TestContainerCreateWithName(t *testing.T) { + expectedURL := "/containers/create" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + name := req.URL.Query().Get("name") + if name != "container_name" { + return nil, fmt.Errorf("container name not set in URL query properly. Expected `container_name`, got %s", name) + } + b, err := json.Marshal(types.ContainerCreateResponse{ + ID: "container_id", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + r, err := client.ContainerCreate(context.Background(), nil, nil, nil, "container_name") + if err != nil { + t.Fatal(err) + } + if r.ID != "container_id" { + t.Fatalf("expected `container_id`, got %s", r.ID) + } +} diff --git a/components/engine/client/container_diff.go b/components/engine/client/container_diff.go new file mode 100644 index 0000000000..1e3e554fc5 --- /dev/null +++ b/components/engine/client/container_diff.go @@ -0,0 +1,23 @@ +package client + +import ( + "encoding/json" + "net/url" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ContainerDiff shows differences in a container filesystem since it was started. +func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]types.ContainerChange, error) { + var changes []types.ContainerChange + + serverResp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil) + if err != nil { + return changes, err + } + + err = json.NewDecoder(serverResp.body).Decode(&changes) + ensureReaderClosed(serverResp) + return changes, err +} diff --git a/components/engine/client/container_diff_test.go b/components/engine/client/container_diff_test.go new file mode 100644 index 0000000000..03ea3354d2 --- /dev/null +++ b/components/engine/client/container_diff_test.go @@ -0,0 +1,61 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestContainerDiffError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerDiff(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } + +} + +func TestContainerDiff(t *testing.T) { + expectedURL := "/containers/container_id/changes" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + b, err := json.Marshal([]types.ContainerChange{ + { + Kind: 0, + Path: "/path/1", + }, + { + Kind: 1, + Path: "/path/2", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + changes, err := client.ContainerDiff(context.Background(), "container_id") + if err != nil { + t.Fatal(err) + } + if len(changes) != 2 { + t.Fatalf("expected an array of 2 changes, got %v", changes) + } +} diff --git a/components/engine/client/container_exec.go b/components/engine/client/container_exec.go new file mode 100644 index 0000000000..34173d3194 --- /dev/null +++ b/components/engine/client/container_exec.go @@ -0,0 +1,49 @@ +package client + +import ( + "encoding/json" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ContainerExecCreate creates a new exec configuration to run an exec process. +func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.ContainerExecCreateResponse, error) { + var response types.ContainerExecCreateResponse + resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, config, nil) + if err != nil { + return response, err + } + err = json.NewDecoder(resp.body).Decode(&response) + ensureReaderClosed(resp) + return response, err +} + +// ContainerExecStart starts an exec process already created in the docker host. +func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error { + resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, config, nil) + ensureReaderClosed(resp) + return err +} + +// ContainerExecAttach attaches a connection to an exec process in the server. +// It returns a types.HijackedConnection with the hijacked connection +// and the a reader to get output. It's up to the called to close +// the hijacked connection by calling types.HijackedResponse.Close. +func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error) { + headers := map[string][]string{"Content-Type": {"application/json"}} + return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, headers) +} + +// ContainerExecInspect returns information about a specific exec process on the docker host. +func (cli *Client) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) { + var response types.ContainerExecInspect + resp, err := cli.get(ctx, "/exec/"+execID+"/json", nil, nil) + if err != nil { + return response, err + } + + err = json.NewDecoder(resp.body).Decode(&response) + ensureReaderClosed(resp) + return response, err +} diff --git a/components/engine/client/container_exec_test.go b/components/engine/client/container_exec_test.go new file mode 100644 index 0000000000..abe824e47b --- /dev/null +++ b/components/engine/client/container_exec_test.go @@ -0,0 +1,157 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" +) + +func TestContainerExecCreateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerExecCreate(context.Background(), "container_id", types.ExecConfig{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerExecCreate(t *testing.T) { + expectedURL := "/containers/container_id/exec" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + // FIXME validate the content is the given ExecConfig ? + if err := req.ParseForm(); err != nil { + return nil, err + } + execConfig := &types.ExecConfig{} + if err := json.NewDecoder(req.Body).Decode(execConfig); err != nil { + return nil, err + } + if execConfig.User != "user" { + return nil, fmt.Errorf("expected an execConfig with User == 'user', got %v", execConfig) + } + b, err := json.Marshal(types.ContainerExecCreateResponse{ + ID: "exec_id", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + r, err := client.ContainerExecCreate(context.Background(), "container_id", types.ExecConfig{ + User: "user", + }) + if err != nil { + t.Fatal(err) + } + if r.ID != "exec_id" { + t.Fatalf("expected `exec_id`, got %s", r.ID) + } +} + +func TestContainerExecStartError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.ContainerExecStart(context.Background(), "nothing", types.ExecStartCheck{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerExecStart(t *testing.T) { + expectedURL := "/exec/exec_id/start" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if err := req.ParseForm(); err != nil { + return nil, err + } + execStartCheck := &types.ExecStartCheck{} + if err := json.NewDecoder(req.Body).Decode(execStartCheck); err != nil { + return nil, err + } + if execStartCheck.Tty || !execStartCheck.Detach { + return nil, fmt.Errorf("expected execStartCheck{Detach:true,Tty:false}, got %v", execStartCheck) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.ContainerExecStart(context.Background(), "exec_id", types.ExecStartCheck{ + Detach: true, + Tty: false, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestContainerExecInspectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerExecInspect(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerExecInspect(t *testing.T) { + expectedURL := "/exec/exec_id/json" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + b, err := json.Marshal(types.ContainerExecInspect{ + ExecID: "exec_id", + ContainerID: "container_id", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + inspect, err := client.ContainerExecInspect(context.Background(), "exec_id") + if err != nil { + t.Fatal(err) + } + if inspect.ExecID != "exec_id" { + t.Fatalf("expected ExecID to be `exec_id`, got %s", inspect.ExecID) + } + if inspect.ContainerID != "container_id" { + t.Fatalf("expected ContainerID `container_id`, got %s", inspect.ContainerID) + } +} diff --git a/components/engine/client/container_export.go b/components/engine/client/container_export.go new file mode 100644 index 0000000000..52194f3d34 --- /dev/null +++ b/components/engine/client/container_export.go @@ -0,0 +1,20 @@ +package client + +import ( + "io" + "net/url" + + "golang.org/x/net/context" +) + +// ContainerExport retrieves the raw contents of a container +// and returns them as an io.ReadCloser. It's up to the caller +// to close the stream. +func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) { + serverResp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil) + if err != nil { + return nil, err + } + + return serverResp.body, nil +} diff --git a/components/engine/client/container_export_test.go b/components/engine/client/container_export_test.go new file mode 100644 index 0000000000..10eba33d2f --- /dev/null +++ b/components/engine/client/container_export_test.go @@ -0,0 +1,50 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestContainerExportError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerExport(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerExport(t *testing.T) { + expectedURL := "/containers/container_id/export" + client := &Client{ + transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { + if !strings.HasPrefix(r.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))), + }, nil + }), + } + body, err := client.ContainerExport(context.Background(), "container_id") + if err != nil { + t.Fatal(err) + } + defer body.Close() + content, err := ioutil.ReadAll(body) + if err != nil { + t.Fatal(err) + } + if string(content) != "response" { + t.Fatalf("expected response to contain 'response', got %s", string(content)) + } +} diff --git a/components/engine/client/container_inspect.go b/components/engine/client/container_inspect.go new file mode 100644 index 0000000000..17f1809747 --- /dev/null +++ b/components/engine/client/container_inspect.go @@ -0,0 +1,54 @@ +package client + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ContainerInspect returns the container information. +func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) { + serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil) + if err != nil { + if serverResp.statusCode == http.StatusNotFound { + return types.ContainerJSON{}, containerNotFoundError{containerID} + } + return types.ContainerJSON{}, err + } + + var response types.ContainerJSON + err = json.NewDecoder(serverResp.body).Decode(&response) + ensureReaderClosed(serverResp) + return response, err +} + +// ContainerInspectWithRaw returns the container information and its raw representation. +func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (types.ContainerJSON, []byte, error) { + query := url.Values{} + if getSize { + query.Set("size", "1") + } + serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil) + if err != nil { + if serverResp.statusCode == http.StatusNotFound { + return types.ContainerJSON{}, nil, containerNotFoundError{containerID} + } + return types.ContainerJSON{}, nil, err + } + defer ensureReaderClosed(serverResp) + + body, err := ioutil.ReadAll(serverResp.body) + if err != nil { + return types.ContainerJSON{}, nil, err + } + + var response types.ContainerJSON + rdr := bytes.NewReader(body) + err = json.NewDecoder(rdr).Decode(&response) + return response, body, err +} diff --git a/components/engine/client/container_inspect_test.go b/components/engine/client/container_inspect_test.go new file mode 100644 index 0000000000..0dc8ac3753 --- /dev/null +++ b/components/engine/client/container_inspect_test.go @@ -0,0 +1,125 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestContainerInspectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.ContainerInspect(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerInspectContainerNotFound(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), + } + + _, err := client.ContainerInspect(context.Background(), "unknown") + if err == nil || !IsErrContainerNotFound(err) { + t.Fatalf("expected a containerNotFound error, got %v", err) + } +} + +func TestContainerInspect(t *testing.T) { + expectedURL := "/containers/container_id/json" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + content, err := json.Marshal(types.ContainerJSON{ + ContainerJSONBase: &types.ContainerJSONBase{ + ID: "container_id", + Image: "image", + Name: "name", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + r, err := client.ContainerInspect(context.Background(), "container_id") + if err != nil { + t.Fatal(err) + } + if r.ID != "container_id" { + t.Fatalf("expected `container_id`, got %s", r.ID) + } + if r.Image != "image" { + t.Fatalf("expected `image`, got %s", r.ID) + } + if r.Name != "name" { + t.Fatalf("expected `name`, got %s", r.ID) + } +} + +func TestContainerInspectNode(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + content, err := json.Marshal(types.ContainerJSON{ + ContainerJSONBase: &types.ContainerJSONBase{ + ID: "container_id", + Image: "image", + Name: "name", + Node: &types.ContainerNode{ + ID: "container_node_id", + Addr: "container_node", + Labels: map[string]string{"foo": "bar"}, + }, + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + r, err := client.ContainerInspect(context.Background(), "container_id") + if err != nil { + t.Fatal(err) + } + if r.ID != "container_id" { + t.Fatalf("expected `container_id`, got %s", r.ID) + } + if r.Image != "image" { + t.Fatalf("expected `image`, got %s", r.ID) + } + if r.Name != "name" { + t.Fatalf("expected `name`, got %s", r.ID) + } + if r.Node.ID != "container_node_id" { + t.Fatalf("expected `container_node_id`, got %s", r.Node.ID) + } + if r.Node.Addr != "container_node" { + t.Fatalf("expected `container_node`, got %s", r.Node.Addr) + } + foo, ok := r.Node.Labels["foo"] + if foo != "bar" || !ok { + t.Fatalf("expected `bar` for label `foo`") + } +} diff --git a/components/engine/client/container_kill.go b/components/engine/client/container_kill.go new file mode 100644 index 0000000000..29f80c73ad --- /dev/null +++ b/components/engine/client/container_kill.go @@ -0,0 +1,17 @@ +package client + +import ( + "net/url" + + "golang.org/x/net/context" +) + +// ContainerKill terminates the container process but does not remove the container from the docker host. +func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error { + query := url.Values{} + query.Set("signal", signal) + + resp, err := cli.post(ctx, "/containers/"+containerID+"/kill", query, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/container_kill_test.go b/components/engine/client/container_kill_test.go new file mode 100644 index 0000000000..a34a7b5b11 --- /dev/null +++ b/components/engine/client/container_kill_test.go @@ -0,0 +1,46 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestContainerKillError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.ContainerKill(context.Background(), "nothing", "SIGKILL") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerKill(t *testing.T) { + expectedURL := "/containers/container_id/kill" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + signal := req.URL.Query().Get("signal") + if signal != "SIGKILL" { + return nil, fmt.Errorf("signal not set in URL query properly. Expected 'SIGKILL', got %s", signal) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.ContainerKill(context.Background(), "container_id", "SIGKILL") + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/container_list.go b/components/engine/client/container_list.go new file mode 100644 index 0000000000..a8945d84f1 --- /dev/null +++ b/components/engine/client/container_list.go @@ -0,0 +1,56 @@ +package client + +import ( + "encoding/json" + "net/url" + "strconv" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "golang.org/x/net/context" +) + +// ContainerList returns the list of containers in the docker host. +func (cli *Client) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) { + query := url.Values{} + + if options.All { + query.Set("all", "1") + } + + if options.Limit != -1 { + query.Set("limit", strconv.Itoa(options.Limit)) + } + + if options.Since != "" { + query.Set("since", options.Since) + } + + if options.Before != "" { + query.Set("before", options.Before) + } + + if options.Size { + query.Set("size", "1") + } + + if options.Filter.Len() > 0 { + filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filter) + + if err != nil { + return nil, err + } + + query.Set("filters", filterJSON) + } + + resp, err := cli.get(ctx, "/containers/json", query, nil) + if err != nil { + return nil, err + } + + var containers []types.Container + err = json.NewDecoder(resp.body).Decode(&containers) + ensureReaderClosed(resp) + return containers, err +} diff --git a/components/engine/client/container_list_test.go b/components/engine/client/container_list_test.go new file mode 100644 index 0000000000..3aa2101f27 --- /dev/null +++ b/components/engine/client/container_list_test.go @@ -0,0 +1,96 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "golang.org/x/net/context" +) + +func TestContainerListError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerList(context.Background(), types.ContainerListOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerList(t *testing.T) { + expectedURL := "/containers/json" + expectedFilters := `{"before":{"container":true},"label":{"label1":true,"label2":true}}` + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + all := query.Get("all") + if all != "1" { + return nil, fmt.Errorf("all not set in URL query properly. Expected '1', got %s", all) + } + limit := query.Get("limit") + if limit != "0" { + return nil, fmt.Errorf("limit should have not be present in query. Expected '0', got %s", limit) + } + since := query.Get("since") + if since != "container" { + return nil, fmt.Errorf("since not set in URL query properly. Expected 'container', got %s", since) + } + before := query.Get("before") + if before != "" { + return nil, fmt.Errorf("before should have not be present in query, go %s", before) + } + size := query.Get("size") + if size != "1" { + return nil, fmt.Errorf("size not set in URL query properly. Expected '1', got %s", size) + } + filters := query.Get("filters") + if filters != expectedFilters { + return nil, fmt.Errorf("expected filters incoherent '%v' with actual filters %v", expectedFilters, filters) + } + + b, err := json.Marshal([]types.Container{ + { + ID: "container_id1", + }, + { + ID: "container_id2", + }, + }) + if err != nil { + return nil, err + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + filters := filters.NewArgs() + filters.Add("label", "label1") + filters.Add("label", "label2") + filters.Add("before", "container") + containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{ + Size: true, + All: true, + Since: "container", + Filter: filters, + }) + if err != nil { + t.Fatal(err) + } + if len(containers) != 2 { + t.Fatalf("expected 2 containers, got %v", containers) + } +} diff --git a/components/engine/client/container_logs.go b/components/engine/client/container_logs.go new file mode 100644 index 0000000000..69056b6321 --- /dev/null +++ b/components/engine/client/container_logs.go @@ -0,0 +1,52 @@ +package client + +import ( + "io" + "net/url" + "time" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + timetypes "github.com/docker/docker/api/types/time" +) + +// ContainerLogs returns the logs generated by a container in an io.ReadCloser. +// It's up to the caller to close the stream. +func (cli *Client) ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) { + query := url.Values{} + if options.ShowStdout { + query.Set("stdout", "1") + } + + if options.ShowStderr { + query.Set("stderr", "1") + } + + if options.Since != "" { + ts, err := timetypes.GetTimestamp(options.Since, time.Now()) + if err != nil { + return nil, err + } + query.Set("since", ts) + } + + if options.Timestamps { + query.Set("timestamps", "1") + } + + if options.Details { + query.Set("details", "1") + } + + if options.Follow { + query.Set("follow", "1") + } + query.Set("tail", options.Tail) + + resp, err := cli.get(ctx, "/containers/"+container+"/logs", query, nil) + if err != nil { + return nil, err + } + return resp.body, nil +} diff --git a/components/engine/client/container_logs_test.go b/components/engine/client/container_logs_test.go new file mode 100644 index 0000000000..d7f0adc9c0 --- /dev/null +++ b/components/engine/client/container_logs_test.go @@ -0,0 +1,133 @@ +package client + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + "testing" + "time" + + "github.com/docker/docker/api/types" + + "golang.org/x/net/context" +) + +func TestContainerLogsError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerLogs(context.Background(), "container_id", types.ContainerLogsOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } + _, err = client.ContainerLogs(context.Background(), "container_id", types.ContainerLogsOptions{ + Since: "2006-01-02TZ", + }) + if err == nil || !strings.Contains(err.Error(), `parsing time "2006-01-02TZ"`) { + t.Fatalf("expected a 'parsing time' error, got %v", err) + } +} + +func TestContainerLogs(t *testing.T) { + expectedURL := "/containers/container_id/logs" + cases := []struct { + options types.ContainerLogsOptions + expectedQueryParams map[string]string + }{ + { + expectedQueryParams: map[string]string{ + "tail": "", + }, + }, + { + options: types.ContainerLogsOptions{ + Tail: "any", + }, + expectedQueryParams: map[string]string{ + "tail": "any", + }, + }, + { + options: types.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + Timestamps: true, + Details: true, + Follow: true, + }, + expectedQueryParams: map[string]string{ + "tail": "", + "stdout": "1", + "stderr": "1", + "timestamps": "1", + "details": "1", + "follow": "1", + }, + }, + { + options: types.ContainerLogsOptions{ + // An complete invalid date, timestamp or go duration will be + // passed as is + Since: "invalid but valid", + }, + expectedQueryParams: map[string]string{ + "tail": "", + "since": "invalid but valid", + }, + }, + } + for _, logCase := range cases { + client := &Client{ + transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { + if !strings.HasPrefix(r.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) + } + // Check query parameters + query := r.URL.Query() + for key, expected := range logCase.expectedQueryParams { + actual := query.Get(key) + if actual != expected { + return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) + } + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))), + }, nil + }), + } + body, err := client.ContainerLogs(context.Background(), "container_id", logCase.options) + if err != nil { + t.Fatal(err) + } + defer body.Close() + content, err := ioutil.ReadAll(body) + if err != nil { + t.Fatal(err) + } + if string(content) != "response" { + t.Fatalf("expected response to contain 'response', got %s", string(content)) + } + } +} + +func ExampleClient_ContainerLogs_withTimeout() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + client, _ := NewEnvClient() + reader, err := client.ContainerLogs(ctx, "container_id", types.ContainerLogsOptions{}) + if err != nil { + log.Fatal(err) + } + + _, err = io.Copy(os.Stdout, reader) + if err != nil && err != io.EOF { + log.Fatal(err) + } +} diff --git a/components/engine/client/container_pause.go b/components/engine/client/container_pause.go new file mode 100644 index 0000000000..412067a782 --- /dev/null +++ b/components/engine/client/container_pause.go @@ -0,0 +1,10 @@ +package client + +import "golang.org/x/net/context" + +// ContainerPause pauses the main process of a given container without terminating it. +func (cli *Client) ContainerPause(ctx context.Context, containerID string) error { + resp, err := cli.post(ctx, "/containers/"+containerID+"/pause", nil, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/container_pause_test.go b/components/engine/client/container_pause_test.go new file mode 100644 index 0000000000..ebd12a6ac7 --- /dev/null +++ b/components/engine/client/container_pause_test.go @@ -0,0 +1,41 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestContainerPauseError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.ContainerPause(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerPause(t *testing.T) { + expectedURL := "/containers/container_id/pause" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + err := client.ContainerPause(context.Background(), "container_id") + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/container_remove.go b/components/engine/client/container_remove.go new file mode 100644 index 0000000000..3a79590ced --- /dev/null +++ b/components/engine/client/container_remove.go @@ -0,0 +1,27 @@ +package client + +import ( + "net/url" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ContainerRemove kills and removes a container from the docker host. +func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options types.ContainerRemoveOptions) error { + query := url.Values{} + if options.RemoveVolumes { + query.Set("v", "1") + } + if options.RemoveLinks { + query.Set("link", "1") + } + + if options.Force { + query.Set("force", "1") + } + + resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/container_remove_test.go b/components/engine/client/container_remove_test.go new file mode 100644 index 0000000000..6e135d6ef2 --- /dev/null +++ b/components/engine/client/container_remove_test.go @@ -0,0 +1,59 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestContainerRemoveError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerRemove(t *testing.T) { + expectedURL := "/containers/container_id" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + volume := query.Get("v") + if volume != "1" { + return nil, fmt.Errorf("v (volume) not set in URL query properly. Expected '1', got %s", volume) + } + force := query.Get("force") + if force != "1" { + return nil, fmt.Errorf("force not set in URL query properly. Expected '1', got %s", force) + } + link := query.Get("link") + if link != "" { + return nil, fmt.Errorf("link should have not be present in query, go %s", link) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{ + RemoveVolumes: true, + Force: true, + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/container_rename.go b/components/engine/client/container_rename.go new file mode 100644 index 0000000000..0e718da7c6 --- /dev/null +++ b/components/engine/client/container_rename.go @@ -0,0 +1,16 @@ +package client + +import ( + "net/url" + + "golang.org/x/net/context" +) + +// ContainerRename changes the name of a given container. +func (cli *Client) ContainerRename(ctx context.Context, containerID, newContainerName string) error { + query := url.Values{} + query.Set("name", newContainerName) + resp, err := cli.post(ctx, "/containers/"+containerID+"/rename", query, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/container_rename_test.go b/components/engine/client/container_rename_test.go new file mode 100644 index 0000000000..9344bab7db --- /dev/null +++ b/components/engine/client/container_rename_test.go @@ -0,0 +1,46 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestContainerRenameError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.ContainerRename(context.Background(), "nothing", "newNothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerRename(t *testing.T) { + expectedURL := "/containers/container_id/rename" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + name := req.URL.Query().Get("name") + if name != "newName" { + return nil, fmt.Errorf("name not set in URL query properly. Expected 'newName', got %s", name) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.ContainerRename(context.Background(), "container_id", "newName") + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/container_resize.go b/components/engine/client/container_resize.go new file mode 100644 index 0000000000..a7f38b024b --- /dev/null +++ b/components/engine/client/container_resize.go @@ -0,0 +1,29 @@ +package client + +import ( + "net/url" + "strconv" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ContainerResize changes the size of the tty for a container. +func (cli *Client) ContainerResize(ctx context.Context, containerID string, options types.ResizeOptions) error { + return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width) +} + +// ContainerExecResize changes the size of the tty for an exec process running inside a container. +func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options types.ResizeOptions) error { + return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width) +} + +func (cli *Client) resize(ctx context.Context, basePath string, height, width int) error { + query := url.Values{} + query.Set("h", strconv.Itoa(height)) + query.Set("w", strconv.Itoa(width)) + + resp, err := cli.post(ctx, basePath+"/resize", query, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/container_resize_test.go b/components/engine/client/container_resize_test.go new file mode 100644 index 0000000000..e0056c88d1 --- /dev/null +++ b/components/engine/client/container_resize_test.go @@ -0,0 +1,82 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestContainerResizeError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.ContainerResize(context.Background(), "container_id", types.ResizeOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerExecResizeError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.ContainerExecResize(context.Background(), "exec_id", types.ResizeOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerResize(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, resizeTransport("/containers/container_id/resize")), + } + + err := client.ContainerResize(context.Background(), "container_id", types.ResizeOptions{ + Height: 500, + Width: 600, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestContainerExecResize(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, resizeTransport("/exec/exec_id/resize")), + } + + err := client.ContainerExecResize(context.Background(), "exec_id", types.ResizeOptions{ + Height: 500, + Width: 600, + }) + if err != nil { + t.Fatal(err) + } +} + +func resizeTransport(expectedURL string) func(req *http.Request) (*http.Response, error) { + return func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + h := query.Get("h") + if h != "500" { + return nil, fmt.Errorf("h not set in URL query properly. Expected '500', got %s", h) + } + w := query.Get("w") + if w != "600" { + return nil, fmt.Errorf("w not set in URL query properly. Expected '600', got %s", w) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + } +} diff --git a/components/engine/client/container_restart.go b/components/engine/client/container_restart.go new file mode 100644 index 0000000000..74d7455f02 --- /dev/null +++ b/components/engine/client/container_restart.go @@ -0,0 +1,22 @@ +package client + +import ( + "net/url" + "time" + + timetypes "github.com/docker/docker/api/types/time" + "golang.org/x/net/context" +) + +// ContainerRestart stops and starts a container again. +// It makes the daemon to wait for the container to be up again for +// a specific amount of time, given the timeout. +func (cli *Client) ContainerRestart(ctx context.Context, containerID string, timeout *time.Duration) error { + query := url.Values{} + if timeout != nil { + query.Set("t", timetypes.DurationToSecondsString(*timeout)) + } + resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/container_restart_test.go b/components/engine/client/container_restart_test.go new file mode 100644 index 0000000000..080656d368 --- /dev/null +++ b/components/engine/client/container_restart_test.go @@ -0,0 +1,48 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + "time" + + "golang.org/x/net/context" +) + +func TestContainerRestartError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + timeout := 0 * time.Second + err := client.ContainerRestart(context.Background(), "nothing", &timeout) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerRestart(t *testing.T) { + expectedURL := "/containers/container_id/restart" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + t := req.URL.Query().Get("t") + if t != "100" { + return nil, fmt.Errorf("t (timeout) not set in URL query properly. Expected '100', got %s", t) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + timeout := 100 * time.Second + err := client.ContainerRestart(context.Background(), "container_id", &timeout) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/container_start.go b/components/engine/client/container_start.go new file mode 100644 index 0000000000..44bb0080c0 --- /dev/null +++ b/components/engine/client/container_start.go @@ -0,0 +1,21 @@ +package client + +import ( + "net/url" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" +) + +// ContainerStart sends a request to the docker daemon to start a container. +func (cli *Client) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error { + query := url.Values{} + if len(options.CheckpointID) != 0 { + query.Set("checkpoint", options.CheckpointID) + } + + resp, err := cli.post(ctx, "/containers/"+containerID+"/start", query, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/container_start_test.go b/components/engine/client/container_start_test.go new file mode 100644 index 0000000000..79f85b332a --- /dev/null +++ b/components/engine/client/container_start_test.go @@ -0,0 +1,58 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" +) + +func TestContainerStartError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.ContainerStart(context.Background(), "nothing", types.ContainerStartOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerStart(t *testing.T) { + expectedURL := "/containers/container_id/start" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + // we're not expecting any payload, but if one is supplied, check it is valid. + if req.Header.Get("Content-Type") == "application/json" { + var startConfig interface{} + if err := json.NewDecoder(req.Body).Decode(&startConfig); err != nil { + return nil, fmt.Errorf("Unable to parse json: %s", err) + } + } + + checkpoint := req.URL.Query().Get("checkpoint") + if checkpoint != "checkpoint_id" { + return nil, fmt.Errorf("checkpoint not set in URL query properly. Expected 'checkpoint_id', got %s", checkpoint) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.ContainerStart(context.Background(), "container_id", types.ContainerStartOptions{CheckpointID: "checkpoint_id"}) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/container_stats.go b/components/engine/client/container_stats.go new file mode 100644 index 0000000000..2cc67c3af1 --- /dev/null +++ b/components/engine/client/container_stats.go @@ -0,0 +1,24 @@ +package client + +import ( + "io" + "net/url" + + "golang.org/x/net/context" +) + +// ContainerStats returns near realtime stats for a given container. +// It's up to the caller to close the io.ReadCloser returned. +func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error) { + query := url.Values{} + query.Set("stream", "0") + if stream { + query.Set("stream", "1") + } + + resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil) + if err != nil { + return nil, err + } + return resp.body, err +} diff --git a/components/engine/client/container_stats_test.go b/components/engine/client/container_stats_test.go new file mode 100644 index 0000000000..22ecd6170f --- /dev/null +++ b/components/engine/client/container_stats_test.go @@ -0,0 +1,70 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestContainerStatsError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerStats(context.Background(), "nothing", false) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerStats(t *testing.T) { + expectedURL := "/containers/container_id/stats" + cases := []struct { + stream bool + expectedStream string + }{ + { + expectedStream: "0", + }, + { + stream: true, + expectedStream: "1", + }, + } + for _, c := range cases { + client := &Client{ + transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { + if !strings.HasPrefix(r.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) + } + + query := r.URL.Query() + stream := query.Get("stream") + if stream != c.expectedStream { + return nil, fmt.Errorf("stream not set in URL query properly. Expected '%s', got %s", c.expectedStream, stream) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))), + }, nil + }), + } + body, err := client.ContainerStats(context.Background(), "container_id", c.stream) + if err != nil { + t.Fatal(err) + } + defer body.Close() + content, err := ioutil.ReadAll(body) + if err != nil { + t.Fatal(err) + } + if string(content) != "response" { + t.Fatalf("expected response to contain 'response', got %s", string(content)) + } + } +} diff --git a/components/engine/client/container_stop.go b/components/engine/client/container_stop.go new file mode 100644 index 0000000000..b5418ae8c8 --- /dev/null +++ b/components/engine/client/container_stop.go @@ -0,0 +1,21 @@ +package client + +import ( + "net/url" + "time" + + timetypes "github.com/docker/docker/api/types/time" + "golang.org/x/net/context" +) + +// ContainerStop stops a container without terminating the process. +// The process is blocked until the container stops or the timeout expires. +func (cli *Client) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error { + query := url.Values{} + if timeout != nil { + query.Set("t", timetypes.DurationToSecondsString(*timeout)) + } + resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/container_stop_test.go b/components/engine/client/container_stop_test.go new file mode 100644 index 0000000000..4b052f9908 --- /dev/null +++ b/components/engine/client/container_stop_test.go @@ -0,0 +1,48 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + "time" + + "golang.org/x/net/context" +) + +func TestContainerStopError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + timeout := 0 * time.Second + err := client.ContainerStop(context.Background(), "nothing", &timeout) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerStop(t *testing.T) { + expectedURL := "/containers/container_id/stop" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + t := req.URL.Query().Get("t") + if t != "100" { + return nil, fmt.Errorf("t (timeout) not set in URL query properly. Expected '100', got %s", t) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + timeout := 100 * time.Second + err := client.ContainerStop(context.Background(), "container_id", &timeout) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/container_top.go b/components/engine/client/container_top.go new file mode 100644 index 0000000000..4e7270ea22 --- /dev/null +++ b/components/engine/client/container_top.go @@ -0,0 +1,28 @@ +package client + +import ( + "encoding/json" + "net/url" + "strings" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ContainerTop shows process information from within a container. +func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (types.ContainerProcessList, error) { + var response types.ContainerProcessList + query := url.Values{} + if len(arguments) > 0 { + query.Set("ps_args", strings.Join(arguments, " ")) + } + + resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil) + if err != nil { + return response, err + } + + err = json.NewDecoder(resp.body).Decode(&response) + ensureReaderClosed(resp) + return response, err +} diff --git a/components/engine/client/container_top_test.go b/components/engine/client/container_top_test.go new file mode 100644 index 0000000000..4df7d82d84 --- /dev/null +++ b/components/engine/client/container_top_test.go @@ -0,0 +1,74 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestContainerTopError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerTop(context.Background(), "nothing", []string{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerTop(t *testing.T) { + expectedURL := "/containers/container_id/top" + expectedProcesses := [][]string{ + {"p1", "p2"}, + {"p3"}, + } + expectedTitles := []string{"title1", "title2"} + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + args := query.Get("ps_args") + if args != "arg1 arg2" { + return nil, fmt.Errorf("args not set in URL query properly. Expected 'arg1 arg2', got %v", args) + } + + b, err := json.Marshal(types.ContainerProcessList{ + Processes: [][]string{ + {"p1", "p2"}, + {"p3"}, + }, + Titles: []string{"title1", "title2"}, + }) + if err != nil { + return nil, err + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + processList, err := client.ContainerTop(context.Background(), "container_id", []string{"arg1", "arg2"}) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(expectedProcesses, processList.Processes) { + t.Fatalf("Processes: expected %v, got %v", expectedProcesses, processList.Processes) + } + if !reflect.DeepEqual(expectedTitles, processList.Titles) { + t.Fatalf("Titles: expected %v, got %v", expectedTitles, processList.Titles) + } +} diff --git a/components/engine/client/container_unpause.go b/components/engine/client/container_unpause.go new file mode 100644 index 0000000000..5c76211256 --- /dev/null +++ b/components/engine/client/container_unpause.go @@ -0,0 +1,10 @@ +package client + +import "golang.org/x/net/context" + +// ContainerUnpause resumes the process execution within a container +func (cli *Client) ContainerUnpause(ctx context.Context, containerID string) error { + resp, err := cli.post(ctx, "/containers/"+containerID+"/unpause", nil, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/container_unpause_test.go b/components/engine/client/container_unpause_test.go new file mode 100644 index 0000000000..a5b21bf56c --- /dev/null +++ b/components/engine/client/container_unpause_test.go @@ -0,0 +1,41 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestContainerUnpauseError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + err := client.ContainerUnpause(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerUnpause(t *testing.T) { + expectedURL := "/containers/container_id/unpause" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + err := client.ContainerUnpause(context.Background(), "container_id") + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/container_update.go b/components/engine/client/container_update.go new file mode 100644 index 0000000000..48b75bee30 --- /dev/null +++ b/components/engine/client/container_update.go @@ -0,0 +1,23 @@ +package client + +import ( + "encoding/json" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "golang.org/x/net/context" +) + +// ContainerUpdate updates resources of a container +func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (types.ContainerUpdateResponse, error) { + var response types.ContainerUpdateResponse + serverResp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil) + if err != nil { + return response, err + } + + err = json.NewDecoder(serverResp.body).Decode(&response) + + ensureReaderClosed(serverResp) + return response, err +} diff --git a/components/engine/client/container_update_test.go b/components/engine/client/container_update_test.go new file mode 100644 index 0000000000..46e34d6936 --- /dev/null +++ b/components/engine/client/container_update_test.go @@ -0,0 +1,59 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "golang.org/x/net/context" +) + +func TestContainerUpdateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerUpdate(context.Background(), "nothing", container.UpdateConfig{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestContainerUpdate(t *testing.T) { + expectedURL := "/containers/container_id/update" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + + b, err := json.Marshal(types.ContainerUpdateResponse{}) + if err != nil { + return nil, err + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + _, err := client.ContainerUpdate(context.Background(), "container_id", container.UpdateConfig{ + Resources: container.Resources{ + CPUPeriod: 1, + }, + RestartPolicy: container.RestartPolicy{ + Name: "always", + }, + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/container_wait.go b/components/engine/client/container_wait.go new file mode 100644 index 0000000000..8a858f0ea3 --- /dev/null +++ b/components/engine/client/container_wait.go @@ -0,0 +1,26 @@ +package client + +import ( + "encoding/json" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" +) + +// ContainerWait pauses execution until a container exits. +// It returns the API status code as response of its readiness. +func (cli *Client) ContainerWait(ctx context.Context, containerID string) (int, error) { + resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil) + if err != nil { + return -1, err + } + defer ensureReaderClosed(resp) + + var res types.ContainerWaitResponse + if err := json.NewDecoder(resp.body).Decode(&res); err != nil { + return -1, err + } + + return res.StatusCode, nil +} diff --git a/components/engine/client/container_wait_test.go b/components/engine/client/container_wait_test.go new file mode 100644 index 0000000000..bf2ba6b925 --- /dev/null +++ b/components/engine/client/container_wait_test.go @@ -0,0 +1,70 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "strings" + "testing" + "time" + + "github.com/docker/docker/api/types" + + "golang.org/x/net/context" +) + +func TestContainerWaitError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + code, err := client.ContainerWait(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } + if code != -1 { + t.Fatalf("expected a status code equal to '-1', got %d", code) + } +} + +func TestContainerWait(t *testing.T) { + expectedURL := "/containers/container_id/wait" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + b, err := json.Marshal(types.ContainerWaitResponse{ + StatusCode: 15, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + code, err := client.ContainerWait(context.Background(), "container_id") + if err != nil { + t.Fatal(err) + } + if code != 15 { + t.Fatalf("expected a status code equal to '15', got %d", code) + } +} + +func ExampleClient_ContainerWait_withTimeout() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + client, _ := NewEnvClient() + _, err := client.ContainerWait(ctx, "container_id") + if err != nil { + log.Fatal(err) + } +} diff --git a/components/engine/client/errors.go b/components/engine/client/errors.go new file mode 100644 index 0000000000..71e25a7ae1 --- /dev/null +++ b/components/engine/client/errors.go @@ -0,0 +1,208 @@ +package client + +import ( + "errors" + "fmt" +) + +// ErrConnectionFailed is an error raised when the connection between the client and the server failed. +var ErrConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?") + +// ErrorConnectionFailed returns an error with host in the error message when connection to docker daemon failed. +func ErrorConnectionFailed(host string) error { + return fmt.Errorf("Cannot connect to the Docker daemon at %s. Is the docker daemon running?", host) +} + +type notFound interface { + error + NotFound() bool // Is the error a NotFound error +} + +// IsErrNotFound returns true if the error is caused with an +// object (image, container, network, volume, …) is not found in the docker host. +func IsErrNotFound(err error) bool { + te, ok := err.(notFound) + return ok && te.NotFound() +} + +// imageNotFoundError implements an error returned when an image is not in the docker host. +type imageNotFoundError struct { + imageID string +} + +// NoFound indicates that this error type is of NotFound +func (e imageNotFoundError) NotFound() bool { + return true +} + +// Error returns a string representation of an imageNotFoundError +func (e imageNotFoundError) Error() string { + return fmt.Sprintf("Error: No such image: %s", e.imageID) +} + +// IsErrImageNotFound returns true if the error is caused +// when an image is not found in the docker host. +func IsErrImageNotFound(err error) bool { + return IsErrNotFound(err) +} + +// containerNotFoundError implements an error returned when a container is not in the docker host. +type containerNotFoundError struct { + containerID string +} + +// NoFound indicates that this error type is of NotFound +func (e containerNotFoundError) NotFound() bool { + return true +} + +// Error returns a string representation of a containerNotFoundError +func (e containerNotFoundError) Error() string { + return fmt.Sprintf("Error: No such container: %s", e.containerID) +} + +// IsErrContainerNotFound returns true if the error is caused +// when a container is not found in the docker host. +func IsErrContainerNotFound(err error) bool { + return IsErrNotFound(err) +} + +// networkNotFoundError implements an error returned when a network is not in the docker host. +type networkNotFoundError struct { + networkID string +} + +// NoFound indicates that this error type is of NotFound +func (e networkNotFoundError) NotFound() bool { + return true +} + +// Error returns a string representation of a networkNotFoundError +func (e networkNotFoundError) Error() string { + return fmt.Sprintf("Error: No such network: %s", e.networkID) +} + +// IsErrNetworkNotFound returns true if the error is caused +// when a network is not found in the docker host. +func IsErrNetworkNotFound(err error) bool { + return IsErrNotFound(err) +} + +// volumeNotFoundError implements an error returned when a volume is not in the docker host. +type volumeNotFoundError struct { + volumeID string +} + +// NoFound indicates that this error type is of NotFound +func (e volumeNotFoundError) NotFound() bool { + return true +} + +// Error returns a string representation of a networkNotFoundError +func (e volumeNotFoundError) Error() string { + return fmt.Sprintf("Error: No such volume: %s", e.volumeID) +} + +// IsErrVolumeNotFound returns true if the error is caused +// when a volume is not found in the docker host. +func IsErrVolumeNotFound(err error) bool { + return IsErrNotFound(err) +} + +// unauthorizedError represents an authorization error in a remote registry. +type unauthorizedError struct { + cause error +} + +// Error returns a string representation of an unauthorizedError +func (u unauthorizedError) Error() string { + return u.cause.Error() +} + +// IsErrUnauthorized returns true if the error is caused +// when a remote registry authentication fails +func IsErrUnauthorized(err error) bool { + _, ok := err.(unauthorizedError) + return ok +} + +// nodeNotFoundError implements an error returned when a node is not found. +type nodeNotFoundError struct { + nodeID string +} + +// Error returns a string representation of a nodeNotFoundError +func (e nodeNotFoundError) Error() string { + return fmt.Sprintf("Error: No such node: %s", e.nodeID) +} + +// NoFound indicates that this error type is of NotFound +func (e nodeNotFoundError) NotFound() bool { + return true +} + +// IsErrNodeNotFound returns true if the error is caused +// when a node is not found. +func IsErrNodeNotFound(err error) bool { + _, ok := err.(nodeNotFoundError) + return ok +} + +// serviceNotFoundError implements an error returned when a service is not found. +type serviceNotFoundError struct { + serviceID string +} + +// Error returns a string representation of a serviceNotFoundError +func (e serviceNotFoundError) Error() string { + return fmt.Sprintf("Error: No such service: %s", e.serviceID) +} + +// NoFound indicates that this error type is of NotFound +func (e serviceNotFoundError) NotFound() bool { + return true +} + +// IsErrServiceNotFound returns true if the error is caused +// when a service is not found. +func IsErrServiceNotFound(err error) bool { + _, ok := err.(serviceNotFoundError) + return ok +} + +// taskNotFoundError implements an error returned when a task is not found. +type taskNotFoundError struct { + taskID string +} + +// Error returns a string representation of a taskNotFoundError +func (e taskNotFoundError) Error() string { + return fmt.Sprintf("Error: No such task: %s", e.taskID) +} + +// NoFound indicates that this error type is of NotFound +func (e taskNotFoundError) NotFound() bool { + return true +} + +// IsErrTaskNotFound returns true if the error is caused +// when a task is not found. +func IsErrTaskNotFound(err error) bool { + _, ok := err.(taskNotFoundError) + return ok +} + +type pluginPermissionDenied struct { + name string +} + +func (e pluginPermissionDenied) Error() string { + return "Permission denied while installing plugin " + e.name +} + +// IsErrPluginPermissionDenied returns true if the error is caused +// when a user denies a plugin's permissions +func IsErrPluginPermissionDenied(err error) bool { + _, ok := err.(pluginPermissionDenied) + return ok +} diff --git a/components/engine/client/events.go b/components/engine/client/events.go new file mode 100644 index 0000000000..0ba7114f94 --- /dev/null +++ b/components/engine/client/events.go @@ -0,0 +1,48 @@ +package client + +import ( + "io" + "net/url" + "time" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + timetypes "github.com/docker/docker/api/types/time" +) + +// Events returns a stream of events in the daemon in a ReadCloser. +// It's up to the caller to close the stream. +func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) { + query := url.Values{} + ref := time.Now() + + if options.Since != "" { + ts, err := timetypes.GetTimestamp(options.Since, ref) + if err != nil { + return nil, err + } + query.Set("since", ts) + } + if options.Until != "" { + ts, err := timetypes.GetTimestamp(options.Until, ref) + if err != nil { + return nil, err + } + query.Set("until", ts) + } + if options.Filters.Len() > 0 { + filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters) + if err != nil { + return nil, err + } + query.Set("filters", filterJSON) + } + + serverResponse, err := cli.get(ctx, "/events", query, nil) + if err != nil { + return nil, err + } + return serverResponse.body, nil +} diff --git a/components/engine/client/events_test.go b/components/engine/client/events_test.go new file mode 100644 index 0000000000..f7cb33f611 --- /dev/null +++ b/components/engine/client/events_test.go @@ -0,0 +1,126 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" +) + +func TestEventsErrorInOptions(t *testing.T) { + errorCases := []struct { + options types.EventsOptions + expectedError string + }{ + { + options: types.EventsOptions{ + Since: "2006-01-02TZ", + }, + expectedError: `parsing time "2006-01-02TZ"`, + }, + { + options: types.EventsOptions{ + Until: "2006-01-02TZ", + }, + expectedError: `parsing time "2006-01-02TZ"`, + }, + } + for _, e := range errorCases { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.Events(context.Background(), e.options) + if err == nil || !strings.Contains(err.Error(), e.expectedError) { + t.Fatalf("expected a error %q, got %v", e.expectedError, err) + } + } +} + +func TestEventsErrorFromServer(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.Events(context.Background(), types.EventsOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestEvents(t *testing.T) { + expectedURL := "/events" + + filters := filters.NewArgs() + filters.Add("label", "label1") + filters.Add("label", "label2") + expectedFiltersJSON := `{"label":{"label1":true,"label2":true}}` + + eventsCases := []struct { + options types.EventsOptions + expectedQueryParams map[string]string + }{ + { + options: types.EventsOptions{ + Since: "invalid but valid", + }, + expectedQueryParams: map[string]string{ + "since": "invalid but valid", + }, + }, + { + options: types.EventsOptions{ + Until: "invalid but valid", + }, + expectedQueryParams: map[string]string{ + "until": "invalid but valid", + }, + }, + { + options: types.EventsOptions{ + Filters: filters, + }, + expectedQueryParams: map[string]string{ + "filters": expectedFiltersJSON, + }, + }, + } + + for _, eventsCase := range eventsCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + for key, expected := range eventsCase.expectedQueryParams { + actual := query.Get(key) + if actual != expected { + return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) + } + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))), + }, nil + }), + } + body, err := client.Events(context.Background(), eventsCase.options) + if err != nil { + t.Fatal(err) + } + defer body.Close() + content, err := ioutil.ReadAll(body) + if err != nil { + t.Fatal(err) + } + if string(content) != "response" { + t.Fatalf("expected response to contain 'response', got %s", string(content)) + } + } +} diff --git a/components/engine/client/hijack.go b/components/engine/client/hijack.go new file mode 100644 index 0000000000..9376d21b97 --- /dev/null +++ b/components/engine/client/hijack.go @@ -0,0 +1,174 @@ +package client + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + "net/http/httputil" + "net/url" + "strings" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client/transport" + "github.com/docker/go-connections/sockets" + "golang.org/x/net/context" +) + +// tlsClientCon holds tls information and a dialed connection. +type tlsClientCon struct { + *tls.Conn + rawConn net.Conn +} + +func (c *tlsClientCon) CloseWrite() error { + // Go standard tls.Conn doesn't provide the CloseWrite() method so we do it + // on its underlying connection. + if conn, ok := c.rawConn.(types.CloseWriter); ok { + return conn.CloseWrite() + } + return nil +} + +// postHijacked sends a POST request and hijacks the connection. +func (cli *Client) postHijacked(ctx context.Context, path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) { + bodyEncoded, err := encodeData(body) + if err != nil { + return types.HijackedResponse{}, err + } + + req, err := cli.newRequest("POST", path, query, bodyEncoded, headers) + if err != nil { + return types.HijackedResponse{}, err + } + req.Host = cli.addr + + req.Header.Set("Connection", "Upgrade") + req.Header.Set("Upgrade", "tcp") + + conn, err := dial(cli.proto, cli.addr, cli.transport.TLSConfig()) + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") + } + return types.HijackedResponse{}, err + } + + // When we set up a TCP connection for hijack, there could be long periods + // of inactivity (a long running command with no output) that in certain + // network setups may cause ECONNTIMEOUT, leaving the client in an unknown + // state. Setting TCP KeepAlive on the socket connection will prohibit + // ECONNTIMEOUT unless the socket connection truly is broken + if tcpConn, ok := conn.(*net.TCPConn); ok { + tcpConn.SetKeepAlive(true) + tcpConn.SetKeepAlivePeriod(30 * time.Second) + } + + clientconn := httputil.NewClientConn(conn, nil) + defer clientconn.Close() + + // Server hijacks the connection, error 'connection closed' expected + _, err = clientconn.Do(req) + + rwc, br := clientconn.Hijack() + + return types.HijackedResponse{Conn: rwc, Reader: br}, err +} + +func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) { + return tlsDialWithDialer(new(net.Dialer), network, addr, config) +} + +// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in +// order to return our custom tlsClientCon struct which holds both the tls.Conn +// object _and_ its underlying raw connection. The rationale for this is that +// we need to be able to close the write end of the connection when attaching, +// which tls.Conn does not provide. +func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) { + // We want the Timeout and Deadline values from dialer to cover the + // whole process: TCP connection and TLS handshake. This means that we + // also need to start our own timers now. + timeout := dialer.Timeout + + if !dialer.Deadline.IsZero() { + deadlineTimeout := dialer.Deadline.Sub(time.Now()) + if timeout == 0 || deadlineTimeout < timeout { + timeout = deadlineTimeout + } + } + + var errChannel chan error + + if timeout != 0 { + errChannel = make(chan error, 2) + time.AfterFunc(timeout, func() { + errChannel <- errors.New("") + }) + } + + proxyDialer, err := sockets.DialerFromEnvironment(dialer) + if err != nil { + return nil, err + } + + rawConn, err := proxyDialer.Dial(network, addr) + if err != nil { + return nil, err + } + // When we set up a TCP connection for hijack, there could be long periods + // of inactivity (a long running command with no output) that in certain + // network setups may cause ECONNTIMEOUT, leaving the client in an unknown + // state. Setting TCP KeepAlive on the socket connection will prohibit + // ECONNTIMEOUT unless the socket connection truly is broken + if tcpConn, ok := rawConn.(*net.TCPConn); ok { + tcpConn.SetKeepAlive(true) + tcpConn.SetKeepAlivePeriod(30 * time.Second) + } + + colonPos := strings.LastIndex(addr, ":") + if colonPos == -1 { + colonPos = len(addr) + } + hostname := addr[:colonPos] + + // If no ServerName is set, infer the ServerName + // from the hostname we're connecting to. + if config.ServerName == "" { + // Make a copy to avoid polluting argument or default. + config = transport.TLSConfigClone(config) + config.ServerName = hostname + } + + conn := tls.Client(rawConn, config) + + if timeout == 0 { + err = conn.Handshake() + } else { + go func() { + errChannel <- conn.Handshake() + }() + + err = <-errChannel + } + + if err != nil { + rawConn.Close() + return nil, err + } + + // This is Docker difference with standard's crypto/tls package: returned a + // wrapper which holds both the TLS and raw connections. + return &tlsClientCon{conn, rawConn}, nil +} + +func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) { + if tlsConfig != nil && proto != "unix" && proto != "npipe" { + // Notice this isn't Go standard's tls.Dial function + return tlsDial(proto, addr, tlsConfig) + } + if proto == "npipe" { + return sockets.DialPipe(addr, 32*time.Second) + } + return net.Dial(proto, addr) +} diff --git a/components/engine/client/image_build.go b/components/engine/client/image_build.go new file mode 100644 index 0000000000..8dd6744859 --- /dev/null +++ b/components/engine/client/image_build.go @@ -0,0 +1,123 @@ +package client + +import ( + "encoding/base64" + "encoding/json" + "io" + "net/http" + "net/url" + "regexp" + "strconv" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" +) + +var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`) + +// ImageBuild sends request to the daemon to build images. +// The Body in the response implement an io.ReadCloser and it's up to the caller to +// close it. +func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { + query, err := imageBuildOptionsToQuery(options) + if err != nil { + return types.ImageBuildResponse{}, err + } + + headers := http.Header(make(map[string][]string)) + buf, err := json.Marshal(options.AuthConfigs) + if err != nil { + return types.ImageBuildResponse{}, err + } + headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) + headers.Set("Content-Type", "application/tar") + + serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers) + if err != nil { + return types.ImageBuildResponse{}, err + } + + osType := getDockerOS(serverResp.header.Get("Server")) + + return types.ImageBuildResponse{ + Body: serverResp.body, + OSType: osType, + }, nil +} + +func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) { + query := url.Values{ + "t": options.Tags, + } + if options.SuppressOutput { + query.Set("q", "1") + } + if options.RemoteContext != "" { + query.Set("remote", options.RemoteContext) + } + if options.NoCache { + query.Set("nocache", "1") + } + if options.Remove { + query.Set("rm", "1") + } else { + query.Set("rm", "0") + } + + if options.ForceRemove { + query.Set("forcerm", "1") + } + + if options.PullParent { + query.Set("pull", "1") + } + + if options.Squash { + query.Set("squash", "1") + } + + if !container.Isolation.IsDefault(options.Isolation) { + query.Set("isolation", string(options.Isolation)) + } + + query.Set("cpusetcpus", options.CPUSetCPUs) + query.Set("cpusetmems", options.CPUSetMems) + query.Set("cpushares", strconv.FormatInt(options.CPUShares, 10)) + query.Set("cpuquota", strconv.FormatInt(options.CPUQuota, 10)) + query.Set("cpuperiod", strconv.FormatInt(options.CPUPeriod, 10)) + query.Set("memory", strconv.FormatInt(options.Memory, 10)) + query.Set("memswap", strconv.FormatInt(options.MemorySwap, 10)) + query.Set("cgroupparent", options.CgroupParent) + query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10)) + query.Set("dockerfile", options.Dockerfile) + + ulimitsJSON, err := json.Marshal(options.Ulimits) + if err != nil { + return query, err + } + query.Set("ulimits", string(ulimitsJSON)) + + buildArgsJSON, err := json.Marshal(options.BuildArgs) + if err != nil { + return query, err + } + query.Set("buildargs", string(buildArgsJSON)) + + labelsJSON, err := json.Marshal(options.Labels) + if err != nil { + return query, err + } + query.Set("labels", string(labelsJSON)) + return query, nil +} + +func getDockerOS(serverHeader string) string { + var osType string + matches := headerRegexp.FindStringSubmatch(serverHeader) + if len(matches) > 0 { + osType = matches[1] + } + return osType +} diff --git a/components/engine/client/image_build_test.go b/components/engine/client/image_build_test.go new file mode 100644 index 0000000000..8261c54854 --- /dev/null +++ b/components/engine/client/image_build_test.go @@ -0,0 +1,230 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/go-units" +) + +func TestImageBuildError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ImageBuild(context.Background(), nil, types.ImageBuildOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestImageBuild(t *testing.T) { + emptyRegistryConfig := "bnVsbA==" + buildCases := []struct { + buildOptions types.ImageBuildOptions + expectedQueryParams map[string]string + expectedTags []string + expectedRegistryConfig string + }{ + { + buildOptions: types.ImageBuildOptions{ + SuppressOutput: true, + NoCache: true, + Remove: true, + ForceRemove: true, + PullParent: true, + }, + expectedQueryParams: map[string]string{ + "q": "1", + "nocache": "1", + "rm": "1", + "forcerm": "1", + "pull": "1", + }, + expectedTags: []string{}, + expectedRegistryConfig: emptyRegistryConfig, + }, + { + buildOptions: types.ImageBuildOptions{ + SuppressOutput: false, + NoCache: false, + Remove: false, + ForceRemove: false, + PullParent: false, + }, + expectedQueryParams: map[string]string{ + "q": "", + "nocache": "", + "rm": "0", + "forcerm": "", + "pull": "", + }, + expectedTags: []string{}, + expectedRegistryConfig: emptyRegistryConfig, + }, + { + buildOptions: types.ImageBuildOptions{ + RemoteContext: "remoteContext", + Isolation: container.Isolation("isolation"), + CPUSetCPUs: "2", + CPUSetMems: "12", + CPUShares: 20, + CPUQuota: 10, + CPUPeriod: 30, + Memory: 256, + MemorySwap: 512, + ShmSize: 10, + CgroupParent: "cgroup_parent", + Dockerfile: "Dockerfile", + }, + expectedQueryParams: map[string]string{ + "remote": "remoteContext", + "isolation": "isolation", + "cpusetcpus": "2", + "cpusetmems": "12", + "cpushares": "20", + "cpuquota": "10", + "cpuperiod": "30", + "memory": "256", + "memswap": "512", + "shmsize": "10", + "cgroupparent": "cgroup_parent", + "dockerfile": "Dockerfile", + "rm": "0", + }, + expectedTags: []string{}, + expectedRegistryConfig: emptyRegistryConfig, + }, + { + buildOptions: types.ImageBuildOptions{ + BuildArgs: map[string]string{ + "ARG1": "value1", + "ARG2": "value2", + }, + }, + expectedQueryParams: map[string]string{ + "buildargs": `{"ARG1":"value1","ARG2":"value2"}`, + "rm": "0", + }, + expectedTags: []string{}, + expectedRegistryConfig: emptyRegistryConfig, + }, + { + buildOptions: types.ImageBuildOptions{ + Ulimits: []*units.Ulimit{ + { + Name: "nproc", + Hard: 65557, + Soft: 65557, + }, + { + Name: "nofile", + Hard: 20000, + Soft: 40000, + }, + }, + }, + expectedQueryParams: map[string]string{ + "ulimits": `[{"Name":"nproc","Hard":65557,"Soft":65557},{"Name":"nofile","Hard":20000,"Soft":40000}]`, + "rm": "0", + }, + expectedTags: []string{}, + expectedRegistryConfig: emptyRegistryConfig, + }, + { + buildOptions: types.ImageBuildOptions{ + AuthConfigs: map[string]types.AuthConfig{ + "https://index.docker.io/v1/": { + Auth: "dG90bwo=", + }, + }, + }, + expectedQueryParams: map[string]string{ + "rm": "0", + }, + expectedTags: []string{}, + expectedRegistryConfig: "eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsiYXV0aCI6ImRHOTBid289In19", + }, + } + for _, buildCase := range buildCases { + expectedURL := "/build" + client := &Client{ + transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { + if !strings.HasPrefix(r.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) + } + // Check request headers + registryConfig := r.Header.Get("X-Registry-Config") + if registryConfig != buildCase.expectedRegistryConfig { + return nil, fmt.Errorf("X-Registry-Config header not properly set in the request. Expected '%s', got %s", buildCase.expectedRegistryConfig, registryConfig) + } + contentType := r.Header.Get("Content-Type") + if contentType != "application/tar" { + return nil, fmt.Errorf("Content-type header not properly set in the request. Expected 'application/tar', got %s", contentType) + } + + // Check query parameters + query := r.URL.Query() + for key, expected := range buildCase.expectedQueryParams { + actual := query.Get(key) + if actual != expected { + return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) + } + } + + // Check tags + if len(buildCase.expectedTags) > 0 { + tags := query["t"] + if !reflect.DeepEqual(tags, buildCase.expectedTags) { + return nil, fmt.Errorf("t (tags) not set in URL query properly. Expected '%s', got %s", buildCase.expectedTags, tags) + } + } + + headers := http.Header{} + headers.Add("Server", "Docker/v1.23 (MyOS)") + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), + Header: headers, + }, nil + }), + } + buildResponse, err := client.ImageBuild(context.Background(), nil, buildCase.buildOptions) + if err != nil { + t.Fatal(err) + } + if buildResponse.OSType != "MyOS" { + t.Fatalf("expected OSType to be 'MyOS', got %s", buildResponse.OSType) + } + response, err := ioutil.ReadAll(buildResponse.Body) + if err != nil { + t.Fatal(err) + } + buildResponse.Body.Close() + if string(response) != "body" { + t.Fatalf("expected Body to contain 'body' string, got %s", response) + } + } +} + +func TestGetDockerOS(t *testing.T) { + cases := map[string]string{ + "Docker/v1.22 (linux)": "linux", + "Docker/v1.22 (windows)": "windows", + "Foo/v1.22 (bar)": "", + } + for header, os := range cases { + g := getDockerOS(header) + if g != os { + t.Fatalf("Expected %s, got %s", os, g) + } + } +} diff --git a/components/engine/client/image_create.go b/components/engine/client/image_create.go new file mode 100644 index 0000000000..cf023a7186 --- /dev/null +++ b/components/engine/client/image_create.go @@ -0,0 +1,34 @@ +package client + +import ( + "io" + "net/url" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/reference" +) + +// ImageCreate creates a new image based in the parent options. +// It returns the JSON content in the response body. +func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) { + repository, tag, err := reference.Parse(parentReference) + if err != nil { + return nil, err + } + + query := url.Values{} + query.Set("fromImage", repository) + query.Set("tag", tag) + resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) + if err != nil { + return nil, err + } + return resp.body, nil +} + +func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { + headers := map[string][]string{"X-Registry-Auth": {registryAuth}} + return cli.post(ctx, "/images/create", query, nil, headers) +} diff --git a/components/engine/client/image_create_test.go b/components/engine/client/image_create_test.go new file mode 100644 index 0000000000..a2e001be5d --- /dev/null +++ b/components/engine/client/image_create_test.go @@ -0,0 +1,76 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" +) + +func TestImageCreateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ImageCreate(context.Background(), "reference", types.ImageCreateOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server error, got %v", err) + } +} + +func TestImageCreate(t *testing.T) { + expectedURL := "/images/create" + expectedImage := "test:5000/my_image" + expectedTag := "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + expectedReference := fmt.Sprintf("%s@%s", expectedImage, expectedTag) + expectedRegistryAuth := "eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsiYXV0aCI6ImRHOTBid289IiwiZW1haWwiOiJqb2huQGRvZS5jb20ifX0=" + client := &Client{ + transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { + if !strings.HasPrefix(r.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) + } + registryAuth := r.Header.Get("X-Registry-Auth") + if registryAuth != expectedRegistryAuth { + return nil, fmt.Errorf("X-Registry-Auth header not properly set in the request. Expected '%s', got %s", expectedRegistryAuth, registryAuth) + } + + query := r.URL.Query() + fromImage := query.Get("fromImage") + if fromImage != expectedImage { + return nil, fmt.Errorf("fromImage not set in URL query properly. Expected '%s', got %s", expectedImage, fromImage) + } + + tag := query.Get("tag") + if tag != expectedTag { + return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", expectedTag, tag) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), + }, nil + }), + } + + createResponse, err := client.ImageCreate(context.Background(), expectedReference, types.ImageCreateOptions{ + RegistryAuth: expectedRegistryAuth, + }) + if err != nil { + t.Fatal(err) + } + response, err := ioutil.ReadAll(createResponse) + if err != nil { + t.Fatal(err) + } + if err = createResponse.Close(); err != nil { + t.Fatal(err) + } + if string(response) != "body" { + t.Fatalf("expected Body to contain 'body' string, got %s", response) + } +} diff --git a/components/engine/client/image_history.go b/components/engine/client/image_history.go new file mode 100644 index 0000000000..acb1ee9278 --- /dev/null +++ b/components/engine/client/image_history.go @@ -0,0 +1,22 @@ +package client + +import ( + "encoding/json" + "net/url" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ImageHistory returns the changes in an image in history format. +func (cli *Client) ImageHistory(ctx context.Context, imageID string) ([]types.ImageHistory, error) { + var history []types.ImageHistory + serverResp, err := cli.get(ctx, "/images/"+imageID+"/history", url.Values{}, nil) + if err != nil { + return history, err + } + + err = json.NewDecoder(serverResp.body).Decode(&history) + ensureReaderClosed(serverResp) + return history, err +} diff --git a/components/engine/client/image_history_test.go b/components/engine/client/image_history_test.go new file mode 100644 index 0000000000..c9516151b7 --- /dev/null +++ b/components/engine/client/image_history_test.go @@ -0,0 +1,60 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestImageHistoryError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ImageHistory(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server error, got %v", err) + } +} + +func TestImageHistory(t *testing.T) { + expectedURL := "/images/image_id/history" + client := &Client{ + transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { + if !strings.HasPrefix(r.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) + } + b, err := json.Marshal([]types.ImageHistory{ + { + ID: "image_id1", + Tags: []string{"tag1", "tag2"}, + }, + { + ID: "image_id2", + Tags: []string{"tag1", "tag2"}, + }, + }) + if err != nil { + return nil, err + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + imageHistories, err := client.ImageHistory(context.Background(), "image_id") + if err != nil { + t.Fatal(err) + } + if len(imageHistories) != 2 { + t.Fatalf("expected 2 containers, got %v", imageHistories) + } +} diff --git a/components/engine/client/image_import.go b/components/engine/client/image_import.go new file mode 100644 index 0000000000..c6f154b249 --- /dev/null +++ b/components/engine/client/image_import.go @@ -0,0 +1,37 @@ +package client + +import ( + "io" + "net/url" + + "golang.org/x/net/context" + + "github.com/docker/distribution/reference" + "github.com/docker/docker/api/types" +) + +// ImageImport creates a new image based in the source options. +// It returns the JSON content in the response body. +func (cli *Client) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) { + if ref != "" { + //Check if the given image name can be resolved + if _, err := reference.ParseNamed(ref); err != nil { + return nil, err + } + } + + query := url.Values{} + query.Set("fromSrc", source.SourceName) + query.Set("repo", ref) + query.Set("tag", options.Tag) + query.Set("message", options.Message) + for _, change := range options.Changes { + query.Add("changes", change) + } + + resp, err := cli.postRaw(ctx, "/images/create", query, source.Source, nil) + if err != nil { + return nil, err + } + return resp.body, nil +} diff --git a/components/engine/client/image_import_test.go b/components/engine/client/image_import_test.go new file mode 100644 index 0000000000..b64ca74d7b --- /dev/null +++ b/components/engine/client/image_import_test.go @@ -0,0 +1,81 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestImageImportError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ImageImport(context.Background(), types.ImageImportSource{}, "image:tag", types.ImageImportOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server error, got %v", err) + } +} + +func TestImageImport(t *testing.T) { + expectedURL := "/images/create" + client := &Client{ + transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { + if !strings.HasPrefix(r.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) + } + query := r.URL.Query() + fromSrc := query.Get("fromSrc") + if fromSrc != "image_source" { + return nil, fmt.Errorf("fromSrc not set in URL query properly. Expected 'image_source', got %s", fromSrc) + } + repo := query.Get("repo") + if repo != "repository_name:imported" { + return nil, fmt.Errorf("repo not set in URL query properly. Expected 'repository_name', got %s", repo) + } + tag := query.Get("tag") + if tag != "imported" { + return nil, fmt.Errorf("tag not set in URL query properly. Expected 'imported', got %s", tag) + } + message := query.Get("message") + if message != "A message" { + return nil, fmt.Errorf("message not set in URL query properly. Expected 'A message', got %s", message) + } + changes := query["changes"] + expectedChanges := []string{"change1", "change2"} + if !reflect.DeepEqual(expectedChanges, changes) { + return nil, fmt.Errorf("changes not set in URL query properly. Expected %v, got %v", expectedChanges, changes) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))), + }, nil + }), + } + importResponse, err := client.ImageImport(context.Background(), types.ImageImportSource{ + Source: strings.NewReader("source"), + SourceName: "image_source", + }, "repository_name:imported", types.ImageImportOptions{ + Tag: "imported", + Message: "A message", + Changes: []string{"change1", "change2"}, + }) + if err != nil { + t.Fatal(err) + } + response, err := ioutil.ReadAll(importResponse) + if err != nil { + t.Fatal(err) + } + importResponse.Close() + if string(response) != "response" { + t.Fatalf("expected response to contain 'response', got %s", string(response)) + } +} diff --git a/components/engine/client/image_inspect.go b/components/engine/client/image_inspect.go new file mode 100644 index 0000000000..b3a64ce2f8 --- /dev/null +++ b/components/engine/client/image_inspect.go @@ -0,0 +1,33 @@ +package client + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ImageInspectWithRaw returns the image information and its raw representation. +func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) { + serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil) + if err != nil { + if serverResp.statusCode == http.StatusNotFound { + return types.ImageInspect{}, nil, imageNotFoundError{imageID} + } + return types.ImageInspect{}, nil, err + } + defer ensureReaderClosed(serverResp) + + body, err := ioutil.ReadAll(serverResp.body) + if err != nil { + return types.ImageInspect{}, nil, err + } + + var response types.ImageInspect + rdr := bytes.NewReader(body) + err = json.NewDecoder(rdr).Decode(&response) + return response, body, err +} diff --git a/components/engine/client/image_inspect_test.go b/components/engine/client/image_inspect_test.go new file mode 100644 index 0000000000..5c7ca2721f --- /dev/null +++ b/components/engine/client/image_inspect_test.go @@ -0,0 +1,71 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestImageInspectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, _, err := client.ImageInspectWithRaw(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestImageInspectImageNotFound(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), + } + + _, _, err := client.ImageInspectWithRaw(context.Background(), "unknown") + if err == nil || !IsErrImageNotFound(err) { + t.Fatalf("expected an imageNotFound error, got %v", err) + } +} + +func TestImageInspect(t *testing.T) { + expectedURL := "/images/image_id/json" + expectedTags := []string{"tag1", "tag2"} + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + content, err := json.Marshal(types.ImageInspect{ + ID: "image_id", + RepoTags: expectedTags, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + imageInspect, _, err := client.ImageInspectWithRaw(context.Background(), "image_id") + if err != nil { + t.Fatal(err) + } + if imageInspect.ID != "image_id" { + t.Fatalf("expected `image_id`, got %s", imageInspect.ID) + } + if !reflect.DeepEqual(imageInspect.RepoTags, expectedTags) { + t.Fatalf("expected `%v`, got %v", expectedTags, imageInspect.RepoTags) + } +} diff --git a/components/engine/client/image_list.go b/components/engine/client/image_list.go new file mode 100644 index 0000000000..00f27dc0c9 --- /dev/null +++ b/components/engine/client/image_list.go @@ -0,0 +1,40 @@ +package client + +import ( + "encoding/json" + "net/url" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "golang.org/x/net/context" +) + +// ImageList returns a list of images in the docker host. +func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.Image, error) { + var images []types.Image + query := url.Values{} + + if options.Filters.Len() > 0 { + filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters) + if err != nil { + return images, err + } + query.Set("filters", filterJSON) + } + if options.MatchName != "" { + // FIXME rename this parameter, to not be confused with the filters flag + query.Set("filter", options.MatchName) + } + if options.All { + query.Set("all", "1") + } + + serverResp, err := cli.get(ctx, "/images/json", query, nil) + if err != nil { + return images, err + } + + err = json.NewDecoder(serverResp.body).Decode(&images) + ensureReaderClosed(serverResp) + return images, err +} diff --git a/components/engine/client/image_list_test.go b/components/engine/client/image_list_test.go new file mode 100644 index 0000000000..99ed1964a2 --- /dev/null +++ b/components/engine/client/image_list_test.go @@ -0,0 +1,122 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "golang.org/x/net/context" +) + +func TestImageListError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.ImageList(context.Background(), types.ImageListOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestImageList(t *testing.T) { + expectedURL := "/images/json" + + noDanglingfilters := filters.NewArgs() + noDanglingfilters.Add("dangling", "false") + + filters := filters.NewArgs() + filters.Add("label", "label1") + filters.Add("label", "label2") + filters.Add("dangling", "true") + + listCases := []struct { + options types.ImageListOptions + expectedQueryParams map[string]string + }{ + { + options: types.ImageListOptions{}, + expectedQueryParams: map[string]string{ + "all": "", + "filter": "", + "filters": "", + }, + }, + { + options: types.ImageListOptions{ + All: true, + MatchName: "image_name", + }, + expectedQueryParams: map[string]string{ + "all": "1", + "filter": "image_name", + "filters": "", + }, + }, + { + options: types.ImageListOptions{ + Filters: filters, + }, + expectedQueryParams: map[string]string{ + "all": "", + "filter": "", + "filters": `{"dangling":{"true":true},"label":{"label1":true,"label2":true}}`, + }, + }, + { + options: types.ImageListOptions{ + Filters: noDanglingfilters, + }, + expectedQueryParams: map[string]string{ + "all": "", + "filter": "", + "filters": `{"dangling":{"false":true}}`, + }, + }, + } + for _, listCase := range listCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + for key, expected := range listCase.expectedQueryParams { + actual := query.Get(key) + if actual != expected { + return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) + } + } + content, err := json.Marshal([]types.Image{ + { + ID: "image_id2", + }, + { + ID: "image_id2", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + images, err := client.ImageList(context.Background(), listCase.options) + if err != nil { + t.Fatal(err) + } + if len(images) != 2 { + t.Fatalf("expected 2 images, got %v", images) + } + } +} diff --git a/components/engine/client/image_load.go b/components/engine/client/image_load.go new file mode 100644 index 0000000000..77aaf1af36 --- /dev/null +++ b/components/engine/client/image_load.go @@ -0,0 +1,30 @@ +package client + +import ( + "io" + "net/url" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" +) + +// ImageLoad loads an image in the docker host from the client host. +// It's up to the caller to close the io.ReadCloser in the +// ImageLoadResponse returned by this function. +func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) { + v := url.Values{} + v.Set("quiet", "0") + if quiet { + v.Set("quiet", "1") + } + headers := map[string][]string{"Content-Type": {"application/x-tar"}} + resp, err := cli.postRaw(ctx, "/images/load", v, input, headers) + if err != nil { + return types.ImageLoadResponse{}, err + } + return types.ImageLoadResponse{ + Body: resp.body, + JSON: resp.header.Get("Content-Type") == "application/json", + }, nil +} diff --git a/components/engine/client/image_load_test.go b/components/engine/client/image_load_test.go new file mode 100644 index 0000000000..0ee7cf35a6 --- /dev/null +++ b/components/engine/client/image_load_test.go @@ -0,0 +1,95 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestImageLoadError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.ImageLoad(context.Background(), nil, true) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestImageLoad(t *testing.T) { + expectedURL := "/images/load" + expectedInput := "inputBody" + expectedOutput := "outputBody" + loadCases := []struct { + quiet bool + responseContentType string + expectedResponseJSON bool + expectedQueryParams map[string]string + }{ + { + quiet: false, + responseContentType: "text/plain", + expectedResponseJSON: false, + expectedQueryParams: map[string]string{ + "quiet": "0", + }, + }, + { + quiet: true, + responseContentType: "application/json", + expectedResponseJSON: true, + expectedQueryParams: map[string]string{ + "quiet": "1", + }, + }, + } + for _, loadCase := range loadCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + contentType := req.Header.Get("Content-Type") + if contentType != "application/x-tar" { + return nil, fmt.Errorf("content-type not set in URL headers properly. Expected 'application/x-tar', got %s", contentType) + } + query := req.URL.Query() + for key, expected := range loadCase.expectedQueryParams { + actual := query.Get(key) + if actual != expected { + return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) + } + } + headers := http.Header{} + headers.Add("Content-Type", loadCase.responseContentType) + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(expectedOutput))), + Header: headers, + }, nil + }), + } + + input := bytes.NewReader([]byte(expectedInput)) + imageLoadResponse, err := client.ImageLoad(context.Background(), input, loadCase.quiet) + if err != nil { + t.Fatal(err) + } + if imageLoadResponse.JSON != loadCase.expectedResponseJSON { + t.Fatalf("expected a JSON response, was not.") + } + body, err := ioutil.ReadAll(imageLoadResponse.Body) + if err != nil { + t.Fatal(err) + } + if string(body) != expectedOutput { + t.Fatalf("expected %s, got %s", expectedOutput, string(body)) + } + } +} diff --git a/components/engine/client/image_pull.go b/components/engine/client/image_pull.go new file mode 100644 index 0000000000..3bffdb70e8 --- /dev/null +++ b/components/engine/client/image_pull.go @@ -0,0 +1,46 @@ +package client + +import ( + "io" + "net/http" + "net/url" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/reference" +) + +// ImagePull requests the docker host to pull an image from a remote registry. +// It executes the privileged function if the operation is unauthorized +// and it tries one more time. +// It's up to the caller to handle the io.ReadCloser and close it properly. +// +// FIXME(vdemeester): there is currently used in a few way in docker/docker +// - if not in trusted content, ref is used to pass the whole reference, and tag is empty +// - if in trusted content, ref is used to pass the reference name, and tag for the digest +func (cli *Client) ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) { + repository, tag, err := reference.Parse(ref) + if err != nil { + return nil, err + } + + query := url.Values{} + query.Set("fromImage", repository) + if tag != "" && !options.All { + query.Set("tag", tag) + } + + resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) + if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil { + newAuthHeader, privilegeErr := options.PrivilegeFunc() + if privilegeErr != nil { + return nil, privilegeErr + } + resp, err = cli.tryImageCreate(ctx, query, newAuthHeader) + } + if err != nil { + return nil, err + } + return resp.body, nil +} diff --git a/components/engine/client/image_pull_test.go b/components/engine/client/image_pull_test.go new file mode 100644 index 0000000000..c33a6dcc8a --- /dev/null +++ b/components/engine/client/image_pull_test.go @@ -0,0 +1,199 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" +) + +func TestImagePullReferenceParseError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + return nil, nil + }), + } + // An empty reference is an invalid reference + _, err := client.ImagePull(context.Background(), "", types.ImagePullOptions{}) + if err == nil || err.Error() != "repository name must have at least one component" { + t.Fatalf("expected an error, got %v", err) + } +} + +func TestImagePullAnyError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ImagePull(context.Background(), "myimage", types.ImagePullOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestImagePullStatusUnauthorizedError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")), + } + _, err := client.ImagePull(context.Background(), "myimage", types.ImagePullOptions{}) + if err == nil || err.Error() != "Error response from daemon: Unauthorized error" { + t.Fatalf("expected an Unauthorized Error, got %v", err) + } +} + +func TestImagePullWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")), + } + privilegeFunc := func() (string, error) { + return "", fmt.Errorf("Error requesting privilege") + } + _, err := client.ImagePull(context.Background(), "myimage", types.ImagePullOptions{ + PrivilegeFunc: privilegeFunc, + }) + if err == nil || err.Error() != "Error requesting privilege" { + t.Fatalf("expected an error requesting privilege, got %v", err) + } +} + +func TestImagePullWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")), + } + privilegeFunc := func() (string, error) { + return "a-auth-header", nil + } + _, err := client.ImagePull(context.Background(), "myimage", types.ImagePullOptions{ + PrivilegeFunc: privilegeFunc, + }) + if err == nil || err.Error() != "Error response from daemon: Unauthorized error" { + t.Fatalf("expected an Unauthorized Error, got %v", err) + } +} + +func TestImagePullWithPrivilegedFuncNoError(t *testing.T) { + expectedURL := "/images/create" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + auth := req.Header.Get("X-Registry-Auth") + if auth == "NotValid" { + return &http.Response{ + StatusCode: http.StatusUnauthorized, + Body: ioutil.NopCloser(bytes.NewReader([]byte("Invalid credentials"))), + }, nil + } + if auth != "IAmValid" { + return nil, fmt.Errorf("Invalid auth header : expected %s, got %s", "IAmValid", auth) + } + query := req.URL.Query() + fromImage := query.Get("fromImage") + if fromImage != "myimage" { + return nil, fmt.Errorf("fromimage not set in URL query properly. Expected '%s', got %s", "myimage", fromImage) + } + tag := query.Get("tag") + if tag != "latest" { + return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "latest", tag) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("hello world"))), + }, nil + }), + } + privilegeFunc := func() (string, error) { + return "IAmValid", nil + } + resp, err := client.ImagePull(context.Background(), "myimage", types.ImagePullOptions{ + RegistryAuth: "NotValid", + PrivilegeFunc: privilegeFunc, + }) + if err != nil { + t.Fatal(err) + } + body, err := ioutil.ReadAll(resp) + if err != nil { + t.Fatal(err) + } + if string(body) != "hello world" { + t.Fatalf("expected 'hello world', got %s", string(body)) + } +} + +func TestImagePullWithoutErrors(t *testing.T) { + expectedURL := "/images/create" + expectedOutput := "hello world" + pullCases := []struct { + all bool + reference string + expectedImage string + expectedTag string + }{ + { + all: false, + reference: "myimage", + expectedImage: "myimage", + expectedTag: "latest", + }, + { + all: false, + reference: "myimage:tag", + expectedImage: "myimage", + expectedTag: "tag", + }, + { + all: true, + reference: "myimage", + expectedImage: "myimage", + expectedTag: "", + }, + { + all: true, + reference: "myimage:anything", + expectedImage: "myimage", + expectedTag: "", + }, + } + for _, pullCase := range pullCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + fromImage := query.Get("fromImage") + if fromImage != pullCase.expectedImage { + return nil, fmt.Errorf("fromimage not set in URL query properly. Expected '%s', got %s", pullCase.expectedImage, fromImage) + } + tag := query.Get("tag") + if tag != pullCase.expectedTag { + return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", pullCase.expectedTag, tag) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(expectedOutput))), + }, nil + }), + } + resp, err := client.ImagePull(context.Background(), pullCase.reference, types.ImagePullOptions{ + All: pullCase.all, + }) + if err != nil { + t.Fatal(err) + } + body, err := ioutil.ReadAll(resp) + if err != nil { + t.Fatal(err) + } + if string(body) != expectedOutput { + t.Fatalf("expected '%s', got %s", expectedOutput, string(body)) + } + } +} diff --git a/components/engine/client/image_push.go b/components/engine/client/image_push.go new file mode 100644 index 0000000000..8e73d28f56 --- /dev/null +++ b/components/engine/client/image_push.go @@ -0,0 +1,54 @@ +package client + +import ( + "errors" + "io" + "net/http" + "net/url" + + "golang.org/x/net/context" + + distreference "github.com/docker/distribution/reference" + "github.com/docker/docker/api/types" +) + +// ImagePush requests the docker host to push an image to a remote registry. +// It executes the privileged function if the operation is unauthorized +// and it tries one more time. +// It's up to the caller to handle the io.ReadCloser and close it properly. +func (cli *Client) ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) { + distributionRef, err := distreference.ParseNamed(ref) + if err != nil { + return nil, err + } + + if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { + return nil, errors.New("cannot push a digest reference") + } + + var tag = "" + if nameTaggedRef, isNamedTagged := distributionRef.(distreference.NamedTagged); isNamedTagged { + tag = nameTaggedRef.Tag() + } + + query := url.Values{} + query.Set("tag", tag) + + resp, err := cli.tryImagePush(ctx, distributionRef.Name(), query, options.RegistryAuth) + if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil { + newAuthHeader, privilegeErr := options.PrivilegeFunc() + if privilegeErr != nil { + return nil, privilegeErr + } + resp, err = cli.tryImagePush(ctx, distributionRef.Name(), query, newAuthHeader) + } + if err != nil { + return nil, err + } + return resp.body, nil +} + +func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (serverResponse, error) { + headers := map[string][]string{"X-Registry-Auth": {registryAuth}} + return cli.post(ctx, "/images/"+imageID+"/push", query, nil, headers) +} diff --git a/components/engine/client/image_push_test.go b/components/engine/client/image_push_test.go new file mode 100644 index 0000000000..d32f3ef3c7 --- /dev/null +++ b/components/engine/client/image_push_test.go @@ -0,0 +1,180 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" +) + +func TestImagePushReferenceError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + return nil, nil + }), + } + // An empty reference is an invalid reference + _, err := client.ImagePush(context.Background(), "", types.ImagePushOptions{}) + if err == nil || err.Error() != "repository name must have at least one component" { + t.Fatalf("expected an error, got %v", err) + } + // An canonical reference cannot be pushed + _, err = client.ImagePush(context.Background(), "repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", types.ImagePushOptions{}) + if err == nil || err.Error() != "cannot push a digest reference" { + t.Fatalf("expected an error, got %v", err) + } +} + +func TestImagePushAnyError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ImagePush(context.Background(), "myimage", types.ImagePushOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestImagePushStatusUnauthorizedError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")), + } + _, err := client.ImagePush(context.Background(), "myimage", types.ImagePushOptions{}) + if err == nil || err.Error() != "Error response from daemon: Unauthorized error" { + t.Fatalf("expected an Unauthorized Error, got %v", err) + } +} + +func TestImagePushWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")), + } + privilegeFunc := func() (string, error) { + return "", fmt.Errorf("Error requesting privilege") + } + _, err := client.ImagePush(context.Background(), "myimage", types.ImagePushOptions{ + PrivilegeFunc: privilegeFunc, + }) + if err == nil || err.Error() != "Error requesting privilege" { + t.Fatalf("expected an error requesting privilege, got %v", err) + } +} + +func TestImagePushWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")), + } + privilegeFunc := func() (string, error) { + return "a-auth-header", nil + } + _, err := client.ImagePush(context.Background(), "myimage", types.ImagePushOptions{ + PrivilegeFunc: privilegeFunc, + }) + if err == nil || err.Error() != "Error response from daemon: Unauthorized error" { + t.Fatalf("expected an Unauthorized Error, got %v", err) + } +} + +func TestImagePushWithPrivilegedFuncNoError(t *testing.T) { + expectedURL := "/images/myimage/push" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + auth := req.Header.Get("X-Registry-Auth") + if auth == "NotValid" { + return &http.Response{ + StatusCode: http.StatusUnauthorized, + Body: ioutil.NopCloser(bytes.NewReader([]byte("Invalid credentials"))), + }, nil + } + if auth != "IAmValid" { + return nil, fmt.Errorf("Invalid auth header : expected %s, got %s", "IAmValid", auth) + } + query := req.URL.Query() + tag := query.Get("tag") + if tag != "tag" { + return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "tag", tag) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("hello world"))), + }, nil + }), + } + privilegeFunc := func() (string, error) { + return "IAmValid", nil + } + resp, err := client.ImagePush(context.Background(), "myimage:tag", types.ImagePushOptions{ + RegistryAuth: "NotValid", + PrivilegeFunc: privilegeFunc, + }) + if err != nil { + t.Fatal(err) + } + body, err := ioutil.ReadAll(resp) + if err != nil { + t.Fatal(err) + } + if string(body) != "hello world" { + t.Fatalf("expected 'hello world', got %s", string(body)) + } +} + +func TestImagePushWithoutErrors(t *testing.T) { + expectedOutput := "hello world" + expectedURLFormat := "/images/%s/push" + pullCases := []struct { + reference string + expectedImage string + expectedTag string + }{ + { + reference: "myimage", + expectedImage: "myimage", + expectedTag: "", + }, + { + reference: "myimage:tag", + expectedImage: "myimage", + expectedTag: "tag", + }, + } + for _, pullCase := range pullCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + expectedURL := fmt.Sprintf(expectedURLFormat, pullCase.expectedImage) + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + tag := query.Get("tag") + if tag != pullCase.expectedTag { + return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", pullCase.expectedTag, tag) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(expectedOutput))), + }, nil + }), + } + resp, err := client.ImagePush(context.Background(), pullCase.reference, types.ImagePushOptions{}) + if err != nil { + t.Fatal(err) + } + body, err := ioutil.ReadAll(resp) + if err != nil { + t.Fatal(err) + } + if string(body) != expectedOutput { + t.Fatalf("expected '%s', got %s", expectedOutput, string(body)) + } + } +} diff --git a/components/engine/client/image_remove.go b/components/engine/client/image_remove.go new file mode 100644 index 0000000000..839e5311c4 --- /dev/null +++ b/components/engine/client/image_remove.go @@ -0,0 +1,31 @@ +package client + +import ( + "encoding/json" + "net/url" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ImageRemove removes an image from the docker host. +func (cli *Client) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDelete, error) { + query := url.Values{} + + if options.Force { + query.Set("force", "1") + } + if !options.PruneChildren { + query.Set("noprune", "1") + } + + resp, err := cli.delete(ctx, "/images/"+imageID, query, nil) + if err != nil { + return nil, err + } + + var dels []types.ImageDelete + err = json.NewDecoder(resp.body).Decode(&dels) + ensureReaderClosed(resp) + return dels, err +} diff --git a/components/engine/client/image_remove_test.go b/components/engine/client/image_remove_test.go new file mode 100644 index 0000000000..696d06729d --- /dev/null +++ b/components/engine/client/image_remove_test.go @@ -0,0 +1,95 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestImageRemoveError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.ImageRemove(context.Background(), "image_id", types.ImageRemoveOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestImageRemove(t *testing.T) { + expectedURL := "/images/image_id" + removeCases := []struct { + force bool + pruneChildren bool + expectedQueryParams map[string]string + }{ + { + force: false, + pruneChildren: false, + expectedQueryParams: map[string]string{ + "force": "", + "noprune": "1", + }, + }, { + force: true, + pruneChildren: true, + expectedQueryParams: map[string]string{ + "force": "1", + "noprune": "", + }, + }, + } + for _, removeCase := range removeCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "DELETE" { + return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) + } + query := req.URL.Query() + for key, expected := range removeCase.expectedQueryParams { + actual := query.Get(key) + if actual != expected { + return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) + } + } + b, err := json.Marshal([]types.ImageDelete{ + { + Untagged: "image_id1", + }, + { + Deleted: "image_id", + }, + }) + if err != nil { + return nil, err + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + imageDeletes, err := client.ImageRemove(context.Background(), "image_id", types.ImageRemoveOptions{ + Force: removeCase.force, + PruneChildren: removeCase.pruneChildren, + }) + if err != nil { + t.Fatal(err) + } + if len(imageDeletes) != 2 { + t.Fatalf("expected 2 deleted images, got %v", imageDeletes) + } + } +} diff --git a/components/engine/client/image_save.go b/components/engine/client/image_save.go new file mode 100644 index 0000000000..ecac880a32 --- /dev/null +++ b/components/engine/client/image_save.go @@ -0,0 +1,22 @@ +package client + +import ( + "io" + "net/url" + + "golang.org/x/net/context" +) + +// ImageSave retrieves one or more images from the docker host as an io.ReadCloser. +// It's up to the caller to store the images and close the stream. +func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) { + query := url.Values{ + "names": imageIDs, + } + + resp, err := cli.get(ctx, "/images/get", query, nil) + if err != nil { + return nil, err + } + return resp.body, nil +} diff --git a/components/engine/client/image_save_test.go b/components/engine/client/image_save_test.go new file mode 100644 index 0000000000..8ee40c43ae --- /dev/null +++ b/components/engine/client/image_save_test.go @@ -0,0 +1,58 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "testing" + + "golang.org/x/net/context" + + "strings" +) + +func TestImageSaveError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ImageSave(context.Background(), []string{"nothing"}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server error, got %v", err) + } +} + +func TestImageSave(t *testing.T) { + expectedURL := "/images/get" + client := &Client{ + transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { + if !strings.HasPrefix(r.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) + } + query := r.URL.Query() + names := query["names"] + expectedNames := []string{"image_id1", "image_id2"} + if !reflect.DeepEqual(names, expectedNames) { + return nil, fmt.Errorf("names not set in URL query properly. Expected %v, got %v", names, expectedNames) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))), + }, nil + }), + } + saveResponse, err := client.ImageSave(context.Background(), []string{"image_id1", "image_id2"}) + if err != nil { + t.Fatal(err) + } + response, err := ioutil.ReadAll(saveResponse) + if err != nil { + t.Fatal(err) + } + saveResponse.Close() + if string(response) != "response" { + t.Fatalf("expected response to contain 'response', got %s", string(response)) + } +} diff --git a/components/engine/client/image_search.go b/components/engine/client/image_search.go new file mode 100644 index 0000000000..b0fcd5c23d --- /dev/null +++ b/components/engine/client/image_search.go @@ -0,0 +1,51 @@ +package client + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/registry" + "golang.org/x/net/context" +) + +// ImageSearch makes the docker host to search by a term in a remote registry. +// The list of results is not sorted in any fashion. +func (cli *Client) ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) { + var results []registry.SearchResult + query := url.Values{} + query.Set("term", term) + query.Set("limit", fmt.Sprintf("%d", options.Limit)) + + if options.Filters.Len() > 0 { + filterJSON, err := filters.ToParam(options.Filters) + if err != nil { + return results, err + } + query.Set("filters", filterJSON) + } + + resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth) + if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil { + newAuthHeader, privilegeErr := options.PrivilegeFunc() + if privilegeErr != nil { + return results, privilegeErr + } + resp, err = cli.tryImageSearch(ctx, query, newAuthHeader) + } + if err != nil { + return results, err + } + + err = json.NewDecoder(resp.body).Decode(&results) + ensureReaderClosed(resp) + return results, err +} + +func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { + headers := map[string][]string{"X-Registry-Auth": {registryAuth}} + return cli.get(ctx, "/images/search", query, headers) +} diff --git a/components/engine/client/image_search_test.go b/components/engine/client/image_search_test.go new file mode 100644 index 0000000000..2f21b2cc14 --- /dev/null +++ b/components/engine/client/image_search_test.go @@ -0,0 +1,165 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "encoding/json" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/registry" +) + +func TestImageSearchAnyError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestImageSearchStatusUnauthorizedError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")), + } + _, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{}) + if err == nil || err.Error() != "Error response from daemon: Unauthorized error" { + t.Fatalf("expected an Unauthorized Error, got %v", err) + } +} + +func TestImageSearchWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")), + } + privilegeFunc := func() (string, error) { + return "", fmt.Errorf("Error requesting privilege") + } + _, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{ + PrivilegeFunc: privilegeFunc, + }) + if err == nil || err.Error() != "Error requesting privilege" { + t.Fatalf("expected an error requesting privilege, got %v", err) + } +} + +func TestImageSearchWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")), + } + privilegeFunc := func() (string, error) { + return "a-auth-header", nil + } + _, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{ + PrivilegeFunc: privilegeFunc, + }) + if err == nil || err.Error() != "Error response from daemon: Unauthorized error" { + t.Fatalf("expected an Unauthorized Error, got %v", err) + } +} + +func TestImageSearchWithPrivilegedFuncNoError(t *testing.T) { + expectedURL := "/images/search" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + auth := req.Header.Get("X-Registry-Auth") + if auth == "NotValid" { + return &http.Response{ + StatusCode: http.StatusUnauthorized, + Body: ioutil.NopCloser(bytes.NewReader([]byte("Invalid credentials"))), + }, nil + } + if auth != "IAmValid" { + return nil, fmt.Errorf("Invalid auth header : expected %s, got %s", "IAmValid", auth) + } + query := req.URL.Query() + term := query.Get("term") + if term != "some-image" { + return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "some-image", term) + } + content, err := json.Marshal([]registry.SearchResult{ + { + Name: "anything", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + privilegeFunc := func() (string, error) { + return "IAmValid", nil + } + results, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{ + RegistryAuth: "NotValid", + PrivilegeFunc: privilegeFunc, + }) + if err != nil { + t.Fatal(err) + } + if len(results) != 1 { + t.Fatalf("expected a result, got %v", results) + } +} + +func TestImageSearchWithoutErrors(t *testing.T) { + expectedURL := "/images/search" + filterArgs := filters.NewArgs() + filterArgs.Add("is-automated", "true") + filterArgs.Add("stars", "3") + + expectedFilters := `{"is-automated":{"true":true},"stars":{"3":true}}` + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + term := query.Get("term") + if term != "some-image" { + return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "some-image", term) + } + filters := query.Get("filters") + if filters != expectedFilters { + return nil, fmt.Errorf("filters not set in URL query properly. Expected '%s', got %s", expectedFilters, filters) + } + content, err := json.Marshal([]registry.SearchResult{ + { + Name: "anything", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + results, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{ + Filters: filterArgs, + }) + if err != nil { + t.Fatal(err) + } + if len(results) != 1 { + t.Fatalf("expected a result, got %v", results) + } +} diff --git a/components/engine/client/image_tag.go b/components/engine/client/image_tag.go new file mode 100644 index 0000000000..bdbf94add2 --- /dev/null +++ b/components/engine/client/image_tag.go @@ -0,0 +1,34 @@ +package client + +import ( + "errors" + "fmt" + "net/url" + + "golang.org/x/net/context" + + distreference "github.com/docker/distribution/reference" + "github.com/docker/docker/api/types/reference" +) + +// ImageTag tags an image in the docker host +func (cli *Client) ImageTag(ctx context.Context, imageID, ref string) error { + distributionRef, err := distreference.ParseNamed(ref) + if err != nil { + return fmt.Errorf("Error parsing reference: %q is not a valid repository/tag", ref) + } + + if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { + return errors.New("refusing to create a tag with a digest reference") + } + + tag := reference.GetTagFromNamedRef(distributionRef) + + query := url.Values{} + query.Set("repo", distributionRef.Name()) + query.Set("tag", tag) + + resp, err := cli.post(ctx, "/images/"+imageID+"/tag", query, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/image_tag_test.go b/components/engine/client/image_tag_test.go new file mode 100644 index 0000000000..f3571dfdd3 --- /dev/null +++ b/components/engine/client/image_tag_test.go @@ -0,0 +1,121 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestImageTagError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.ImageTag(context.Background(), "image_id", "repo:tag") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +// Note: this is not testing all the InvalidReference as it's the reponsability +// of distribution/reference package. +func TestImageTagInvalidReference(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.ImageTag(context.Background(), "image_id", "aa/asdf$$^/aa") + if err == nil || err.Error() != `Error parsing reference: "aa/asdf$$^/aa" is not a valid repository/tag` { + t.Fatalf("expected ErrReferenceInvalidFormat, got %v", err) + } +} + +func TestImageTag(t *testing.T) { + expectedURL := "/images/image_id/tag" + tagCases := []struct { + reference string + expectedQueryParams map[string]string + }{ + { + reference: "repository:tag1", + expectedQueryParams: map[string]string{ + "repo": "repository", + "tag": "tag1", + }, + }, { + reference: "another_repository:latest", + expectedQueryParams: map[string]string{ + "repo": "another_repository", + "tag": "latest", + }, + }, { + reference: "another_repository", + expectedQueryParams: map[string]string{ + "repo": "another_repository", + "tag": "latest", + }, + }, { + reference: "test/another_repository", + expectedQueryParams: map[string]string{ + "repo": "test/another_repository", + "tag": "latest", + }, + }, { + reference: "test/another_repository:tag1", + expectedQueryParams: map[string]string{ + "repo": "test/another_repository", + "tag": "tag1", + }, + }, { + reference: "test/test/another_repository:tag1", + expectedQueryParams: map[string]string{ + "repo": "test/test/another_repository", + "tag": "tag1", + }, + }, { + reference: "test:5000/test/another_repository:tag1", + expectedQueryParams: map[string]string{ + "repo": "test:5000/test/another_repository", + "tag": "tag1", + }, + }, { + reference: "test:5000/test/another_repository", + expectedQueryParams: map[string]string{ + "repo": "test:5000/test/another_repository", + "tag": "latest", + }, + }, + } + for _, tagCase := range tagCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + query := req.URL.Query() + for key, expected := range tagCase.expectedQueryParams { + actual := query.Get(key) + if actual != expected { + return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) + } + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + err := client.ImageTag(context.Background(), "image_id", tagCase.reference) + if err != nil { + t.Fatal(err) + } + } +} diff --git a/components/engine/client/info.go b/components/engine/client/info.go new file mode 100644 index 0000000000..ac07961224 --- /dev/null +++ b/components/engine/client/info.go @@ -0,0 +1,26 @@ +package client + +import ( + "encoding/json" + "fmt" + "net/url" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// Info returns information about the docker server. +func (cli *Client) Info(ctx context.Context) (types.Info, error) { + var info types.Info + serverResp, err := cli.get(ctx, "/info", url.Values{}, nil) + if err != nil { + return info, err + } + defer ensureReaderClosed(serverResp) + + if err := json.NewDecoder(serverResp.body).Decode(&info); err != nil { + return info, fmt.Errorf("Error reading remote info: %v", err) + } + + return info, nil +} diff --git a/components/engine/client/info_test.go b/components/engine/client/info_test.go new file mode 100644 index 0000000000..9d51b1a78b --- /dev/null +++ b/components/engine/client/info_test.go @@ -0,0 +1,76 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestInfoServerError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.Info(context.Background()) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestInfoInvalidResponseJSONError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("invalid json"))), + }, nil + }), + } + _, err := client.Info(context.Background()) + if err == nil || !strings.Contains(err.Error(), "invalid character") { + t.Fatalf("expected a 'invalid character' error, got %v", err) + } +} + +func TestInfo(t *testing.T) { + expectedURL := "/info" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + info := &types.Info{ + ID: "daemonID", + Containers: 3, + } + b, err := json.Marshal(info) + if err != nil { + return nil, err + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + info, err := client.Info(context.Background()) + if err != nil { + t.Fatal(err) + } + + if info.ID != "daemonID" { + t.Fatalf("expected daemonID, got %s", info.ID) + } + + if info.Containers != 3 { + t.Fatalf("expected 3 containers, got %d", info.Containers) + } +} diff --git a/components/engine/client/interface.go b/components/engine/client/interface.go new file mode 100644 index 0000000000..1bfeb6aeb6 --- /dev/null +++ b/components/engine/client/interface.go @@ -0,0 +1,135 @@ +package client + +import ( + "io" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// CommonAPIClient is the common methods between stable and experimental versions of APIClient. +type CommonAPIClient interface { + ContainerAPIClient + ImageAPIClient + NodeAPIClient + NetworkAPIClient + ServiceAPIClient + SwarmAPIClient + SystemAPIClient + VolumeAPIClient + ClientVersion() string + ServerVersion(ctx context.Context) (types.Version, error) + UpdateClientVersion(v string) +} + +// ContainerAPIClient defines API client methods for the containers +type ContainerAPIClient interface { + ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) + ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) + ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) + ContainerDiff(ctx context.Context, container string) ([]types.ContainerChange, error) + ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error) + ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.ContainerExecCreateResponse, error) + ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) + ContainerExecResize(ctx context.Context, execID string, options types.ResizeOptions) error + ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error + ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) + ContainerInspect(ctx context.Context, container string) (types.ContainerJSON, error) + ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (types.ContainerJSON, []byte, error) + ContainerKill(ctx context.Context, container, signal string) error + ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) + ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) + ContainerPause(ctx context.Context, container string) error + ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error + ContainerRename(ctx context.Context, container, newContainerName string) error + ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error + ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error + ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error) + ContainerStats(ctx context.Context, container string, stream bool) (io.ReadCloser, error) + ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error + ContainerStop(ctx context.Context, container string, timeout *time.Duration) error + ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error) + ContainerUnpause(ctx context.Context, container string) error + ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (types.ContainerUpdateResponse, error) + ContainerWait(ctx context.Context, container string) (int, error) + CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) + CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error +} + +// ImageAPIClient defines API client methods for the images +type ImageAPIClient interface { + ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) + ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) + ImageHistory(ctx context.Context, image string) ([]types.ImageHistory, error) + ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) + ImageInspectWithRaw(ctx context.Context, image string) (types.ImageInspect, []byte, error) + ImageList(ctx context.Context, options types.ImageListOptions) ([]types.Image, error) + ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) + ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) + ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) + ImageRemove(ctx context.Context, image string, options types.ImageRemoveOptions) ([]types.ImageDelete, error) + ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) + ImageSave(ctx context.Context, images []string) (io.ReadCloser, error) + ImageTag(ctx context.Context, image, ref string) error +} + +// NetworkAPIClient defines API client methods for the networks +type NetworkAPIClient interface { + NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error + NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) + NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error + NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) + NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) + NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) + NetworkRemove(ctx context.Context, networkID string) error +} + +// NodeAPIClient defines API client methods for the nodes +type NodeAPIClient interface { + NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) + NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) + NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error + NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error +} + +// ServiceAPIClient defines API client methods for the services +type ServiceAPIClient interface { + ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) + ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error) + ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) + ServiceRemove(ctx context.Context, serviceID string) error + ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) error + TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) + TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) +} + +// SwarmAPIClient defines API client methods for the swarm +type SwarmAPIClient interface { + SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) + SwarmJoin(ctx context.Context, req swarm.JoinRequest) error + SwarmLeave(ctx context.Context, force bool) error + SwarmInspect(ctx context.Context) (swarm.Swarm, error) + SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error +} + +// SystemAPIClient defines API client methods for the system +type SystemAPIClient interface { + Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) + Info(ctx context.Context) (types.Info, error) + RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error) +} + +// VolumeAPIClient defines API client methods for the volumes +type VolumeAPIClient interface { + VolumeCreate(ctx context.Context, options types.VolumeCreateRequest) (types.Volume, error) + VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) + VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) + VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error) + VolumeRemove(ctx context.Context, volumeID string, force bool) error +} diff --git a/components/engine/client/interface_experimental.go b/components/engine/client/interface_experimental.go new file mode 100644 index 0000000000..1ddc517c9a --- /dev/null +++ b/components/engine/client/interface_experimental.go @@ -0,0 +1,37 @@ +// +build experimental + +package client + +import ( + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// APIClient is an interface that clients that talk with a docker server must implement. +type APIClient interface { + CommonAPIClient + CheckpointAPIClient + PluginAPIClient +} + +// CheckpointAPIClient defines API client methods for the checkpoints +type CheckpointAPIClient interface { + CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error + CheckpointDelete(ctx context.Context, container string, checkpointID string) error + CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error) +} + +// PluginAPIClient defines API client methods for the plugins +type PluginAPIClient interface { + PluginList(ctx context.Context) (types.PluginsListResponse, error) + PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error + PluginEnable(ctx context.Context, name string) error + PluginDisable(ctx context.Context, name string) error + PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error + PluginPush(ctx context.Context, name string, registryAuth string) error + PluginSet(ctx context.Context, name string, args []string) error + PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) +} + +// Ensure that Client always implements APIClient. +var _ APIClient = &Client{} diff --git a/components/engine/client/interface_stable.go b/components/engine/client/interface_stable.go new file mode 100644 index 0000000000..496f522d51 --- /dev/null +++ b/components/engine/client/interface_stable.go @@ -0,0 +1,11 @@ +// +build !experimental + +package client + +// APIClient is an interface that clients that talk with a docker server must implement. +type APIClient interface { + CommonAPIClient +} + +// Ensure that Client always implements APIClient. +var _ APIClient = &Client{} diff --git a/components/engine/client/login.go b/components/engine/client/login.go new file mode 100644 index 0000000000..d8d277ccba --- /dev/null +++ b/components/engine/client/login.go @@ -0,0 +1,28 @@ +package client + +import ( + "encoding/json" + "net/http" + "net/url" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// RegistryLogin authenticates the docker server with a given docker registry. +// It returns UnauthorizerError when the authentication fails. +func (cli *Client) RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error) { + resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil) + + if resp.statusCode == http.StatusUnauthorized { + return types.AuthResponse{}, unauthorizedError{err} + } + if err != nil { + return types.AuthResponse{}, err + } + + var response types.AuthResponse + err = json.NewDecoder(resp.body).Decode(&response) + ensureReaderClosed(resp) + return response, err +} diff --git a/components/engine/client/network_connect.go b/components/engine/client/network_connect.go new file mode 100644 index 0000000000..c022c17b5b --- /dev/null +++ b/components/engine/client/network_connect.go @@ -0,0 +1,18 @@ +package client + +import ( + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" + "golang.org/x/net/context" +) + +// NetworkConnect connects a container to an existent network in the docker host. +func (cli *Client) NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error { + nc := types.NetworkConnect{ + Container: containerID, + EndpointConfig: config, + } + resp, err := cli.post(ctx, "/networks/"+networkID+"/connect", nil, nc, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/network_connect_test.go b/components/engine/client/network_connect_test.go new file mode 100644 index 0000000000..95b149e685 --- /dev/null +++ b/components/engine/client/network_connect_test.go @@ -0,0 +1,107 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" +) + +func TestNetworkConnectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.NetworkConnect(context.Background(), "network_id", "container_id", nil) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestNetworkConnectEmptyNilEndpointSettings(t *testing.T) { + expectedURL := "/networks/network_id/connect" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + + var connect types.NetworkConnect + if err := json.NewDecoder(req.Body).Decode(&connect); err != nil { + return nil, err + } + + if connect.Container != "container_id" { + return nil, fmt.Errorf("expected 'container_id', got %s", connect.Container) + } + + if connect.EndpointConfig != nil { + return nil, fmt.Errorf("expected connect.EndpointConfig to be nil, got %v", connect.EndpointConfig) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.NetworkConnect(context.Background(), "network_id", "container_id", nil) + if err != nil { + t.Fatal(err) + } +} + +func TestNetworkConnect(t *testing.T) { + expectedURL := "/networks/network_id/connect" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + + var connect types.NetworkConnect + if err := json.NewDecoder(req.Body).Decode(&connect); err != nil { + return nil, err + } + + if connect.Container != "container_id" { + return nil, fmt.Errorf("expected 'container_id', got %s", connect.Container) + } + + if connect.EndpointConfig.NetworkID != "NetworkID" { + return nil, fmt.Errorf("expected 'NetworkID', got %s", connect.EndpointConfig.NetworkID) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.NetworkConnect(context.Background(), "network_id", "container_id", &network.EndpointSettings{ + NetworkID: "NetworkID", + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/network_create.go b/components/engine/client/network_create.go new file mode 100644 index 0000000000..4067a541ff --- /dev/null +++ b/components/engine/client/network_create.go @@ -0,0 +1,25 @@ +package client + +import ( + "encoding/json" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// NetworkCreate creates a new network in the docker host. +func (cli *Client) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) { + networkCreateRequest := types.NetworkCreateRequest{ + NetworkCreate: options, + Name: name, + } + var response types.NetworkCreateResponse + serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil) + if err != nil { + return response, err + } + + json.NewDecoder(serverResp.body).Decode(&response) + ensureReaderClosed(serverResp) + return response, err +} diff --git a/components/engine/client/network_create_test.go b/components/engine/client/network_create_test.go new file mode 100644 index 0000000000..611ed8173e --- /dev/null +++ b/components/engine/client/network_create_test.go @@ -0,0 +1,72 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestNetworkCreateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.NetworkCreate(context.Background(), "mynetwork", types.NetworkCreate{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestNetworkCreate(t *testing.T) { + expectedURL := "/networks/create" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + + content, err := json.Marshal(types.NetworkCreateResponse{ + ID: "network_id", + Warning: "warning", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + networkResponse, err := client.NetworkCreate(context.Background(), "mynetwork", types.NetworkCreate{ + CheckDuplicate: true, + Driver: "mydriver", + EnableIPv6: true, + Internal: true, + Options: map[string]string{ + "opt-key": "opt-value", + }, + }) + if err != nil { + t.Fatal(err) + } + if networkResponse.ID != "network_id" { + t.Fatalf("expected networkResponse.ID to be 'network_id', got %s", networkResponse.ID) + } + if networkResponse.Warning != "warning" { + t.Fatalf("expected networkResponse.Warning to be 'warning', got %s", networkResponse.Warning) + } +} diff --git a/components/engine/client/network_disconnect.go b/components/engine/client/network_disconnect.go new file mode 100644 index 0000000000..24b58e3c12 --- /dev/null +++ b/components/engine/client/network_disconnect.go @@ -0,0 +1,14 @@ +package client + +import ( + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// NetworkDisconnect disconnects a container from an existent network in the docker host. +func (cli *Client) NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error { + nd := types.NetworkDisconnect{Container: containerID, Force: force} + resp, err := cli.post(ctx, "/networks/"+networkID+"/disconnect", nil, nd, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/network_disconnect_test.go b/components/engine/client/network_disconnect_test.go new file mode 100644 index 0000000000..d9dbb67159 --- /dev/null +++ b/components/engine/client/network_disconnect_test.go @@ -0,0 +1,64 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestNetworkDisconnectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.NetworkDisconnect(context.Background(), "network_id", "container_id", false) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestNetworkDisconnect(t *testing.T) { + expectedURL := "/networks/network_id/disconnect" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + + var disconnect types.NetworkDisconnect + if err := json.NewDecoder(req.Body).Decode(&disconnect); err != nil { + return nil, err + } + + if disconnect.Container != "container_id" { + return nil, fmt.Errorf("expected 'container_id', got %s", disconnect.Container) + } + + if !disconnect.Force { + return nil, fmt.Errorf("expected Force to be true, got %v", disconnect.Force) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.NetworkDisconnect(context.Background(), "network_id", "container_id", true) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/network_inspect.go b/components/engine/client/network_inspect.go new file mode 100644 index 0000000000..5ad4ea5bf3 --- /dev/null +++ b/components/engine/client/network_inspect.go @@ -0,0 +1,38 @@ +package client + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// NetworkInspect returns the information for a specific network configured in the docker host. +func (cli *Client) NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) { + networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID) + return networkResource, err +} + +// NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation. +func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) { + var networkResource types.NetworkResource + resp, err := cli.get(ctx, "/networks/"+networkID, nil, nil) + if err != nil { + if resp.statusCode == http.StatusNotFound { + return networkResource, nil, networkNotFoundError{networkID} + } + return networkResource, nil, err + } + defer ensureReaderClosed(resp) + + body, err := ioutil.ReadAll(resp.body) + if err != nil { + return networkResource, nil, err + } + rdr := bytes.NewReader(body) + err = json.NewDecoder(rdr).Decode(&networkResource) + return networkResource, body, err +} diff --git a/components/engine/client/network_inspect_test.go b/components/engine/client/network_inspect_test.go new file mode 100644 index 0000000000..a6eb626c67 --- /dev/null +++ b/components/engine/client/network_inspect_test.go @@ -0,0 +1,69 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestNetworkInspectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.NetworkInspect(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestNetworkInspectContainerNotFound(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), + } + + _, err := client.NetworkInspect(context.Background(), "unknown") + if err == nil || !IsErrNetworkNotFound(err) { + t.Fatalf("expected a containerNotFound error, got %v", err) + } +} + +func TestNetworkInspect(t *testing.T) { + expectedURL := "/networks/network_id" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "GET" { + return nil, fmt.Errorf("expected GET method, got %s", req.Method) + } + + content, err := json.Marshal(types.NetworkResource{ + Name: "mynetwork", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + r, err := client.NetworkInspect(context.Background(), "network_id") + if err != nil { + t.Fatal(err) + } + if r.Name != "mynetwork" { + t.Fatalf("expected `mynetwork`, got %s", r.Name) + } +} diff --git a/components/engine/client/network_list.go b/components/engine/client/network_list.go new file mode 100644 index 0000000000..e566a93e23 --- /dev/null +++ b/components/engine/client/network_list.go @@ -0,0 +1,31 @@ +package client + +import ( + "encoding/json" + "net/url" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "golang.org/x/net/context" +) + +// NetworkList returns the list of networks configured in the docker host. +func (cli *Client) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { + query := url.Values{} + if options.Filters.Len() > 0 { + filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters) + if err != nil { + return nil, err + } + + query.Set("filters", filterJSON) + } + var networkResources []types.NetworkResource + resp, err := cli.get(ctx, "/networks", query, nil) + if err != nil { + return networkResources, err + } + err = json.NewDecoder(resp.body).Decode(&networkResources) + ensureReaderClosed(resp) + return networkResources, err +} diff --git a/components/engine/client/network_list_test.go b/components/engine/client/network_list_test.go new file mode 100644 index 0000000000..cb66139271 --- /dev/null +++ b/components/engine/client/network_list_test.go @@ -0,0 +1,108 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "golang.org/x/net/context" +) + +func TestNetworkListError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.NetworkList(context.Background(), types.NetworkListOptions{ + Filters: filters.NewArgs(), + }) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestNetworkList(t *testing.T) { + expectedURL := "/networks" + + noDanglingFilters := filters.NewArgs() + noDanglingFilters.Add("dangling", "false") + + danglingFilters := filters.NewArgs() + danglingFilters.Add("dangling", "true") + + labelFilters := filters.NewArgs() + labelFilters.Add("label", "label1") + labelFilters.Add("label", "label2") + + listCases := []struct { + options types.NetworkListOptions + expectedFilters string + }{ + { + options: types.NetworkListOptions{ + Filters: filters.NewArgs(), + }, + expectedFilters: "", + }, { + options: types.NetworkListOptions{ + Filters: noDanglingFilters, + }, + expectedFilters: `{"dangling":{"false":true}}`, + }, { + options: types.NetworkListOptions{ + Filters: danglingFilters, + }, + expectedFilters: `{"dangling":{"true":true}}`, + }, { + options: types.NetworkListOptions{ + Filters: labelFilters, + }, + expectedFilters: `{"label":{"label1":true,"label2":true}}`, + }, + } + + for _, listCase := range listCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "GET" { + return nil, fmt.Errorf("expected GET method, got %s", req.Method) + } + query := req.URL.Query() + actualFilters := query.Get("filters") + if actualFilters != listCase.expectedFilters { + return nil, fmt.Errorf("filters not set in URL query properly. Expected '%s', got %s", listCase.expectedFilters, actualFilters) + } + content, err := json.Marshal([]types.NetworkResource{ + { + Name: "network", + Driver: "bridge", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + networkResources, err := client.NetworkList(context.Background(), listCase.options) + if err != nil { + t.Fatal(err) + } + if len(networkResources) != 1 { + t.Fatalf("expected 1 network resource, got %v", networkResources) + } + } +} diff --git a/components/engine/client/network_remove.go b/components/engine/client/network_remove.go new file mode 100644 index 0000000000..6bd6748924 --- /dev/null +++ b/components/engine/client/network_remove.go @@ -0,0 +1,10 @@ +package client + +import "golang.org/x/net/context" + +// NetworkRemove removes an existent network from the docker host. +func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error { + resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/network_remove_test.go b/components/engine/client/network_remove_test.go new file mode 100644 index 0000000000..d8cfa0ed6e --- /dev/null +++ b/components/engine/client/network_remove_test.go @@ -0,0 +1,47 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestNetworkRemoveError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.NetworkRemove(context.Background(), "network_id") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestNetworkRemove(t *testing.T) { + expectedURL := "/networks/network_id" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "DELETE" { + return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), + }, nil + }), + } + + err := client.NetworkRemove(context.Background(), "network_id") + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/node_inspect.go b/components/engine/client/node_inspect.go new file mode 100644 index 0000000000..abf505d29c --- /dev/null +++ b/components/engine/client/node_inspect.go @@ -0,0 +1,33 @@ +package client + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// NodeInspectWithRaw returns the node information. +func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) { + serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil) + if err != nil { + if serverResp.statusCode == http.StatusNotFound { + return swarm.Node{}, nil, nodeNotFoundError{nodeID} + } + return swarm.Node{}, nil, err + } + defer ensureReaderClosed(serverResp) + + body, err := ioutil.ReadAll(serverResp.body) + if err != nil { + return swarm.Node{}, nil, err + } + + var response swarm.Node + rdr := bytes.NewReader(body) + err = json.NewDecoder(rdr).Decode(&response) + return response, body, err +} diff --git a/components/engine/client/node_inspect_test.go b/components/engine/client/node_inspect_test.go new file mode 100644 index 0000000000..bf67728311 --- /dev/null +++ b/components/engine/client/node_inspect_test.go @@ -0,0 +1,65 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +func TestNodeInspectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, _, err := client.NodeInspectWithRaw(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestNodeInspectNodeNotFound(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), + } + + _, _, err := client.NodeInspectWithRaw(context.Background(), "unknown") + if err == nil || !IsErrNodeNotFound(err) { + t.Fatalf("expected an nodeNotFoundError error, got %v", err) + } +} + +func TestNodeInspect(t *testing.T) { + expectedURL := "/nodes/node_id" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + content, err := json.Marshal(swarm.Node{ + ID: "node_id", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + nodeInspect, _, err := client.NodeInspectWithRaw(context.Background(), "node_id") + if err != nil { + t.Fatal(err) + } + if nodeInspect.ID != "node_id" { + t.Fatalf("expected `node_id`, got %s", nodeInspect.ID) + } +} diff --git a/components/engine/client/node_list.go b/components/engine/client/node_list.go new file mode 100644 index 0000000000..0716875ccc --- /dev/null +++ b/components/engine/client/node_list.go @@ -0,0 +1,36 @@ +package client + +import ( + "encoding/json" + "net/url" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// NodeList returns the list of nodes. +func (cli *Client) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) { + query := url.Values{} + + if options.Filter.Len() > 0 { + filterJSON, err := filters.ToParam(options.Filter) + + if err != nil { + return nil, err + } + + query.Set("filters", filterJSON) + } + + resp, err := cli.get(ctx, "/nodes", query, nil) + if err != nil { + return nil, err + } + + var nodes []swarm.Node + err = json.NewDecoder(resp.body).Decode(&nodes) + ensureReaderClosed(resp) + return nodes, err +} diff --git a/components/engine/client/node_list_test.go b/components/engine/client/node_list_test.go new file mode 100644 index 0000000000..899ac7f455 --- /dev/null +++ b/components/engine/client/node_list_test.go @@ -0,0 +1,94 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +func TestNodeListError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.NodeList(context.Background(), types.NodeListOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestNodeList(t *testing.T) { + expectedURL := "/nodes" + + filters := filters.NewArgs() + filters.Add("label", "label1") + filters.Add("label", "label2") + + listCases := []struct { + options types.NodeListOptions + expectedQueryParams map[string]string + }{ + { + options: types.NodeListOptions{}, + expectedQueryParams: map[string]string{ + "filters": "", + }, + }, + { + options: types.NodeListOptions{ + Filter: filters, + }, + expectedQueryParams: map[string]string{ + "filters": `{"label":{"label1":true,"label2":true}}`, + }, + }, + } + for _, listCase := range listCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + for key, expected := range listCase.expectedQueryParams { + actual := query.Get(key) + if actual != expected { + return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) + } + } + content, err := json.Marshal([]swarm.Node{ + { + ID: "node_id1", + }, + { + ID: "node_id2", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + nodes, err := client.NodeList(context.Background(), listCase.options) + if err != nil { + t.Fatal(err) + } + if len(nodes) != 2 { + t.Fatalf("expected 2 nodes, got %v", nodes) + } + } +} diff --git a/components/engine/client/node_remove.go b/components/engine/client/node_remove.go new file mode 100644 index 0000000000..0a77f3d578 --- /dev/null +++ b/components/engine/client/node_remove.go @@ -0,0 +1,21 @@ +package client + +import ( + "net/url" + + "github.com/docker/docker/api/types" + + "golang.org/x/net/context" +) + +// NodeRemove removes a Node. +func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error { + query := url.Values{} + if options.Force { + query.Set("force", "1") + } + + resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/node_remove_test.go b/components/engine/client/node_remove_test.go new file mode 100644 index 0000000000..9fdf2d7eb3 --- /dev/null +++ b/components/engine/client/node_remove_test.go @@ -0,0 +1,69 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + + "golang.org/x/net/context" +) + +func TestNodeRemoveError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.NodeRemove(context.Background(), "node_id", types.NodeRemoveOptions{Force: false}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestNodeRemove(t *testing.T) { + expectedURL := "/nodes/node_id" + + removeCases := []struct { + force bool + expectedForce string + }{ + { + expectedForce: "", + }, + { + force: true, + expectedForce: "1", + }, + } + + for _, removeCase := range removeCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "DELETE" { + return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) + } + force := req.URL.Query().Get("force") + if force != removeCase.expectedForce { + return nil, fmt.Errorf("force not set in URL query properly. expected '%s', got %s", removeCase.expectedForce, force) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), + }, nil + }), + } + + err := client.NodeRemove(context.Background(), "node_id", types.NodeRemoveOptions{Force: removeCase.force}) + if err != nil { + t.Fatal(err) + } + } +} diff --git a/components/engine/client/node_update.go b/components/engine/client/node_update.go new file mode 100644 index 0000000000..3ca9760282 --- /dev/null +++ b/components/engine/client/node_update.go @@ -0,0 +1,18 @@ +package client + +import ( + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// NodeUpdate updates a Node. +func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error { + query := url.Values{} + query.Set("version", strconv.FormatUint(version.Index, 10)) + resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/node_update_test.go b/components/engine/client/node_update_test.go new file mode 100644 index 0000000000..1acf65854a --- /dev/null +++ b/components/engine/client/node_update_test.go @@ -0,0 +1,49 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types/swarm" +) + +func TestNodeUpdateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.NodeUpdate(context.Background(), "node_id", swarm.Version{}, swarm.NodeSpec{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestNodeUpdate(t *testing.T) { + expectedURL := "/nodes/node_id/update" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), + }, nil + }), + } + + err := client.NodeUpdate(context.Background(), "node_id", swarm.Version{}, swarm.NodeSpec{}) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/plugin_disable.go b/components/engine/client/plugin_disable.go new file mode 100644 index 0000000000..893fc6e823 --- /dev/null +++ b/components/engine/client/plugin_disable.go @@ -0,0 +1,14 @@ +// +build experimental + +package client + +import ( + "golang.org/x/net/context" +) + +// PluginDisable disables a plugin +func (cli *Client) PluginDisable(ctx context.Context, name string) error { + resp, err := cli.post(ctx, "/plugins/"+name+"/disable", nil, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/plugin_disable_test.go b/components/engine/client/plugin_disable_test.go new file mode 100644 index 0000000000..f37c157866 --- /dev/null +++ b/components/engine/client/plugin_disable_test.go @@ -0,0 +1,49 @@ +// +build experimental + +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestPluginDisableError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.PluginDisable(context.Background(), "plugin_name") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestPluginDisable(t *testing.T) { + expectedURL := "/plugins/plugin_name/disable" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.PluginDisable(context.Background(), "plugin_name") + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/plugin_enable.go b/components/engine/client/plugin_enable.go new file mode 100644 index 0000000000..84422abc79 --- /dev/null +++ b/components/engine/client/plugin_enable.go @@ -0,0 +1,14 @@ +// +build experimental + +package client + +import ( + "golang.org/x/net/context" +) + +// PluginEnable enables a plugin +func (cli *Client) PluginEnable(ctx context.Context, name string) error { + resp, err := cli.post(ctx, "/plugins/"+name+"/enable", nil, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/plugin_enable_test.go b/components/engine/client/plugin_enable_test.go new file mode 100644 index 0000000000..fc0fe226a9 --- /dev/null +++ b/components/engine/client/plugin_enable_test.go @@ -0,0 +1,49 @@ +// +build experimental + +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestPluginEnableError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.PluginEnable(context.Background(), "plugin_name") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestPluginEnable(t *testing.T) { + expectedURL := "/plugins/plugin_name/enable" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.PluginEnable(context.Background(), "plugin_name") + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/plugin_inspect.go b/components/engine/client/plugin_inspect.go new file mode 100644 index 0000000000..7ba8db57a8 --- /dev/null +++ b/components/engine/client/plugin_inspect.go @@ -0,0 +1,30 @@ +// +build experimental + +package client + +import ( + "bytes" + "encoding/json" + "io/ioutil" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// PluginInspectWithRaw inspects an existing plugin +func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) { + resp, err := cli.get(ctx, "/plugins/"+name, nil, nil) + if err != nil { + return nil, nil, err + } + + defer ensureReaderClosed(resp) + body, err := ioutil.ReadAll(resp.body) + if err != nil { + return nil, nil, err + } + var p types.Plugin + rdr := bytes.NewReader(body) + err = json.NewDecoder(rdr).Decode(&p) + return &p, body, err +} diff --git a/components/engine/client/plugin_inspect_test.go b/components/engine/client/plugin_inspect_test.go new file mode 100644 index 0000000000..19f829b2de --- /dev/null +++ b/components/engine/client/plugin_inspect_test.go @@ -0,0 +1,56 @@ +// +build experimental + +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestPluginInspectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, _, err := client.PluginInspectWithRaw(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestPluginInspect(t *testing.T) { + expectedURL := "/plugins/plugin_name" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + content, err := json.Marshal(types.Plugin{ + ID: "plugin_id", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + pluginInspect, _, err := client.PluginInspectWithRaw(context.Background(), "plugin_name") + if err != nil { + t.Fatal(err) + } + if pluginInspect.ID != "plugin_id" { + t.Fatalf("expected `plugin_id`, got %s", pluginInspect.ID) + } +} diff --git a/components/engine/client/plugin_install.go b/components/engine/client/plugin_install.go new file mode 100644 index 0000000000..9ee32eea92 --- /dev/null +++ b/components/engine/client/plugin_install.go @@ -0,0 +1,59 @@ +// +build experimental + +package client + +import ( + "encoding/json" + "net/http" + "net/url" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// PluginInstall installs a plugin +func (cli *Client) PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error { + // FIXME(vdemeester) name is a ref, we might want to parse/validate it here. + query := url.Values{} + query.Set("name", name) + resp, err := cli.tryPluginPull(ctx, query, options.RegistryAuth) + if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil { + newAuthHeader, privilegeErr := options.PrivilegeFunc() + if privilegeErr != nil { + ensureReaderClosed(resp) + return privilegeErr + } + resp, err = cli.tryPluginPull(ctx, query, newAuthHeader) + } + if err != nil { + ensureReaderClosed(resp) + return err + } + var privileges types.PluginPrivileges + if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil { + ensureReaderClosed(resp) + return err + } + ensureReaderClosed(resp) + + if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 { + accept, err := options.AcceptPermissionsFunc(privileges) + if err != nil { + return err + } + if !accept { + resp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil) + ensureReaderClosed(resp) + return pluginPermissionDenied{name} + } + } + if options.Disabled { + return nil + } + return cli.PluginEnable(ctx, name) +} + +func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { + headers := map[string][]string{"X-Registry-Auth": {registryAuth}} + return cli.post(ctx, "/plugins/pull", query, nil, headers) +} diff --git a/components/engine/client/plugin_list.go b/components/engine/client/plugin_list.go new file mode 100644 index 0000000000..48b470247b --- /dev/null +++ b/components/engine/client/plugin_list.go @@ -0,0 +1,23 @@ +// +build experimental + +package client + +import ( + "encoding/json" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// PluginList returns the installed plugins +func (cli *Client) PluginList(ctx context.Context) (types.PluginsListResponse, error) { + var plugins types.PluginsListResponse + resp, err := cli.get(ctx, "/plugins", nil, nil) + if err != nil { + return plugins, err + } + + err = json.NewDecoder(resp.body).Decode(&plugins) + ensureReaderClosed(resp) + return plugins, err +} diff --git a/components/engine/client/plugin_list_test.go b/components/engine/client/plugin_list_test.go new file mode 100644 index 0000000000..92aee61187 --- /dev/null +++ b/components/engine/client/plugin_list_test.go @@ -0,0 +1,61 @@ +// +build experimental + +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestPluginListError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.PluginList(context.Background()) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestPluginList(t *testing.T) { + expectedURL := "/plugins" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + content, err := json.Marshal([]*types.Plugin{ + { + ID: "plugin_id1", + }, + { + ID: "plugin_id2", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + plugins, err := client.PluginList(context.Background()) + if err != nil { + t.Fatal(err) + } + if len(plugins) != 2 { + t.Fatalf("expected 2 plugins, got %v", plugins) + } +} diff --git a/components/engine/client/plugin_push.go b/components/engine/client/plugin_push.go new file mode 100644 index 0000000000..3afea5ed79 --- /dev/null +++ b/components/engine/client/plugin_push.go @@ -0,0 +1,15 @@ +// +build experimental + +package client + +import ( + "golang.org/x/net/context" +) + +// PluginPush pushes a plugin to a registry +func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) error { + headers := map[string][]string{"X-Registry-Auth": {registryAuth}} + resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, headers) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/plugin_push_test.go b/components/engine/client/plugin_push_test.go new file mode 100644 index 0000000000..b77ea00273 --- /dev/null +++ b/components/engine/client/plugin_push_test.go @@ -0,0 +1,53 @@ +// +build experimental + +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestPluginPushError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.PluginPush(context.Background(), "plugin_name", "") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestPluginPush(t *testing.T) { + expectedURL := "/plugins/plugin_name" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + auth := req.Header.Get("X-Registry-Auth") + if auth != "authtoken" { + return nil, fmt.Errorf("Invalid auth header : expected %s, got %s", "authtoken", auth) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.PluginPush(context.Background(), "plugin_name", "authtoken") + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/plugin_remove.go b/components/engine/client/plugin_remove.go new file mode 100644 index 0000000000..1483f2854d --- /dev/null +++ b/components/engine/client/plugin_remove.go @@ -0,0 +1,22 @@ +// +build experimental + +package client + +import ( + "net/url" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// PluginRemove removes a plugin +func (cli *Client) PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error { + query := url.Values{} + if options.Force { + query.Set("force", "1") + } + + resp, err := cli.delete(ctx, "/plugins/"+name, query, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/plugin_remove_test.go b/components/engine/client/plugin_remove_test.go new file mode 100644 index 0000000000..de565f441b --- /dev/null +++ b/components/engine/client/plugin_remove_test.go @@ -0,0 +1,51 @@ +// +build experimental + +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + + "golang.org/x/net/context" +) + +func TestPluginRemoveError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.PluginRemove(context.Background(), "plugin_name", types.PluginRemoveOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestPluginRemove(t *testing.T) { + expectedURL := "/plugins/plugin_name" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "DELETE" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.PluginRemove(context.Background(), "plugin_name", types.PluginRemoveOptions{}) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/plugin_set.go b/components/engine/client/plugin_set.go new file mode 100644 index 0000000000..fb40f38b22 --- /dev/null +++ b/components/engine/client/plugin_set.go @@ -0,0 +1,14 @@ +// +build experimental + +package client + +import ( + "golang.org/x/net/context" +) + +// PluginSet modifies settings for an existing plugin +func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error { + resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/plugin_set_test.go b/components/engine/client/plugin_set_test.go new file mode 100644 index 0000000000..128dee04be --- /dev/null +++ b/components/engine/client/plugin_set_test.go @@ -0,0 +1,49 @@ +// +build experimental + +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestPluginSetError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.PluginSet(context.Background(), "plugin_name", []string{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestPluginSet(t *testing.T) { + expectedURL := "/plugins/plugin_name/set" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.PluginSet(context.Background(), "plugin_name", []string{"arg1"}) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/request.go b/components/engine/client/request.go new file mode 100644 index 0000000000..024e973520 --- /dev/null +++ b/components/engine/client/request.go @@ -0,0 +1,208 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions" + "github.com/docker/docker/client/transport/cancellable" + "golang.org/x/net/context" +) + +// serverResponse is a wrapper for http API responses. +type serverResponse struct { + body io.ReadCloser + header http.Header + statusCode int +} + +// head sends an http request to the docker API using the method HEAD. +func (cli *Client) head(ctx context.Context, path string, query url.Values, headers map[string][]string) (serverResponse, error) { + return cli.sendRequest(ctx, "HEAD", path, query, nil, headers) +} + +// getWithContext sends an http request to the docker API using the method GET with a specific go context. +func (cli *Client) get(ctx context.Context, path string, query url.Values, headers map[string][]string) (serverResponse, error) { + return cli.sendRequest(ctx, "GET", path, query, nil, headers) +} + +// postWithContext sends an http request to the docker API using the method POST with a specific go context. +func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (serverResponse, error) { + return cli.sendRequest(ctx, "POST", path, query, obj, headers) +} + +func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (serverResponse, error) { + return cli.sendClientRequest(ctx, "POST", path, query, body, headers) +} + +// put sends an http request to the docker API using the method PUT. +func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (serverResponse, error) { + return cli.sendRequest(ctx, "PUT", path, query, obj, headers) +} + +// put sends an http request to the docker API using the method PUT. +func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (serverResponse, error) { + return cli.sendClientRequest(ctx, "PUT", path, query, body, headers) +} + +// delete sends an http request to the docker API using the method DELETE. +func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers map[string][]string) (serverResponse, error) { + return cli.sendRequest(ctx, "DELETE", path, query, nil, headers) +} + +func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, obj interface{}, headers map[string][]string) (serverResponse, error) { + var body io.Reader + + if obj != nil { + var err error + body, err = encodeData(obj) + if err != nil { + return serverResponse{}, err + } + if headers == nil { + headers = make(map[string][]string) + } + headers["Content-Type"] = []string{"application/json"} + } + + return cli.sendClientRequest(ctx, method, path, query, body, headers) +} + +func (cli *Client) sendClientRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers map[string][]string) (serverResponse, error) { + serverResp := serverResponse{ + body: nil, + statusCode: -1, + } + + expectedPayload := (method == "POST" || method == "PUT") + if expectedPayload && body == nil { + body = bytes.NewReader([]byte{}) + } + + req, err := cli.newRequest(method, path, query, body, headers) + if err != nil { + return serverResp, err + } + + if cli.proto == "unix" || cli.proto == "npipe" { + // For local communications, it doesn't matter what the host is. We just + // need a valid and meaningful host name. (See #189) + req.Host = "docker" + } + req.URL.Host = cli.addr + req.URL.Scheme = cli.transport.Scheme() + + if expectedPayload && req.Header.Get("Content-Type") == "" { + req.Header.Set("Content-Type", "text/plain") + } + + resp, err := cancellable.Do(ctx, cli.transport, req) + if err != nil { + if !cli.transport.Secure() && strings.Contains(err.Error(), "malformed HTTP response") { + return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err) + } + + if cli.transport.Secure() && strings.Contains(err.Error(), "bad certificate") { + return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err) + } + + // Don't decorate context sentinel errors; users may be comparing to + // them directly. + switch err { + case context.Canceled, context.DeadlineExceeded: + return serverResp, err + } + + if err, ok := err.(net.Error); ok { + if err.Timeout() { + return serverResp, ErrorConnectionFailed(cli.host) + } + if !err.Temporary() { + if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") { + return serverResp, ErrorConnectionFailed(cli.host) + } + } + } + return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err) + } + + if resp != nil { + serverResp.statusCode = resp.StatusCode + } + + if serverResp.statusCode < 200 || serverResp.statusCode >= 400 { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return serverResp, err + } + if len(body) == 0 { + return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL) + } + + var errorMessage string + if (cli.version == "" || versions.GreaterThan(cli.version, "1.23")) && + resp.Header.Get("Content-Type") == "application/json" { + var errorResponse types.ErrorResponse + if err := json.Unmarshal(body, &errorResponse); err != nil { + return serverResp, fmt.Errorf("Error reading JSON: %v", err) + } + errorMessage = errorResponse.Message + } else { + errorMessage = string(body) + } + + return serverResp, fmt.Errorf("Error response from daemon: %s", strings.TrimSpace(errorMessage)) + } + + serverResp.body = resp.Body + serverResp.header = resp.Header + return serverResp, nil +} + +func (cli *Client) newRequest(method, path string, query url.Values, body io.Reader, headers map[string][]string) (*http.Request, error) { + apiPath := cli.getAPIPath(path, query) + req, err := http.NewRequest(method, apiPath, body) + if err != nil { + return nil, err + } + + // Add CLI Config's HTTP Headers BEFORE we set the Docker headers + // then the user can't change OUR headers + for k, v := range cli.customHTTPHeaders { + req.Header.Set(k, v) + } + + if headers != nil { + for k, v := range headers { + req.Header[k] = v + } + } + + return req, nil +} + +func encodeData(data interface{}) (*bytes.Buffer, error) { + params := bytes.NewBuffer(nil) + if data != nil { + if err := json.NewEncoder(params).Encode(data); err != nil { + return nil, err + } + } + return params, nil +} + +func ensureReaderClosed(response serverResponse) { + if body := response.body; body != nil { + // Drain up to 512 bytes and close the body to let the Transport reuse the connection + io.CopyN(ioutil.Discard, body, 512) + response.body.Close() + } +} diff --git a/components/engine/client/request_test.go b/components/engine/client/request_test.go new file mode 100644 index 0000000000..446adf9c66 --- /dev/null +++ b/components/engine/client/request_test.go @@ -0,0 +1,91 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// TestSetHostHeader should set fake host for local communications, set real host +// for normal communications. +func TestSetHostHeader(t *testing.T) { + testURL := "/test" + testCases := []struct { + host string + expectedHost string + expectedURLHost string + }{ + { + "unix:///var/run/docker.sock", + "docker", + "/var/run/docker.sock", + }, + { + "npipe:////./pipe/docker_engine", + "docker", + "//./pipe/docker_engine", + }, + { + "tcp://0.0.0.0:4243", + "", + "0.0.0.0:4243", + }, + { + "tcp://localhost:4243", + "", + "localhost:4243", + }, + } + + for c, test := range testCases { + proto, addr, basePath, err := ParseHost(test.host) + if err != nil { + t.Fatal(err) + } + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, testURL) { + return nil, fmt.Errorf("Test Case #%d: Expected URL %q, got %q", c, testURL, req.URL) + } + if req.Host != test.expectedHost { + return nil, fmt.Errorf("Test Case #%d: Expected host %q, got %q", c, test.expectedHost, req.Host) + } + if req.URL.Host != test.expectedURLHost { + return nil, fmt.Errorf("Test Case #%d: Expected URL host %q, got %q", c, test.expectedURLHost, req.URL.Host) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(([]byte("")))), + }, nil + }), + proto: proto, + addr: addr, + basePath: basePath, + } + + _, err = client.sendRequest(context.Background(), "GET", testURL, nil, nil, nil) + if err != nil { + t.Fatal(err) + } + } +} + +// TestPlainTextError tests the server returning an error in plain text for +// backwards compatibility with API versions <1.24. All other tests use +// errors returned as JSON +func TestPlainTextError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, plainTextErrorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ContainerList(context.Background(), types.ContainerListOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} diff --git a/components/engine/client/service_create.go b/components/engine/client/service_create.go new file mode 100644 index 0000000000..3d1be225bd --- /dev/null +++ b/components/engine/client/service_create.go @@ -0,0 +1,30 @@ +package client + +import ( + "encoding/json" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// ServiceCreate creates a new Service. +func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) { + var headers map[string][]string + + if options.EncodedRegistryAuth != "" { + headers = map[string][]string{ + "X-Registry-Auth": {options.EncodedRegistryAuth}, + } + } + + var response types.ServiceCreateResponse + resp, err := cli.post(ctx, "/services/create", nil, service, headers) + if err != nil { + return response, err + } + + err = json.NewDecoder(resp.body).Decode(&response) + ensureReaderClosed(resp) + return response, err +} diff --git a/components/engine/client/service_create_test.go b/components/engine/client/service_create_test.go new file mode 100644 index 0000000000..a79f040c0a --- /dev/null +++ b/components/engine/client/service_create_test.go @@ -0,0 +1,57 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +func TestServiceCreateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + _, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestServiceCreate(t *testing.T) { + expectedURL := "/services/create" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + b, err := json.Marshal(types.ServiceCreateResponse{ + ID: "service_id", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(b)), + }, nil + }), + } + + r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{}) + if err != nil { + t.Fatal(err) + } + if r.ID != "service_id" { + t.Fatalf("expected `service_id`, got %s", r.ID) + } +} diff --git a/components/engine/client/service_inspect.go b/components/engine/client/service_inspect.go new file mode 100644 index 0000000000..ca71cbde1a --- /dev/null +++ b/components/engine/client/service_inspect.go @@ -0,0 +1,33 @@ +package client + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// ServiceInspectWithRaw returns the service information and the raw data. +func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error) { + serverResp, err := cli.get(ctx, "/services/"+serviceID, nil, nil) + if err != nil { + if serverResp.statusCode == http.StatusNotFound { + return swarm.Service{}, nil, serviceNotFoundError{serviceID} + } + return swarm.Service{}, nil, err + } + defer ensureReaderClosed(serverResp) + + body, err := ioutil.ReadAll(serverResp.body) + if err != nil { + return swarm.Service{}, nil, err + } + + var response swarm.Service + rdr := bytes.NewReader(body) + err = json.NewDecoder(rdr).Decode(&response) + return response, body, err +} diff --git a/components/engine/client/service_inspect_test.go b/components/engine/client/service_inspect_test.go new file mode 100644 index 0000000000..e4eafff5d7 --- /dev/null +++ b/components/engine/client/service_inspect_test.go @@ -0,0 +1,65 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +func TestServiceInspectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, _, err := client.ServiceInspectWithRaw(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestServiceInspectServiceNotFound(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), + } + + _, _, err := client.ServiceInspectWithRaw(context.Background(), "unknown") + if err == nil || !IsErrServiceNotFound(err) { + t.Fatalf("expected an serviceNotFoundError error, got %v", err) + } +} + +func TestServiceInspect(t *testing.T) { + expectedURL := "/services/service_id" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + content, err := json.Marshal(swarm.Service{ + ID: "service_id", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + serviceInspect, _, err := client.ServiceInspectWithRaw(context.Background(), "service_id") + if err != nil { + t.Fatal(err) + } + if serviceInspect.ID != "service_id" { + t.Fatalf("expected `service_id`, got %s", serviceInspect.ID) + } +} diff --git a/components/engine/client/service_list.go b/components/engine/client/service_list.go new file mode 100644 index 0000000000..4ebc9f3011 --- /dev/null +++ b/components/engine/client/service_list.go @@ -0,0 +1,35 @@ +package client + +import ( + "encoding/json" + "net/url" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// ServiceList returns the list of services. +func (cli *Client) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { + query := url.Values{} + + if options.Filter.Len() > 0 { + filterJSON, err := filters.ToParam(options.Filter) + if err != nil { + return nil, err + } + + query.Set("filters", filterJSON) + } + + resp, err := cli.get(ctx, "/services", query, nil) + if err != nil { + return nil, err + } + + var services []swarm.Service + err = json.NewDecoder(resp.body).Decode(&services) + ensureReaderClosed(resp) + return services, err +} diff --git a/components/engine/client/service_list_test.go b/components/engine/client/service_list_test.go new file mode 100644 index 0000000000..6e6851a3a5 --- /dev/null +++ b/components/engine/client/service_list_test.go @@ -0,0 +1,94 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +func TestServiceListError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.ServiceList(context.Background(), types.ServiceListOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestServiceList(t *testing.T) { + expectedURL := "/services" + + filters := filters.NewArgs() + filters.Add("label", "label1") + filters.Add("label", "label2") + + listCases := []struct { + options types.ServiceListOptions + expectedQueryParams map[string]string + }{ + { + options: types.ServiceListOptions{}, + expectedQueryParams: map[string]string{ + "filters": "", + }, + }, + { + options: types.ServiceListOptions{ + Filter: filters, + }, + expectedQueryParams: map[string]string{ + "filters": `{"label":{"label1":true,"label2":true}}`, + }, + }, + } + for _, listCase := range listCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + for key, expected := range listCase.expectedQueryParams { + actual := query.Get(key) + if actual != expected { + return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) + } + } + content, err := json.Marshal([]swarm.Service{ + { + ID: "service_id1", + }, + { + ID: "service_id2", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + services, err := client.ServiceList(context.Background(), listCase.options) + if err != nil { + t.Fatal(err) + } + if len(services) != 2 { + t.Fatalf("expected 2 services, got %v", services) + } + } +} diff --git a/components/engine/client/service_remove.go b/components/engine/client/service_remove.go new file mode 100644 index 0000000000..a9331f92c2 --- /dev/null +++ b/components/engine/client/service_remove.go @@ -0,0 +1,10 @@ +package client + +import "golang.org/x/net/context" + +// ServiceRemove kills and removes a service. +func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error { + resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/service_remove_test.go b/components/engine/client/service_remove_test.go new file mode 100644 index 0000000000..e1316f959b --- /dev/null +++ b/components/engine/client/service_remove_test.go @@ -0,0 +1,47 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestServiceRemoveError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.ServiceRemove(context.Background(), "service_id") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestServiceRemove(t *testing.T) { + expectedURL := "/services/service_id" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "DELETE" { + return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), + }, nil + }), + } + + err := client.ServiceRemove(context.Background(), "service_id") + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/service_update.go b/components/engine/client/service_update.go new file mode 100644 index 0000000000..c5d07e8394 --- /dev/null +++ b/components/engine/client/service_update.go @@ -0,0 +1,30 @@ +package client + +import ( + "net/url" + "strconv" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// ServiceUpdate updates a Service. +func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) error { + var ( + headers map[string][]string + query = url.Values{} + ) + + if options.EncodedRegistryAuth != "" { + headers = map[string][]string{ + "X-Registry-Auth": {options.EncodedRegistryAuth}, + } + } + + query.Set("version", strconv.FormatUint(version.Index, 10)) + + resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/service_update_test.go b/components/engine/client/service_update_test.go new file mode 100644 index 0000000000..bd616c09bf --- /dev/null +++ b/components/engine/client/service_update_test.go @@ -0,0 +1,77 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" +) + +func TestServiceUpdateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.ServiceUpdate(context.Background(), "service_id", swarm.Version{}, swarm.ServiceSpec{}, types.ServiceUpdateOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestServiceUpdate(t *testing.T) { + expectedURL := "/services/service_id/update" + + updateCases := []struct { + swarmVersion swarm.Version + expectedVersion string + }{ + { + expectedVersion: "0", + }, + { + swarmVersion: swarm.Version{ + Index: 0, + }, + expectedVersion: "0", + }, + { + swarmVersion: swarm.Version{ + Index: 10, + }, + expectedVersion: "10", + }, + } + + for _, updateCase := range updateCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + version := req.URL.Query().Get("version") + if version != updateCase.expectedVersion { + return nil, fmt.Errorf("version not set in URL query properly, expected '%s', got %s", updateCase.expectedVersion, version) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), + }, nil + }), + } + + err := client.ServiceUpdate(context.Background(), "service_id", updateCase.swarmVersion, swarm.ServiceSpec{}, types.ServiceUpdateOptions{}) + if err != nil { + t.Fatal(err) + } + } +} diff --git a/components/engine/client/swarm_init.go b/components/engine/client/swarm_init.go new file mode 100644 index 0000000000..fd45d066e3 --- /dev/null +++ b/components/engine/client/swarm_init.go @@ -0,0 +1,21 @@ +package client + +import ( + "encoding/json" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// SwarmInit initializes the Swarm. +func (cli *Client) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) { + serverResp, err := cli.post(ctx, "/swarm/init", nil, req, nil) + if err != nil { + return "", err + } + + var response string + err = json.NewDecoder(serverResp.body).Decode(&response) + ensureReaderClosed(serverResp) + return response, err +} diff --git a/components/engine/client/swarm_init_test.go b/components/engine/client/swarm_init_test.go new file mode 100644 index 0000000000..077c8c4efb --- /dev/null +++ b/components/engine/client/swarm_init_test.go @@ -0,0 +1,54 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types/swarm" +) + +func TestSwarmInitError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.SwarmInit(context.Background(), swarm.InitRequest{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestSwarmInit(t *testing.T) { + expectedURL := "/swarm/init" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(`"body"`))), + }, nil + }), + } + + resp, err := client.SwarmInit(context.Background(), swarm.InitRequest{ + ListenAddr: "0.0.0.0:2377", + }) + if err != nil { + t.Fatal(err) + } + if resp != "body" { + t.Fatalf("Expected 'body', got %s", resp) + } +} diff --git a/components/engine/client/swarm_inspect.go b/components/engine/client/swarm_inspect.go new file mode 100644 index 0000000000..6d95cfc05e --- /dev/null +++ b/components/engine/client/swarm_inspect.go @@ -0,0 +1,21 @@ +package client + +import ( + "encoding/json" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// SwarmInspect inspects the Swarm. +func (cli *Client) SwarmInspect(ctx context.Context) (swarm.Swarm, error) { + serverResp, err := cli.get(ctx, "/swarm", nil, nil) + if err != nil { + return swarm.Swarm{}, err + } + + var response swarm.Swarm + err = json.NewDecoder(serverResp.body).Decode(&response) + ensureReaderClosed(serverResp) + return response, err +} diff --git a/components/engine/client/swarm_inspect_test.go b/components/engine/client/swarm_inspect_test.go new file mode 100644 index 0000000000..7143e77181 --- /dev/null +++ b/components/engine/client/swarm_inspect_test.go @@ -0,0 +1,56 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +func TestSwarmInspectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.SwarmInspect(context.Background()) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestSwarmInspect(t *testing.T) { + expectedURL := "/swarm" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + content, err := json.Marshal(swarm.Swarm{ + ClusterInfo: swarm.ClusterInfo{ + ID: "swarm_id", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + swarmInspect, err := client.SwarmInspect(context.Background()) + if err != nil { + t.Fatal(err) + } + if swarmInspect.ID != "swarm_id" { + t.Fatalf("expected `swarm_id`, got %s", swarmInspect.ID) + } +} diff --git a/components/engine/client/swarm_join.go b/components/engine/client/swarm_join.go new file mode 100644 index 0000000000..cda99930eb --- /dev/null +++ b/components/engine/client/swarm_join.go @@ -0,0 +1,13 @@ +package client + +import ( + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// SwarmJoin joins the Swarm. +func (cli *Client) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error { + resp, err := cli.post(ctx, "/swarm/join", nil, req, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/swarm_join_test.go b/components/engine/client/swarm_join_test.go new file mode 100644 index 0000000000..922716d85f --- /dev/null +++ b/components/engine/client/swarm_join_test.go @@ -0,0 +1,51 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types/swarm" +) + +func TestSwarmJoinError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.SwarmJoin(context.Background(), swarm.JoinRequest{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestSwarmJoin(t *testing.T) { + expectedURL := "/swarm/join" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.SwarmJoin(context.Background(), swarm.JoinRequest{ + ListenAddr: "0.0.0.0:2377", + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/swarm_leave.go b/components/engine/client/swarm_leave.go new file mode 100644 index 0000000000..a4df732174 --- /dev/null +++ b/components/engine/client/swarm_leave.go @@ -0,0 +1,18 @@ +package client + +import ( + "net/url" + + "golang.org/x/net/context" +) + +// SwarmLeave leaves the Swarm. +func (cli *Client) SwarmLeave(ctx context.Context, force bool) error { + query := url.Values{} + if force { + query.Set("force", "1") + } + resp, err := cli.post(ctx, "/swarm/leave", query, nil, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/swarm_leave_test.go b/components/engine/client/swarm_leave_test.go new file mode 100644 index 0000000000..d0bef2b257 --- /dev/null +++ b/components/engine/client/swarm_leave_test.go @@ -0,0 +1,66 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestSwarmLeaveError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.SwarmLeave(context.Background(), false) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestSwarmLeave(t *testing.T) { + expectedURL := "/swarm/leave" + + leaveCases := []struct { + force bool + expectedForce string + }{ + { + expectedForce: "", + }, + { + force: true, + expectedForce: "1", + }, + } + + for _, leaveCase := range leaveCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + force := req.URL.Query().Get("force") + if force != leaveCase.expectedForce { + return nil, fmt.Errorf("force not set in URL query properly. expected '%s', got %s", leaveCase.expectedForce, force) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.SwarmLeave(context.Background(), leaveCase.force) + if err != nil { + t.Fatal(err) + } + } +} diff --git a/components/engine/client/swarm_update.go b/components/engine/client/swarm_update.go new file mode 100644 index 0000000000..f0be145ba2 --- /dev/null +++ b/components/engine/client/swarm_update.go @@ -0,0 +1,21 @@ +package client + +import ( + "fmt" + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// SwarmUpdate updates the Swarm. +func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error { + query := url.Values{} + query.Set("version", strconv.FormatUint(version.Index, 10)) + query.Set("rotateWorkerToken", fmt.Sprintf("%v", flags.RotateWorkerToken)) + query.Set("rotateManagerToken", fmt.Sprintf("%v", flags.RotateManagerToken)) + resp, err := cli.post(ctx, "/swarm/update", query, swarm, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/swarm_update_test.go b/components/engine/client/swarm_update_test.go new file mode 100644 index 0000000000..ecf1731e5b --- /dev/null +++ b/components/engine/client/swarm_update_test.go @@ -0,0 +1,49 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types/swarm" +) + +func TestSwarmUpdateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.SwarmUpdate(context.Background(), swarm.Version{}, swarm.Spec{}, swarm.UpdateFlags{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestSwarmUpdate(t *testing.T) { + expectedURL := "/swarm/update" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }), + } + + err := client.SwarmUpdate(context.Background(), swarm.Version{}, swarm.Spec{}, swarm.UpdateFlags{}) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/client/task_inspect.go b/components/engine/client/task_inspect.go new file mode 100644 index 0000000000..bc8058fc32 --- /dev/null +++ b/components/engine/client/task_inspect.go @@ -0,0 +1,34 @@ +package client + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/docker/docker/api/types/swarm" + + "golang.org/x/net/context" +) + +// TaskInspectWithRaw returns the task information and its raw representation.. +func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) { + serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil) + if err != nil { + if serverResp.statusCode == http.StatusNotFound { + return swarm.Task{}, nil, taskNotFoundError{taskID} + } + return swarm.Task{}, nil, err + } + defer ensureReaderClosed(serverResp) + + body, err := ioutil.ReadAll(serverResp.body) + if err != nil { + return swarm.Task{}, nil, err + } + + var response swarm.Task + rdr := bytes.NewReader(body) + err = json.NewDecoder(rdr).Decode(&response) + return response, body, err +} diff --git a/components/engine/client/task_inspect_test.go b/components/engine/client/task_inspect_test.go new file mode 100644 index 0000000000..2c73b37642 --- /dev/null +++ b/components/engine/client/task_inspect_test.go @@ -0,0 +1,54 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +func TestTaskInspectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, _, err := client.TaskInspectWithRaw(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestTaskInspect(t *testing.T) { + expectedURL := "/tasks/task_id" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + content, err := json.Marshal(swarm.Task{ + ID: "task_id", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + taskInspect, _, err := client.TaskInspectWithRaw(context.Background(), "task_id") + if err != nil { + t.Fatal(err) + } + if taskInspect.ID != "task_id" { + t.Fatalf("expected `task_id`, got %s", taskInspect.ID) + } +} diff --git a/components/engine/client/task_list.go b/components/engine/client/task_list.go new file mode 100644 index 0000000000..07c8324c83 --- /dev/null +++ b/components/engine/client/task_list.go @@ -0,0 +1,35 @@ +package client + +import ( + "encoding/json" + "net/url" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// TaskList returns the list of tasks. +func (cli *Client) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) { + query := url.Values{} + + if options.Filter.Len() > 0 { + filterJSON, err := filters.ToParam(options.Filter) + if err != nil { + return nil, err + } + + query.Set("filters", filterJSON) + } + + resp, err := cli.get(ctx, "/tasks", query, nil) + if err != nil { + return nil, err + } + + var tasks []swarm.Task + err = json.NewDecoder(resp.body).Decode(&tasks) + ensureReaderClosed(resp) + return tasks, err +} diff --git a/components/engine/client/task_list_test.go b/components/engine/client/task_list_test.go new file mode 100644 index 0000000000..b520ab589f --- /dev/null +++ b/components/engine/client/task_list_test.go @@ -0,0 +1,94 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +func TestTaskListError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.TaskList(context.Background(), types.TaskListOptions{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestTaskList(t *testing.T) { + expectedURL := "/tasks" + + filters := filters.NewArgs() + filters.Add("label", "label1") + filters.Add("label", "label2") + + listCases := []struct { + options types.TaskListOptions + expectedQueryParams map[string]string + }{ + { + options: types.TaskListOptions{}, + expectedQueryParams: map[string]string{ + "filters": "", + }, + }, + { + options: types.TaskListOptions{ + Filter: filters, + }, + expectedQueryParams: map[string]string{ + "filters": `{"label":{"label1":true,"label2":true}}`, + }, + }, + } + for _, listCase := range listCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + for key, expected := range listCase.expectedQueryParams { + actual := query.Get(key) + if actual != expected { + return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) + } + } + content, err := json.Marshal([]swarm.Task{ + { + ID: "task_id1", + }, + { + ID: "task_id2", + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + tasks, err := client.TaskList(context.Background(), listCase.options) + if err != nil { + t.Fatal(err) + } + if len(tasks) != 2 { + t.Fatalf("expected 2 tasks, got %v", tasks) + } + } +} diff --git a/components/engine/client/testdata/ca.pem b/components/engine/client/testdata/ca.pem new file mode 100644 index 0000000000..ad14d47065 --- /dev/null +++ b/components/engine/client/testdata/ca.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC0jCCAbqgAwIBAgIRAILlP5WWLaHkQ/m2ASHP7SowDQYJKoZIhvcNAQELBQAw +EjEQMA4GA1UEChMHdmluY2VudDAeFw0xNjAzMjQxMDE5MDBaFw0xOTAzMDkxMDE5 +MDBaMBIxEDAOBgNVBAoTB3ZpbmNlbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQD0yZPKAGncoaxaU/QW9tWEHbrvDoGVF/65L8Si/jBrlAgLjhmmV1di +vKG9QPzuU8snxHro3/uCwyA6kTqw0U8bGwHxJq2Bpa6JBYj8N2jMJ+M+sjXgSo2t +E0zIzjTW2Pir3C8qwfrVL6NFp9xClwMD23SFZ0UsEH36NkfyrKBVeM8IOjJd4Wjs +xIcuvF3BTVkji84IJBW2JIKf9ZrzJwUlSCPgptRp4Evdbyp5d+UPxtwxD7qjW4lM +yQQ8vfcC4lKkVx5s/RNJ4fzd5uEgLdEbZ20qt7Zt/bLcxFHpUhH2teA0QjmrOWFh +gbL83s95/+hbSVhsO4hoFW7vTeiCCY4xAgMBAAGjIzAhMA4GA1UdDwEB/wQEAwIC +rDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBY51RHajuDuhO2 +tcm26jeNROzfffnjhvbOVPjSEdo9vI3JpMU/RuQw+nbNcLwJrdjL6UH7tD/36Y+q +NXH+xSIjWFH0zXGxrIUsVrvt6f8CbOvw7vD+gygOG+849PDQMbL6czP8rvXY7vZV +9pdpQfrENk4b5kePRW/6HaGSTvtgN7XOrYD9fp3pm/G534T2e3IxgYMRNwdB9Ul9 +bLwMqQqf4eiqqMs6x4IVmZUkGVMKiFKcvkNg9a+Ozx5pMizHeAezWMcZ5V+QJZVT +8lElSCKZ2Yy2xkcl7aeQMLwcAeZwfTp+Yu9dVzlqXiiBTLd1+LtAQCuKHzmw4Q8k +EvD5m49l +-----END CERTIFICATE----- diff --git a/components/engine/client/testdata/cert.pem b/components/engine/client/testdata/cert.pem new file mode 100644 index 0000000000..9000ffb32b --- /dev/null +++ b/components/engine/client/testdata/cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC8DCCAdigAwIBAgIRAJAS1glgcke4q7eCaretwgUwDQYJKoZIhvcNAQELBQAw +EjEQMA4GA1UEChMHdmluY2VudDAeFw0xNjAzMjQxMDE5MDBaFw0xOTAzMDkxMDE5 +MDBaMB4xHDAaBgNVBAoME3ZpbmNlbnQuPGJvb3RzdHJhcD4wggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQClpvG442dGEvrRgmCrqY4kBml1LVlw2Y7ZDn6B +TKa52+MuGDmfXbO1UhclNqTXjLgAwKjPz/OvnPRxNEUoQEDbBd+Xev7rxTY5TvYI +27YH3fMH2LL2j62jum649abfhZ6ekD5eD8tCn3mnrEOgqRIlK7efPIVixq/ZqU1H +7ez0ggB7dmWHlhnUaxyQOCSnAX/7nKYQXqZgVvGhDeR2jp7GcnhbK/qPrZ/mOm83 +2IjCeYN145opYlzTSp64GYIZz7uqMNcnDKK37ZbS8MYcTjrRaHEiqZVVdIC+ghbx +qYqzbZRVfgztI9jwmifn0mYrN4yt+nhNYwBcRJ4Pv3uLFbo7AgMBAAGjNTAzMA4G +A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAA +MA0GCSqGSIb3DQEBCwUAA4IBAQDg1r7nksjYgDFYEcBbrRrRHddIoK+RVmSBTTrq +8giC77m0srKdh9XTVWK1PUbGfODV1oD8m9QhPE8zPDyYQ8jeXNRSU5wXdkrTRmmY +w/T3SREqmE7CObMtusokHidjYFuqqCR07sJzqBKRlzr3o0EGe3tuEhUlF5ARY028 +eipaDcVlT5ChGcDa6LeJ4e05u4cVap0dd6Rp1w3Rx1AYAecdgtgBMnw1iWdl/nrC +sp26ZXNaAhFOUovlY9VY257AMd9hQV7WvAK4yNEHcckVu3uXTBmDgNSOPtl0QLsL +Kjlj75ksCx8nCln/hCut/0+kGTsGZqdV5c6ktgcGYRir/5Hs +-----END CERTIFICATE----- diff --git a/components/engine/client/testdata/key.pem b/components/engine/client/testdata/key.pem new file mode 100644 index 0000000000..c0869dfc1a --- /dev/null +++ b/components/engine/client/testdata/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEApabxuONnRhL60YJgq6mOJAZpdS1ZcNmO2Q5+gUymudvjLhg5 +n12ztVIXJTak14y4AMCoz8/zr5z0cTRFKEBA2wXfl3r+68U2OU72CNu2B93zB9iy +9o+to7puuPWm34WenpA+Xg/LQp95p6xDoKkSJSu3nzyFYsav2alNR+3s9IIAe3Zl +h5YZ1GsckDgkpwF/+5ymEF6mYFbxoQ3kdo6exnJ4Wyv6j62f5jpvN9iIwnmDdeOa +KWJc00qeuBmCGc+7qjDXJwyit+2W0vDGHE460WhxIqmVVXSAvoIW8amKs22UVX4M +7SPY8Jon59JmKzeMrfp4TWMAXESeD797ixW6OwIDAQABAoIBAHfyAAleL8NfrtnR +S+pApbmUIvxD0AWUooispBE/zWG6xC72P5MTqDJctIGvpYCmVf3Fgvamns7EGYN2 +07Sngc6V3Ca1WqyhaffpIuGbJZ1gqr89u6gotRRexBmNVj13ZTlvPJmjWgxtqQsu +AvHsOkVL+HOGwRaaw24Z1umEcBVCepl7PGTqsLeJUtBUZBiqdJTu4JYLAB6BggBI +OxhHoTWvlNWwzezo2C/IXkXcXD/tp3i5vTn5rAXHSMQkdMAUh7/xJ73Fl36gxZhp +W7NoPKaS9qNh8jhs6p54S7tInb6+mrKtvRFKl5XAR3istXrXteT5UaukpuBbQ/5d +qf4BXuECgYEAzoOKxMee5tG/G9iC6ImNq5xGAZm0OnmteNgIEQj49If1Q68av525 +FioqdC9zV+blfHQqXEIUeum4JAou4xqmB8Lw2H0lYwOJ1IkpUy3QJjU1IrI+U5Qy +ryZuA9cxSTLf1AJFbROsoZDpjaBh0uUQkD/4PHpwXMgHu/3CaJ4nTEkCgYEAzVjE +VWgczWJGyRxmHSeR51ft1jrlChZHEd3HwgLfo854JIj+MGUH4KPLSMIkYNuyiwNQ +W7zdXCB47U8afSL/lPTv1M5+ZsWY6sZAT6gtp/IeU0Va943h9cj10fAOBJaz1H6M +jnZS4jjWhVInE7wpCDVCwDRoHHJ84kb6JeflamMCgYBDQDcKie9HP3q6uLE4xMKr +5gIuNz2n5UQGnGNUGNXp2/SVDArr55MEksqsd19aesi01KeOz74XoNDke6R1NJJo +6KTB+08XhWl3GwuoGL02FBGvsNf3I8W1oBAnlAZqzfRx+CNfuA55ttU318jDgvD3 +6L0QBNdef411PNf4dbhacQKBgAd/e0PHFm4lbYJAaDYeUMSKwGN3KQ/SOmwblgSu +iC36BwcGfYmU1tHMCUsx05Q50W4kA9Ylskt/4AqCPexdz8lHnE4/7/uesXO5I3YF +JQ2h2Jufx6+MXbjUyq0Mv+ZI/m3+5PD6vxIFk0ew9T5SO4lSMIrGHxsSzx6QCuhB +bG4TAoGBAJ5PWG7d2CyCjLtfF8J4NxykRvIQ8l/3kDvDdNrXiXbgonojo2lgRYaM +5LoK9ApN8KHdedpTRipBaDA22Sp5SjMcUE7A6q42PJCL9r+BRYF0foFQx/rqpCff +pVWKgwIPoKnfxDqN1RUgyFcx1jbA3XVJZCuT+wbMuDQ9nlvulD1W +-----END RSA PRIVATE KEY----- diff --git a/components/engine/client/transport/cancellable/LICENSE b/components/engine/client/transport/cancellable/LICENSE new file mode 100644 index 0000000000..6a66aea5ea --- /dev/null +++ b/components/engine/client/transport/cancellable/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 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/client/transport/cancellable/canceler.go b/components/engine/client/transport/cancellable/canceler.go new file mode 100644 index 0000000000..62770b777b --- /dev/null +++ b/components/engine/client/transport/cancellable/canceler.go @@ -0,0 +1,23 @@ +// 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. + +// +build go1.5 + +package cancellable + +import ( + "net/http" + + "github.com/docker/docker/client/transport" +) + +func canceler(client transport.Sender, req *http.Request) func() { + // TODO(djd): Respect any existing value of req.Cancel. + ch := make(chan struct{}) + req.Cancel = ch + + return func() { + close(ch) + } +} diff --git a/components/engine/client/transport/cancellable/canceler_go14.go b/components/engine/client/transport/cancellable/canceler_go14.go new file mode 100644 index 0000000000..dd2723d94f --- /dev/null +++ b/components/engine/client/transport/cancellable/canceler_go14.go @@ -0,0 +1,27 @@ +// 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. + +// +build !go1.5 + +package cancellable + +import ( + "net/http" + + "github.com/docker/docker/client/transport" +) + +type requestCanceler interface { + CancelRequest(*http.Request) +} + +func canceler(client transport.Sender, req *http.Request) func() { + rc, ok := client.(requestCanceler) + if !ok { + return func() {} + } + return func() { + rc.CancelRequest(req) + } +} diff --git a/components/engine/client/transport/cancellable/cancellable.go b/components/engine/client/transport/cancellable/cancellable.go new file mode 100644 index 0000000000..1f8eac5c1c --- /dev/null +++ b/components/engine/client/transport/cancellable/cancellable.go @@ -0,0 +1,115 @@ +// 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. + +// Package cancellable provides helper function to cancel http requests. +package cancellable + +import ( + "io" + "net/http" + "sync" + + "github.com/docker/docker/client/transport" + + "golang.org/x/net/context" +) + +func nop() {} + +var ( + testHookContextDoneBeforeHeaders = nop + testHookDoReturned = nop + testHookDidBodyClose = nop +) + +// Do sends an HTTP request with the provided transport.Sender and returns an HTTP response. +// If the client is nil, http.DefaultClient is used. +// If the context is canceled or times out, ctx.Err() will be returned. +// +// FORK INFORMATION: +// +// This function deviates from the upstream version in golang.org/x/net/context/ctxhttp by +// taking a Sender interface rather than a *http.Client directly. That allow us to use +// this function with mocked clients and hijacked connections. +func Do(ctx context.Context, client transport.Sender, req *http.Request) (*http.Response, error) { + if client == nil { + client = http.DefaultClient + } + + // Request cancelation changed in Go 1.5, see canceler.go and canceler_go14.go. + cancel := canceler(client, req) + + type responseAndError struct { + resp *http.Response + err error + } + result := make(chan responseAndError, 1) + + go func() { + resp, err := client.Do(req) + testHookDoReturned() + result <- responseAndError{resp, err} + }() + + var resp *http.Response + + select { + case <-ctx.Done(): + testHookContextDoneBeforeHeaders() + cancel() + // Clean up after the goroutine calling client.Do: + go func() { + if r := <-result; r.resp != nil && r.resp.Body != nil { + testHookDidBodyClose() + r.resp.Body.Close() + } + }() + return nil, ctx.Err() + case r := <-result: + var err error + resp, err = r.resp, r.err + if err != nil { + return resp, err + } + } + + c := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + cancel() + case <-c: + // The response's Body is closed. + } + }() + resp.Body = ¬ifyingReader{ReadCloser: resp.Body, notify: c} + + return resp, nil +} + +// notifyingReader is an io.ReadCloser that closes the notify channel after +// Close is called or a Read fails on the underlying ReadCloser. +type notifyingReader struct { + io.ReadCloser + notify chan<- struct{} + notifyOnce sync.Once +} + +func (r *notifyingReader) Read(p []byte) (int, error) { + n, err := r.ReadCloser.Read(p) + if err != nil { + r.notifyOnce.Do(func() { + close(r.notify) + }) + } + return n, err +} + +func (r *notifyingReader) Close() error { + err := r.ReadCloser.Close() + r.notifyOnce.Do(func() { + close(r.notify) + }) + return err +} diff --git a/components/engine/client/transport/client.go b/components/engine/client/transport/client.go new file mode 100644 index 0000000000..13d4b3ab3d --- /dev/null +++ b/components/engine/client/transport/client.go @@ -0,0 +1,47 @@ +package transport + +import ( + "crypto/tls" + "net/http" +) + +// Sender is an interface that clients must implement +// to be able to send requests to a remote connection. +type Sender interface { + // Do sends request to a remote endpoint. + Do(*http.Request) (*http.Response, error) +} + +// Client is an interface that abstracts all remote connections. +type Client interface { + Sender + // Secure tells whether the connection is secure or not. + Secure() bool + // Scheme returns the connection protocol the client uses. + Scheme() string + // TLSConfig returns any TLS configuration the client uses. + TLSConfig() *tls.Config +} + +// tlsInfo returns information about the TLS configuration. +type tlsInfo struct { + tlsConfig *tls.Config +} + +// TLSConfig returns the TLS configuration. +func (t *tlsInfo) TLSConfig() *tls.Config { + return t.tlsConfig +} + +// Scheme returns protocol scheme to use. +func (t *tlsInfo) Scheme() string { + if t.tlsConfig != nil { + return "https" + } + return "http" +} + +// Secure returns true if there is a TLS configuration. +func (t *tlsInfo) Secure() bool { + return t.tlsConfig != nil +} diff --git a/components/engine/client/transport/tlsconfig_clone.go b/components/engine/client/transport/tlsconfig_clone.go new file mode 100644 index 0000000000..033d5dc0f2 --- /dev/null +++ b/components/engine/client/transport/tlsconfig_clone.go @@ -0,0 +1,11 @@ +// +build !go1.7,!windows + +package transport + +import "crypto/tls" + +// TLSConfigClone returns a clone of tls.Config. This function is provided for +// compatibility for go1.7 that doesn't include this method in stdlib. +func TLSConfigClone(c *tls.Config) *tls.Config { + return c.Clone() +} diff --git a/components/engine/client/transport/tlsconfig_clone_go17.go b/components/engine/client/transport/tlsconfig_clone_go17.go new file mode 100644 index 0000000000..a28c9141b2 --- /dev/null +++ b/components/engine/client/transport/tlsconfig_clone_go17.go @@ -0,0 +1,33 @@ +// +build go1.7 + +package transport + +import "crypto/tls" + +// TLSConfigClone returns a clone of tls.Config. This function is provided for +// compatibility for go1.7 that doesn't include this method in stdlib. +func TLSConfigClone(c *tls.Config) *tls.Config { + return &tls.Config{ + Rand: c.Rand, + Time: c.Time, + Certificates: c.Certificates, + NameToCertificate: c.NameToCertificate, + GetCertificate: c.GetCertificate, + RootCAs: c.RootCAs, + NextProtos: c.NextProtos, + ServerName: c.ServerName, + ClientAuth: c.ClientAuth, + ClientCAs: c.ClientCAs, + InsecureSkipVerify: c.InsecureSkipVerify, + CipherSuites: c.CipherSuites, + PreferServerCipherSuites: c.PreferServerCipherSuites, + SessionTicketsDisabled: c.SessionTicketsDisabled, + SessionTicketKey: c.SessionTicketKey, + ClientSessionCache: c.ClientSessionCache, + MinVersion: c.MinVersion, + MaxVersion: c.MaxVersion, + CurvePreferences: c.CurvePreferences, + DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled, + Renegotiation: c.Renegotiation, + } +} diff --git a/components/engine/client/transport/transport.go b/components/engine/client/transport/transport.go new file mode 100644 index 0000000000..ff28af1855 --- /dev/null +++ b/components/engine/client/transport/transport.go @@ -0,0 +1,57 @@ +// Package transport provides function to send request to remote endpoints. +package transport + +import ( + "fmt" + "net/http" + + "github.com/docker/go-connections/sockets" +) + +// apiTransport holds information about the http transport to connect with the API. +type apiTransport struct { + *http.Client + *tlsInfo + transport *http.Transport +} + +// NewTransportWithHTTP creates a new transport based on the provided proto, address and http client. +// It uses Docker's default http transport configuration if the client is nil. +// It does not modify the client's transport if it's not nil. +func NewTransportWithHTTP(proto, addr string, client *http.Client) (Client, error) { + var transport *http.Transport + + if client != nil { + tr, ok := client.Transport.(*http.Transport) + if !ok { + return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport) + } + transport = tr + } else { + transport = defaultTransport(proto, addr) + client = &http.Client{ + Transport: transport, + } + } + + return &apiTransport{ + Client: client, + tlsInfo: &tlsInfo{transport.TLSClientConfig}, + transport: transport, + }, nil +} + +// CancelRequest stops a request execution. +func (a *apiTransport) CancelRequest(req *http.Request) { + a.transport.CancelRequest(req) +} + +// defaultTransport creates a new http.Transport with Docker's +// default transport configuration. +func defaultTransport(proto, addr string) *http.Transport { + tr := new(http.Transport) + sockets.ConfigureTransport(tr, proto, addr) + return tr +} + +var _ Client = &apiTransport{} diff --git a/components/engine/client/version.go b/components/engine/client/version.go new file mode 100644 index 0000000000..933ceb4a49 --- /dev/null +++ b/components/engine/client/version.go @@ -0,0 +1,21 @@ +package client + +import ( + "encoding/json" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// ServerVersion returns information of the docker client and server host. +func (cli *Client) ServerVersion(ctx context.Context) (types.Version, error) { + resp, err := cli.get(ctx, "/version", nil, nil) + if err != nil { + return types.Version{}, err + } + + var server types.Version + err = json.NewDecoder(resp.body).Decode(&server) + ensureReaderClosed(resp) + return server, err +} diff --git a/components/engine/client/volume_create.go b/components/engine/client/volume_create.go new file mode 100644 index 0000000000..f3a79f1e11 --- /dev/null +++ b/components/engine/client/volume_create.go @@ -0,0 +1,20 @@ +package client + +import ( + "encoding/json" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// VolumeCreate creates a volume in the docker host. +func (cli *Client) VolumeCreate(ctx context.Context, options types.VolumeCreateRequest) (types.Volume, error) { + var volume types.Volume + resp, err := cli.post(ctx, "/volumes/create", nil, options, nil) + if err != nil { + return volume, err + } + err = json.NewDecoder(resp.body).Decode(&volume) + ensureReaderClosed(resp) + return volume, err +} diff --git a/components/engine/client/volume_create_test.go b/components/engine/client/volume_create_test.go new file mode 100644 index 0000000000..d3cfa7132f --- /dev/null +++ b/components/engine/client/volume_create_test.go @@ -0,0 +1,74 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestVolumeCreateError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.VolumeCreate(context.Background(), types.VolumeCreateRequest{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestVolumeCreate(t *testing.T) { + expectedURL := "/volumes/create" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + + content, err := json.Marshal(types.Volume{ + Name: "volume", + Driver: "local", + Mountpoint: "mountpoint", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + volume, err := client.VolumeCreate(context.Background(), types.VolumeCreateRequest{ + Name: "myvolume", + Driver: "mydriver", + DriverOpts: map[string]string{ + "opt-key": "opt-value", + }, + }) + if err != nil { + t.Fatal(err) + } + if volume.Name != "volume" { + t.Fatalf("expected volume.Name to be 'volume', got %s", volume.Name) + } + if volume.Driver != "local" { + t.Fatalf("expected volume.Driver to be 'local', got %s", volume.Driver) + } + if volume.Mountpoint != "mountpoint" { + t.Fatalf("expected volume.Mountpoint to be 'mountpoint', got %s", volume.Mountpoint) + } +} diff --git a/components/engine/client/volume_inspect.go b/components/engine/client/volume_inspect.go new file mode 100644 index 0000000000..3860e9b22c --- /dev/null +++ b/components/engine/client/volume_inspect.go @@ -0,0 +1,38 @@ +package client + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// VolumeInspect returns the information about a specific volume in the docker host. +func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) { + volume, _, err := cli.VolumeInspectWithRaw(ctx, volumeID) + return volume, err +} + +// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation +func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) { + var volume types.Volume + resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil) + if err != nil { + if resp.statusCode == http.StatusNotFound { + return volume, nil, volumeNotFoundError{volumeID} + } + return volume, nil, err + } + defer ensureReaderClosed(resp) + + body, err := ioutil.ReadAll(resp.body) + if err != nil { + return volume, nil, err + } + rdr := bytes.NewReader(body) + err = json.NewDecoder(rdr).Decode(&volume) + return volume, body, err +} diff --git a/components/engine/client/volume_inspect_test.go b/components/engine/client/volume_inspect_test.go new file mode 100644 index 0000000000..4b9f47358d --- /dev/null +++ b/components/engine/client/volume_inspect_test.go @@ -0,0 +1,76 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +func TestVolumeInspectError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.VolumeInspect(context.Background(), "nothing") + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestVolumeInspectNotFound(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), + } + + _, err := client.VolumeInspect(context.Background(), "unknown") + if err == nil || !IsErrVolumeNotFound(err) { + t.Fatalf("expected a volumeNotFound error, got %v", err) + } +} + +func TestVolumeInspect(t *testing.T) { + expectedURL := "/volumes/volume_id" + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "GET" { + return nil, fmt.Errorf("expected GET method, got %s", req.Method) + } + content, err := json.Marshal(types.Volume{ + Name: "name", + Driver: "driver", + Mountpoint: "mountpoint", + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + v, err := client.VolumeInspect(context.Background(), "volume_id") + if err != nil { + t.Fatal(err) + } + if v.Name != "name" { + t.Fatalf("expected `name`, got %s", v.Name) + } + if v.Driver != "driver" { + t.Fatalf("expected `driver`, got %s", v.Driver) + } + if v.Mountpoint != "mountpoint" { + t.Fatalf("expected `mountpoint`, got %s", v.Mountpoint) + } +} diff --git a/components/engine/client/volume_list.go b/components/engine/client/volume_list.go new file mode 100644 index 0000000000..44f03cfac7 --- /dev/null +++ b/components/engine/client/volume_list.go @@ -0,0 +1,32 @@ +package client + +import ( + "encoding/json" + "net/url" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "golang.org/x/net/context" +) + +// VolumeList returns the volumes configured in the docker host. +func (cli *Client) VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error) { + var volumes types.VolumesListResponse + query := url.Values{} + + if filter.Len() > 0 { + filterJSON, err := filters.ToParamWithVersion(cli.version, filter) + if err != nil { + return volumes, err + } + query.Set("filters", filterJSON) + } + resp, err := cli.get(ctx, "/volumes", query, nil) + if err != nil { + return volumes, err + } + + err = json.NewDecoder(resp.body).Decode(&volumes) + ensureReaderClosed(resp) + return volumes, err +} diff --git a/components/engine/client/volume_list_test.go b/components/engine/client/volume_list_test.go new file mode 100644 index 0000000000..d30d9fcd52 --- /dev/null +++ b/components/engine/client/volume_list_test.go @@ -0,0 +1,97 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "golang.org/x/net/context" +) + +func TestVolumeListError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + _, err := client.VolumeList(context.Background(), filters.NewArgs()) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestVolumeList(t *testing.T) { + expectedURL := "/volumes" + + noDanglingFilters := filters.NewArgs() + noDanglingFilters.Add("dangling", "false") + + danglingFilters := filters.NewArgs() + danglingFilters.Add("dangling", "true") + + labelFilters := filters.NewArgs() + labelFilters.Add("label", "label1") + labelFilters.Add("label", "label2") + + listCases := []struct { + filters filters.Args + expectedFilters string + }{ + { + filters: filters.NewArgs(), + expectedFilters: "", + }, { + filters: noDanglingFilters, + expectedFilters: `{"dangling":{"false":true}}`, + }, { + filters: danglingFilters, + expectedFilters: `{"dangling":{"true":true}}`, + }, { + filters: labelFilters, + expectedFilters: `{"label":{"label1":true,"label2":true}}`, + }, + } + + for _, listCase := range listCases { + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + query := req.URL.Query() + actualFilters := query.Get("filters") + if actualFilters != listCase.expectedFilters { + return nil, fmt.Errorf("filters not set in URL query properly. Expected '%s', got %s", listCase.expectedFilters, actualFilters) + } + content, err := json.Marshal(types.VolumesListResponse{ + Volumes: []*types.Volume{ + { + Name: "volume", + Driver: "local", + }, + }, + }) + if err != nil { + return nil, err + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(content)), + }, nil + }), + } + + volumeResponse, err := client.VolumeList(context.Background(), listCase.filters) + if err != nil { + t.Fatal(err) + } + if len(volumeResponse.Volumes) != 1 { + t.Fatalf("expected 1 volume, got %v", volumeResponse.Volumes) + } + } +} diff --git a/components/engine/client/volume_remove.go b/components/engine/client/volume_remove.go new file mode 100644 index 0000000000..3d5aeff252 --- /dev/null +++ b/components/engine/client/volume_remove.go @@ -0,0 +1,18 @@ +package client + +import ( + "net/url" + + "golang.org/x/net/context" +) + +// VolumeRemove removes a volume from the docker host. +func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error { + query := url.Values{} + if force { + query.Set("force", "1") + } + resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil) + ensureReaderClosed(resp) + return err +} diff --git a/components/engine/client/volume_remove_test.go b/components/engine/client/volume_remove_test.go new file mode 100644 index 0000000000..0675bfd458 --- /dev/null +++ b/components/engine/client/volume_remove_test.go @@ -0,0 +1,47 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" +) + +func TestVolumeRemoveError(t *testing.T) { + client := &Client{ + transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.VolumeRemove(context.Background(), "volume_id", false) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestVolumeRemove(t *testing.T) { + expectedURL := "/volumes/volume_id" + + client := &Client{ + transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "DELETE" { + return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), + }, nil + }), + } + + err := client.VolumeRemove(context.Background(), "volume_id", false) + if err != nil { + t.Fatal(err) + } +} diff --git a/components/engine/daemon/cluster/executor/container/adapter.go b/components/engine/daemon/cluster/executor/container/adapter.go index e3840aca3e..85a7017ba1 100644 --- a/components/engine/daemon/cluster/executor/container/adapter.go +++ b/components/engine/daemon/cluster/executor/container/adapter.go @@ -11,10 +11,10 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/server/httputils" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/versions" executorpkg "github.com/docker/docker/daemon/cluster/executor" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/events" - "github.com/docker/engine-api/types/versions" "github.com/docker/libnetwork" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/log" diff --git a/components/engine/daemon/cluster/executor/container/container.go b/components/engine/daemon/cluster/executor/container/container.go index a5209ca0a2..c48873121b 100644 --- a/components/engine/daemon/cluster/executor/container/container.go +++ b/components/engine/daemon/cluster/executor/container/container.go @@ -9,13 +9,13 @@ import ( "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + enginecontainer "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" clustertypes "github.com/docker/docker/daemon/cluster/provider" "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - enginecontainer "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/events" - "github.com/docker/engine-api/types/filters" - "github.com/docker/engine-api/types/network" "github.com/docker/swarmkit/agent/exec" "github.com/docker/swarmkit/api" ) diff --git a/components/engine/daemon/cluster/executor/container/controller.go b/components/engine/daemon/cluster/executor/container/controller.go index fc488fd5fc..7b8ed663e8 100644 --- a/components/engine/daemon/cluster/executor/container/controller.go +++ b/components/engine/daemon/cluster/executor/container/controller.go @@ -4,9 +4,9 @@ import ( "fmt" "os" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/events" executorpkg "github.com/docker/docker/daemon/cluster/executor" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/events" "github.com/docker/libnetwork" "github.com/docker/swarmkit/agent/exec" "github.com/docker/swarmkit/api" diff --git a/components/engine/daemon/cluster/executor/container/executor.go b/components/engine/daemon/cluster/executor/container/executor.go index b535db0c1a..2295c44108 100644 --- a/components/engine/daemon/cluster/executor/container/executor.go +++ b/components/engine/daemon/cluster/executor/container/executor.go @@ -4,10 +4,10 @@ import ( "sort" "strings" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" executorpkg "github.com/docker/docker/daemon/cluster/executor" clustertypes "github.com/docker/docker/daemon/cluster/provider" - "github.com/docker/engine-api/types" - "github.com/docker/engine-api/types/network" networktypes "github.com/docker/libnetwork/types" "github.com/docker/swarmkit/agent/exec" "github.com/docker/swarmkit/api" diff --git a/components/engine/daemon/cluster/executor/container/health_test.go b/components/engine/daemon/cluster/executor/container/health_test.go index 472624b54d..16a1e0c096 100644 --- a/components/engine/daemon/cluster/executor/container/health_test.go +++ b/components/engine/daemon/cluster/executor/container/health_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/events" - containertypes "github.com/docker/engine-api/types/container" "github.com/docker/swarmkit/api" "golang.org/x/net/context" ) diff --git a/components/engine/daemon/logs.go b/components/engine/daemon/logs.go index 55ef1bac48..0e1fdfaa5a 100644 --- a/components/engine/daemon/logs.go +++ b/components/engine/daemon/logs.go @@ -68,7 +68,8 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c close(started) wf.Flush() - var outStream io.Writer = wf + var outStream io.Writer + outStream = wf errStream := outStream if !container.Config.Tty { errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)