Merge pull request #27745 from vieux/cli_backward_compose_api

allow client to talk to an older server
This commit is contained in:
Victor Vieux 2016-11-08 18:27:23 -08:00 committed by GitHub
commit d3c780bb90
51 changed files with 192 additions and 182 deletions

View file

@ -1,47 +0,0 @@
package middleware
import (
"net/http"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types/versions"
"golang.org/x/net/context"
)
// UserAgentMiddleware is a middleware that
// validates the client user-agent.
type UserAgentMiddleware struct {
serverVersion string
}
// NewUserAgentMiddleware creates a new UserAgentMiddleware
// with the server version.
func NewUserAgentMiddleware(s string) UserAgentMiddleware {
return UserAgentMiddleware{
serverVersion: s,
}
}
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
func (u UserAgentMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
ctx = context.WithValue(ctx, httputils.UAStringKey, r.Header.Get("User-Agent"))
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
// v1.20 onwards includes the GOOS of the client after the version
// such as Docker/1.7.0 (linux)
if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
userAgent[1] = strings.Split(userAgent[1], " ")[0]
}
if len(userAgent) == 2 && !versions.Equal(u.serverVersion, userAgent[1]) {
logrus.Debugf("Client and server don't have the same version (client: %s, server: %s)", userAgent[1], u.serverVersion)
}
}
return handler(ctx, w, r, vars)
}
}

View file

