func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperimental bool) { cmd.Flags().VisitAll(func(f *pflag.Flag) { // hide experimental flags if !hasExperimental { 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 experimental subcommands if !hasExperimental { 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 } } }
func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { info, err := s.backend.SystemInfo() if err != nil { return err } if s.cluster != nil { info.Swarm = s.cluster.Info() } if versions.LessThan(httputils.VersionFromContext(ctx), "1.25") { // TODO: handle this conversion in engine-api type oldInfo struct { *types.Info ExecutionDriver string } old := &oldInfo{ Info: info, ExecutionDriver: "<not supported>", } nameOnlySecurityOptions := []string{} kvSecOpts, err := types.DecodeSecurityOptions(old.SecurityOptions) if err != nil { return err } for _, s := range kvSecOpts { nameOnlySecurityOptions = append(nameOnlySecurityOptions, s.Name) } old.SecurityOptions = nameOnlySecurityOptions return httputils.WriteJSON(w, http.StatusOK, old) } return httputils.WriteJSON(w, http.StatusOK, info) }
func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return err } if err := httputils.CheckForJSON(r); err != nil { return err } name := r.Form.Get("name") config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body) if err != nil { return err } version := httputils.VersionFromContext(ctx) adjustCPUShares := versions.LessThan(version, "1.19") ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{ Name: name, Config: config, HostConfig: hostConfig, NetworkingConfig: networkingConfig, AdjustCPUShares: adjustCPUShares, }) if err != nil { return err } return httputils.WriteJSON(w, http.StatusCreated, ccr) }
// ImageList returns a list of images in the docker host. func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) { var images []types.ImageSummary query := url.Values{} optionFilters := options.Filters referenceFilters := optionFilters.Get("reference") if versions.LessThan(cli.version, "1.25") && len(referenceFilters) > 0 { query.Set("filter", referenceFilters[0]) for _, filterValue := range referenceFilters { optionFilters.Del("reference", filterValue) } } if optionFilters.Len() > 0 { filterJSON, err := filters.ToParamWithVersion(cli.version, optionFilters) if err != nil { return images, err } query.Set("filters", filterJSON) } if options.All { query.Set("all", "1") } serverResp, err := cli.get(ctx, "/images/json", query, nil) if err != nil { return images, err } err = json.NewDecoder(serverResp.body).Decode(&images) ensureReaderClosed(serverResp) return images, err }
func (s *DockerSuite) TestAPIStatsNetworkStatsVersioning(c *check.C) { // Windows doesn't support API versions less than 1.25, so no point testing 1.17 .. 1.21 testRequires(c, SameHostDaemon, DaemonIsLinux) out, _ := runSleepingContainer(c) id := strings.TrimSpace(out) c.Assert(waitRun(id), checker.IsNil) wg := sync.WaitGroup{} for i := 17; i <= 21; i++ { wg.Add(1) go func(i int) { defer wg.Done() apiVersion := fmt.Sprintf("v1.%d", i) statsJSONBlob := getVersionedStats(c, id, apiVersion) if versions.LessThan(apiVersion, "v1.21") { c.Assert(jsonBlobHasLTv121NetworkStats(statsJSONBlob), checker.Equals, true, check.Commentf("Stats JSON blob from API %s %#v does not look like a <v1.21 API stats structure", apiVersion, statsJSONBlob)) } else { c.Assert(jsonBlobHasGTE121NetworkStats(statsJSONBlob), checker.Equals, true, check.Commentf("Stats JSON blob from API %s %#v does not look like a >=v1.21 API stats structure", apiVersion, statsJSONBlob)) } }(i) } wg.Wait() }
// ContainerCreate creates a new container based in the given configuration. // 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 } // When using API 1.24 and under, the client is responsible for removing the container if hostConfig != nil && versions.LessThan(cli.ClientVersion(), "1.25") { hostConfig.AutoRemove = false } query := url.Values{} if containerName != "" { query.Set("name", containerName) } body := configWrapper{ Config: config, HostConfig: hostConfig, NetworkingConfig: networkingConfig, } serverResp, err := cli.post(ctx, "/containers/create", query, body, nil) if err != nil { if serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") { return response, imageNotFoundError{config.Image} } return response, err } err = json.NewDecoder(serverResp.body).Decode(&response) ensureReaderClosed(serverResp) return response, err }
func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { info, err := s.backend.SystemInfo() if err != nil { return err } if s.clusterProvider != nil { info.Swarm = s.clusterProvider.Info() } if versions.LessThan(httputils.VersionFromContext(ctx), "1.25") { // TODO: handle this conversion in engine-api type oldInfo struct { *types.InfoBase ExecutionDriver string SecurityOptions []string } old := &oldInfo{ InfoBase: info.InfoBase, ExecutionDriver: "<not supported>", } for _, s := range info.SecurityOptions { if s.Key == "Name" { old.SecurityOptions = append(old.SecurityOptions, s.Value) } } return httputils.WriteJSON(w, http.StatusOK, old) } return httputils.WriteJSON(w, http.StatusOK, info) }
// Initialize the dockerCli runs initialization that must happen after command // line flags are parsed. func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error { cli.configFile = LoadDefaultConfigFile(cli.err) var err error cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile) 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 }
// ContainerInspect returns low-level information about a // container. Returns an error if the container cannot be found, or if // there is an error getting the data. func (daemon *Daemon) ContainerInspect(name string, size bool, version string) (interface{}, error) { switch { case versions.LessThan(version, "1.20"): return daemon.containerInspectPre120(name) case versions.Equal(version, "1.20"): return daemon.containerInspect120(name) } return daemon.ContainerInspectCurrent(name, size) }
func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) error { if !hasExperimental { if _, ok := cmd.Tags["experimental"]; ok { return errors.New("only supported with experimental daemon") } } if cmdVersion, ok := cmd.Tags["version"]; ok && versions.LessThan(clientVersion, cmdVersion) { return fmt.Errorf("only supported with daemon version >= %s", cmdVersion) } return nil }
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) } if headers != nil { for k, v := range headers { req.Header[k] = v } } return req }
func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { info, err := s.backend.SystemInfo() if err != nil { return err } if s.clusterProvider != nil { info.Swarm = s.clusterProvider.Info() } if versions.LessThan(httputils.VersionFromContext(ctx), "1.25") { // TODO: handle this conversion in engine-api type oldInfo struct { *types.Info ExecutionDriver string } return httputils.WriteJSON(w, http.StatusOK, &oldInfo{Info: info, ExecutionDriver: "<not supported>"}) } return httputils.WriteJSON(w, http.StatusOK, info) }
// ToParamWithVersion packs the Args into a string for easy transport from client to server. // The generated string will depend on the specified version (corresponding to the API version). func ToParamWithVersion(version string, a Args) (string, error) { // this way we don't URL encode {}, just empty space if a.Len() == 0 { return "", nil } // for daemons older than v1.10, filter must be of the form map[string][]string var buf []byte var err error if version != "" && versions.LessThan(version, "1.22") { buf, err = json.Marshal(convertArgsToSlice(a.fields)) } else { buf, err = json.Marshal(a.fields) } if err != nil { return "", err } return string(buf), nil }
// WrapHandler returns a new handler function wrapping the previous one in the request chain. func (v VersionMiddleware) 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 { apiVersion := vars["version"] if apiVersion == "" { 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) } }
// MakeConfigFromV1Config creates an image config from the legacy V1 config format. func MakeConfigFromV1Config(imageJSON []byte, rootfs *image.RootFS, history []image.History) ([]byte, error) { var dver struct { DockerVersion string `json:"docker_version"` } if err := json.Unmarshal(imageJSON, &dver); err != nil { return nil, err } useFallback := versions.LessThan(dver.DockerVersion, noFallbackMinVersion) if useFallback { var v1Image image.V1Image err := json.Unmarshal(imageJSON, &v1Image) if err != nil { return nil, err } imageJSON, err = json.Marshal(v1Image) if err != nil { return nil, err } } var c map[string]*json.RawMessage if err := json.Unmarshal(imageJSON, &c); err != nil { return nil, err } delete(c, "id") delete(c, "parent") delete(c, "Size") // Size is calculated from data on disk and is inconsistent delete(c, "parent_id") delete(c, "layer_id") delete(c, "throwaway") c["rootfs"] = rawJSON(rootfs) c["history"] = rawJSON(history) return json.Marshal(c) }
func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return err } imageFilters, err := filters.FromParam(r.Form.Get("filters")) if err != nil { return err } version := httputils.VersionFromContext(ctx) filterParam := r.Form.Get("filter") if versions.LessThan(version, "1.28") && filterParam != "" { imageFilters.Add("reference", filterParam) } images, err := s.backend.Images(imageFilters, httputils.BoolValue(r, "all"), false) if err != nil { return err } return httputils.WriteJSON(w, http.StatusOK, images) }
// ContainerStats writes information about the container to the stream // given in the config object. func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error { if runtime.GOOS == "solaris" { return fmt.Errorf("%+v does not support stats", runtime.GOOS) } // Remote API version (used for backwards compatibility) apiVersion := config.Version container, err := daemon.GetContainer(prefixOrName) if err != nil { return err } // If the container is not running and requires no stream, return an empty stats. if !container.IsRunning() && !config.Stream { return json.NewEncoder(config.OutStream).Encode(&types.Stats{}) } outStream := config.OutStream if config.Stream { wf := ioutils.NewWriteFlusher(outStream) defer wf.Close() wf.Flush() outStream = wf } var preCPUStats types.CPUStats var preRead time.Time getStatJSON := func(v interface{}) *types.StatsJSON { ss := v.(types.StatsJSON) ss.PreCPUStats = preCPUStats ss.PreRead = preRead preCPUStats = ss.CPUStats preRead = ss.Read return &ss } enc := json.NewEncoder(outStream) updates := daemon.subscribeToContainerStats(container) defer daemon.unsubscribeToContainerStats(container, updates) noStreamFirstFrame := true for { select { case v, ok := <-updates: if !ok { return nil } var statsJSON interface{} statsJSONPost120 := getStatJSON(v) if versions.LessThan(apiVersion, "1.21") { if runtime.GOOS == "windows" { return errors.New("API versions pre v1.21 do not support stats on Windows") } var ( rxBytes uint64 rxPackets uint64 rxErrors uint64 rxDropped uint64 txBytes uint64 txPackets uint64 txErrors uint64 txDropped uint64 ) for _, v := range statsJSONPost120.Networks { rxBytes += v.RxBytes rxPackets += v.RxPackets rxErrors += v.RxErrors rxDropped += v.RxDropped txBytes += v.TxBytes txPackets += v.TxPackets txErrors += v.TxErrors txDropped += v.TxDropped } statsJSON = &v1p20.StatsJSON{ Stats: statsJSONPost120.Stats, Network: types.NetworkStats{ RxBytes: rxBytes, RxPackets: rxPackets, RxErrors: rxErrors, RxDropped: rxDropped, TxBytes: txBytes, TxPackets: txPackets, TxErrors: txErrors, TxDropped: txDropped, }, } } else { statsJSON = statsJSONPost120 } if !config.Stream && noStreamFirstFrame { // prime the cpu stats so they aren't 0 in the final output noStreamFirstFrame = false continue } if err := enc.Encode(statsJSON); err != nil { return err } if !config.Stream { return nil } case <-ctx.Done(): return nil } } }
// 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 }
func waitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) chan int { if len(containerID) == 0 { // containerID can never be empty panic("Internal Error: waitExitOrRemoved needs a containerID as parameter") } var removeErr error statusChan := make(chan int) exitCode := 125 // Get events via Events API f := filters.NewArgs() f.Add("type", "container") f.Add("container", containerID) options := types.EventsOptions{ Filters: f, } eventCtx, cancel := context.WithCancel(ctx) eventq, errq := dockerCli.Client().Events(eventCtx, options) eventProcessor := func(e events.Message) bool { stopProcessing := false switch e.Status { case "die": if v, ok := e.Actor.Attributes["exitCode"]; ok { code, cerr := strconv.Atoi(v) if cerr != nil { logrus.Errorf("failed to convert exitcode '%q' to int: %v", v, cerr) } else { exitCode = code } } if !waitRemove { stopProcessing = true } else { // If we are talking to an older daemon, `AutoRemove` is not supported. // We need to fall back to the old behavior, which is client-side removal if versions.LessThan(dockerCli.Client().ClientVersion(), "1.25") { go func() { removeErr = dockerCli.Client().ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{RemoveVolumes: true}) if removeErr != nil { logrus.Errorf("error removing container: %v", removeErr) cancel() // cancel the event Q } }() } } case "detach": exitCode = 0 stopProcessing = true case "destroy": stopProcessing = true } return stopProcessing } go func() { defer func() { statusChan <- exitCode // must always send an exit code or the caller will block cancel() }() for { select { case <-eventCtx.Done(): if removeErr != nil { return } case evt := <-eventq: if eventProcessor(evt) { return } case err := <-errq: logrus.Errorf("error getting events from daemon: %v", err) return } } }() return statusChan }