always add but hide experimental cmds and flags

Signed-off-by: Victor Vieux <vieux@docker.com>

update cobra and use Tags

Signed-off-by: Victor Vieux <vieux@docker.com>

allow client to talk to an older server

Signed-off-by: Victor Vieux <vieux@docker.com>
This commit is contained in:
Victor Vieux 2016-11-02 17:43:32 -07:00
parent 3f7264473d
commit 4f63bfb619
12 changed files with 81 additions and 17 deletions

View File

@ -79,6 +79,8 @@ type Client struct {
version string version string
// custom http headers configured by users. // custom http headers configured by users.
customHTTPHeaders map[string]string 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. // NewEnvClient initializes a new API client based on environment variables.
@ -111,13 +113,19 @@ func NewEnvClient() (*Client, error) {
if host == "" { if host == "" {
host = DefaultDockerHost host = DefaultDockerHost
} }
version := os.Getenv("DOCKER_API_VERSION") version := os.Getenv("DOCKER_API_VERSION")
if version == "" { if version == "" {
version = DefaultVersion 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. // 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 // UpdateClientVersion updates the version string associated with this
// instance of the Client. // instance of the Client.
func (cli *Client) UpdateClientVersion(v string) { func (cli *Client) UpdateClientVersion(v string) {
cli.version = v if !cli.manualOverride {
cli.version = v
}
} }
// ParseHost verifies that the given host strings is valid. // ParseHost verifies that the given host strings is valid.

View File

@ -20,6 +20,11 @@ type configWrapper struct {
// It can be associated with a name, but it's not mandatory. // 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) { 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 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{} query := url.Values{}
if containerName != "" { if containerName != "" {
query.Set("name", containerName) query.Set("name", containerName)

View File

@ -10,6 +10,11 @@ import (
// ContainerExecCreate creates a new exec configuration to run an exec process. // 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) { func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error) {
var response types.IDResponse 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) resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, config, nil)
if err != nil { if err != nil {
return response, err return response, err

View File

@ -12,6 +12,10 @@ import (
func (cli *Client) ContainersPrune(ctx context.Context, cfg types.ContainersPruneConfig) (types.ContainersPruneReport, error) { func (cli *Client) ContainersPrune(ctx context.Context, cfg types.ContainersPruneConfig) (types.ContainersPruneReport, error) {
var report types.ContainersPruneReport 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) serverResp, err := cli.post(ctx, "/containers/prune", nil, cfg, nil)
if err != nil { if err != nil {
return report, err return report, err

View File

@ -3,6 +3,8 @@ package client
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/docker/docker/api/types/versions"
) )
// ErrConnectionFailed is an error raised when the connection between the client and the server failed. // 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) _, ok := err.(pluginPermissionDenied)
return ok 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
}

View File

@ -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 // The Body in the response implement an io.ReadCloser and it's up to the caller to
// close it. // close it.
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { 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 { if err != nil {
return types.ImageBuildResponse{}, err return types.ImageBuildResponse{}, err
} }
@ -47,7 +47,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
}, nil }, nil
} }
func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) { func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) {
query := url.Values{ query := url.Values{
"t": options.Tags, "t": options.Tags,
"securityopt": options.SecurityOpt, "securityopt": options.SecurityOpt,
@ -76,6 +76,9 @@ func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, erro
} }
if options.Squash { if options.Squash {
if err := cli.NewVersionError("1.25", "squash"); err != nil {
return query, err
}
query.Set("squash", "1") query.Set("squash", "1")
} }

View File

@ -12,6 +12,10 @@ import (
func (cli *Client) ImagesPrune(ctx context.Context, cfg types.ImagesPruneConfig) (types.ImagesPruneReport, error) { func (cli *Client) ImagesPrune(ctx context.Context, cfg types.ImagesPruneConfig) (types.ImagesPruneReport, error) {
var report types.ImagesPruneReport 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) serverResp, err := cli.post(ctx, "/images/prune", nil, cfg, nil)
if err != nil { if err != nil {
return report, err return report, err

View File

@ -129,7 +129,7 @@ type SystemAPIClient interface {
Info(ctx context.Context) (types.Info, error) Info(ctx context.Context) (types.Info, error)
RegistryLogin(ctx context.Context, auth types.AuthConfig) (registry.AuthenticateOKBody, error) RegistryLogin(ctx context.Context, auth types.AuthConfig) (registry.AuthenticateOKBody, error)
DiskUsage(ctx context.Context) (types.DiskUsage, 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 // VolumeAPIClient defines API client methods for the volumes

29
ping.go
View File

@ -1,19 +1,30 @@
package client package client
import "golang.org/x/net/context" import (
"fmt"
// Ping pings the server and return the value of the "Docker-Experimental" header "github.com/docker/docker/api/types"
func (cli *Client) Ping(ctx context.Context) (bool, error) { "golang.org/x/net/context"
serverResp, err := cli.get(ctx, "/_ping", nil, nil) )
// 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 { if err != nil {
return false, err return ping, err
}
serverResp, err := cli.doRequest(ctx, req)
if err != nil {
return ping, err
} }
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)
exp := serverResp.header.Get("Docker-Experimental") ping.APIVersion = serverResp.header.Get("API-Version")
if exp != "true" {
return false, nil if serverResp.header.Get("Docker-Experimental") == "true" {
ping.Experimental = true
} }
return true, nil return ping, nil
} }

View File

@ -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 // Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers // then the user can't change OUR headers
for k, v := range cli.customHTTPHeaders { for k, v := range cli.customHTTPHeaders {
if versions.LessThan(cli.version, "1.25") && k == "User-Agent" {
continue
}
req.Header.Set(k, v) req.Header.Set(k, v)
} }

View File

@ -12,6 +12,10 @@ import (
func (cli *Client) VolumesPrune(ctx context.Context, cfg types.VolumesPruneConfig) (types.VolumesPruneReport, error) { func (cli *Client) VolumesPrune(ctx context.Context, cfg types.VolumesPruneConfig) (types.VolumesPruneReport, error) {
var report types.VolumesPruneReport 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) serverResp, err := cli.post(ctx, "/volumes/prune", nil, cfg, nil)
if err != nil { if err != nil {
return report, err return report, err

View File

@ -3,14 +3,17 @@ package client
import ( import (
"net/url" "net/url"
"github.com/docker/docker/api/types/versions"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// VolumeRemove removes a volume from the docker host. // VolumeRemove removes a volume from the docker host.
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error { func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
query := url.Values{} query := url.Values{}
if force { if versions.GreaterThanOrEqualTo(cli.version, "1.25") {
query.Set("force", "1") if force {
query.Set("force", "1")
}
} }
resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil) resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)