@ -36,15 +36,13 @@ func (v VersionMiddleware) WrapHandler(handler func(ctx context.Context, w http.
apiVersion = v.defaultVersion
}
if versions.GreaterThan(apiVersion, v.defaultVersion) {
return errors.NewBadRequestError(fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", apiVersion, v.defaultVersion))
}
if versions.LessThan(apiVersion, v.minVersion) {
return errors.NewBadRequestError(fmt.Errorf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", apiVersion, v.minVersion))
}
header := fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS)
w.Header().Set("Server", header)
w.Header().Set("API-Version", v.defaultVersion)
ctx = context.WithValue(ctx, "api-version", apiVersion)
return handler(ctx, w, r, vars)
}

View file

@ -54,10 +54,4 @@ func TestVersionMiddlewareWithErrors(t *testing.T) {
if !strings.Contains(err.Error(), "client version 0.1 is too old. Minimum supported API version is 1.2.0") {
t.Fatalf("Expected too old client error, got %v", err)
}
vars["version"] = "100000"
err = h(ctx, resp, req, vars)
if !strings.Contains(err.Error(), "client is newer than server") {
t.Fatalf("Expected client newer than server error, got %v", err)
}
}

View file

@ -128,7 +128,7 @@ func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
// apply to all requests. Data that is specific to the
// immediate function being called should still be passed
// as 'args' on the function call.
ctx := context.Background()
ctx := context.WithValue(context.Background(), httputils.UAStringKey, r.Header.Get("User-Agent"))
handlerFunc := s.handlerWithGlobalMiddlewares(handler)
vars := mux.Vars(r)

View file

@ -128,6 +128,13 @@ type ContainerProcessList struct {
Titles []string
}
// Info contains response of Remote API:
// GET "/_ping"
type Ping struct {
APIVersion string
Experimental bool
}
// Version contains response of Remote API:
// GET "/version"
type Version struct {

View file

@ -1,8 +1,6 @@
package checkpoint
import (
"fmt"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/spf13/cobra"
@ -15,9 +13,10 @@ func NewCheckpointCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Manage checkpoints",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
Tags: map[string]string{"experimental": ""},
Tags: map[string]string{"experimental": "", "version": "1.25"},
}
cmd.AddCommand(
newCreateCommand(dockerCli),

View file

@ -10,6 +10,7 @@ import (
"runtime"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types/versions"
cliflags "github.com/docker/docker/cli/flags"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/cliconfig/configfile"
@ -32,21 +33,24 @@ type Streams interface {
// DockerCli represents the docker command line client.
// Instances of the client can be returned from NewDockerCli.
type DockerCli struct {
configFile *configfile.ConfigFile
in *InStream
out *OutStream
err io.Writer
keyFile string
client client.APIClient
configFile *configfile.ConfigFile
in *InStream
out *OutStream
err io.Writer
keyFile string
client client.APIClient
hasExperimental bool
defaultVersion string
}
// HasExperimental returns true if experimental features are accessible
// HasExperimental returns true if experimental features are accessible.
func (cli *DockerCli) HasExperimental() bool {
if cli.client == nil {
return false
}
enabled, _ := cli.client.Ping(context.Background())
return enabled
return cli.hasExperimental
}
// DefaultVersion returns api.defaultVersion of DOCKER_API_VERSION if specified.
func (cli *DockerCli) DefaultVersion() string {
return cli.defaultVersion
}
// Client returns the APIClient
@ -93,12 +97,28 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
if err != nil {
return err
}
cli.defaultVersion = cli.client.ClientVersion()
if opts.Common.TrustKey == "" {
cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
} else {
cli.keyFile = opts.Common.TrustKey
}
if ping, err := cli.client.Ping(context.Background()); err == nil {
cli.hasExperimental = ping.Experimental
// since the new header was added in 1.25, assume server is 1.24 if header is not present.
if ping.APIVersion == "" {
ping.APIVersion = "1.24"
}
// if server version is lower than the current cli, downgrade
if versions.LessThan(ping.APIVersion, cli.client.ClientVersion()) {
cli.client.UpdateClientVersion(ping.APIVersion)
}
}
return nil
}

View file

@ -1,8 +1,6 @@
package container
import (
"fmt"
"github.com/spf13/cobra"
"github.com/docker/docker/cli"
@ -16,7 +14,8 @@ func NewContainerCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Manage containers",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
}
cmd.AddCommand(

View file

@ -59,6 +59,7 @@ func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command {
flags.StringVarP(&opts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
flags.BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the command")
flags.VarP(opts.env, "env", "e", "Set environment variables")
flags.SetAnnotation("env", "version", []string{"1.25"})
return cmd
}

View file

@ -35,6 +35,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
return nil
},
Tags: map[string]string{"version": "1.25"},
}
flags := cmd.Flags()

View file

@ -113,6 +113,7 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
flags.SetAnnotation("squash", "experimental", nil)
flags.SetAnnotation("squash", "version", []string{"1.25"})
return cmd
}
@ -144,7 +145,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
progBuff io.Writer
buildBuff io.Writer
)
specifiedContext := options.context
progBuff = dockerCli.Out()
buildBuff = dockerCli.Out()

View file

@ -1,8 +1,6 @@
package image
import (
"fmt"
"github.com/spf13/cobra"
"github.com/docker/docker/cli"
@ -16,7 +14,8 @@ func NewImageCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Manage images",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
}
cmd.AddCommand(
@ -33,6 +32,5 @@ func NewImageCommand(dockerCli *command.DockerCli) *cobra.Command {
newInspectCommand(dockerCli),
NewPruneCommand(dockerCli),
)
return cmd
}

View file

@ -36,6 +36,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
return nil
},
Tags: map[string]string{"version": "1.25"},
}
flags := cmd.Flags()

View file

@ -1,8 +1,6 @@
package network
import (
"fmt"
"github.com/spf13/cobra"
"github.com/docker/docker/cli"
@ -16,7 +14,8 @@ func NewNetworkCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Manage networks",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
}
cmd.AddCommand(

View file

@ -33,6 +33,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
}
return nil
},
Tags: map[string]string{"version": "1.25"},
}
flags := cmd.Flags()

View file

