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
// 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.

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
}

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
// 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")
}

View File

@ -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

View File

@ -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

29
ping.go
View File

@ -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
}

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
// 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)
}

View File

@ -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

View File

@ -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)