From 4f63bfb619a88fa3df8b57bf1b11e89f90250061 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 2 Nov 2016 17:43:32 -0700 Subject: [PATCH] always add but hide experimental cmds and flags Signed-off-by: Victor Vieux update cobra and use Tags Signed-off-by: Victor Vieux allow client to talk to an older server Signed-off-by: Victor Vieux --- client.go | 17 ++++++++++++++--- container_create.go | 5 +++++ container_exec.go | 5 +++++ container_prune.go | 4 ++++ errors.go | 11 +++++++++++ image_build.go | 7 +++++-- image_prune.go | 4 ++++ interface.go | 2 +- ping.go | 29 ++++++++++++++++++++--------- request.go | 3 +++ volume_prune.go | 4 ++++ volume_remove.go | 7 +++++-- 12 files changed, 81 insertions(+), 17 deletions(-) diff --git a/client.go b/client.go index 3b97720e00..76a1ac07c0 100644 --- a/client.go +++ b/client.go @@ -79,6 +79,8 @@ type Client struct { version string // custom http headers configured by users. customHTTPHeaders map[string]string + // manualOverride is set to true when the version was set by users. + manualOverride bool } // NewEnvClient initializes a new API client based on environment variables. @@ -111,13 +113,19 @@ func NewEnvClient() (*Client, error) { if host == "" { host = DefaultDockerHost } - version := os.Getenv("DOCKER_API_VERSION") if version == "" { version = DefaultVersion } - return NewClient(host, version, client, nil) + cli, err := NewClient(host, version, client, nil) + if err != nil { + return cli, err + } + if version != "" { + cli.manualOverride = true + } + return cli, nil } // NewClient initializes a new API client for the given host and API version. @@ -211,7 +219,10 @@ func (cli *Client) ClientVersion() string { // UpdateClientVersion updates the version string associated with this // instance of the Client. func (cli *Client) UpdateClientVersion(v string) { - cli.version = v + if !cli.manualOverride { + cli.version = v + } + } // ParseHost verifies that the given host strings is valid. diff --git a/container_create.go b/container_create.go index c042b17468..9f627aafa6 100644 --- a/container_create.go +++ b/container_create.go @@ -20,6 +20,11 @@ type configWrapper struct { // 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) (container.ContainerCreateCreatedBody, error) { var response container.ContainerCreateCreatedBody + + if err := cli.NewVersionError("1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil { + return response, err + } + query := url.Values{} if containerName != "" { query.Set("name", containerName) diff --git a/container_exec.go b/container_exec.go index f6df722918..0665c54fbd 100644 --- a/container_exec.go +++ b/container_exec.go @@ -10,6 +10,11 @@ import ( // ContainerExecCreate creates a new exec configuration to run an exec process. func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error) { var response types.IDResponse + + if err := cli.NewVersionError("1.25", "env"); len(config.Env) != 0 && err != nil { + return response, err + } + resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, config, nil) if err != nil { return response, err diff --git a/container_prune.go b/container_prune.go index 0d8bd3292c..3eabe71a7f 100644 --- a/container_prune.go +++ b/container_prune.go @@ -12,6 +12,10 @@ import ( func (cli *Client) ContainersPrune(ctx context.Context, cfg types.ContainersPruneConfig) (types.ContainersPruneReport, error) { var report types.ContainersPruneReport + if err := cli.NewVersionError("1.25", "container prune"); err != nil { + return report, err + } + serverResp, err := cli.post(ctx, "/containers/prune", nil, cfg, nil) if err != nil { return report, err diff --git a/errors.go b/errors.go index ad1dadabb6..53e2065332 100644 --- a/errors.go +++ b/errors.go @@ -3,6 +3,8 @@ package client import ( "errors" "fmt" + + "github.com/docker/docker/api/types/versions" ) // ErrConnectionFailed is an error raised when the connection between the client and the server failed. @@ -206,3 +208,12 @@ func IsErrPluginPermissionDenied(err error) bool { _, ok := err.(pluginPermissionDenied) return ok } + +// NewVersionError returns an error if the APIVersion required +// if less than the current supported version +func (cli *Client) NewVersionError(APIrequired, feature string) error { + if versions.LessThan(cli.version, APIrequired) { + return fmt.Errorf("%q requires API version %s, but the Docker server is version %s", feature, APIrequired, cli.version) + } + return nil +} diff --git a/image_build.go b/image_build.go index 4d611d5430..0049e4e290 100644 --- a/image_build.go +++ b/image_build.go @@ -21,7 +21,7 @@ var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`) // 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) + query, err := cli.imageBuildOptionsToQuery(options) if err != nil { return types.ImageBuildResponse{}, err } @@ -47,7 +47,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio }, nil } -func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) { +func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) { query := url.Values{ "t": options.Tags, "securityopt": options.SecurityOpt, @@ -76,6 +76,9 @@ func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, erro } if options.Squash { + if err := cli.NewVersionError("1.25", "squash"); err != nil { + return query, err + } query.Set("squash", "1") } diff --git a/image_prune.go b/image_prune.go index f6752e5043..d5e69d5b19 100644 --- a/image_prune.go +++ b/image_prune.go @@ -12,6 +12,10 @@ import ( func (cli *Client) ImagesPrune(ctx context.Context, cfg types.ImagesPruneConfig) (types.ImagesPruneReport, error) { var report types.ImagesPruneReport + if err := cli.NewVersionError("1.25", "image prune"); err != nil { + return report, err + } + serverResp, err := cli.post(ctx, "/images/prune", nil, cfg, nil) if err != nil { return report, err diff --git a/interface.go b/interface.go index a78cb759cd..99b06709b5 100644 --- a/interface.go +++ b/interface.go @@ -129,7 +129,7 @@ type SystemAPIClient interface { Info(ctx context.Context) (types.Info, error) RegistryLogin(ctx context.Context, auth types.AuthConfig) (registry.AuthenticateOKBody, error) DiskUsage(ctx context.Context) (types.DiskUsage, error) - Ping(ctx context.Context) (bool, error) + Ping(ctx context.Context) (types.Ping, error) } // VolumeAPIClient defines API client methods for the volumes diff --git a/ping.go b/ping.go index 5e99e1bba1..22dcda24fd 100644 --- a/ping.go +++ b/ping.go @@ -1,19 +1,30 @@ package client -import "golang.org/x/net/context" +import ( + "fmt" -// Ping pings the server and return the value of the "Docker-Experimental" header -func (cli *Client) Ping(ctx context.Context) (bool, error) { - serverResp, err := cli.get(ctx, "/_ping", nil, nil) + "github.com/docker/docker/api/types" + "golang.org/x/net/context" +) + +// Ping pings the server and return the value of the "Docker-Experimental" & "API-Version" headers +func (cli *Client) Ping(ctx context.Context) (types.Ping, error) { + var ping types.Ping + req, err := cli.buildRequest("GET", fmt.Sprintf("%s/_ping", cli.basePath), nil, nil) if err != nil { - return false, err + return ping, err + } + serverResp, err := cli.doRequest(ctx, req) + if err != nil { + return ping, err } defer ensureReaderClosed(serverResp) - exp := serverResp.header.Get("Docker-Experimental") - if exp != "true" { - return false, nil + ping.APIVersion = serverResp.header.Get("API-Version") + + if serverResp.header.Get("Docker-Experimental") == "true" { + ping.Experimental = true } - return true, nil + return ping, nil } diff --git a/request.go b/request.go index c73464b54d..ac05363655 100644 --- a/request.go +++ b/request.go @@ -214,6 +214,9 @@ func (cli *Client) addHeaders(req *http.Request, headers headers) *http.Request // 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 { + if versions.LessThan(cli.version, "1.25") && k == "User-Agent" { + continue + } req.Header.Set(k, v) } diff --git a/volume_prune.go b/volume_prune.go index e7ea7b591d..ea4e234a30 100644 --- a/volume_prune.go +++ b/volume_prune.go @@ -12,6 +12,10 @@ import ( func (cli *Client) VolumesPrune(ctx context.Context, cfg types.VolumesPruneConfig) (types.VolumesPruneReport, error) { var report types.VolumesPruneReport + if err := cli.NewVersionError("1.25", "volume prune"); err != nil { + return report, err + } + serverResp, err := cli.post(ctx, "/volumes/prune", nil, cfg, nil) if err != nil { return report, err diff --git a/volume_remove.go b/volume_remove.go index 3d5aeff252..6c26575b49 100644 --- a/volume_remove.go +++ b/volume_remove.go @@ -3,14 +3,17 @@ package client import ( "net/url" + "github.com/docker/docker/api/types/versions" "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") + if versions.GreaterThanOrEqualTo(cli.version, "1.25") { + if force { + query.Set("force", "1") + } } resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil) ensureReaderClosed(resp)