allow client to talk to an older server
| 1 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,47 +0,0 @@ |
| 1 |
-package middleware |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "net/http" |
|
| 5 |
- "strings" |
|
| 6 |
- |
|
| 7 |
- "github.com/Sirupsen/logrus" |
|
| 8 |
- "github.com/docker/docker/api/server/httputils" |
|
| 9 |
- "github.com/docker/docker/api/types/versions" |
|
| 10 |
- "golang.org/x/net/context" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-// UserAgentMiddleware is a middleware that |
|
| 14 |
-// validates the client user-agent. |
|
| 15 |
-type UserAgentMiddleware struct {
|
|
| 16 |
- serverVersion string |
|
| 17 |
-} |
|
| 18 |
- |
|
| 19 |
-// NewUserAgentMiddleware creates a new UserAgentMiddleware |
|
| 20 |
-// with the server version. |
|
| 21 |
-func NewUserAgentMiddleware(s string) UserAgentMiddleware {
|
|
| 22 |
- return UserAgentMiddleware{
|
|
| 23 |
- serverVersion: s, |
|
| 24 |
- } |
|
| 25 |
-} |
|
| 26 |
- |
|
| 27 |
-// WrapHandler returns a new handler function wrapping the previous one in the request chain. |
|
| 28 |
-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 {
|
|
| 29 |
- return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 30 |
- ctx = context.WithValue(ctx, httputils.UAStringKey, r.Header.Get("User-Agent"))
|
|
| 31 |
- |
|
| 32 |
- if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
|
| 33 |
- userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
|
| 34 |
- |
|
| 35 |
- // v1.20 onwards includes the GOOS of the client after the version |
|
| 36 |
- // such as Docker/1.7.0 (linux) |
|
| 37 |
- if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
|
|
| 38 |
- userAgent[1] = strings.Split(userAgent[1], " ")[0] |
|
| 39 |
- } |
|
| 40 |
- |
|
| 41 |
- if len(userAgent) == 2 && !versions.Equal(u.serverVersion, userAgent[1]) {
|
|
| 42 |
- logrus.Debugf("Client and server don't have the same version (client: %s, server: %s)", userAgent[1], u.serverVersion)
|
|
| 43 |
- } |
|
| 44 |
- } |
|
| 45 |
- return handler(ctx, w, r, vars) |
|
| 46 |
- } |
|
| 47 |
-} |
| ... | ... |
@@ -36,15 +36,13 @@ func (v VersionMiddleware) WrapHandler(handler func(ctx context.Context, w http. |
| 36 | 36 |
apiVersion = v.defaultVersion |
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 |
- if versions.GreaterThan(apiVersion, v.defaultVersion) {
|
|
| 40 |
- return errors.NewBadRequestError(fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", apiVersion, v.defaultVersion))
|
|
| 41 |
- } |
|
| 42 | 39 |
if versions.LessThan(apiVersion, v.minVersion) {
|
| 43 | 40 |
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))
|
| 44 | 41 |
} |
| 45 | 42 |
|
| 46 | 43 |
header := fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS)
|
| 47 | 44 |
w.Header().Set("Server", header)
|
| 45 |
+ w.Header().Set("API-Version", v.defaultVersion)
|
|
| 48 | 46 |
ctx = context.WithValue(ctx, "api-version", apiVersion) |
| 49 | 47 |
return handler(ctx, w, r, vars) |
| 50 | 48 |
} |
| ... | ... |
@@ -54,10 +54,4 @@ func TestVersionMiddlewareWithErrors(t *testing.T) {
|
| 54 | 54 |
if !strings.Contains(err.Error(), "client version 0.1 is too old. Minimum supported API version is 1.2.0") {
|
| 55 | 55 |
t.Fatalf("Expected too old client error, got %v", err)
|
| 56 | 56 |
} |
| 57 |
- |
|
| 58 |
- vars["version"] = "100000" |
|
| 59 |
- err = h(ctx, resp, req, vars) |
|
| 60 |
- if !strings.Contains(err.Error(), "client is newer than server") {
|
|
| 61 |
- t.Fatalf("Expected client newer than server error, got %v", err)
|
|
| 62 |
- } |
|
| 63 | 57 |
} |
| ... | ... |
@@ -128,7 +128,7 @@ func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
|
| 128 | 128 |
// apply to all requests. Data that is specific to the |
| 129 | 129 |
// immediate function being called should still be passed |
| 130 | 130 |
// as 'args' on the function call. |
| 131 |
- ctx := context.Background() |
|
| 131 |
+ ctx := context.WithValue(context.Background(), httputils.UAStringKey, r.Header.Get("User-Agent"))
|
|
| 132 | 132 |
handlerFunc := s.handlerWithGlobalMiddlewares(handler) |
| 133 | 133 |
|
| 134 | 134 |
vars := mux.Vars(r) |
| ... | ... |
@@ -128,6 +128,13 @@ type ContainerProcessList struct {
|
| 128 | 128 |
Titles []string |
| 129 | 129 |
} |
| 130 | 130 |
|
| 131 |
+// Info contains response of Remote API: |
|
| 132 |
+// GET "/_ping" |
|
| 133 |
+type Ping struct {
|
|
| 134 |
+ APIVersion string |
|
| 135 |
+ Experimental bool |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 131 | 138 |
// Version contains response of Remote API: |
| 132 | 139 |
// GET "/version" |
| 133 | 140 |
type Version struct {
|
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package checkpoint |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/docker/docker/cli" |
| 7 | 5 |
"github.com/docker/docker/cli/command" |
| 8 | 6 |
"github.com/spf13/cobra" |
| ... | ... |
@@ -15,9 +13,10 @@ func NewCheckpointCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 15 | 15 |
Short: "Manage checkpoints", |
| 16 | 16 |
Args: cli.NoArgs, |
| 17 | 17 |
Run: func(cmd *cobra.Command, args []string) {
|
| 18 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 18 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 19 |
+ cmd.HelpFunc()(cmd, args) |
|
| 19 | 20 |
}, |
| 20 |
- Tags: map[string]string{"experimental": ""},
|
|
| 21 |
+ Tags: map[string]string{"experimental": "", "version": "1.25"},
|
|
| 21 | 22 |
} |
| 22 | 23 |
cmd.AddCommand( |
| 23 | 24 |
newCreateCommand(dockerCli), |
| ... | ... |
@@ -10,6 +10,7 @@ import ( |
| 10 | 10 |
"runtime" |
| 11 | 11 |
|
| 12 | 12 |
"github.com/docker/docker/api" |
| 13 |
+ "github.com/docker/docker/api/types/versions" |
|
| 13 | 14 |
cliflags "github.com/docker/docker/cli/flags" |
| 14 | 15 |
"github.com/docker/docker/cliconfig" |
| 15 | 16 |
"github.com/docker/docker/cliconfig/configfile" |
| ... | ... |
@@ -32,21 +33,24 @@ type Streams interface {
|
| 32 | 32 |
// DockerCli represents the docker command line client. |
| 33 | 33 |
// Instances of the client can be returned from NewDockerCli. |
| 34 | 34 |
type DockerCli struct {
|
| 35 |
- configFile *configfile.ConfigFile |
|
| 36 |
- in *InStream |
|
| 37 |
- out *OutStream |
|
| 38 |
- err io.Writer |
|
| 39 |
- keyFile string |
|
| 40 |
- client client.APIClient |
|
| 35 |
+ configFile *configfile.ConfigFile |
|
| 36 |
+ in *InStream |
|
| 37 |
+ out *OutStream |
|
| 38 |
+ err io.Writer |
|
| 39 |
+ keyFile string |
|
| 40 |
+ client client.APIClient |
|
| 41 |
+ hasExperimental bool |
|
| 42 |
+ defaultVersion string |
|
| 41 | 43 |
} |
| 42 | 44 |
|
| 43 |
-// HasExperimental returns true if experimental features are accessible |
|
| 45 |
+// HasExperimental returns true if experimental features are accessible. |
|
| 44 | 46 |
func (cli *DockerCli) HasExperimental() bool {
|
| 45 |
- if cli.client == nil {
|
|
| 46 |
- return false |
|
| 47 |
- } |
|
| 48 |
- enabled, _ := cli.client.Ping(context.Background()) |
|
| 49 |
- return enabled |
|
| 47 |
+ return cli.hasExperimental |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+// DefaultVersion returns api.defaultVersion of DOCKER_API_VERSION if specified. |
|
| 51 |
+func (cli *DockerCli) DefaultVersion() string {
|
|
| 52 |
+ return cli.defaultVersion |
|
| 50 | 53 |
} |
| 51 | 54 |
|
| 52 | 55 |
// Client returns the APIClient |
| ... | ... |
@@ -93,12 +97,28 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
| 93 | 93 |
if err != nil {
|
| 94 | 94 |
return err |
| 95 | 95 |
} |
| 96 |
+ |
|
| 97 |
+ cli.defaultVersion = cli.client.ClientVersion() |
|
| 98 |
+ |
|
| 96 | 99 |
if opts.Common.TrustKey == "" {
|
| 97 | 100 |
cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) |
| 98 | 101 |
} else {
|
| 99 | 102 |
cli.keyFile = opts.Common.TrustKey |
| 100 | 103 |
} |
| 101 | 104 |
|
| 105 |
+ if ping, err := cli.client.Ping(context.Background()); err == nil {
|
|
| 106 |
+ cli.hasExperimental = ping.Experimental |
|
| 107 |
+ |
|
| 108 |
+ // since the new header was added in 1.25, assume server is 1.24 if header is not present. |
|
| 109 |
+ if ping.APIVersion == "" {
|
|
| 110 |
+ ping.APIVersion = "1.24" |
|
| 111 |
+ } |
|
| 112 |
+ |
|
| 113 |
+ // if server version is lower than the current cli, downgrade |
|
| 114 |
+ if versions.LessThan(ping.APIVersion, cli.client.ClientVersion()) {
|
|
| 115 |
+ cli.client.UpdateClientVersion(ping.APIVersion) |
|
| 116 |
+ } |
|
| 117 |
+ } |
|
| 102 | 118 |
return nil |
| 103 | 119 |
} |
| 104 | 120 |
|
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package container |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/spf13/cobra" |
| 7 | 5 |
|
| 8 | 6 |
"github.com/docker/docker/cli" |
| ... | ... |
@@ -16,7 +14,8 @@ func NewContainerCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 16 | 16 |
Short: "Manage containers", |
| 17 | 17 |
Args: cli.NoArgs, |
| 18 | 18 |
Run: func(cmd *cobra.Command, args []string) {
|
| 19 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 19 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 20 |
+ cmd.HelpFunc()(cmd, args) |
|
| 20 | 21 |
}, |
| 21 | 22 |
} |
| 22 | 23 |
cmd.AddCommand( |
| ... | ... |
@@ -59,6 +59,7 @@ func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 59 | 59 |
flags.StringVarP(&opts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") |
| 60 | 60 |
flags.BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the command") |
| 61 | 61 |
flags.VarP(opts.env, "env", "e", "Set environment variables") |
| 62 |
+ flags.SetAnnotation("env", "version", []string{"1.25"})
|
|
| 62 | 63 |
|
| 63 | 64 |
return cmd |
| 64 | 65 |
} |
| ... | ... |
@@ -35,6 +35,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 35 | 35 |
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) |
| 36 | 36 |
return nil |
| 37 | 37 |
}, |
| 38 |
+ Tags: map[string]string{"version": "1.25"},
|
|
| 38 | 39 |
} |
| 39 | 40 |
|
| 40 | 41 |
flags := cmd.Flags() |
| ... | ... |
@@ -113,6 +113,7 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 113 | 113 |
|
| 114 | 114 |
flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer") |
| 115 | 115 |
flags.SetAnnotation("squash", "experimental", nil)
|
| 116 |
+ flags.SetAnnotation("squash", "version", []string{"1.25"})
|
|
| 116 | 117 |
|
| 117 | 118 |
return cmd |
| 118 | 119 |
} |
| ... | ... |
@@ -144,7 +145,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
| 144 | 144 |
progBuff io.Writer |
| 145 | 145 |
buildBuff io.Writer |
| 146 | 146 |
) |
| 147 |
- |
|
| 147 |
+ |
|
| 148 | 148 |
specifiedContext := options.context |
| 149 | 149 |
progBuff = dockerCli.Out() |
| 150 | 150 |
buildBuff = dockerCli.Out() |
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package image |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/spf13/cobra" |
| 7 | 5 |
|
| 8 | 6 |
"github.com/docker/docker/cli" |
| ... | ... |
@@ -16,7 +14,8 @@ func NewImageCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 16 | 16 |
Short: "Manage images", |
| 17 | 17 |
Args: cli.NoArgs, |
| 18 | 18 |
Run: func(cmd *cobra.Command, args []string) {
|
| 19 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 19 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 20 |
+ cmd.HelpFunc()(cmd, args) |
|
| 20 | 21 |
}, |
| 21 | 22 |
} |
| 22 | 23 |
cmd.AddCommand( |
| ... | ... |
@@ -33,6 +32,5 @@ func NewImageCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 33 | 33 |
newInspectCommand(dockerCli), |
| 34 | 34 |
NewPruneCommand(dockerCli), |
| 35 | 35 |
) |
| 36 |
- |
|
| 37 | 36 |
return cmd |
| 38 | 37 |
} |
| ... | ... |
@@ -36,6 +36,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 36 | 36 |
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) |
| 37 | 37 |
return nil |
| 38 | 38 |
}, |
| 39 |
+ Tags: map[string]string{"version": "1.25"},
|
|
| 39 | 40 |
} |
| 40 | 41 |
|
| 41 | 42 |
flags := cmd.Flags() |
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package network |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/spf13/cobra" |
| 7 | 5 |
|
| 8 | 6 |
"github.com/docker/docker/cli" |
| ... | ... |
@@ -16,7 +14,8 @@ func NewNetworkCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 16 | 16 |
Short: "Manage networks", |
| 17 | 17 |
Args: cli.NoArgs, |
| 18 | 18 |
Run: func(cmd *cobra.Command, args []string) {
|
| 19 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 19 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 20 |
+ cmd.HelpFunc()(cmd, args) |
|
| 20 | 21 |
}, |
| 21 | 22 |
} |
| 22 | 23 |
cmd.AddCommand( |
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package node |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/docker/docker/cli" |
| 7 | 5 |
"github.com/docker/docker/cli/command" |
| 8 | 6 |
apiclient "github.com/docker/docker/client" |
| ... | ... |
@@ -17,7 +15,8 @@ func NewNodeCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 17 | 17 |
Short: "Manage Swarm nodes", |
| 18 | 18 |
Args: cli.NoArgs, |
| 19 | 19 |
Run: func(cmd *cobra.Command, args []string) {
|
| 20 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 20 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 21 |
+ cmd.HelpFunc()(cmd, args) |
|
| 21 | 22 |
}, |
| 22 | 23 |
} |
| 23 | 24 |
cmd.AddCommand( |
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package plugin |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/docker/docker/cli" |
| 7 | 5 |
"github.com/docker/docker/cli/command" |
| 8 | 6 |
"github.com/spf13/cobra" |
| ... | ... |
@@ -15,7 +13,8 @@ func NewPluginCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 15 | 15 |
Short: "Manage plugins", |
| 16 | 16 |
Args: cli.NoArgs, |
| 17 | 17 |
Run: func(cmd *cobra.Command, args []string) {
|
| 18 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 18 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 19 |
+ cmd.HelpFunc()(cmd, args) |
|
| 19 | 20 |
}, |
| 20 | 21 |
Tags: map[string]string{"experimental": ""},
|
| 21 | 22 |
} |
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package service |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/spf13/cobra" |
| 7 | 5 |
|
| 8 | 6 |
"github.com/docker/docker/cli" |
| ... | ... |
@@ -16,7 +14,8 @@ func NewServiceCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 16 | 16 |
Short: "Manage services", |
| 17 | 17 |
Args: cli.NoArgs, |
| 18 | 18 |
Run: func(cmd *cobra.Command, args []string) {
|
| 19 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 19 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 20 |
+ cmd.HelpFunc()(cmd, args) |
|
| 20 | 21 |
}, |
| 21 | 22 |
} |
| 22 | 23 |
cmd.AddCommand( |
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package stack |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/docker/docker/cli" |
| 7 | 5 |
"github.com/docker/docker/cli/command" |
| 8 | 6 |
"github.com/spf13/cobra" |
| ... | ... |
@@ -15,9 +13,10 @@ func NewStackCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 15 | 15 |
Short: "Manage Docker stacks", |
| 16 | 16 |
Args: cli.NoArgs, |
| 17 | 17 |
Run: func(cmd *cobra.Command, args []string) {
|
| 18 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 18 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 19 |
+ cmd.HelpFunc()(cmd, args) |
|
| 19 | 20 |
}, |
| 20 |
- Tags: map[string]string{"experimental": ""},
|
|
| 21 |
+ Tags: map[string]string{"experimental": "", "version": "1.25"},
|
|
| 21 | 22 |
} |
| 22 | 23 |
cmd.AddCommand( |
| 23 | 24 |
newConfigCommand(dockerCli), |
| ... | ... |
@@ -36,7 +36,7 @@ func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 36 | 36 |
opts.namespace = strings.TrimSuffix(args[0], ".dab") |
| 37 | 37 |
return runDeploy(dockerCli, opts) |
| 38 | 38 |
}, |
| 39 |
- Tags: map[string]string{"experimental": ""},
|
|
| 39 |
+ Tags: map[string]string{"experimental": "", "version": "1.25"},
|
|
| 40 | 40 |
} |
| 41 | 41 |
|
| 42 | 42 |
flags := cmd.Flags() |
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package swarm |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/spf13/cobra" |
| 7 | 5 |
|
| 8 | 6 |
"github.com/docker/docker/cli" |
| ... | ... |
@@ -16,7 +14,8 @@ func NewSwarmCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 16 | 16 |
Short: "Manage Swarm", |
| 17 | 17 |
Args: cli.NoArgs, |
| 18 | 18 |
Run: func(cmd *cobra.Command, args []string) {
|
| 19 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 19 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 20 |
+ cmd.HelpFunc()(cmd, args) |
|
| 20 | 21 |
}, |
| 21 | 22 |
} |
| 22 | 23 |
cmd.AddCommand( |
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package system |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/spf13/cobra" |
| 7 | 5 |
|
| 8 | 6 |
"github.com/docker/docker/cli" |
| ... | ... |
@@ -16,7 +14,8 @@ func NewSystemCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 16 | 16 |
Short: "Manage Docker", |
| 17 | 17 |
Args: cli.NoArgs, |
| 18 | 18 |
Run: func(cmd *cobra.Command, args []string) {
|
| 19 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 19 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 20 |
+ cmd.HelpFunc()(cmd, args) |
|
| 20 | 21 |
}, |
| 21 | 22 |
} |
| 22 | 23 |
cmd.AddCommand( |
| ... | ... |
@@ -25,5 +24,6 @@ func NewSystemCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 25 | 25 |
NewDiskUsageCommand(dockerCli), |
| 26 | 26 |
NewPruneCommand(dockerCli), |
| 27 | 27 |
) |
| 28 |
+ |
|
| 28 | 29 |
return cmd |
| 29 | 30 |
} |
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package system |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 4 | 5 |
"runtime" |
| 5 | 6 |
"time" |
| 6 | 7 |
|
| ... | ... |
@@ -70,10 +71,15 @@ func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
|
| 70 | 70 |
Status: "Template parsing error: " + err.Error()} |
| 71 | 71 |
} |
| 72 | 72 |
|
| 73 |
+ APIVersion := dockerCli.Client().ClientVersion() |
|
| 74 |
+ if defaultAPIVersion := dockerCli.DefaultVersion(); APIVersion != defaultAPIVersion {
|
|
| 75 |
+ APIVersion = fmt.Sprintf("%s (downgraded from %s)", APIVersion, defaultAPIVersion)
|
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 73 | 78 |
vd := types.VersionResponse{
|
| 74 | 79 |
Client: &types.Version{
|
| 75 | 80 |
Version: dockerversion.Version, |
| 76 |
- APIVersion: dockerCli.Client().ClientVersion(), |
|
| 81 |
+ APIVersion: APIVersion, |
|
| 77 | 82 |
GoVersion: runtime.Version(), |
| 78 | 83 |
GitCommit: dockerversion.GitCommit, |
| 79 | 84 |
BuildTime: dockerversion.BuildTime, |
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package volume |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/spf13/cobra" |
| 7 | 5 |
|
| 8 | 6 |
"github.com/docker/docker/cli" |
| ... | ... |
@@ -17,7 +15,8 @@ func NewVolumeCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 17 | 17 |
Long: volumeDescription, |
| 18 | 18 |
Args: cli.NoArgs, |
| 19 | 19 |
Run: func(cmd *cobra.Command, args []string) {
|
| 20 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 20 |
+ cmd.SetOutput(dockerCli.Err()) |
|
| 21 |
+ cmd.HelpFunc()(cmd, args) |
|
| 21 | 22 |
}, |
| 22 | 23 |
} |
| 23 | 24 |
cmd.AddCommand( |
| ... | ... |
@@ -35,6 +35,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 35 | 35 |
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) |
| 36 | 36 |
return nil |
| 37 | 37 |
}, |
| 38 |
+ Tags: map[string]string{"version": "1.25"},
|
|
| 38 | 39 |
} |
| 39 | 40 |
|
| 40 | 41 |
flags := cmd.Flags() |
| ... | ... |
@@ -34,7 +34,7 @@ func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 34 | 34 |
|
| 35 | 35 |
flags := cmd.Flags() |
| 36 | 36 |
flags.BoolVarP(&opts.force, "force", "f", false, "Force the removal of one or more volumes") |
| 37 |
- |
|
| 37 |
+ flags.SetAnnotation("force", "version", []string{"1.25"})
|
|
| 38 | 38 |
return cmd |
| 39 | 39 |
} |
| 40 | 40 |
|
| ... | ... |
@@ -79,6 +79,8 @@ type Client struct {
|
| 79 | 79 |
version string |
| 80 | 80 |
// custom http headers configured by users. |
| 81 | 81 |
customHTTPHeaders map[string]string |
| 82 |
+ // manualOverride is set to true when the version was set by users. |
|
| 83 |
+ manualOverride bool |
|
| 82 | 84 |
} |
| 83 | 85 |
|
| 84 | 86 |
// NewEnvClient initializes a new API client based on environment variables. |
| ... | ... |
@@ -111,13 +113,19 @@ func NewEnvClient() (*Client, error) {
|
| 111 | 111 |
if host == "" {
|
| 112 | 112 |
host = DefaultDockerHost |
| 113 | 113 |
} |
| 114 |
- |
|
| 115 | 114 |
version := os.Getenv("DOCKER_API_VERSION")
|
| 116 | 115 |
if version == "" {
|
| 117 | 116 |
version = DefaultVersion |
| 118 | 117 |
} |
| 119 | 118 |
|
| 120 |
- return NewClient(host, version, client, nil) |
|
| 119 |
+ cli, err := NewClient(host, version, client, nil) |
|
| 120 |
+ if err != nil {
|
|
| 121 |
+ return cli, err |
|
| 122 |
+ } |
|
| 123 |
+ if version != "" {
|
|
| 124 |
+ cli.manualOverride = true |
|
| 125 |
+ } |
|
| 126 |
+ return cli, nil |
|
| 121 | 127 |
} |
| 122 | 128 |
|
| 123 | 129 |
// NewClient initializes a new API client for the given host and API version. |
| ... | ... |
@@ -211,7 +219,10 @@ func (cli *Client) ClientVersion() string {
|
| 211 | 211 |
// UpdateClientVersion updates the version string associated with this |
| 212 | 212 |
// instance of the Client. |
| 213 | 213 |
func (cli *Client) UpdateClientVersion(v string) {
|
| 214 |
- cli.version = v |
|
| 214 |
+ if !cli.manualOverride {
|
|
| 215 |
+ cli.version = v |
|
| 216 |
+ } |
|
| 217 |
+ |
|
| 215 | 218 |
} |
| 216 | 219 |
|
| 217 | 220 |
// ParseHost verifies that the given host strings is valid. |
| ... | ... |
@@ -20,6 +20,11 @@ type configWrapper struct {
|
| 20 | 20 |
// It can be associated with a name, but it's not mandatory. |
| 21 | 21 |
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
|
| 22 | 22 |
var response container.ContainerCreateCreatedBody |
| 23 |
+ |
|
| 24 |
+ if err := cli.NewVersionError("1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
|
|
| 25 |
+ return response, err |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 23 | 28 |
query := url.Values{}
|
| 24 | 29 |
if containerName != "" {
|
| 25 | 30 |
query.Set("name", containerName)
|
| ... | ... |
@@ -10,6 +10,11 @@ import ( |
| 10 | 10 |
// ContainerExecCreate creates a new exec configuration to run an exec process. |
| 11 | 11 |
func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error) {
|
| 12 | 12 |
var response types.IDResponse |
| 13 |
+ |
|
| 14 |
+ if err := cli.NewVersionError("1.25", "env"); len(config.Env) != 0 && err != nil {
|
|
| 15 |
+ return response, err |
|
| 16 |
+ } |
|
| 17 |
+ |
|
| 13 | 18 |
resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, config, nil) |
| 14 | 19 |
if err != nil {
|
| 15 | 20 |
return response, err |
| ... | ... |
@@ -12,6 +12,10 @@ import ( |
| 12 | 12 |
func (cli *Client) ContainersPrune(ctx context.Context, cfg types.ContainersPruneConfig) (types.ContainersPruneReport, error) {
|
| 13 | 13 |
var report types.ContainersPruneReport |
| 14 | 14 |
|
| 15 |
+ if err := cli.NewVersionError("1.25", "container prune"); err != nil {
|
|
| 16 |
+ return report, err |
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 15 | 19 |
serverResp, err := cli.post(ctx, "/containers/prune", nil, cfg, nil) |
| 16 | 20 |
if err != nil {
|
| 17 | 21 |
return report, err |
| ... | ... |
@@ -3,6 +3,8 @@ package client |
| 3 | 3 |
import ( |
| 4 | 4 |
"errors" |
| 5 | 5 |
"fmt" |
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/api/types/versions" |
|
| 6 | 8 |
) |
| 7 | 9 |
|
| 8 | 10 |
// ErrConnectionFailed is an error raised when the connection between the client and the server failed. |
| ... | ... |
@@ -206,3 +208,12 @@ func IsErrPluginPermissionDenied(err error) bool {
|
| 206 | 206 |
_, ok := err.(pluginPermissionDenied) |
| 207 | 207 |
return ok |
| 208 | 208 |
} |
| 209 |
+ |
|
| 210 |
+// NewVersionError returns an error if the APIVersion required |
|
| 211 |
+// if less than the current supported version |
|
| 212 |
+func (cli *Client) NewVersionError(APIrequired, feature string) error {
|
|
| 213 |
+ if versions.LessThan(cli.version, APIrequired) {
|
|
| 214 |
+ return fmt.Errorf("%q requires API version %s, but the Docker server is version %s", feature, APIrequired, cli.version)
|
|
| 215 |
+ } |
|
| 216 |
+ return nil |
|
| 217 |
+} |
| ... | ... |
@@ -21,7 +21,7 @@ var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`) |
| 21 | 21 |
// The Body in the response implement an io.ReadCloser and it's up to the caller to |
| 22 | 22 |
// close it. |
| 23 | 23 |
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
|
| 24 |
- query, err := imageBuildOptionsToQuery(options) |
|
| 24 |
+ query, err := cli.imageBuildOptionsToQuery(options) |
|
| 25 | 25 |
if err != nil {
|
| 26 | 26 |
return types.ImageBuildResponse{}, err
|
| 27 | 27 |
} |
| ... | ... |
@@ -47,7 +47,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio |
| 47 | 47 |
}, nil |
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 |
-func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) {
|
|
| 50 |
+func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) {
|
|
| 51 | 51 |
query := url.Values{
|
| 52 | 52 |
"t": options.Tags, |
| 53 | 53 |
"securityopt": options.SecurityOpt, |
| ... | ... |
@@ -76,6 +76,9 @@ func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, erro |
| 76 | 76 |
} |
| 77 | 77 |
|
| 78 | 78 |
if options.Squash {
|
| 79 |
+ if err := cli.NewVersionError("1.25", "squash"); err != nil {
|
|
| 80 |
+ return query, err |
|
| 81 |
+ } |
|
| 79 | 82 |
query.Set("squash", "1")
|
| 80 | 83 |
} |
| 81 | 84 |
|
| ... | ... |
@@ -12,6 +12,10 @@ import ( |
| 12 | 12 |
func (cli *Client) ImagesPrune(ctx context.Context, cfg types.ImagesPruneConfig) (types.ImagesPruneReport, error) {
|
| 13 | 13 |
var report types.ImagesPruneReport |
| 14 | 14 |
|
| 15 |
+ if err := cli.NewVersionError("1.25", "image prune"); err != nil {
|
|
| 16 |
+ return report, err |
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 15 | 19 |
serverResp, err := cli.post(ctx, "/images/prune", nil, cfg, nil) |
| 16 | 20 |
if err != nil {
|
| 17 | 21 |
return report, err |
| ... | ... |
@@ -129,7 +129,7 @@ type SystemAPIClient interface {
|
| 129 | 129 |
Info(ctx context.Context) (types.Info, error) |
| 130 | 130 |
RegistryLogin(ctx context.Context, auth types.AuthConfig) (registry.AuthenticateOKBody, error) |
| 131 | 131 |
DiskUsage(ctx context.Context) (types.DiskUsage, error) |
| 132 |
- Ping(ctx context.Context) (bool, error) |
|
| 132 |
+ Ping(ctx context.Context) (types.Ping, error) |
|
| 133 | 133 |
} |
| 134 | 134 |
|
| 135 | 135 |
// VolumeAPIClient defines API client methods for the volumes |
| ... | ... |
@@ -1,19 +1,30 @@ |
| 1 | 1 |
package client |
| 2 | 2 |
|
| 3 |
-import "golang.org/x/net/context" |
|
| 3 |
+import ( |
|
| 4 |
+ "fmt" |
|
| 4 | 5 |
|
| 5 |
-// Ping pings the server and return the value of the "Docker-Experimental" header |
|
| 6 |
-func (cli *Client) Ping(ctx context.Context) (bool, error) {
|
|
| 7 |
- serverResp, err := cli.get(ctx, "/_ping", nil, nil) |
|
| 6 |
+ "github.com/docker/docker/api/types" |
|
| 7 |
+ "golang.org/x/net/context" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// Ping pings the server and return the value of the "Docker-Experimental" & "API-Version" headers |
|
| 11 |
+func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
|
|
| 12 |
+ var ping types.Ping |
|
| 13 |
+ req, err := cli.buildRequest("GET", fmt.Sprintf("%s/_ping", cli.basePath), nil, nil)
|
|
| 14 |
+ if err != nil {
|
|
| 15 |
+ return ping, err |
|
| 16 |
+ } |
|
| 17 |
+ serverResp, err := cli.doRequest(ctx, req) |
|
| 8 | 18 |
if err != nil {
|
| 9 |
- return false, err |
|
| 19 |
+ return ping, err |
|
| 10 | 20 |
} |
| 11 | 21 |
defer ensureReaderClosed(serverResp) |
| 12 | 22 |
|
| 13 |
- exp := serverResp.header.Get("Docker-Experimental")
|
|
| 14 |
- if exp != "true" {
|
|
| 15 |
- return false, nil |
|
| 23 |
+ ping.APIVersion = serverResp.header.Get("API-Version")
|
|
| 24 |
+ |
|
| 25 |
+ if serverResp.header.Get("Docker-Experimental") == "true" {
|
|
| 26 |
+ ping.Experimental = true |
|
| 16 | 27 |
} |
| 17 | 28 |
|
| 18 |
- return true, nil |
|
| 29 |
+ return ping, nil |
|
| 19 | 30 |
} |
| ... | ... |
@@ -214,6 +214,9 @@ func (cli *Client) addHeaders(req *http.Request, headers headers) *http.Request |
| 214 | 214 |
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers |
| 215 | 215 |
// then the user can't change OUR headers |
| 216 | 216 |
for k, v := range cli.customHTTPHeaders {
|
| 217 |
+ if versions.LessThan(cli.version, "1.25") && k == "User-Agent" {
|
|
| 218 |
+ continue |
|
| 219 |
+ } |
|
| 217 | 220 |
req.Header.Set(k, v) |
| 218 | 221 |
} |
| 219 | 222 |
|
| ... | ... |
@@ -12,6 +12,10 @@ import ( |
| 12 | 12 |
func (cli *Client) VolumesPrune(ctx context.Context, cfg types.VolumesPruneConfig) (types.VolumesPruneReport, error) {
|
| 13 | 13 |
var report types.VolumesPruneReport |
| 14 | 14 |
|
| 15 |
+ if err := cli.NewVersionError("1.25", "volume prune"); err != nil {
|
|
| 16 |
+ return report, err |
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 15 | 19 |
serverResp, err := cli.post(ctx, "/volumes/prune", nil, cfg, nil) |
| 16 | 20 |
if err != nil {
|
| 17 | 21 |
return report, err |
| ... | ... |
@@ -3,14 +3,17 @@ package client |
| 3 | 3 |
import ( |
| 4 | 4 |
"net/url" |
| 5 | 5 |
|
| 6 |
+ "github.com/docker/docker/api/types/versions" |
|
| 6 | 7 |
"golang.org/x/net/context" |
| 7 | 8 |
) |
| 8 | 9 |
|
| 9 | 10 |
// VolumeRemove removes a volume from the docker host. |
| 10 | 11 |
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
|
| 11 | 12 |
query := url.Values{}
|
| 12 |
- if force {
|
|
| 13 |
- query.Set("force", "1")
|
|
| 13 |
+ if versions.GreaterThanOrEqualTo(cli.version, "1.25") {
|
|
| 14 |
+ if force {
|
|
| 15 |
+ query.Set("force", "1")
|
|
| 16 |
+ } |
|
| 14 | 17 |
} |
| 15 | 18 |
resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil) |
| 16 | 19 |
ensureReaderClosed(resp) |
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
"os" |
| 6 | 6 |
|
| 7 | 7 |
"github.com/Sirupsen/logrus" |
| 8 |
+ "github.com/docker/docker/api/types/versions" |
|
| 8 | 9 |
"github.com/docker/docker/cli" |
| 9 | 10 |
"github.com/docker/docker/cli/command" |
| 10 | 11 |
"github.com/docker/docker/cli/command/commands" |
| ... | ... |
@@ -47,16 +48,15 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 47 | 47 |
cli.SetupRootCommand(cmd) |
| 48 | 48 |
|
| 49 | 49 |
cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) {
|
| 50 |
- var err error |
|
| 51 | 50 |
if dockerCli.Client() == nil {
|
| 52 | 51 |
// flags must be the top-level command flags, not cmd.Flags() |
| 53 | 52 |
opts.Common.SetDefaultOptions(flags) |
| 54 | 53 |
dockerPreRun(opts) |
| 55 |
- err = dockerCli.Initialize(opts) |
|
| 56 |
- } |
|
| 57 |
- if err != nil || !dockerCli.HasExperimental() {
|
|
| 58 |
- hideExperimentalFeatures(ccmd) |
|
| 54 |
+ dockerCli.Initialize(opts) |
|
| 59 | 55 |
} |
| 56 |
+ |
|
| 57 |
+ hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()) |
|
| 58 |
+ |
|
| 60 | 59 |
if err := ccmd.Help(); err != nil {
|
| 61 | 60 |
ccmd.Println(err) |
| 62 | 61 |
} |
| ... | ... |
@@ -123,18 +123,29 @@ func dockerPreRun(opts *cliflags.ClientOptions) {
|
| 123 | 123 |
} |
| 124 | 124 |
} |
| 125 | 125 |
|
| 126 |
-func hideExperimentalFeatures(cmd *cobra.Command) {
|
|
| 127 |
- // hide flags |
|
| 126 |
+func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperimental bool) {
|
|
| 128 | 127 |
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
| 128 |
+ // hide experimental flags |
|
| 129 | 129 |
if _, ok := f.Annotations["experimental"]; ok {
|
| 130 | 130 |
f.Hidden = true |
| 131 | 131 |
} |
| 132 |
+ |
|
| 133 |
+ // hide flags not supported by the server |
|
| 134 |
+ if flagVersion, ok := f.Annotations["version"]; ok && len(flagVersion) == 1 && versions.LessThan(clientVersion, flagVersion[0]) {
|
|
| 135 |
+ f.Hidden = true |
|
| 136 |
+ } |
|
| 137 |
+ |
|
| 132 | 138 |
}) |
| 133 | 139 |
|
| 134 | 140 |
for _, subcmd := range cmd.Commands() {
|
| 135 |
- // hide subcommands |
|
| 141 |
+ // hide experimental subcommands |
|
| 136 | 142 |
if _, ok := subcmd.Tags["experimental"]; ok {
|
| 137 | 143 |
subcmd.Hidden = true |
| 138 | 144 |
} |
| 145 |
+ |
|
| 146 |
+ // hide subcommands not supported by the server |
|
| 147 |
+ if subcmdVersion, ok := subcmd.Tags["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
|
|
| 148 |
+ subcmd.Hidden = true |
|
| 149 |
+ } |
|
| 139 | 150 |
} |
| 140 | 151 |
} |
| ... | ... |
@@ -480,9 +480,6 @@ func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config |
| 480 | 480 |
s.UseMiddleware(c) |
| 481 | 481 |
} |
| 482 | 482 |
|
| 483 |
- u := middleware.NewUserAgentMiddleware(v) |
|
| 484 |
- s.UseMiddleware(u) |
|
| 485 |
- |
|
| 486 | 483 |
if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, cli.d.PluginStore); err != nil {
|
| 487 | 484 |
return fmt.Errorf("Error validating authorization plugin: %v", err)
|
| 488 | 485 |
} |
| ... | ... |
@@ -162,6 +162,7 @@ This section lists each version from latest to oldest. Each listing includes a |
| 162 | 162 |
* `POST /volumes/prune` prunes unused volumes. |
| 163 | 163 |
* `POST /networks/prune` prunes unused networks. |
| 164 | 164 |
* Every API response now includes a `Docker-Experimental` header specifying if experimental features are enabled (value can be `true` or `false`). |
| 165 |
+* Every API response now includes a `API-Version` header specifying the default API version of the server. |
|
| 165 | 166 |
* 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. |
| 166 | 167 |
* The `SecurityOptions` field within the `GET /info` response now includes `userns` if user namespaces are enabled in the daemon. |
| 167 | 168 |
* `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" |
| 15 | 15 |
|
| 16 | 16 |
# Docker Remote API v1.18 |
| 17 | 17 |
|
| 18 |
-## 1. Brief introduction |
|
| 18 |
+# 1. Brief introduction |
|
| 19 | 19 |
|
| 20 | 20 |
- The Remote API has replaced `rcli`. |
| 21 | 21 |
- The daemon listens on `unix:///var/run/docker.sock` but you can |
| ... | ... |
@@ -23,8 +23,6 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 23 | 23 |
- The API tends to be REST, but for some complex commands, like `attach` |
| 24 | 24 |
or `pull`, the HTTP connection is hijacked to transport `STDOUT`, |
| 25 | 25 |
`STDIN` and `STDERR`. |
| 26 |
- - When the client API version is newer than the daemon's, these calls return an HTTP |
|
| 27 |
- `400 Bad Request` error message. |
|
| 28 | 26 |
|
| 29 | 27 |
# 2. Endpoints |
| 30 | 28 |
|
| ... | ... |
@@ -15,7 +15,7 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 15 | 15 |
|
| 16 | 16 |
# Docker Remote API v1.20 |
| 17 | 17 |
|
| 18 |
-## 1. Brief introduction |
|
| 18 |
+# 1. Brief introduction |
|
| 19 | 19 |
|
| 20 | 20 |
- The Remote API has replaced `rcli`. |
| 21 | 21 |
- The daemon listens on `unix:///var/run/docker.sock` but you can |
| ... | ... |
@@ -23,8 +23,6 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 23 | 23 |
- The API tends to be REST. However, for some complex commands, like `attach` |
| 24 | 24 |
or `pull`, the HTTP connection is hijacked to transport `stdout`, |
| 25 | 25 |
`stdin` and `stderr`. |
| 26 |
- - When the client API version is newer than the daemon's, these calls return an HTTP |
|
| 27 |
- `400 Bad Request` error message. |
|
| 28 | 26 |
|
| 29 | 27 |
# 2. Endpoints |
| 30 | 28 |
|
| ... | ... |
@@ -15,7 +15,7 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 15 | 15 |
|
| 16 | 16 |
# Docker Remote API v1.22 |
| 17 | 17 |
|
| 18 |
-## 1. Brief introduction |
|
| 18 |
+# 1. Brief introduction |
|
| 19 | 19 |
|
| 20 | 20 |
- The Remote API has replaced `rcli`. |
| 21 | 21 |
- The daemon listens on `unix:///var/run/docker.sock` but you can |
| ... | ... |
@@ -23,8 +23,6 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 23 | 23 |
- The API tends to be REST. However, for some complex commands, like `attach` |
| 24 | 24 |
or `pull`, the HTTP connection is hijacked to transport `stdout`, |
| 25 | 25 |
`stdin` and `stderr`. |
| 26 |
- - When the client API version is newer than the daemon's, these calls return an HTTP |
|
| 27 |
- `400 Bad Request` error message. |
|
| 28 | 26 |
|
| 29 | 27 |
# 2. Endpoints |
| 30 | 28 |
|
| ... | ... |
@@ -23,8 +23,6 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 23 | 23 |
- The API tends to be REST. However, for some complex commands, like `attach` |
| 24 | 24 |
or `pull`, the HTTP connection is hijacked to transport `stdout`, |
| 25 | 25 |
`stdin` and `stderr`. |
| 26 |
- - When the client API version is newer than the daemon's, these calls return an HTTP |
|
| 27 |
- `400 Bad Request` error message. |
|
| 28 | 26 |
|
| 29 | 27 |
# 2. Errors |
| 30 | 28 |
|
| ... | ... |
@@ -23,8 +23,6 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 23 | 23 |
- The API tends to be REST. However, for some complex commands, like `attach` |
| 24 | 24 |
or `pull`, the HTTP connection is hijacked to transport `stdout`, |
| 25 | 25 |
`stdin` and `stderr`. |
| 26 |
- - When the client API version is newer than the daemon's, these calls return an HTTP |
|
| 27 |
- `400 Bad Request` error message. |
|
| 28 | 26 |
|
| 29 | 27 |
# 2. Errors |
| 30 | 28 |
|
| ... | ... |
@@ -4,11 +4,9 @@ import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"net/http" |
| 6 | 6 |
"net/http/httptest" |
| 7 |
- "net/http/httputil" |
|
| 8 | 7 |
"runtime" |
| 9 | 8 |
"strconv" |
| 10 | 9 |
"strings" |
| 11 |
- "time" |
|
| 12 | 10 |
|
| 13 | 11 |
"github.com/docker/docker/api" |
| 14 | 12 |
"github.com/docker/docker/pkg/integration/checker" |
| ... | ... |
@@ -34,36 +32,6 @@ func (s *DockerSuite) TestAPIGetEnabledCORS(c *check.C) {
|
| 34 | 34 |
//c.Assert(res.Header.Get("Access-Control-Allow-Headers"), check.Equals, "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
|
| 35 | 35 |
} |
| 36 | 36 |
|
| 37 |
-func (s *DockerSuite) TestAPIVersionStatusCode(c *check.C) {
|
|
| 38 |
- conn, err := sockConn(time.Duration(10*time.Second), "") |
|
| 39 |
- c.Assert(err, checker.IsNil) |
|
| 40 |
- |
|
| 41 |
- client := httputil.NewClientConn(conn, nil) |
|
| 42 |
- defer client.Close() |
|
| 43 |
- |
|
| 44 |
- req, err := http.NewRequest("GET", "/v999.0/version", nil)
|
|
| 45 |
- c.Assert(err, checker.IsNil) |
|
| 46 |
- req.Header.Set("User-Agent", "Docker-Client/999.0 (os)")
|
|
| 47 |
- |
|
| 48 |
- res, err := client.Do(req) |
|
| 49 |
- c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) |
|
| 50 |
-} |
|
| 51 |
- |
|
| 52 |
-func (s *DockerSuite) TestAPIClientVersionNewerThanServer(c *check.C) {
|
|
| 53 |
- v := strings.Split(api.DefaultVersion, ".") |
|
| 54 |
- vMinInt, err := strconv.Atoi(v[1]) |
|
| 55 |
- c.Assert(err, checker.IsNil) |
|
| 56 |
- vMinInt++ |
|
| 57 |
- v[1] = strconv.Itoa(vMinInt) |
|
| 58 |
- version := strings.Join(v, ".") |
|
| 59 |
- |
|
| 60 |
- status, body, err := sockRequest("GET", "/v"+version+"/version", nil)
|
|
| 61 |
- c.Assert(err, checker.IsNil) |
|
| 62 |
- c.Assert(status, checker.Equals, http.StatusBadRequest) |
|
| 63 |
- expected := fmt.Sprintf("client is newer than server (client API version: %s, server API version: %s)", version, api.DefaultVersion)
|
|
| 64 |
- c.Assert(getErrorMessage(c, body), checker.Equals, expected) |
|
| 65 |
-} |
|
| 66 |
- |
|
| 67 | 37 |
func (s *DockerSuite) TestAPIClientVersionOldNotSupported(c *check.C) {
|
| 68 | 38 |
if daemonPlatform != runtime.GOOS {
|
| 69 | 39 |
c.Skip("Daemon platform doesn't match test platform")
|
| ... | ... |
@@ -90,6 +58,7 @@ func (s *DockerSuite) TestAPIDockerAPIVersion(c *check.C) {
|
| 90 | 90 |
|
| 91 | 91 |
server := httptest.NewServer(http.HandlerFunc( |
| 92 | 92 |
func(w http.ResponseWriter, r *http.Request) {
|
| 93 |
+ w.Header().Set("API-Version", api.DefaultVersion)
|
|
| 93 | 94 |
url := r.URL.Path |
| 94 | 95 |
svrVersion = url |
| 95 | 96 |
})) |
| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
"path/filepath" |
| 10 | 10 |
"runtime" |
| 11 | 11 |
|
| 12 |
+ "github.com/docker/docker/api" |
|
| 12 | 13 |
"github.com/docker/docker/dockerversion" |
| 13 | 14 |
"github.com/docker/docker/pkg/homedir" |
| 14 | 15 |
"github.com/docker/docker/pkg/integration/checker" |
| ... | ... |
@@ -25,6 +26,7 @@ func (s *DockerSuite) TestConfigHTTPHeader(c *check.C) {
|
| 25 | 25 |
|
| 26 | 26 |
server := httptest.NewServer(http.HandlerFunc( |
| 27 | 27 |
func(w http.ResponseWriter, r *http.Request) {
|
| 28 |
+ w.Header().Set("API-Version", api.DefaultVersion)
|
|
| 28 | 29 |
headers = r.Header |
| 29 | 30 |
})) |
| 30 | 31 |
defer server.Close() |
| ... | ... |
@@ -168,6 +168,7 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
|
| 168 | 168 |
flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits") |
| 169 | 169 |
flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
|
| 170 | 170 |
flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container") |
| 171 |
+ flags.SetAnnotation("stop-timeout", "version", []string{"1.25"})
|
|
| 171 | 172 |
flags.Var(copts.sysctls, "sysctl", "Sysctl options") |
| 172 | 173 |
flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY") |
| 173 | 174 |
flags.Var(copts.ulimits, "ulimit", "Ulimit options") |