@ -1,8 +1,6 @@
package node
import (
"fmt"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
apiclient "github.com/docker/docker/client"
@ -17,7 +15,8 @@ func NewNodeCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Manage Swarm nodes",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
}
cmd.AddCommand(

View file

@ -1,8 +1,6 @@
package plugin
import (
"fmt"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/spf13/cobra"
@ -15,7 +13,8 @@ func NewPluginCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Manage plugins",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
Tags: map[string]string{"experimental": ""},
}

View file

@ -1,8 +1,6 @@
package service
import (
"fmt"
"github.com/spf13/cobra"
"github.com/docker/docker/cli"
@ -16,7 +14,8 @@ func NewServiceCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Manage services",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
}
cmd.AddCommand(

View file

@ -1,8 +1,6 @@
package stack
import (
"fmt"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/spf13/cobra"
@ -15,9 +13,10 @@ func NewStackCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Manage Docker stacks",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
Tags: map[string]string{"experimental": ""},
Tags: map[string]string{"experimental": "", "version": "1.25"},
}
cmd.AddCommand(
newConfigCommand(dockerCli),

View file

@ -36,7 +36,7 @@ func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
opts.namespace = strings.TrimSuffix(args[0], ".dab")
return runDeploy(dockerCli, opts)
},
Tags: map[string]string{"experimental": ""},
Tags: map[string]string{"experimental": "", "version": "1.25"},
}
flags := cmd.Flags()

View file

@ -1,8 +1,6 @@
package swarm
import (
"fmt"
"github.com/spf13/cobra"
"github.com/docker/docker/cli"
@ -16,7 +14,8 @@ func NewSwarmCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Manage Swarm",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
}
cmd.AddCommand(

View file

@ -1,8 +1,6 @@
package system
import (
"fmt"
"github.com/spf13/cobra"
"github.com/docker/docker/cli"
@ -16,7 +14,8 @@ func NewSystemCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Manage Docker",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
}
cmd.AddCommand(
@ -25,5 +24,6 @@ func NewSystemCommand(dockerCli *command.DockerCli) *cobra.Command {
NewDiskUsageCommand(dockerCli),
NewPruneCommand(dockerCli),
)
return cmd
}

View file

@ -23,6 +23,7 @@ func NewDiskUsageCommand(dockerCli *command.DockerCli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runDiskUsage(dockerCli, opts)
},
Tags: map[string]string{"version": "1.25"},
}
flags := cmd.Flags()

View file

@ -26,6 +26,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runPrune(dockerCli, opts)
},
Tags: map[string]string{"version": "1.25"},
}
flags := cmd.Flags()

View file

@ -1,6 +1,7 @@
package system
import (
"fmt"
"runtime"
"time"
@ -70,10 +71,15 @@ func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
Status: "Template parsing error: " + err.Error()}
}
APIVersion := dockerCli.Client().ClientVersion()
if defaultAPIVersion := dockerCli.DefaultVersion(); APIVersion != defaultAPIVersion {
APIVersion = fmt.Sprintf("%s (downgraded from %s)", APIVersion, defaultAPIVersion)
}
vd := types.VersionResponse{
Client: &types.Version{
Version: dockerversion.Version,
APIVersion: dockerCli.Client().ClientVersion(),
APIVersion: APIVersion,
GoVersion: runtime.Version(),
GitCommit: dockerversion.GitCommit,
BuildTime: dockerversion.BuildTime,

View file

@ -1,8 +1,6 @@
package volume
import (
"fmt"
"github.com/spf13/cobra"
"github.com/docker/docker/cli"
@ -17,7 +15,8 @@ func NewVolumeCommand(dockerCli *command.DockerCli) *cobra.Command {
Long: volumeDescription,
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
cmd.SetOutput(dockerCli.Err())
cmd.HelpFunc()(cmd, args)
},
}
cmd.AddCommand(

View file

@ -35,6 +35,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
return nil
},
Tags: map[string]string{"version": "1.25"},
}
flags := cmd.Flags()

View file

@ -34,7 +34,7 @@ func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Force the removal of one or more volumes")
flags.SetAnnotation("force", "version", []string{"1.25"})
return cmd
}

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

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)

View file

