diff --git a/api/server/middleware/version.go b/api/server/middleware/version.go index 11014659e5..390a7f0594 100644 --- a/api/server/middleware/version.go +++ b/api/server/middleware/version.go @@ -43,6 +43,7 @@ func (v VersionMiddleware) WrapHandler(handler func(ctx context.Context, w http. header := fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS) w.Header().Set("Server", header) w.Header().Set("API-Version", v.defaultVersion) + w.Header().Set("OSType", runtime.GOOS) ctx = context.WithValue(ctx, "api-version", apiVersion) return handler(ctx, w, r, vars) } diff --git a/api/types/types.go b/api/types/types.go index 509a78b633..20efba1c3c 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -97,6 +97,7 @@ type ContainerStats struct { // GET "/_ping" type Ping struct { APIVersion string + OSType string Experimental bool } diff --git a/cli/command/cli.go b/cli/command/cli.go index 782c3a5074..783e516f3d 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -51,6 +51,7 @@ type DockerCli struct { keyFile string client client.APIClient hasExperimental bool + osType string defaultVersion string } @@ -59,6 +60,11 @@ func (cli *DockerCli) HasExperimental() bool { return cli.hasExperimental } +// OSType returns the operating system the daemon is running on. +func (cli *DockerCli) OSType() string { + return cli.osType +} + // DefaultVersion returns api.defaultVersion of DOCKER_API_VERSION if specified. func (cli *DockerCli) DefaultVersion() string { return cli.defaultVersion @@ -166,6 +172,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error { if ping, err := cli.client.Ping(context.Background()); err == nil { cli.hasExperimental = ping.Experimental + cli.osType = ping.OSType // since the new header was added in 1.25, assume server is 1.24 if header is not present. if ping.APIVersion == "" { diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index 16bb1aa434..4ce872b556 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -189,6 +189,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.Var(&copts.securityOpt, "security-opt", "Security Options") flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use") flags.StringVar(&copts.credentialSpec, "credentialspec", "", "Credential spec for managed service account (Windows only)") + flags.SetAnnotation("credentialspec", "ostype", []string{"windows"}) // Network and port publishing flag flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)") @@ -239,7 +240,9 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)") flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)") + flags.SetAnnotation("cpu-count", "ostype", []string{"windows"}) flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)") + flags.SetAnnotation("cpu-percent", "ostype", []string{"windows"}) flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period") flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota") flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds") @@ -254,7 +257,9 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device") flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device") flags.Var(&copts.ioMaxBandwidth, "io-maxbandwidth", "Maximum IO bandwidth limit for the system drive (Windows only)") + flags.SetAnnotation("io-maxbandwidth", "ostype", []string{"windows"}) flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)") + flags.SetAnnotation("io-maxiops", "ostype", []string{"windows"}) flags.Var(&copts.kernelMemory, "kernel-memory", "Kernel memory limit") flags.VarP(&copts.memory, "memory", "m", "Memory limit") flags.Var(&copts.memoryReservation, "memory-reservation", "Memory soft limit") diff --git a/client/ping.go b/client/ping.go index 150b1dc8d8..d6212ef8bb 100644 --- a/client/ping.go +++ b/client/ping.go @@ -7,7 +7,7 @@ import ( "golang.org/x/net/context" ) -// Ping pings the server and returns the value of the "Docker-Experimental" & "API-Version" headers +// Ping pings the server and returns the value of the "Docker-Experimental", "OS-Type" & "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) @@ -26,5 +26,7 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) { ping.Experimental = true } + ping.OSType = serverResp.header.Get("OSType") + return ping, nil } diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index efc1cac25e..570a52a72e 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -49,7 +49,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { if err := dockerCli.Initialize(opts); err != nil { return err } - return isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()) + return isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()) }, } cli.SetupRootCommand(cmd) @@ -80,7 +80,7 @@ func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *p flagErrorFunc := cmd.FlagErrorFunc() cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { initializeDockerCli(dockerCli, flags, opts) - if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { + if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()); err != nil { return err } return flagErrorFunc(cmd, err) @@ -90,12 +90,12 @@ func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *p func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { initializeDockerCli(dockerCli, flags, opts) - if err := isSupported(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { + if err := isSupported(ccmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()); err != nil { ccmd.Println(err) return } - hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()) + hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()) if err := ccmd.Help(); err != nil { ccmd.Println(err) @@ -122,7 +122,7 @@ func setValidateArgs(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pf cmdArgs := ccmd.Args ccmd.Args = func(cmd *cobra.Command, args []string) error { initializeDockerCli(dockerCli, flags, opts) - if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { + if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()); err != nil { return err } return cmdArgs(cmd, args) @@ -198,7 +198,7 @@ func dockerPreRun(opts *cliflags.ClientOptions) { } } -func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperimental bool) { +func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion, osType string, hasExperimental bool) { cmd.Flags().VisitAll(func(f *pflag.Flag) { // hide experimental flags if !hasExperimental { @@ -208,10 +208,9 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperi } // hide flags not supported by the server - if !isFlagSupported(f, clientVersion) { + if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) { f.Hidden = true } - }) for _, subcmd := range cmd.Commands() { @@ -229,7 +228,7 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperi } } -func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) error { +func isSupported(cmd *cobra.Command, clientVersion, osType string, hasExperimental bool) error { // We check recursively so that, e.g., `docker stack ls` will return the same output as `docker stack` if !hasExperimental { for curr := cmd; curr != nil; curr = curr.Parent() { @@ -247,8 +246,12 @@ func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) cmd.Flags().VisitAll(func(f *pflag.Flag) { if f.Changed { - if !isFlagSupported(f, clientVersion) { - errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagVersion(f), clientVersion)) + if !isVersionSupported(f, clientVersion) { + errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagAnnotation(f, "version"), clientVersion)) + return + } + if !isOSTypeSupported(f, osType) { + errs = append(errs, fmt.Sprintf("\"--%s\" requires the Docker daemon to run on %s, but the Docker daemon is running on %s", f.Name, getFlagAnnotation(f, "ostype"), osType)) return } if _, ok := f.Annotations["experimental"]; ok && !hasExperimental { @@ -263,20 +266,27 @@ func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) return nil } -func getFlagVersion(f *pflag.Flag) string { - if flagVersion, ok := f.Annotations["version"]; ok && len(flagVersion) == 1 { - return flagVersion[0] +func getFlagAnnotation(f *pflag.Flag, annotation string) string { + if value, ok := f.Annotations[annotation]; ok && len(value) == 1 { + return value[0] } return "" } -func isFlagSupported(f *pflag.Flag, clientVersion string) bool { - if v := getFlagVersion(f); v != "" { +func isVersionSupported(f *pflag.Flag, clientVersion string) bool { + if v := getFlagAnnotation(f, "version"); v != "" { return versions.GreaterThanOrEqualTo(clientVersion, v) } return true } +func isOSTypeSupported(f *pflag.Flag, osType string) bool { + if v := getFlagAnnotation(f, "ostype"); v != "" && osType != "" { + return osType == v + } + return true +} + // hasTags return true if any of the command's parents has tags func hasTags(cmd *cobra.Command) bool { for curr := cmd; curr != nil; curr = curr.Parent() {