Merge pull request #27745 from vieux/cli_backward_compose_api
allow client to talk to an older server
This commit is contained in:
commit
d3c780bb90
51 changed files with 192 additions and 182 deletions
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -33,6 +33,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
}
|
||||
return nil
|
||||
},
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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": ""},
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in a new issue