@ -5,6 +5,7 @@ import (
"os"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/commands"
@ -47,16 +48,15 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
cli.SetupRootCommand(cmd)
cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) {
var err error
if dockerCli.Client() == nil {
// flags must be the top-level command flags, not cmd.Flags()
opts.Common.SetDefaultOptions(flags)
dockerPreRun(opts)
err = dockerCli.Initialize(opts)
}
if err != nil || !dockerCli.HasExperimental() {
hideExperimentalFeatures(ccmd)
dockerCli.Initialize(opts)
}
hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental())
if err := ccmd.Help(); err != nil {
ccmd.Println(err)
}
@ -123,18 +123,29 @@ func dockerPreRun(opts *cliflags.ClientOptions) {
}
}
func hideExperimentalFeatures(cmd *cobra.Command) {
// hide flags
func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperimental bool) {
cmd.Flags().VisitAll(func(f *pflag.Flag) {
// hide experimental flags
if _, ok := f.Annotations["experimental"]; ok {
f.Hidden = true
}
// hide flags not supported by the server
if flagVersion, ok := f.Annotations["version"]; ok && len(flagVersion) == 1 && versions.LessThan(clientVersion, flagVersion[0]) {
f.Hidden = true
}
})
for _, subcmd := range cmd.Commands() {
// hide subcommands
// hide experimental subcommands
if _, ok := subcmd.Tags["experimental"]; ok {
subcmd.Hidden = true
}
// hide subcommands not supported by the server
if subcmdVersion, ok := subcmd.Tags["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
subcmd.Hidden = true
}
}
}

View file

@ -480,9 +480,6 @@ func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config
s.UseMiddleware(c)
}
u := middleware.NewUserAgentMiddleware(v)
s.UseMiddleware(u)
if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, cli.d.PluginStore); err != nil {
return fmt.Errorf("Error validating authorization plugin: %v", err)
}

View file

@ -162,6 +162,7 @@ This section lists each version from latest to oldest. Each listing includes a
* `POST /volumes/prune` prunes unused volumes.
* `POST /networks/prune` prunes unused networks.
* Every API response now includes a `Docker-Experimental` header specifying if experimental features are enabled (value can be `true` or `false`).
* Every API response now includes a `API-Version` header specifying the default API version of the server.
* The `hostConfig` option now accepts the fields `CpuRealtimePeriod` and `CpuRtRuntime` to allocate cpu runtime to rt tasks when `CONFIG_RT_GROUP_SCHED` is enabled in the kernel.
* The `SecurityOptions` field within the `GET /info` response now includes `userns` if user namespaces are enabled in the daemon.
* `GET /nodes` and `GET /node/(id or name)` now return `Addr` as part of a node's `Status`, which is the address that that node connects to the manager from.

View file

@ -15,7 +15,7 @@ keywords: "API, Docker, rcli, REST, documentation"
# Docker Remote API v1.18
## 1. Brief introduction
# 1. Brief introduction
- The Remote API has replaced `rcli`.
- The daemon listens on `unix:///var/run/docker.sock` but you can
@ -23,8 +23,6 @@ keywords: "API, Docker, rcli, REST, documentation"
- The API tends to be REST, but for some complex commands, like `attach`
or `pull`, the HTTP connection is hijacked to transport `STDOUT`,
`STDIN` and `STDERR`.
- When the client API version is newer than the daemon's, these calls return an HTTP
`400 Bad Request` error message.
# 2. Endpoints

View file

@ -15,7 +15,7 @@ keywords: "API, Docker, rcli, REST, documentation"
# Docker Remote API v1.20
## 1. Brief introduction
# 1. Brief introduction
- The Remote API has replaced `rcli`.
- The daemon listens on `unix:///var/run/docker.sock` but you can
@ -23,8 +23,6 @@ keywords: "API, Docker, rcli, REST, documentation"
- The API tends to be REST. However, for some complex commands, like `attach`
or `pull`, the HTTP connection is hijacked to transport `stdout`,
`stdin` and `stderr`.
- When the client API version is newer than the daemon's, these calls return an HTTP
`400 Bad Request` error message.
# 2. Endpoints

View file

@ -15,7 +15,7 @@ keywords: "API, Docker, rcli, REST, documentation"
# Docker Remote API v1.22
## 1. Brief introduction
# 1. Brief introduction
- The Remote API has replaced `rcli`.
- The daemon listens on `unix:///var/run/docker.sock` but you can
@ -23,8 +23,6 @@ keywords: "API, Docker, rcli, REST, documentation"
- The API tends to be REST. However, for some complex commands, like `attach`
or `pull`, the HTTP connection is hijacked to transport `stdout`,
`stdin` and `stderr`.
- When the client API version is newer than the daemon's, these calls return an HTTP
`400 Bad Request` error message.
# 2. Endpoints

View file

@ -23,8 +23,6 @@ keywords: "API, Docker, rcli, REST, documentation"
- The API tends to be REST. However, for some complex commands, like `attach`
or `pull`, the HTTP connection is hijacked to transport `stdout`,
`stdin` and `stderr`.
- When the client API version is newer than the daemon's, these calls return an HTTP
`400 Bad Request` error message.
# 2. Errors

View file

@ -23,8 +23,6 @@ keywords: "API, Docker, rcli, REST, documentation"
- The API tends to be REST. However, for some complex commands, like `attach`
or `pull`, the HTTP connection is hijacked to transport `stdout`,
`stdin` and `stderr`.
- When the client API version is newer than the daemon's, these calls return an HTTP
`400 Bad Request` error message.
# 2. Errors

View file

@ -4,11 +4,9 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"net/http/httputil"
"runtime"
"strconv"
"strings"
"time"
"github.com/docker/docker/api"
"github.com/docker/docker/pkg/integration/checker"
@ -34,36 +32,6 @@ func (s *DockerSuite) TestAPIGetEnabledCORS(c *check.C) {
//c.Assert(res.Header.Get("Access-Control-Allow-Headers"), check.Equals, "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
}
func (s *DockerSuite) TestAPIVersionStatusCode(c *check.C) {
conn, err := sockConn(time.Duration(10*time.Second), "")
c.Assert(err, checker.IsNil)
client := httputil.NewClientConn(conn, nil)
defer client.Close()
req, err := http.NewRequest("GET", "/v999.0/version", nil)
c.Assert(err, checker.IsNil)
req.Header.Set("User-Agent", "Docker-Client/999.0 (os)")
res, err := client.Do(req)
c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
}
func (s *DockerSuite) TestAPIClientVersionNewerThanServer(c *check.C) {
v := strings.Split(api.DefaultVersion, ".")
vMinInt, err := strconv.Atoi(v[1])
c.Assert(err, checker.IsNil)
vMinInt++
v[1] = strconv.Itoa(vMinInt)
version := strings.Join(v, ".")
status, body, err := sockRequest("GET", "/v"+version+"/version", nil)
c.Assert(err, checker.IsNil)
c.Assert(status, checker.Equals, http.StatusBadRequest)
expected := fmt.Sprintf("client is newer than server (client API version: %s, server API version: %s)", version, api.DefaultVersion)
c.Assert(getErrorMessage(c, body), checker.Equals, expected)
}
func (s *DockerSuite) TestAPIClientVersionOldNotSupported(c *check.C) {
if daemonPlatform != runtime.GOOS {
c.Skip("Daemon platform doesn't match test platform")
@ -90,6 +58,7 @@ func (s *DockerSuite) TestAPIDockerAPIVersion(c *check.C) {
server := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("API-Version", api.DefaultVersion)
url := r.URL.Path
svrVersion = url
}))

View file

@ -9,6 +9,7 @@ import (
"path/filepath"
"runtime"
"github.com/docker/docker/api"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/pkg/homedir"
"github.com/docker/docker/pkg/integration/checker"
@ -25,6 +26,7 @@ func (s *DockerSuite) TestConfigHTTPHeader(c *check.C) {
server := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("API-Version", api.DefaultVersion)
headers = r.Header
}))
defer server.Close()

View file

@ -168,6 +168,7 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
flags.SetAnnotation("stop-timeout", "version", []string{"1.25"})
flags.Var(copts.sysctls, "sysctl", "Sysctl options")
flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
flags.Var(copts.ulimits, "ulimit", "Ulimit options")