Merge pull request #47155 from thaJeztah/remove_deprecated_api_versions

api: remove deprecated API versions (API < v1.24)
This commit is contained in:
Sebastiaan van Stijn 2024-02-07 01:43:04 +01:00 committed by GitHub
commit 9e075f3808
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 200 additions and 1445 deletions

View file

@ -2,9 +2,18 @@ package api // import "github.com/docker/docker/api"
// Common constants for daemon and client. // Common constants for daemon and client.
const ( const (
// DefaultVersion of Current REST API // DefaultVersion of the current REST API.
DefaultVersion = "1.45" DefaultVersion = "1.45"
// MinSupportedAPIVersion is the minimum API version that can be supported
// by the API server, specified as "major.minor". Note that the daemon
// may be configured with a different minimum API version, as returned
// in [github.com/docker/docker/api/types.Version.MinAPIVersion].
//
// API requests for API versions lower than the configured version produce
// an error.
MinSupportedAPIVersion = "1.24"
// NoBaseImageSpecifier is the symbol used by the FROM // NoBaseImageSpecifier is the symbol used by the FROM
// command to specify that no base image is to be used. // command to specify that no base image is to be used.
NoBaseImageSpecifier = "scratch" NoBaseImageSpecifier = "scratch"

View file

@ -1,34 +0,0 @@
package server
import (
"net/http"
"github.com/docker/docker/api/server/httpstatus"
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
"github.com/gorilla/mux"
"google.golang.org/grpc/status"
)
// makeErrorHandler makes an HTTP handler that decodes a Docker error and
// returns it in the response.
func makeErrorHandler(err error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
statusCode := httpstatus.FromError(err)
vars := mux.Vars(r)
if apiVersionSupportsJSONErrors(vars["version"]) {
response := &types.ErrorResponse{
Message: err.Error(),
}
_ = httputils.WriteJSON(w, statusCode, response)
} else {
http.Error(w, status.Convert(err).Message(), statusCode)
}
}
}
func apiVersionSupportsJSONErrors(version string) bool {
const firstAPIVersionWithJSONErrors = "1.23"
return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
}

View file

@ -12,5 +12,4 @@ import (
// container configuration. // container configuration.
type ContainerDecoder interface { type ContainerDecoder interface {
DecodeConfig(src io.Reader) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) DecodeConfig(src io.Reader) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error)
DecodeHostConfig(src io.Reader) (*container.HostConfig, error)
} }

View file

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"runtime" "runtime"
"github.com/docker/docker/api"
"github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
) )
@ -13,19 +14,40 @@ import (
// VersionMiddleware is a middleware that // VersionMiddleware is a middleware that
// validates the client and server versions. // validates the client and server versions.
type VersionMiddleware struct { type VersionMiddleware struct {
serverVersion string serverVersion string
defaultVersion string
minVersion string // defaultAPIVersion is the default API version provided by the API server,
// specified as "major.minor". It is usually configured to the latest API
// version [github.com/docker/docker/api.DefaultVersion].
//
// API requests for API versions greater than this version are rejected by
// the server and produce a [versionUnsupportedError].
defaultAPIVersion string
// minAPIVersion is the minimum API version provided by the API server,
// specified as "major.minor".
//
// API requests for API versions lower than this version are rejected by
// the server and produce a [versionUnsupportedError].
minAPIVersion string
} }
// NewVersionMiddleware creates a new VersionMiddleware // NewVersionMiddleware creates a VersionMiddleware with the given versions.
// with the default versions. func NewVersionMiddleware(serverVersion, defaultAPIVersion, minAPIVersion string) (*VersionMiddleware, error) {
func NewVersionMiddleware(s, d, m string) VersionMiddleware { if versions.LessThan(defaultAPIVersion, api.MinSupportedAPIVersion) || versions.GreaterThan(defaultAPIVersion, api.DefaultVersion) {
return VersionMiddleware{ return nil, fmt.Errorf("invalid default API version (%s): must be between %s and %s", defaultAPIVersion, api.MinSupportedAPIVersion, api.DefaultVersion)
serverVersion: s,
defaultVersion: d,
minVersion: m,
} }
if versions.LessThan(minAPIVersion, api.MinSupportedAPIVersion) || versions.GreaterThan(minAPIVersion, api.DefaultVersion) {
return nil, fmt.Errorf("invalid minimum API version (%s): must be between %s and %s", minAPIVersion, api.MinSupportedAPIVersion, api.DefaultVersion)
}
if versions.GreaterThan(minAPIVersion, defaultAPIVersion) {
return nil, fmt.Errorf("invalid API version: the minimum API version (%s) is higher than the default version (%s)", minAPIVersion, defaultAPIVersion)
}
return &VersionMiddleware{
serverVersion: serverVersion,
defaultAPIVersion: defaultAPIVersion,
minAPIVersion: minAPIVersion,
}, nil
} }
type versionUnsupportedError struct { type versionUnsupportedError struct {
@ -45,18 +67,18 @@ func (e versionUnsupportedError) InvalidParameter() {}
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 { 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 { return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
w.Header().Set("Server", fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS)) w.Header().Set("Server", fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS))
w.Header().Set("API-Version", v.defaultVersion) w.Header().Set("API-Version", v.defaultAPIVersion)
w.Header().Set("OSType", runtime.GOOS) w.Header().Set("OSType", runtime.GOOS)
apiVersion := vars["version"] apiVersion := vars["version"]
if apiVersion == "" { if apiVersion == "" {
apiVersion = v.defaultVersion apiVersion = v.defaultAPIVersion
} }
if versions.LessThan(apiVersion, v.minVersion) { if versions.LessThan(apiVersion, v.minAPIVersion) {
return versionUnsupportedError{version: apiVersion, minVersion: v.minVersion} return versionUnsupportedError{version: apiVersion, minVersion: v.minAPIVersion}
} }
if versions.GreaterThan(apiVersion, v.defaultVersion) { if versions.GreaterThan(apiVersion, v.defaultAPIVersion) {
return versionUnsupportedError{version: apiVersion, maxVersion: v.defaultVersion} return versionUnsupportedError{version: apiVersion, maxVersion: v.defaultAPIVersion}
} }
ctx = context.WithValue(ctx, httputils.APIVersionKey{}, apiVersion) ctx = context.WithValue(ctx, httputils.APIVersionKey{}, apiVersion)
return handler(ctx, w, r, vars) return handler(ctx, w, r, vars)

View file

@ -2,27 +2,82 @@ package middleware // import "github.com/docker/docker/api/server/middleware"
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"runtime" "runtime"
"testing" "testing"
"github.com/docker/docker/api"
"github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/httputils"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp" is "gotest.tools/v3/assert/cmp"
) )
func TestNewVersionMiddlewareValidation(t *testing.T) {
tests := []struct {
doc, defaultVersion, minVersion, expectedErr string
}{
{
doc: "defaults",
defaultVersion: api.DefaultVersion,
minVersion: api.MinSupportedAPIVersion,
},
{
doc: "invalid default lower than min",
defaultVersion: api.MinSupportedAPIVersion,
minVersion: api.DefaultVersion,
expectedErr: fmt.Sprintf("invalid API version: the minimum API version (%s) is higher than the default version (%s)", api.DefaultVersion, api.MinSupportedAPIVersion),
},
{
doc: "invalid default too low",
defaultVersion: "0.1",
minVersion: api.MinSupportedAPIVersion,
expectedErr: fmt.Sprintf("invalid default API version (0.1): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
},
{
doc: "invalid default too high",
defaultVersion: "9999.9999",
minVersion: api.DefaultVersion,
expectedErr: fmt.Sprintf("invalid default API version (9999.9999): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
},
{
doc: "invalid minimum too low",
defaultVersion: api.MinSupportedAPIVersion,
minVersion: "0.1",
expectedErr: fmt.Sprintf("invalid minimum API version (0.1): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
},
{
doc: "invalid minimum too high",
defaultVersion: api.DefaultVersion,
minVersion: "9999.9999",
expectedErr: fmt.Sprintf("invalid minimum API version (9999.9999): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
_, err := NewVersionMiddleware("1.2.3", tc.defaultVersion, tc.minVersion)
if tc.expectedErr == "" {
assert.Check(t, err)
} else {
assert.Check(t, is.Error(err, tc.expectedErr))
}
})
}
}
func TestVersionMiddlewareVersion(t *testing.T) { func TestVersionMiddlewareVersion(t *testing.T) {
defaultVersion := "1.10.0" expectedVersion := "<not set>"
minVersion := "1.2.0"
expectedVersion := defaultVersion
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
v := httputils.VersionFromContext(ctx) v := httputils.VersionFromContext(ctx)
assert.Check(t, is.Equal(expectedVersion, v)) assert.Check(t, is.Equal(expectedVersion, v))
return nil return nil
} }
m := NewVersionMiddleware(defaultVersion, defaultVersion, minVersion) m, err := NewVersionMiddleware("1.2.3", api.DefaultVersion, api.MinSupportedAPIVersion)
assert.NilError(t, err)
h := m.WrapHandler(handler) h := m.WrapHandler(handler)
req, _ := http.NewRequest(http.MethodGet, "/containers/json", nil) req, _ := http.NewRequest(http.MethodGet, "/containers/json", nil)
@ -35,19 +90,19 @@ func TestVersionMiddlewareVersion(t *testing.T) {
errString string errString string
}{ }{
{ {
expectedVersion: "1.10.0", expectedVersion: api.DefaultVersion,
}, },
{ {
reqVersion: "1.9.0", reqVersion: api.MinSupportedAPIVersion,
expectedVersion: "1.9.0", expectedVersion: api.MinSupportedAPIVersion,
}, },
{ {
reqVersion: "0.1", reqVersion: "0.1",
errString: "client version 0.1 is too old. Minimum supported API version is 1.2.0, please upgrade your client to a newer version", errString: fmt.Sprintf("client version 0.1 is too old. Minimum supported API version is %s, please upgrade your client to a newer version", api.MinSupportedAPIVersion),
}, },
{ {
reqVersion: "9999.9999", reqVersion: "9999.9999",
errString: "client version 9999.9999 is too new. Maximum supported API version is 1.10.0", errString: fmt.Sprintf("client version 9999.9999 is too new. Maximum supported API version is %s", api.DefaultVersion),
}, },
} }
@ -71,9 +126,8 @@ func TestVersionMiddlewareWithErrorsReturnsHeaders(t *testing.T) {
return nil return nil
} }
defaultVersion := "1.10.0" m, err := NewVersionMiddleware("1.2.3", api.DefaultVersion, api.MinSupportedAPIVersion)
minVersion := "1.2.0" assert.NilError(t, err)
m := NewVersionMiddleware(defaultVersion, defaultVersion, minVersion)
h := m.WrapHandler(handler) h := m.WrapHandler(handler)
req, _ := http.NewRequest(http.MethodGet, "/containers/json", nil) req, _ := http.NewRequest(http.MethodGet, "/containers/json", nil)
@ -81,12 +135,12 @@ func TestVersionMiddlewareWithErrorsReturnsHeaders(t *testing.T) {
ctx := context.Background() ctx := context.Background()
vars := map[string]string{"version": "0.1"} vars := map[string]string{"version": "0.1"}
err := h(ctx, resp, req, vars) err = h(ctx, resp, req, vars)
assert.Check(t, is.ErrorContains(err, "")) assert.Check(t, is.ErrorContains(err, ""))
hdr := resp.Result().Header hdr := resp.Result().Header
assert.Check(t, is.Contains(hdr.Get("Server"), "Docker/"+defaultVersion)) assert.Check(t, is.Contains(hdr.Get("Server"), "Docker/1.2.3"))
assert.Check(t, is.Contains(hdr.Get("Server"), runtime.GOOS)) assert.Check(t, is.Contains(hdr.Get("Server"), runtime.GOOS))
assert.Check(t, is.Equal(hdr.Get("API-Version"), defaultVersion)) assert.Check(t, is.Equal(hdr.Get("API-Version"), api.DefaultVersion))
assert.Check(t, is.Equal(hdr.Get("OSType"), runtime.GOOS)) assert.Check(t, is.Equal(hdr.Get("OSType"), runtime.GOOS))
} }

View file

@ -42,6 +42,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
SuppressOutput: httputils.BoolValue(r, "q"), SuppressOutput: httputils.BoolValue(r, "q"),
NoCache: httputils.BoolValue(r, "nocache"), NoCache: httputils.BoolValue(r, "nocache"),
ForceRemove: httputils.BoolValue(r, "forcerm"), ForceRemove: httputils.BoolValue(r, "forcerm"),
PullParent: httputils.BoolValue(r, "pull"),
MemorySwap: httputils.Int64ValueOrZero(r, "memswap"), MemorySwap: httputils.Int64ValueOrZero(r, "memswap"),
Memory: httputils.Int64ValueOrZero(r, "memory"), Memory: httputils.Int64ValueOrZero(r, "memory"),
CPUShares: httputils.Int64ValueOrZero(r, "cpushares"), CPUShares: httputils.Int64ValueOrZero(r, "cpushares"),
@ -66,17 +67,14 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
return nil, invalidParam{errors.New("security options are not supported on " + runtime.GOOS)} return nil, invalidParam{errors.New("security options are not supported on " + runtime.GOOS)}
} }
version := httputils.VersionFromContext(ctx) if httputils.BoolValue(r, "forcerm") {
if httputils.BoolValue(r, "forcerm") && versions.GreaterThanOrEqualTo(version, "1.12") {
options.Remove = true options.Remove = true
} else if r.FormValue("rm") == "" && versions.GreaterThanOrEqualTo(version, "1.12") { } else if r.FormValue("rm") == "" {
options.Remove = true options.Remove = true
} else { } else {
options.Remove = httputils.BoolValue(r, "rm") options.Remove = httputils.BoolValue(r, "rm")
} }
if httputils.BoolValue(r, "pull") && versions.GreaterThanOrEqualTo(version, "1.16") { version := httputils.VersionFromContext(ctx)
options.PullParent = true
}
if versions.GreaterThanOrEqualTo(version, "1.32") { if versions.GreaterThanOrEqualTo(version, "1.32") {
options.Platform = r.FormValue("platform") options.Platform = r.FormValue("platform")
} }

View file

@ -24,7 +24,6 @@ type execBackend interface {
// copyBackend includes functions to implement to provide container copy functionality. // copyBackend includes functions to implement to provide container copy functionality.
type copyBackend interface { type copyBackend interface {
ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error)
ContainerCopy(name string, res string) (io.ReadCloser, error)
ContainerExport(ctx context.Context, name string, out io.Writer) error ContainerExport(ctx context.Context, name string, out io.Writer) error
ContainerExtractToDir(name, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) error ContainerExtractToDir(name, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) error
ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error)
@ -39,7 +38,7 @@ type stateBackend interface {
ContainerResize(name string, height, width int) error ContainerResize(name string, height, width int) error
ContainerRestart(ctx context.Context, name string, options container.StopOptions) error ContainerRestart(ctx context.Context, name string, options container.StopOptions) error
ContainerRm(name string, config *backend.ContainerRmConfig) error ContainerRm(name string, config *backend.ContainerRmConfig) error
ContainerStart(ctx context.Context, name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error ContainerStart(ctx context.Context, name string, checkpoint string, checkpointDir string) error
ContainerStop(ctx context.Context, name string, options container.StopOptions) error ContainerStop(ctx context.Context, name string, options container.StopOptions) error
ContainerUnpause(name string) error ContainerUnpause(name string) error
ContainerUpdate(name string, hostConfig *container.HostConfig) (container.ContainerUpdateOKBody, error) ContainerUpdate(name string, hostConfig *container.HostConfig) (container.ContainerUpdateOKBody, error)

View file

@ -56,7 +56,6 @@ func (r *containerRouter) initRoutes() {
router.NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait), router.NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait),
router.NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize), router.NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize),
router.NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach), router.NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach),
router.NewPostRoute("/containers/{name:.*}/copy", r.postContainersCopy), // Deprecated since 1.8 (API v1.20), errors out since 1.12 (API v1.24)
router.NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate), router.NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate),
router.NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart), router.NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
router.NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize), router.NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),

View file

@ -39,13 +39,6 @@ func (s *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter,
return err return err
} }
// TODO: remove pause arg, and always pause in backend
pause := httputils.BoolValue(r, "pause")
version := httputils.VersionFromContext(ctx)
if r.FormValue("pause") == "" && versions.GreaterThanOrEqualTo(version, "1.13") {
pause = true
}
config, _, _, err := s.decoder.DecodeConfig(r.Body) config, _, _, err := s.decoder.DecodeConfig(r.Body)
if err != nil && !errors.Is(err, io.EOF) { // Do not fail if body is empty. if err != nil && !errors.Is(err, io.EOF) { // Do not fail if body is empty.
return err return err
@ -57,7 +50,7 @@ func (s *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter,
} }
imgID, err := s.backend.CreateImageFromContainer(ctx, r.Form.Get("container"), &backend.CreateImageConfig{ imgID, err := s.backend.CreateImageFromContainer(ctx, r.Form.Get("container"), &backend.CreateImageConfig{
Pause: pause, Pause: httputils.BoolValueOrDefault(r, "pause", true), // TODO(dnephin): remove pause arg, and always pause in backend
Tag: ref, Tag: ref,
Author: r.Form.Get("author"), Author: r.Form.Get("author"),
Comment: r.Form.Get("comment"), Comment: r.Form.Get("comment"),
@ -118,14 +111,11 @@ func (s *containerRouter) getContainersStats(ctx context.Context, w http.Respons
oneShot = httputils.BoolValueOrDefault(r, "one-shot", false) oneShot = httputils.BoolValueOrDefault(r, "one-shot", false)
} }
config := &backend.ContainerStatsConfig{ return s.backend.ContainerStats(ctx, vars["name"], &backend.ContainerStatsConfig{
Stream: stream, Stream: stream,
OneShot: oneShot, OneShot: oneShot,
OutStream: w, OutStream: w,
Version: httputils.VersionFromContext(ctx), })
}
return s.backend.ContainerStats(ctx, vars["name"], config)
} }
func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@ -178,14 +168,6 @@ func (s *containerRouter) getContainersExport(ctx context.Context, w http.Respon
return s.backend.ContainerExport(ctx, vars["name"], w) return s.backend.ContainerExport(ctx, vars["name"], w)
} }
type bodyOnStartError struct{}
func (bodyOnStartError) Error() string {
return "starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24"
}
func (bodyOnStartError) InvalidParameter() {}
func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
// If contentLength is -1, we can assumed chunked encoding // If contentLength is -1, we can assumed chunked encoding
// or more technically that the length is unknown // or more technically that the length is unknown
@ -193,33 +175,17 @@ func (s *containerRouter) postContainersStart(ctx context.Context, w http.Respon
// net/http otherwise seems to swallow any headers related to chunked encoding // net/http otherwise seems to swallow any headers related to chunked encoding
// including r.TransferEncoding // including r.TransferEncoding
// allow a nil body for backwards compatibility // allow a nil body for backwards compatibility
//
version := httputils.VersionFromContext(ctx)
var hostConfig *container.HostConfig
// A non-nil json object is at least 7 characters. // A non-nil json object is at least 7 characters.
if r.ContentLength > 7 || r.ContentLength == -1 { if r.ContentLength > 7 || r.ContentLength == -1 {
if versions.GreaterThanOrEqualTo(version, "1.24") { return errdefs.InvalidParameter(errors.New("starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24"))
return bodyOnStartError{}
}
if err := httputils.CheckForJSON(r); err != nil {
return err
}
c, err := s.decoder.DecodeHostConfig(r.Body)
if err != nil {
return err
}
hostConfig = c
} }
if err := httputils.ParseForm(r); err != nil { if err := httputils.ParseForm(r); err != nil {
return err return err
} }
checkpoint := r.Form.Get("checkpoint") if err := s.backend.ContainerStart(ctx, vars["name"], r.Form.Get("checkpoint"), r.Form.Get("checkpoint-dir")); err != nil {
checkpointDir := r.Form.Get("checkpoint-dir")
if err := s.backend.ContainerStart(ctx, vars["name"], hostConfig, checkpoint, checkpointDir); err != nil {
return err return err
} }
@ -255,25 +221,14 @@ func (s *containerRouter) postContainersStop(ctx context.Context, w http.Respons
return nil return nil
} }
func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func (s *containerRouter) postContainersKill(_ context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := httputils.ParseForm(r); err != nil { if err := httputils.ParseForm(r); err != nil {
return err return err
} }
name := vars["name"] name := vars["name"]
if err := s.backend.ContainerKill(name, r.Form.Get("signal")); err != nil { if err := s.backend.ContainerKill(name, r.Form.Get("signal")); err != nil {
var isStopped bool return errors.Wrapf(err, "cannot kill container: %s", name)
if errdefs.IsConflict(err) {
isStopped = true
}
// Return error that's not caused because the container is stopped.
// Return error if the container is not running and the api is >= 1.20
// to keep backwards compatibility.
version := httputils.VersionFromContext(ctx)
if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped {
return errors.Wrapf(err, "Cannot kill container: %s", name)
}
} }
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
@ -512,7 +467,6 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
} }
version := httputils.VersionFromContext(ctx) version := httputils.VersionFromContext(ctx)
adjustCPUShares := versions.LessThan(version, "1.19")
// When using API 1.24 and under, the client is responsible for removing the container // When using API 1.24 and under, the client is responsible for removing the container
if versions.LessThan(version, "1.25") { if versions.LessThan(version, "1.25") {
@ -656,7 +610,6 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
Config: config, Config: config,
HostConfig: hostConfig, HostConfig: hostConfig,
NetworkingConfig: networkingConfig, NetworkingConfig: networkingConfig,
AdjustCPUShares: adjustCPUShares,
Platform: platform, Platform: platform,
}) })
if err != nil { if err != nil {

View file

@ -11,49 +11,10 @@ import (
"github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
gddohttputil "github.com/golang/gddo/httputil" gddohttputil "github.com/golang/gddo/httputil"
) )
type pathError struct{} // setContainerPathStatHeader encodes the stat to JSON, base64 encode, and place in a header.
func (pathError) Error() string {
return "Path cannot be empty"
}
func (pathError) InvalidParameter() {}
// postContainersCopy is deprecated in favor of getContainersArchive.
//
// Deprecated since 1.8 (API v1.20), errors out since 1.12 (API v1.24)
func (s *containerRouter) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
version := httputils.VersionFromContext(ctx)
if versions.GreaterThanOrEqualTo(version, "1.24") {
w.WriteHeader(http.StatusNotFound)
return nil
}
cfg := types.CopyConfig{}
if err := httputils.ReadJSON(r, &cfg); err != nil {
return err
}
if cfg.Resource == "" {
return pathError{}
}
data, err := s.backend.ContainerCopy(vars["name"], cfg.Resource)
if err != nil {
return err
}
defer data.Close()
w.Header().Set("Content-Type", "application/x-tar")
_, err = io.Copy(w, data)
return err
}
// // Encode the stat to JSON, base64 encode, and place in a header.
func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error { func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error {
statJSON, err := json.Marshal(stat) statJSON, err := json.Marshal(stat)
if err != nil { if err != nil {

View file

@ -71,15 +71,6 @@ func (s *containerRouter) postContainerExecStart(ctx context.Context, w http.Res
return err return err
} }
version := httputils.VersionFromContext(ctx)
if versions.LessThan(version, "1.22") {
// API versions before 1.22 did not enforce application/json content-type.
// Allow older clients to work by patching the content-type.
if r.Header.Get("Content-Type") != "application/json" {
r.Header.Set("Content-Type", "application/json")
}
}
var ( var (
execName = vars["name"] execName = vars["name"]
stdin, inStream io.ReadCloser stdin, inStream io.ReadCloser
@ -96,6 +87,8 @@ func (s *containerRouter) postContainerExecStart(ctx context.Context, w http.Res
} }
if execStartCheck.ConsoleSize != nil { if execStartCheck.ConsoleSize != nil {
version := httputils.VersionFromContext(ctx)
// Not supported before 1.42 // Not supported before 1.42
if versions.LessThan(version, "1.42") { if versions.LessThan(version, "1.42") {
execStartCheck.ConsoleSize = nil execStartCheck.ConsoleSize = nil

View file

@ -10,6 +10,7 @@ import (
"github.com/docker/docker/api/server/middleware" "github.com/docker/docker/api/server/middleware"
"github.com/docker/docker/api/server/router" "github.com/docker/docker/api/server/router"
"github.com/docker/docker/api/server/router/debug" "github.com/docker/docker/api/server/router/debug"
"github.com/docker/docker/api/types"
"github.com/docker/docker/dockerversion" "github.com/docker/docker/dockerversion"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
@ -57,19 +58,13 @@ func (s *Server) makeHTTPHandler(handler httputils.APIFunc, operation string) ht
if statusCode >= 500 { if statusCode >= 500 {
log.G(ctx).Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err) log.G(ctx).Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
} }
makeErrorHandler(err)(w, r) _ = httputils.WriteJSON(w, statusCode, &types.ErrorResponse{
Message: err.Error(),
})
} }
}), operation).ServeHTTP }), operation).ServeHTTP
} }
type pageNotFoundError struct{}
func (pageNotFoundError) Error() string {
return "page not found"
}
func (pageNotFoundError) NotFound() {}
// CreateMux returns a new mux with all the routers registered. // CreateMux returns a new mux with all the routers registered.
func (s *Server) CreateMux(routers ...router.Router) *mux.Router { func (s *Server) CreateMux(routers ...router.Router) *mux.Router {
m := mux.NewRouter() m := mux.NewRouter()
@ -91,7 +86,12 @@ func (s *Server) CreateMux(routers ...router.Router) *mux.Router {
m.Path("/debug" + r.Path()).Handler(f) m.Path("/debug" + r.Path()).Handler(f)
} }
notFoundHandler := makeErrorHandler(pageNotFoundError{}) notFoundHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = httputils.WriteJSON(w, http.StatusNotFound, &types.ErrorResponse{
Message: "page not found",
})
})
m.HandleFunc(versionMatcher+"/{path:.*}", notFoundHandler) m.HandleFunc(versionMatcher+"/{path:.*}", notFoundHandler)
m.NotFoundHandler = notFoundHandler m.NotFoundHandler = notFoundHandler
m.MethodNotAllowedHandler = notFoundHandler m.MethodNotAllowedHandler = notFoundHandler

View file

@ -15,8 +15,11 @@ import (
func TestMiddlewares(t *testing.T) { func TestMiddlewares(t *testing.T) {
srv := &Server{} srv := &Server{}
const apiMinVersion = "1.12" m, err := middleware.NewVersionMiddleware("0.1omega2", api.DefaultVersion, api.MinSupportedAPIVersion)
srv.UseMiddleware(middleware.NewVersionMiddleware("0.1omega2", api.DefaultVersion, apiMinVersion)) if err != nil {
t.Fatal(err)
}
srv.UseMiddleware(*m)
req, _ := http.NewRequest(http.MethodGet, "/containers/json", nil) req, _ := http.NewRequest(http.MethodGet, "/containers/json", nil)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()

View file

@ -18,7 +18,6 @@ type ContainerCreateConfig struct {
HostConfig *container.HostConfig HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig NetworkingConfig *network.NetworkingConfig
Platform *ocispec.Platform Platform *ocispec.Platform
AdjustCPUShares bool
} }
// ContainerRmConfig holds arguments for the container remove // ContainerRmConfig holds arguments for the container remove
@ -90,7 +89,6 @@ type ContainerStatsConfig struct {
Stream bool Stream bool
OneShot bool OneShot bool
OutStream io.Writer OutStream io.Writer
Version string
} }
// ExecInspect holds information about a running process started // ExecInspect holds information about a running process started

View file

@ -1,14 +0,0 @@
# Legacy API type versions
This package includes types for legacy API versions. The stable version of the API types live in `api/types/*.go`.
Consider moving a type here when you need to keep backwards compatibility in the API. This legacy types are organized by the latest API version they appear in. For instance, types in the `v1p19` package are valid for API versions below or equal `1.19`. Types in the `v1p20` package are valid for the API version `1.20`, since the versions below that will use the legacy types in `v1p19`.
## Package name conventions
The package name convention is to use `v` as a prefix for the version number and `p`(patch) as a separator. We use this nomenclature due to a few restrictions in the Go package name convention:
1. We cannot use `.` because it's interpreted by the language, think of `v1.20.CallFunction`.
2. We cannot use `_` because golint complains about it. The code is actually valid, but it looks probably more weird: `v1_20.CallFunction`.
For instance, if you want to modify a type that was available in the version `1.21` of the API but it will have different fields in the version `1.22`, you want to create a new package under `api/types/versions/v1p21`.

View file

@ -1,35 +0,0 @@
// Package v1p19 provides specific API types for the API version 1, patch 19.
package v1p19 // import "github.com/docker/docker/api/types/versions/v1p19"
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/versions/v1p20"
"github.com/docker/go-connections/nat"
)
// ContainerJSON is a backcompatibility struct for APIs prior to 1.20.
// Note this is not used by the Windows daemon.
type ContainerJSON struct {
*types.ContainerJSONBase
Volumes map[string]string
VolumesRW map[string]bool
Config *ContainerConfig
NetworkSettings *v1p20.NetworkSettings
}
// ContainerConfig is a backcompatibility struct for APIs prior to 1.20.
type ContainerConfig struct {
*container.Config
MacAddress string
NetworkDisabled bool
ExposedPorts map[nat.Port]struct{}
// backward compatibility, they now live in HostConfig
VolumeDriver string
Memory int64
MemorySwap int64
CPUShares int64 `json:"CpuShares"`
CPUSet string `json:"Cpuset"`
}

View file

@ -1,40 +0,0 @@
// Package v1p20 provides specific API types for the API version 1, patch 20.
package v1p20 // import "github.com/docker/docker/api/types/versions/v1p20"
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
)
// ContainerJSON is a backcompatibility struct for the API 1.20
type ContainerJSON struct {
*types.ContainerJSONBase
Mounts []types.MountPoint
Config *ContainerConfig
NetworkSettings *NetworkSettings
}
// ContainerConfig is a backcompatibility struct used in ContainerJSON for the API 1.20
type ContainerConfig struct {
*container.Config
MacAddress string
NetworkDisabled bool
ExposedPorts map[nat.Port]struct{}
// backward compatibility, they now live in HostConfig
VolumeDriver string
}
// StatsJSON is a backcompatibility struct used in Stats for APIs prior to 1.21
type StatsJSON struct {
types.Stats
Network types.NetworkStats `json:"network,omitempty"`
}
// NetworkSettings is a backward compatible struct for APIs prior to 1.21
type NetworkSettings struct {
types.NetworkSettingsBase
types.DefaultNetworkSettings
}

View file

@ -64,7 +64,7 @@ type ExecBackend interface {
// ContainerRm removes a container specified by `id`. // ContainerRm removes a container specified by `id`.
ContainerRm(name string, config *backend.ContainerRmConfig) error ContainerRm(name string, config *backend.ContainerRmConfig) error
// ContainerStart starts a new container // ContainerStart starts a new container
ContainerStart(ctx context.Context, containerID string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error ContainerStart(ctx context.Context, containerID string, checkpoint string, checkpointDir string) error
// ContainerWait stops processing until the given container is stopped. // ContainerWait stops processing until the given container is stopped.
ContainerWait(ctx context.Context, name string, condition containerpkg.WaitCondition) (<-chan containerpkg.StateStatus, error) ContainerWait(ctx context.Context, name string, condition containerpkg.WaitCondition) (<-chan containerpkg.StateStatus, error)
} }

View file

@ -72,7 +72,7 @@ func (c *containerManager) Run(ctx context.Context, cID string, stdout, stderr i
} }
}() }()
if err := c.backend.ContainerStart(ctx, cID, nil, "", ""); err != nil { if err := c.backend.ContainerStart(ctx, cID, "", ""); err != nil {
close(finished) close(finished)
logCancellationError(cancelErrCh, "error from ContainerStart: "+err.Error()) logCancellationError(cancelErrCh, "error from ContainerStart: "+err.Error())
return err return err

View file

@ -46,7 +46,7 @@ func (m *MockBackend) CommitBuildStep(ctx context.Context, c backend.CommitConfi
return "", nil return "", nil
} }
func (m *MockBackend) ContainerStart(ctx context.Context, containerID string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error { func (m *MockBackend) ContainerStart(ctx context.Context, containerID string, checkpoint string, checkpointDir string) error {
return nil return nil
} }

View file

@ -256,7 +256,10 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
pluginStore := plugin.NewStore() pluginStore := plugin.NewStore()
var apiServer apiserver.Server var apiServer apiserver.Server
cli.authzMiddleware = initMiddlewares(&apiServer, cli.Config, pluginStore) cli.authzMiddleware, err = initMiddlewares(&apiServer, cli.Config, pluginStore)
if err != nil {
return errors.Wrap(err, "failed to start API server")
}
d, err := daemon.NewDaemon(ctx, cli.Config, pluginStore, cli.authzMiddleware) d, err := daemon.NewDaemon(ctx, cli.Config, pluginStore, cli.authzMiddleware)
if err != nil { if err != nil {
@ -708,14 +711,15 @@ func (opts routerOptions) Build() []router.Router {
return routers return routers
} }
func initMiddlewares(s *apiserver.Server, cfg *config.Config, pluginStore plugingetter.PluginGetter) *authorization.Middleware { func initMiddlewares(s *apiserver.Server, cfg *config.Config, pluginStore plugingetter.PluginGetter) (*authorization.Middleware, error) {
v := dockerversion.Version
exp := middleware.NewExperimentalMiddleware(cfg.Experimental) exp := middleware.NewExperimentalMiddleware(cfg.Experimental)
s.UseMiddleware(exp) s.UseMiddleware(exp)
vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, cfg.MinAPIVersion) vm, err := middleware.NewVersionMiddleware(dockerversion.Version, api.DefaultVersion, cfg.MinAPIVersion)
s.UseMiddleware(vm) if err != nil {
return nil, err
}
s.UseMiddleware(*vm)
if cfg.CorsHeaders != "" { if cfg.CorsHeaders != "" {
c := middleware.NewCORSMiddleware(cfg.CorsHeaders) c := middleware.NewCORSMiddleware(cfg.CorsHeaders)
@ -724,7 +728,7 @@ func initMiddlewares(s *apiserver.Server, cfg *config.Config, pluginStore plugin
authzMiddleware := authorization.NewMiddleware(cfg.AuthorizationPlugins, pluginStore) authzMiddleware := authorization.NewMiddleware(cfg.AuthorizationPlugins, pluginStore)
s.UseMiddleware(authzMiddleware) s.UseMiddleware(authzMiddleware)
return authzMiddleware return authzMiddleware, nil
} }
func (cli *DaemonCli) getContainerdDaemonOpts() ([]supervisor.DaemonOpt, error) { func (cli *DaemonCli) getContainerdDaemonOpts() ([]supervisor.DaemonOpt, error) {

View file

@ -8,25 +8,6 @@ import (
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
) )
// ContainerCopy performs a deprecated operation of archiving the resource at
// the specified path in the container identified by the given name.
func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, error) {
ctr, err := daemon.GetContainer(name)
if err != nil {
return nil, err
}
data, err := daemon.containerCopy(ctr, res)
if err == nil {
return data, nil
}
if os.IsNotExist(err) {
return nil, containerFileNotFound{res, name}
}
return nil, errdefs.System(err)
}
// ContainerStatPath stats the filesystem resource at the specified path in the // ContainerStatPath stats the filesystem resource at the specified path in the
// container identified by the given name. // container identified by the given name.
func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) { func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) {

View file

@ -161,55 +161,6 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path
return nil return nil
} }
func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) {
container.Lock()
defer func() {
if err != nil {
// Wait to unlock the container until the archive is fully read
// (see the ReadCloseWrapper func below) or if there is an error
// before that occurs.
container.Unlock()
}
}()
cfs, err := daemon.openContainerFS(container)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
cfs.Close()
}
}()
err = cfs.RunInFS(context.TODO(), func() error {
_, err := os.Stat(resource)
return err
})
if err != nil {
return nil, err
}
tb, err := archive.NewTarballer(resource, &archive.TarOptions{
Compression: archive.Uncompressed,
})
if err != nil {
return nil, err
}
cfs.GoInFS(context.TODO(), tb.Do)
archv := tb.Reader()
reader := ioutils.NewReadCloserWrapper(archv, func() error {
err := archv.Close()
_ = cfs.Close()
container.Unlock()
return err
})
daemon.LogContainerEvent(container, events.ActionCopy)
return reader, nil
}
// checkIfPathIsInAVolume checks if the path is in a volume. If it is, it // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
// cannot be in a read-only volume. If it is not in a volume, the container // cannot be in a read-only volume. If it is not in a volume, the container
// cannot be configured with a read-only rootfs. // cannot be configured with a read-only rootfs.

View file

@ -38,7 +38,7 @@ type Backend interface {
SetupIngress(clustertypes.NetworkCreateRequest, string) (<-chan struct{}, error) SetupIngress(clustertypes.NetworkCreateRequest, string) (<-chan struct{}, error)
ReleaseIngress() (<-chan struct{}, error) ReleaseIngress() (<-chan struct{}, error)
CreateManagedContainer(ctx context.Context, config backend.ContainerCreateConfig) (container.CreateResponse, error) CreateManagedContainer(ctx context.Context, config backend.ContainerCreateConfig) (container.CreateResponse, error)
ContainerStart(ctx context.Context, name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error ContainerStart(ctx context.Context, name string, checkpoint string, checkpointDir string) error
ContainerStop(ctx context.Context, name string, config container.StopOptions) error ContainerStop(ctx context.Context, name string, config container.StopOptions) error
ContainerLogs(ctx context.Context, name string, config *container.LogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error) ContainerLogs(ctx context.Context, name string, config *container.LogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error)
ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error

View file

@ -347,7 +347,7 @@ func (c *containerAdapter) start(ctx context.Context) error {
return err return err
} }
return c.backend.ContainerStart(ctx, c.container.name(), nil, "", "") return c.backend.ContainerStart(ctx, c.container.name(), "", "")
} }
func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) { func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) {

View file

@ -56,9 +56,9 @@ const (
DefaultPluginNamespace = "plugins.moby" DefaultPluginNamespace = "plugins.moby"
// defaultMinAPIVersion is the minimum API version supported by the API. // defaultMinAPIVersion is the minimum API version supported by the API.
// This version can be overridden through the "DOCKER_MIN_API_VERSION" // This version can be overridden through the "DOCKER_MIN_API_VERSION"
// environment variable. The minimum allowed version is determined // environment variable. It currently defaults to the minimum API version
// by [minAPIVersion]. // supported by the API server.
defaultMinAPIVersion = "1.24" defaultMinAPIVersion = api.MinSupportedAPIVersion
// SeccompProfileDefault is the built-in default seccomp profile. // SeccompProfileDefault is the built-in default seccomp profile.
SeccompProfileDefault = "builtin" SeccompProfileDefault = "builtin"
// SeccompProfileUnconfined is a special profile name for seccomp to use an // SeccompProfileUnconfined is a special profile name for seccomp to use an
@ -610,8 +610,8 @@ func ValidateMinAPIVersion(ver string) error {
if strings.EqualFold(ver[0:1], "v") { if strings.EqualFold(ver[0:1], "v") {
return errors.New(`API version must be provided without "v" prefix`) return errors.New(`API version must be provided without "v" prefix`)
} }
if versions.LessThan(ver, minAPIVersion) { if versions.LessThan(ver, defaultMinAPIVersion) {
return errors.Errorf(`minimum supported API version is %s: %s`, minAPIVersion, ver) return errors.Errorf(`minimum supported API version is %s: %s`, defaultMinAPIVersion, ver)
} }
if versions.GreaterThan(ver, api.DefaultVersion) { if versions.GreaterThan(ver, api.DefaultVersion) {
return errors.Errorf(`maximum supported API version is %s: %s`, api.DefaultVersion, ver) return errors.Errorf(`maximum supported API version is %s: %s`, api.DefaultVersion, ver)

View file

@ -33,9 +33,6 @@ const (
// OCI runtime being shipped with the docker daemon package. // OCI runtime being shipped with the docker daemon package.
StockRuntimeName = "runc" StockRuntimeName = "runc"
// minAPIVersion represents Minimum REST API version supported
minAPIVersion = "1.12"
// userlandProxyBinary is the name of the userland-proxy binary. // userlandProxyBinary is the name of the userland-proxy binary.
// In rootless-mode, [rootless.RootlessKitDockerProxyBinary] is used instead. // In rootless-mode, [rootless.RootlessKitDockerProxyBinary] is used instead.
userlandProxyBinary = "docker-proxy" userlandProxyBinary = "docker-proxy"

View file

@ -13,13 +13,6 @@ const (
// default value. On Windows keep this empty so the value is auto-detected // default value. On Windows keep this empty so the value is auto-detected
// based on other options. // based on other options.
StockRuntimeName = "" StockRuntimeName = ""
// minAPIVersion represents Minimum REST API version supported
// Technically the first daemon API version released on Windows is v1.25 in
// engine version 1.13. However, some clients are explicitly using downlevel
// APIs (e.g. docker-compose v2.1 file format) and that is just too restrictive.
// Hence also allowing 1.24 on Windows.
minAPIVersion string = "1.24"
) )
// BridgeConfig is meant to store all the parameters for both the bridge driver and the default bridge network. On // BridgeConfig is meant to store all the parameters for both the bridge driver and the default bridge network. On

View file

@ -108,7 +108,7 @@ func (daemon *Daemon) containerCreate(ctx context.Context, daemonCfg *configStor
if opts.params.HostConfig == nil { if opts.params.HostConfig == nil {
opts.params.HostConfig = &containertypes.HostConfig{} opts.params.HostConfig = &containertypes.HostConfig{}
} }
err = daemon.adaptContainerSettings(&daemonCfg.Config, opts.params.HostConfig, opts.params.AdjustCPUShares) err = daemon.adaptContainerSettings(&daemonCfg.Config, opts.params.HostConfig)
if err != nil { if err != nil {
return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err) return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err)
} }

View file

@ -54,9 +54,16 @@ import (
const ( const (
isWindows = false isWindows = false
// These values were used to adjust the CPU-shares for older API versions,
// but were not used for validation.
//
// TODO(thaJeztah): validate min/max values for CPU-shares, similar to Windows: https://github.com/moby/moby/issues/47340
// https://github.com/moby/moby/blob/27e85c7b6885c2d21ae90791136d9aba78b83d01/daemon/daemon_windows.go#L97-L99
//
// See https://git.kernel.org/cgit/linux/kernel/git/tip/tip.git/tree/kernel/sched/sched.h?id=8cd9234c64c584432f6992fe944ca9e46ca8ea76#n269 // See https://git.kernel.org/cgit/linux/kernel/git/tip/tip.git/tree/kernel/sched/sched.h?id=8cd9234c64c584432f6992fe944ca9e46ca8ea76#n269
linuxMinCPUShares = 2 // linuxMinCPUShares = 2
linuxMaxCPUShares = 262144 // linuxMaxCPUShares = 262144
// It's not kernel limit, we want this 6M limit to account for overhead during startup, and to supply a reasonable functional container // It's not kernel limit, we want this 6M limit to account for overhead during startup, and to supply a reasonable functional container
linuxMinMemory = 6291456 linuxMinMemory = 6291456
// constants for remapped root settings // constants for remapped root settings
@ -306,17 +313,7 @@ func adjustParallelLimit(n int, limit int) int {
// adaptContainerSettings is called during container creation to modify any // adaptContainerSettings is called during container creation to modify any
// settings necessary in the HostConfig structure. // settings necessary in the HostConfig structure.
func (daemon *Daemon) adaptContainerSettings(daemonCfg *config.Config, hostConfig *containertypes.HostConfig, adjustCPUShares bool) error { func (daemon *Daemon) adaptContainerSettings(daemonCfg *config.Config, hostConfig *containertypes.HostConfig) error {
if adjustCPUShares && hostConfig.CPUShares > 0 {
// Handle unsupported CPUShares
if hostConfig.CPUShares < linuxMinCPUShares {
log.G(context.TODO()).Warnf("Changing requested CPUShares of %d to minimum allowed of %d", hostConfig.CPUShares, linuxMinCPUShares)
hostConfig.CPUShares = linuxMinCPUShares
} else if hostConfig.CPUShares > linuxMaxCPUShares {
log.G(context.TODO()).Warnf("Changing requested CPUShares of %d to maximum allowed of %d", hostConfig.CPUShares, linuxMaxCPUShares)
hostConfig.CPUShares = linuxMaxCPUShares
}
}
if hostConfig.Memory > 0 && hostConfig.MemorySwap == 0 { if hostConfig.Memory > 0 && hostConfig.MemorySwap == 0 {
// By default, MemorySwap is set to twice the size of Memory. // By default, MemorySwap is set to twice the size of Memory.
hostConfig.MemorySwap = hostConfig.Memory * 2 hostConfig.MemorySwap = hostConfig.Memory * 2

View file

@ -57,87 +57,6 @@ func TestAdjustSharedNamespaceContainerName(t *testing.T) {
} }
} }
// Unix test as uses settings which are not available on Windows
func TestAdjustCPUShares(t *testing.T) {
tmp, err := os.MkdirTemp("", "docker-daemon-unix-test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
daemon := &Daemon{
repository: tmp,
root: tmp,
}
cfg := &config.Config{}
muteLogs(t)
hostConfig := &containertypes.HostConfig{
Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
}
daemon.adaptContainerSettings(cfg, hostConfig, true)
if hostConfig.CPUShares != linuxMinCPUShares {
t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares)
}
hostConfig.CPUShares = linuxMaxCPUShares + 1
daemon.adaptContainerSettings(cfg, hostConfig, true)
if hostConfig.CPUShares != linuxMaxCPUShares {
t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares)
}
hostConfig.CPUShares = 0
daemon.adaptContainerSettings(cfg, hostConfig, true)
if hostConfig.CPUShares != 0 {
t.Error("Expected CPUShares to be unchanged")
}
hostConfig.CPUShares = 1024
daemon.adaptContainerSettings(cfg, hostConfig, true)
if hostConfig.CPUShares != 1024 {
t.Error("Expected CPUShares to be unchanged")
}
}
// Unix test as uses settings which are not available on Windows
func TestAdjustCPUSharesNoAdjustment(t *testing.T) {
tmp, err := os.MkdirTemp("", "docker-daemon-unix-test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
daemon := &Daemon{
repository: tmp,
root: tmp,
}
cfg := &config.Config{}
hostConfig := &containertypes.HostConfig{
Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
}
daemon.adaptContainerSettings(cfg, hostConfig, false)
if hostConfig.CPUShares != linuxMinCPUShares-1 {
t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares-1)
}
hostConfig.CPUShares = linuxMaxCPUShares + 1
daemon.adaptContainerSettings(cfg, hostConfig, false)
if hostConfig.CPUShares != linuxMaxCPUShares+1 {
t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares+1)
}
hostConfig.CPUShares = 0
daemon.adaptContainerSettings(cfg, hostConfig, false)
if hostConfig.CPUShares != 0 {
t.Error("Expected CPUShares to be unchanged")
}
hostConfig.CPUShares = 1024
daemon.adaptContainerSettings(cfg, hostConfig, false)
if hostConfig.CPUShares != 1024 {
t.Error("Expected CPUShares to be unchanged")
}
}
// Unix test as uses settings which are not available on Windows // Unix test as uses settings which are not available on Windows
func TestParseSecurityOptWithDeprecatedColon(t *testing.T) { func TestParseSecurityOptWithDeprecatedColon(t *testing.T) {
opts := &container.SecurityOptions{} opts := &container.SecurityOptions{}

View file

@ -66,7 +66,7 @@ func setupInitLayer(idMapping idtools.IdentityMapping) func(string) error {
// adaptContainerSettings is called during container creation to modify any // adaptContainerSettings is called during container creation to modify any
// settings necessary in the HostConfig structure. // settings necessary in the HostConfig structure.
func (daemon *Daemon) adaptContainerSettings(daemonCfg *config.Config, hostConfig *containertypes.HostConfig, adjustCPUShares bool) error { func (daemon *Daemon) adaptContainerSettings(daemonCfg *config.Config, hostConfig *containertypes.HostConfig) error {
return nil return nil
} }

View file

@ -14,7 +14,6 @@ import (
containertypes "github.com/docker/docker/api/types/container" containertypes "github.com/docker/docker/api/types/container"
networktypes "github.com/docker/docker/api/types/network" networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
"github.com/docker/docker/api/types/versions/v1p20"
"github.com/docker/docker/container" "github.com/docker/docker/container"
"github.com/docker/docker/daemon/config" "github.com/docker/docker/daemon/config"
"github.com/docker/docker/daemon/network" "github.com/docker/docker/daemon/network"
@ -29,10 +28,6 @@ import (
// there is an error getting the data. // there is an error getting the data.
func (daemon *Daemon) ContainerInspect(ctx context.Context, name string, size bool, version string) (interface{}, error) { func (daemon *Daemon) ContainerInspect(ctx context.Context, name string, size bool, version string) (interface{}, error) {
switch { switch {
case versions.LessThan(version, "1.20"):
return daemon.containerInspectPre120(ctx, name)
case versions.Equal(version, "1.20"):
return daemon.containerInspect120(name)
case versions.LessThan(version, "1.45"): case versions.LessThan(version, "1.45"):
ctr, err := daemon.ContainerInspectCurrent(ctx, name, size) ctr, err := daemon.ContainerInspectCurrent(ctx, name, size)
if err != nil { if err != nil {
@ -117,35 +112,6 @@ func (daemon *Daemon) ContainerInspectCurrent(ctx context.Context, name string,
}, nil }, nil
} }
// containerInspect120 serializes the master version of a container into a json type.
func (daemon *Daemon) containerInspect120(name string) (*v1p20.ContainerJSON, error) {
ctr, err := daemon.GetContainer(name)
if err != nil {
return nil, err
}
ctr.Lock()
defer ctr.Unlock()
base, err := daemon.getInspectData(&daemon.config().Config, ctr)
if err != nil {
return nil, err
}
return &v1p20.ContainerJSON{
ContainerJSONBase: base,
Mounts: ctr.GetMountPoints(),
Config: &v1p20.ContainerConfig{
Config: ctr.Config,
MacAddress: ctr.Config.MacAddress, //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
NetworkDisabled: ctr.Config.NetworkDisabled,
ExposedPorts: ctr.Config.ExposedPorts,
VolumeDriver: ctr.HostConfig.VolumeDriver,
},
NetworkSettings: daemon.getBackwardsCompatibleNetworkSettings(ctr.NetworkSettings),
}, nil
}
func (daemon *Daemon) getInspectData(daemonCfg *config.Config, container *container.Container) (*types.ContainerJSONBase, error) { func (daemon *Daemon) getInspectData(daemonCfg *config.Config, container *container.Container) (*types.ContainerJSONBase, error) {
// make a copy to play with // make a copy to play with
hostConfig := *container.HostConfig hostConfig := *container.HostConfig
@ -283,25 +249,6 @@ func (daemon *Daemon) ContainerExecInspect(id string) (*backend.ExecInspect, err
}, nil }, nil
} }
func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Settings) *v1p20.NetworkSettings {
result := &v1p20.NetworkSettings{
NetworkSettingsBase: types.NetworkSettingsBase{
Bridge: settings.Bridge,
SandboxID: settings.SandboxID,
SandboxKey: settings.SandboxKey,
HairpinMode: settings.HairpinMode,
LinkLocalIPv6Address: settings.LinkLocalIPv6Address,
LinkLocalIPv6PrefixLen: settings.LinkLocalIPv6PrefixLen,
Ports: settings.Ports,
SecondaryIPAddresses: settings.SecondaryIPAddresses,
SecondaryIPv6Addresses: settings.SecondaryIPv6Addresses,
},
DefaultNetworkSettings: daemon.getDefaultNetworkSettings(settings.Networks),
}
return result
}
// getDefaultNetworkSettings creates the deprecated structure that holds the information // getDefaultNetworkSettings creates the deprecated structure that holds the information
// about the bridge network for a container. // about the bridge network for a container.
func (daemon *Daemon) getDefaultNetworkSettings(networks map[string]*network.EndpointSettings) types.DefaultNetworkSettings { func (daemon *Daemon) getDefaultNetworkSettings(networks map[string]*network.EndpointSettings) types.DefaultNetworkSettings {

View file

@ -1,11 +1,8 @@
package daemon // import "github.com/docker/docker/daemon" package daemon // import "github.com/docker/docker/daemon"
import ( import (
"context"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend" "github.com/docker/docker/api/types/backend"
"github.com/docker/docker/api/types/versions/v1p19"
"github.com/docker/docker/container" "github.com/docker/docker/container"
) )
@ -19,47 +16,6 @@ func setPlatformSpecificContainerFields(container *container.Container, contJSON
return contJSONBase return contJSONBase
} }
// containerInspectPre120 gets containers for pre 1.20 APIs.
func (daemon *Daemon) containerInspectPre120(ctx context.Context, name string) (*v1p19.ContainerJSON, error) {
ctr, err := daemon.GetContainer(name)
if err != nil {
return nil, err
}
ctr.Lock()
defer ctr.Unlock()
base, err := daemon.getInspectData(&daemon.config().Config, ctr)
if err != nil {
return nil, err
}
volumes := make(map[string]string)
volumesRW := make(map[string]bool)
for _, m := range ctr.MountPoints {
volumes[m.Destination] = m.Path()
volumesRW[m.Destination] = m.RW
}
return &v1p19.ContainerJSON{
ContainerJSONBase: base,
Volumes: volumes,
VolumesRW: volumesRW,
Config: &v1p19.ContainerConfig{
Config: ctr.Config,
MacAddress: ctr.Config.MacAddress, //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
NetworkDisabled: ctr.Config.NetworkDisabled,
ExposedPorts: ctr.Config.ExposedPorts,
VolumeDriver: ctr.HostConfig.VolumeDriver,
Memory: ctr.HostConfig.Memory,
MemorySwap: ctr.HostConfig.MemorySwap,
CPUShares: ctr.HostConfig.CPUShares,
CPUSet: ctr.HostConfig.CpusetCpus,
},
NetworkSettings: daemon.getBackwardsCompatibleNetworkSettings(ctr.NetworkSettings),
}, nil
}
func inspectExecProcessConfig(e *container.ExecConfig) *backend.ExecProcessConfig { func inspectExecProcessConfig(e *container.ExecConfig) *backend.ExecProcessConfig {
return &backend.ExecProcessConfig{ return &backend.ExecProcessConfig{
Tty: e.Tty, Tty: e.Tty,

View file

@ -1,8 +1,6 @@
package daemon // import "github.com/docker/docker/daemon" package daemon // import "github.com/docker/docker/daemon"
import ( import (
"context"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend" "github.com/docker/docker/api/types/backend"
"github.com/docker/docker/container" "github.com/docker/docker/container"
@ -13,11 +11,6 @@ func setPlatformSpecificContainerFields(container *container.Container, contJSON
return contJSONBase return contJSONBase
} }
// containerInspectPre120 get containers for pre 1.20 APIs.
func (daemon *Daemon) containerInspectPre120(ctx context.Context, name string) (*types.ContainerJSON, error) {
return daemon.ContainerInspectCurrent(ctx, name, false)
}
func inspectExecProcessConfig(e *container.ExecConfig) *backend.ExecProcessConfig { func inspectExecProcessConfig(e *container.ExecConfig) *backend.ExecProcessConfig {
return &backend.ExecProcessConfig{ return &backend.ExecProcessConfig{
Tty: e.Tty, Tty: e.Tty,

View file

@ -2,12 +2,10 @@ package daemon // import "github.com/docker/docker/daemon"
import ( import (
"context" "context"
"runtime"
"time" "time"
"github.com/containerd/log" "github.com/containerd/log"
"github.com/docker/docker/api/types/backend" "github.com/docker/docker/api/types/backend"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/events"
"github.com/docker/docker/container" "github.com/docker/docker/container"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
@ -41,7 +39,7 @@ func validateState(ctr *container.Container) error {
} }
// ContainerStart starts a container. // ContainerStart starts a container.
func (daemon *Daemon) ContainerStart(ctx context.Context, name string, hostConfig *containertypes.HostConfig, checkpoint string, checkpointDir string) error { func (daemon *Daemon) ContainerStart(ctx context.Context, name string, checkpoint string, checkpointDir string) error {
daemonCfg := daemon.config() daemonCfg := daemon.config()
if checkpoint != "" && !daemonCfg.Experimental { if checkpoint != "" && !daemonCfg.Experimental {
return errdefs.InvalidParameter(errors.New("checkpoint is only supported in experimental mode")) return errdefs.InvalidParameter(errors.New("checkpoint is only supported in experimental mode"))
@ -55,51 +53,12 @@ func (daemon *Daemon) ContainerStart(ctx context.Context, name string, hostConfi
return err return err
} }
// Windows does not have the backwards compatibility issue here.
if runtime.GOOS != "windows" {
// This is kept for backward compatibility - hostconfig should be passed when
// creating a container, not during start.
if hostConfig != nil {
log.G(ctx).Warn("DEPRECATED: Setting host configuration options when the container starts is deprecated and has been removed in Docker 1.12")
oldNetworkMode := ctr.HostConfig.NetworkMode
if err := daemon.setSecurityOptions(&daemonCfg.Config, ctr, hostConfig); err != nil {
return errdefs.InvalidParameter(err)
}
if err := daemon.mergeAndVerifyLogConfig(&hostConfig.LogConfig); err != nil {
return errdefs.InvalidParameter(err)
}
if err := daemon.setHostConfig(ctr, hostConfig); err != nil {
return errdefs.InvalidParameter(err)
}
newNetworkMode := ctr.HostConfig.NetworkMode
if string(oldNetworkMode) != string(newNetworkMode) {
// if user has change the network mode on starting, clean up the
// old networks. It is a deprecated feature and has been removed in Docker 1.12
ctr.NetworkSettings.Networks = nil
}
if err := ctr.CheckpointTo(daemon.containersReplica); err != nil {
return errdefs.System(err)
}
ctr.InitDNSHostConfig()
}
} else {
if hostConfig != nil {
return errdefs.InvalidParameter(errors.New("Supplying a hostconfig on start is not supported. It should be supplied on create"))
}
}
// check if hostConfig is in line with the current system settings. // check if hostConfig is in line with the current system settings.
// It may happen cgroups are umounted or the like. // It may happen cgroups are unmounted or the like.
if _, err = daemon.verifyContainerSettings(daemonCfg, ctr.HostConfig, nil, false); err != nil { if _, err = daemon.verifyContainerSettings(daemonCfg, ctr.HostConfig, nil, false); err != nil {
return errdefs.InvalidParameter(err) return errdefs.InvalidParameter(err)
} }
// Adapt for old containers in case we have updates in this function and
// old containers never have chance to call the new function in create stage.
if hostConfig != nil {
if err := daemon.adaptContainerSettings(&daemonCfg.Config, ctr.HostConfig, false); err != nil {
return errdefs.InvalidParameter(err)
}
}
return daemon.containerStart(ctx, daemonCfg, ctr, checkpoint, checkpointDir, true) return daemon.containerStart(ctx, daemonCfg, ctr, checkpoint, checkpointDir, true)
} }

View file

@ -10,8 +10,6 @@ import (
"github.com/containerd/log" "github.com/containerd/log"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend" "github.com/docker/docker/api/types/backend"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/api/types/versions/v1p20"
"github.com/docker/docker/container" "github.com/docker/docker/container"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/ioutils"
@ -20,13 +18,6 @@ import (
// ContainerStats writes information about the container to the stream // ContainerStats writes information about the container to the stream
// given in the config object. // given in the config object.
func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error { func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error {
// Engine API version (used for backwards compatibility)
apiVersion := config.Version
if isWindows && versions.LessThan(apiVersion, "1.21") {
return errors.New("API versions pre v1.21 do not support stats on Windows")
}
ctr, err := daemon.GetContainer(prefixOrName) ctr, err := daemon.GetContainer(prefixOrName)
if err != nil { if err != nil {
return err return err
@ -87,46 +78,7 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c
return nil return nil
} }
var statsJSON interface{} statsJSON := getStatJSON(v)
statsJSONPost120 := getStatJSON(v)
if versions.LessThan(apiVersion, "1.21") {
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 { if !config.Stream && noStreamFirstFrame {
// prime the cpu stats so they aren't 0 in the final output // prime the cpu stats so they aren't 0 in the final output
noStreamFirstFrame = false noStreamFirstFrame = false

View file

@ -46,9 +46,6 @@ export DOCKER_ALLOW_SCHEMA1_PUSH_DONOTUSE=1
export DOCKER_GRAPHDRIVER=${DOCKER_GRAPHDRIVER:-vfs} export DOCKER_GRAPHDRIVER=${DOCKER_GRAPHDRIVER:-vfs}
export DOCKER_USERLANDPROXY=${DOCKER_USERLANDPROXY:-true} export DOCKER_USERLANDPROXY=${DOCKER_USERLANDPROXY:-true}
# Allow testing old API versions
export DOCKER_MIN_API_VERSION=1.12
# example usage: DOCKER_STORAGE_OPTS="dm.basesize=20G,dm.loopdatasize=200G" # example usage: DOCKER_STORAGE_OPTS="dm.basesize=20G,dm.loopdatasize=200G"
storage_params="" storage_params=""
if [ -n "$DOCKER_STORAGE_OPTS" ]; then if [ -n "$DOCKER_STORAGE_OPTS" ]; then

View file

@ -958,81 +958,6 @@ func (s *DockerAPISuite) TestContainerAPICopyNotExistsAnyMore(c *testing.T) {
assert.Equal(c, res.StatusCode, http.StatusNotFound) assert.Equal(c, res.StatusCode, http.StatusNotFound)
} }
func (s *DockerAPISuite) TestContainerAPICopyPre124(c *testing.T) {
testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
const name = "test-container-api-copy"
cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
postData := types.CopyConfig{
Resource: "/test.txt",
}
res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusOK)
found := false
for tarReader := tar.NewReader(body); ; {
h, err := tarReader.Next()
if err != nil {
if err == io.EOF {
break
}
c.Fatal(err)
}
if h.Name == "test.txt" {
found = true
break
}
}
assert.Assert(c, found)
}
func (s *DockerAPISuite) TestContainerAPICopyResourcePathEmptyPre124(c *testing.T) {
testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
const name = "test-container-api-copy-resource-empty"
cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
postData := types.CopyConfig{
Resource: "",
}
res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusBadRequest)
b, err := request.ReadBody(body)
assert.NilError(c, err)
assert.Assert(c, is.Regexp("^Path cannot be empty\n$", string(b)))
}
func (s *DockerAPISuite) TestContainerAPICopyResourcePathNotFoundPre124(c *testing.T) {
testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
const name = "test-container-api-copy-resource-not-found"
cli.DockerCmd(c, "run", "--name", name, "busybox")
postData := types.CopyConfig{
Resource: "/notexist",
}
res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusNotFound)
b, err := request.ReadBody(body)
assert.NilError(c, err)
assert.Assert(c, is.Regexp("^Could not find the file /notexist in container "+name+"\n$", string(b)))
}
func (s *DockerAPISuite) TestContainerAPICopyContainerNotFoundPr124(c *testing.T) {
testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
postData := types.CopyConfig{
Resource: "/something",
}
res, _, err := request.Post(testutil.GetContext(c), "/v1.23/containers/notexists/copy", request.JSONBody(postData))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusNotFound)
}
func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) { func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) {
id := runSleepingContainer(c) id := runSleepingContainer(c)
cli.WaitRun(c, id) cli.WaitRun(c, id)
@ -1251,20 +1176,6 @@ func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *
assert.NilError(c, err) assert.NilError(c, err)
} }
// #14915
func (s *DockerAPISuite) TestContainerAPICreateNoHostConfig118(c *testing.T) {
testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later
config := container.Config{
Image: "busybox",
}
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18"))
assert.NilError(c, err)
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
assert.NilError(c, err)
}
// Ensure an error occurs when you have a container read-only rootfs but you // Ensure an error occurs when you have a container read-only rootfs but you
// extract an archive to a symlink in a writable volume which points to a // extract an archive to a symlink in a writable volume which points to a
// directory outside of the volume. // directory outside of the volume.

View file

@ -109,20 +109,6 @@ func (s *DockerAPISuite) TestExecAPIStartEnsureHeaders(c *testing.T) {
assert.Assert(c, resp.Header.Get("Server") != "") assert.Assert(c, resp.Header.Get("Server") != "")
} }
func (s *DockerAPISuite) TestExecAPIStartBackwardsCompatible(c *testing.T) {
testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
runSleepingContainer(c, "-d", "--name", "test")
id := createExec(c, "test")
resp, body, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/v1.20/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.ContentType("text/plain"))
assert.NilError(c, err)
b, err := request.ReadBody(body)
comment := fmt.Sprintf("response body: %s", b)
assert.NilError(c, err, comment)
assert.Equal(c, resp.StatusCode, http.StatusOK, comment)
}
// #19362 // #19362
func (s *DockerAPISuite) TestExecAPIStartMultipleTimesError(c *testing.T) { func (s *DockerAPISuite) TestExecAPIStartMultipleTimesError(c *testing.T) {
runSleepingContainer(c, "-d", "--name", "test") runSleepingContainer(c, "-d", "--name", "test")

View file

@ -6,7 +6,6 @@ import (
"testing" "testing"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions/v1p20"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/integration-cli/cli" "github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/testutil" "github.com/docker/docker/testutil"
@ -21,26 +20,15 @@ func (s *DockerAPISuite) TestInspectAPIContainerResponse(c *testing.T) {
keysBase := []string{ keysBase := []string{
"Id", "State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings", "Id", "State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings",
"ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "MountLabel", "ProcessLabel", "GraphDriver", "ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "MountLabel", "ProcessLabel", "GraphDriver",
"Mounts",
} }
type acase struct { cases := []struct {
version string version string
keys []string keys []string
}{
{version: "v1.24", keys: keysBase},
} }
var cases []acase
if testEnv.DaemonInfo.OSType == "windows" {
cases = []acase{
{"v1.25", append(keysBase, "Mounts")},
}
} else {
cases = []acase{
{"v1.20", append(keysBase, "Mounts")},
{"v1.19", append(keysBase, "Volumes", "VolumesRW")},
}
}
for _, cs := range cases { for _, cs := range cases {
body := getInspectBody(c, cs.version, cleanedContainerID) body := getInspectBody(c, cs.version, cleanedContainerID)
@ -59,28 +47,6 @@ func (s *DockerAPISuite) TestInspectAPIContainerResponse(c *testing.T) {
} }
} }
func (s *DockerAPISuite) TestInspectAPIContainerVolumeDriverLegacy(c *testing.T) {
// No legacy implications for Windows
testRequires(c, DaemonIsLinux)
out := cli.DockerCmd(c, "run", "-d", "busybox", "true").Stdout()
cleanedContainerID := strings.TrimSpace(out)
cases := []string{"v1.19", "v1.20"}
for _, version := range cases {
body := getInspectBody(c, version, cleanedContainerID)
var inspectJSON map[string]interface{}
err := json.Unmarshal(body, &inspectJSON)
assert.NilError(c, err, "Unable to unmarshal body for version %s", version)
config, ok := inspectJSON["Config"]
assert.Assert(c, ok, "Unable to find 'Config'")
cfg := config.(map[string]interface{})
_, ok = cfg["VolumeDriver"]
assert.Assert(c, ok, "API version %s expected to include VolumeDriver in 'Config'", version)
}
}
func (s *DockerAPISuite) TestInspectAPIContainerVolumeDriver(c *testing.T) { func (s *DockerAPISuite) TestInspectAPIContainerVolumeDriver(c *testing.T) {
out := cli.DockerCmd(c, "run", "-d", "--volume-driver", "local", "busybox", "true").Stdout() out := cli.DockerCmd(c, "run", "-d", "--volume-driver", "local", "busybox", "true").Stdout()
cleanedContainerID := strings.TrimSpace(out) cleanedContainerID := strings.TrimSpace(out)
@ -118,47 +84,6 @@ func (s *DockerAPISuite) TestInspectAPIImageResponse(c *testing.T) {
assert.Check(c, is.Contains(imageJSON.RepoTags, "busybox:mytag")) assert.Check(c, is.Contains(imageJSON.RepoTags, "busybox:mytag"))
} }
// #17131, #17139, #17173
func (s *DockerAPISuite) TestInspectAPIEmptyFieldsInConfigPre121(c *testing.T) {
// Not relevant on Windows
testRequires(c, DaemonIsLinux)
out := cli.DockerCmd(c, "run", "-d", "busybox", "true").Stdout()
cleanedContainerID := strings.TrimSpace(out)
cases := []string{"v1.19", "v1.20"}
for _, version := range cases {
body := getInspectBody(c, version, cleanedContainerID)
var inspectJSON map[string]interface{}
err := json.Unmarshal(body, &inspectJSON)
assert.NilError(c, err, "Unable to unmarshal body for version %s", version)
config, ok := inspectJSON["Config"]
assert.Assert(c, ok, "Unable to find 'Config'")
cfg := config.(map[string]interface{})
for _, f := range []string{"MacAddress", "NetworkDisabled", "ExposedPorts"} {
_, ok := cfg[f]
assert.Check(c, ok, "API version %s expected to include %s in 'Config'", version, f)
}
}
}
func (s *DockerAPISuite) TestInspectAPIBridgeNetworkSettings120(c *testing.T) {
// Not relevant on Windows, and besides it doesn't have any bridge network settings
testRequires(c, DaemonIsLinux)
out := cli.DockerCmd(c, "run", "-d", "busybox", "top").Stdout()
containerID := strings.TrimSpace(out)
cli.WaitRun(c, containerID)
body := getInspectBody(c, "v1.20", containerID)
var inspectJSON v1p20.ContainerJSON
err := json.Unmarshal(body, &inspectJSON)
assert.NilError(c, err)
settings := inspectJSON.NetworkSettings
assert.Assert(c, len(settings.IPAddress) != 0)
}
// Inspect for API v1.21 and up; see // Inspect for API v1.21 and up; see
// //
// - https://github.com/moby/moby/issues/17131 // - https://github.com/moby/moby/issues/17131

View file

@ -8,13 +8,11 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"sync"
"testing" "testing"
"time" "time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/system" "github.com/docker/docker/api/types/system"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/integration-cli/cli" "github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/testutil" "github.com/docker/docker/testutil"
@ -166,27 +164,13 @@ func (s *DockerAPISuite) TestAPIStatsNetworkStats(c *testing.T) {
} }
func (s *DockerAPISuite) TestAPIStatsNetworkStatsVersioning(c *testing.T) { func (s *DockerAPISuite) TestAPIStatsNetworkStatsVersioning(c *testing.T) {
// Windows doesn't support API versions less than 1.25, so no point testing 1.17 .. 1.21
testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux) testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux)
id := runSleepingContainer(c) id := runSleepingContainer(c)
cli.WaitRun(c, id) cli.WaitRun(c, id)
wg := sync.WaitGroup{}
for i := 17; i <= 21; i++ { statsJSONBlob := getStats(c, id)
wg.Add(1) assert.Assert(c, jsonBlobHasGTE121NetworkStats(statsJSONBlob), "Stats JSON blob from API does not look like a >=v1.21 API stats structure", statsJSONBlob)
go func(i int) {
defer wg.Done()
apiVersion := fmt.Sprintf("v1.%d", i)
statsJSONBlob := getVersionedStats(c, id, apiVersion)
if versions.LessThan(apiVersion, "v1.21") {
assert.Assert(c, jsonBlobHasLTv121NetworkStats(statsJSONBlob), "Stats JSON blob from API %s %#v does not look like a <v1.21 API stats structure", apiVersion, statsJSONBlob)
} else {
assert.Assert(c, jsonBlobHasGTE121NetworkStats(statsJSONBlob), "Stats JSON blob from API %s %#v does not look like a >=v1.21 API stats structure", apiVersion, statsJSONBlob)
}
}(i)
}
wg.Wait()
} }
func getNetworkStats(c *testing.T, id string) map[string]types.NetworkStats { func getNetworkStats(c *testing.T, id string) map[string]types.NetworkStats {
@ -202,14 +186,15 @@ func getNetworkStats(c *testing.T, id string) map[string]types.NetworkStats {
return st.Networks return st.Networks
} }
// getVersionedStats returns stats result for the // getStats returns stats result for the
// container with id using an API call with version apiVersion. Since the // container with id using an API call with version apiVersion. Since the
// stats result type differs between API versions, we simply return // stats result type differs between API versions, we simply return
// map[string]interface{}. // map[string]interface{}.
func getVersionedStats(c *testing.T, id string, apiVersion string) map[string]interface{} { func getStats(c *testing.T, id string) map[string]interface{} {
c.Helper()
stats := make(map[string]interface{}) stats := make(map[string]interface{})
_, body, err := request.Get(testutil.GetContext(c), "/"+apiVersion+"/containers/"+id+"/stats?stream=false") _, body, err := request.Get(testutil.GetContext(c), "/containers/"+id+"/stats?stream=false")
assert.NilError(c, err) assert.NilError(c, err)
defer body.Close() defer body.Close()
@ -219,23 +204,6 @@ func getVersionedStats(c *testing.T, id string, apiVersion string) map[string]in
return stats return stats
} }
func jsonBlobHasLTv121NetworkStats(blob map[string]interface{}) bool {
networkStatsIntfc, ok := blob["network"]
if !ok {
return false
}
networkStats, ok := networkStatsIntfc.(map[string]interface{})
if !ok {
return false
}
for _, expectedKey := range expectedNetworkInterfaceStats {
if _, ok := networkStats[expectedKey]; !ok {
return false
}
}
return true
}
func jsonBlobHasGTE121NetworkStats(blob map[string]interface{}) bool { func jsonBlobHasGTE121NetworkStats(blob map[string]interface{}) bool {
networksStatsIntfc, ok := blob["networks"] networksStatsIntfc, ok := blob["networks"]
if !ok { if !ok {

View file

@ -3,7 +3,6 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"runtime" "runtime"
"strconv" "strconv"
@ -62,9 +61,9 @@ func (s *DockerAPISuite) TestAPIClientVersionOldNotSupported(c *testing.T) {
defer body.Close() defer body.Close()
assert.Equal(c, resp.StatusCode, http.StatusBadRequest) assert.Equal(c, resp.StatusCode, http.StatusBadRequest)
expected := fmt.Sprintf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", version, testEnv.DaemonVersion.MinAPIVersion) expected := fmt.Sprintf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", version, testEnv.DaemonVersion.MinAPIVersion)
content, err := io.ReadAll(body) b, err := request.ReadBody(body)
assert.NilError(c, err) assert.NilError(c, err)
assert.Equal(c, strings.TrimSpace(string(content)), expected) assert.Equal(c, getErrorMessage(c, b), expected)
} }
func (s *DockerAPISuite) TestAPIErrorJSON(c *testing.T) { func (s *DockerAPISuite) TestAPIErrorJSON(c *testing.T) {
@ -77,19 +76,6 @@ func (s *DockerAPISuite) TestAPIErrorJSON(c *testing.T) {
assert.Equal(c, getErrorMessage(c, b), runconfig.ErrEmptyConfig.Error()) assert.Equal(c, getErrorMessage(c, b), runconfig.ErrEmptyConfig.Error())
} }
func (s *DockerAPISuite) TestAPIErrorPlainText(c *testing.T) {
// Windows requires API 1.25 or later. This test is validating a behaviour which was present
// in v1.23, but changed in 1.24, hence not applicable on Windows. See apiVersionSupportsJSONErrors
testRequires(c, DaemonIsLinux)
httpResp, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/create", request.JSONBody(struct{}{}))
assert.NilError(c, err)
assert.Equal(c, httpResp.StatusCode, http.StatusBadRequest)
assert.Assert(c, strings.Contains(httpResp.Header.Get("Content-Type"), "text/plain"))
b, err := request.ReadBody(body)
assert.NilError(c, err)
assert.Equal(c, strings.TrimSpace(string(b)), runconfig.ErrEmptyConfig.Error())
}
func (s *DockerAPISuite) TestAPIErrorNotFoundJSON(c *testing.T) { func (s *DockerAPISuite) TestAPIErrorNotFoundJSON(c *testing.T) {
// 404 is a different code path to normal errors, so test separately // 404 is a different code path to normal errors, so test separately
httpResp, body, err := request.Get(testutil.GetContext(c), "/notfound", request.JSON) httpResp, body, err := request.Get(testutil.GetContext(c), "/notfound", request.JSON)
@ -100,13 +86,3 @@ func (s *DockerAPISuite) TestAPIErrorNotFoundJSON(c *testing.T) {
assert.NilError(c, err) assert.NilError(c, err)
assert.Equal(c, getErrorMessage(c, b), "page not found") assert.Equal(c, getErrorMessage(c, b), "page not found")
} }
func (s *DockerAPISuite) TestAPIErrorNotFoundPlainText(c *testing.T) {
httpResp, body, err := request.Get(testutil.GetContext(c), "/v1.23/notfound", request.JSON)
assert.NilError(c, err)
assert.Equal(c, httpResp.StatusCode, http.StatusNotFound)
assert.Assert(c, strings.Contains(httpResp.Header.Get("Content-Type"), "text/plain"))
b, err := request.ReadBody(body)
assert.NilError(c, err)
assert.Equal(c, strings.TrimSpace(string(b)), "page not found")
}

View file

@ -15,7 +15,6 @@ import (
"time" "time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions/v1p20"
"github.com/docker/docker/integration-cli/cli" "github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/daemon" "github.com/docker/docker/integration-cli/daemon"
"github.com/docker/docker/libnetwork/driverapi" "github.com/docker/docker/libnetwork/driverapi"
@ -1015,22 +1014,14 @@ func (s *DockerCLINetworkSuite) TestInspectAPIMultipleNetworks(c *testing.T) {
cli.DockerCmd(c, "network", "connect", "mybridge1", id) cli.DockerCmd(c, "network", "connect", "mybridge1", id)
cli.DockerCmd(c, "network", "connect", "mybridge2", id) cli.DockerCmd(c, "network", "connect", "mybridge2", id)
body := getInspectBody(c, "v1.20", id)
var inspect120 v1p20.ContainerJSON
err := json.Unmarshal(body, &inspect120)
assert.NilError(c, err)
versionedIP := inspect120.NetworkSettings.IPAddress
// Current API version (API v1.21 and up) // Current API version (API v1.21 and up)
body = getInspectBody(c, "", id) body := getInspectBody(c, "", id)
var inspectCurrent types.ContainerJSON var inspectCurrent types.ContainerJSON
err = json.Unmarshal(body, &inspectCurrent) err := json.Unmarshal(body, &inspectCurrent)
assert.NilError(c, err) assert.NilError(c, err)
assert.Equal(c, len(inspectCurrent.NetworkSettings.Networks), 3) assert.Equal(c, len(inspectCurrent.NetworkSettings.Networks), 3)
bridge := inspectCurrent.NetworkSettings.Networks["bridge"] bridge := inspectCurrent.NetworkSettings.Networks["bridge"]
assert.Equal(c, bridge.IPAddress, versionedIP)
assert.Equal(c, bridge.IPAddress, inspectCurrent.NetworkSettings.IPAddress) assert.Equal(c, bridge.IPAddress, inspectCurrent.NetworkSettings.IPAddress)
} }

View file

@ -1,239 +0,0 @@
// This file will be removed when we completely drop support for
// passing HostConfig to container start API.
package main
import (
"net/http"
"strings"
"testing"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/testutil"
"github.com/docker/docker/testutil/request"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func formatV123StartAPIURL(url string) string {
return "/v1.23" + url
}
func (s *DockerAPISuite) TestDeprecatedContainerAPIStartHostConfig(c *testing.T) {
name := "test-deprecated-api-124"
cli.DockerCmd(c, "create", "--name", name, "busybox")
config := map[string]interface{}{
"Binds": []string{"/aa:/bb"},
}
res, body, err := request.Post(testutil.GetContext(c), "/containers/"+name+"/start", request.JSONBody(config))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusBadRequest)
buf, err := request.ReadBody(body)
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusBadRequest)
assert.Assert(c, strings.Contains(string(buf), "was deprecated since API v1.22"))
}
func (s *DockerAPISuite) TestDeprecatedContainerAPIStartVolumeBinds(c *testing.T) {
// TODO Windows CI: Investigate further why this fails on Windows to Windows CI.
testRequires(c, DaemonIsLinux)
path := "/foo"
if testEnv.DaemonInfo.OSType == "windows" {
path = `c:\foo`
}
name := "testing"
config := map[string]interface{}{
"Image": "busybox",
"Volumes": map[string]struct{}{path: {}},
}
res, _, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/create?name="+name), request.JSONBody(config))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusCreated)
bindPath := RandomTmpDirPath("test", testEnv.DaemonInfo.OSType)
config = map[string]interface{}{
"Binds": []string{bindPath + ":" + path},
}
res, _, err = request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/"+name+"/start"), request.JSONBody(config))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusNoContent)
pth, err := inspectMountSourceField(name, path)
assert.NilError(c, err)
assert.Equal(c, pth, bindPath, "expected volume host path to be %s, got %s", bindPath, pth)
}
// Test for GH#10618
func (s *DockerAPISuite) TestDeprecatedContainerAPIStartDupVolumeBinds(c *testing.T) {
// TODO Windows to Windows CI - Port this
testRequires(c, DaemonIsLinux)
name := "testdups"
config := map[string]interface{}{
"Image": "busybox",
"Volumes": map[string]struct{}{"/tmp": {}},
}
res, _, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/create?name="+name), request.JSONBody(config))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusCreated)
bindPath1 := RandomTmpDirPath("test1", testEnv.DaemonInfo.OSType)
bindPath2 := RandomTmpDirPath("test2", testEnv.DaemonInfo.OSType)
config = map[string]interface{}{
"Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"},
}
res, body, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/"+name+"/start"), request.JSONBody(config))
assert.NilError(c, err)
buf, err := request.ReadBody(body)
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusBadRequest)
assert.Assert(c, strings.Contains(string(buf), "Duplicate mount point"), "Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(buf), err)
}
func (s *DockerAPISuite) TestDeprecatedContainerAPIStartVolumesFrom(c *testing.T) {
// TODO Windows to Windows CI - Port this
testRequires(c, DaemonIsLinux)
volName := "voltst"
volPath := "/tmp"
cli.DockerCmd(c, "run", "--name", volName, "-v", volPath, "busybox")
name := "TestContainerAPIStartVolumesFrom"
config := map[string]interface{}{
"Image": "busybox",
"Volumes": map[string]struct{}{volPath: {}},
}
res, _, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/create?name="+name), request.JSONBody(config))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusCreated)
config = map[string]interface{}{
"VolumesFrom": []string{volName},
}
res, _, err = request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/"+name+"/start"), request.JSONBody(config))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusNoContent)
pth, err := inspectMountSourceField(name, volPath)
assert.NilError(c, err)
pth2, err := inspectMountSourceField(volName, volPath)
assert.NilError(c, err)
assert.Equal(c, pth, pth2, "expected volume host path to be %s, got %s", pth, pth2)
}
// #9981 - Allow a docker created volume (ie, one in /var/lib/docker/volumes) to be used to overwrite (via passing in Binds on api start) an existing volume
func (s *DockerAPISuite) TestDeprecatedPostContainerBindNormalVolume(c *testing.T) {
// TODO Windows to Windows CI - Port this
testRequires(c, DaemonIsLinux)
cli.DockerCmd(c, "create", "-v", "/foo", "--name=one", "busybox")
fooDir, err := inspectMountSourceField("one", "/foo")
assert.NilError(c, err)
cli.DockerCmd(c, "create", "-v", "/foo", "--name=two", "busybox")
bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}}
res, _, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/two/start"), request.JSONBody(bindSpec))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusNoContent)
fooDir2, err := inspectMountSourceField("two", "/foo")
assert.NilError(c, err)
assert.Equal(c, fooDir2, fooDir, "expected volume path to be %s, got: %s", fooDir, fooDir2)
}
func (s *DockerAPISuite) TestDeprecatedStartWithTooLowMemoryLimit(c *testing.T) {
// TODO Windows: Port once memory is supported
testRequires(c, DaemonIsLinux)
containerID := cli.DockerCmd(c, "create", "busybox").Stdout()
containerID = strings.TrimSpace(containerID)
const config = `{
"CpuShares": 100,
"Memory": 524287
}`
res, body, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/"+containerID+"/start"), request.RawString(config), request.JSON)
assert.NilError(c, err)
b, err := request.ReadBody(body)
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusBadRequest)
assert.Assert(c, is.Contains(string(b), "Minimum memory limit allowed is 6MB"))
}
// #14640
func (s *DockerAPISuite) TestDeprecatedPostContainersStartWithoutLinksInHostConfig(c *testing.T) {
// TODO Windows: Windows doesn't support supplying a hostconfig on start.
// An alternate test could be written to validate the negative testing aspect of this
testRequires(c, DaemonIsLinux)
name := "test-host-config-links"
cli.DockerCmd(c, append([]string{"create", "--name", name, "busybox"}, sleepCommandForDaemonPlatform()...)...)
hc := inspectFieldJSON(c, name, "HostConfig")
config := `{"HostConfig":` + hc + `}`
res, b, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/"+name+"/start"), request.RawString(config), request.JSON)
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusNoContent)
b.Close()
}
// #14640
func (s *DockerAPISuite) TestDeprecatedPostContainersStartWithLinksInHostConfig(c *testing.T) {
// TODO Windows: Windows doesn't support supplying a hostconfig on start.
// An alternate test could be written to validate the negative testing aspect of this
testRequires(c, DaemonIsLinux)
name := "test-host-config-links"
cli.DockerCmd(c, "run", "--name", "foo", "-d", "busybox", "top")
cli.DockerCmd(c, "create", "--name", name, "--link", "foo:bar", "busybox", "top")
hc := inspectFieldJSON(c, name, "HostConfig")
config := `{"HostConfig":` + hc + `}`
res, b, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/"+name+"/start"), request.RawString(config), request.JSON)
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusNoContent)
b.Close()
}
// #14640
func (s *DockerAPISuite) TestDeprecatedPostContainersStartWithLinksInHostConfigIdLinked(c *testing.T) {
// Windows does not support links
testRequires(c, DaemonIsLinux)
const name = "test-host-config-links"
containerID := cli.DockerCmd(c, "run", "--name", "link0", "-d", "busybox", "top").Combined()
containerID = strings.TrimSpace(containerID)
defer cli.DockerCmd(c, "stop", "link0")
cli.DockerCmd(c, "create", "--name", name, "--link", containerID, "busybox", "top")
defer cli.DockerCmd(c, "stop", name)
hc := inspectFieldJSON(c, name, "HostConfig")
config := `{"HostConfig":` + hc + `}`
res, b, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/"+name+"/start"), request.RawString(config), request.JSON)
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusNoContent)
b.Close()
}
func (s *DockerAPISuite) TestDeprecatedStartWithNilDNS(c *testing.T) {
// TODO Windows: Add once DNS is supported
testRequires(c, DaemonIsLinux)
containerID := cli.DockerCmd(c, "create", "busybox").Stdout()
containerID = strings.TrimSpace(containerID)
const config = `{"HostConfig": {"Dns": null}}`
res, b, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/"+containerID+"/start"), request.RawString(config), request.JSON)
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusNoContent)
b.Close()
dns := inspectFieldJSON(c, containerID, "HostConfig.Dns")
assert.Equal(c, dns, "[]")
}

View file

@ -1,33 +0,0 @@
//go:build !windows
package main
import (
"strings"
"testing"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/testutil"
"github.com/docker/docker/testutil/request"
"gotest.tools/v3/assert"
)
// #19100 This is a deprecated feature test, it should be removed in Docker 1.12
func (s *DockerNetworkSuite) TestDeprecatedDockerNetworkStartAPIWithHostconfig(c *testing.T) {
const netName = "test"
const conName = "foo"
cli.DockerCmd(c, "network", "create", netName)
cli.DockerCmd(c, "create", "--name", conName, "busybox", "top")
config := map[string]interface{}{
"HostConfig": map[string]interface{}{
"NetworkMode": netName,
},
}
_, _, err := request.Post(testutil.GetContext(c), formatV123StartAPIURL("/containers/"+conName+"/start"), request.JSONBody(config))
assert.NilError(c, err)
cli.WaitRun(c, conName)
networks := inspectField(c, conName, "NetworkSettings.Networks")
assert.Assert(c, strings.Contains(networks, netName), "Should contain '%s' network", netName)
assert.Assert(c, !strings.Contains(networks, "bridge"), "Should not contain 'bridge' network")
}

View file

@ -2,7 +2,6 @@ package container // import "github.com/docker/docker/integration/container"
import ( import (
"net/http" "net/http"
"runtime"
"testing" "testing"
"github.com/docker/docker/testutil" "github.com/docker/docker/testutil"
@ -25,15 +24,6 @@ func TestContainerInvalidJSON(t *testing.T) {
"/exec/foobar/start", "/exec/foobar/start",
} }
// windows doesnt support API < v1.24
if runtime.GOOS != "windows" {
endpoints = append(
endpoints,
"/v1.23/containers/foobar/copy", // deprecated since 1.8 (API v1.20), errors out since 1.12 (API v1.24)
"/v1.23/containers/foobar/start", // accepts a body on API < v1.24
)
}
for _, ep := range endpoints { for _, ep := range endpoints {
ep := ep ep := ep
t.Run(ep[1:], func(t *testing.T) { t.Run(ep[1:], func(t *testing.T) {

View file

@ -1,53 +1,17 @@
package container // import "github.com/docker/docker/integration/container" package container // import "github.com/docker/docker/integration/container"
import ( import (
"encoding/json"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
"time"
containertypes "github.com/docker/docker/api/types/container" containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container" "github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/testutil/request" "github.com/docker/docker/testutil/request"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp" is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/poll"
"gotest.tools/v3/skip"
) )
func TestInspectCpusetInConfigPre120(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows" || !testEnv.DaemonInfo.CPUSet)
ctx := setupTest(t)
apiClient := request.NewAPIClient(t, client.WithVersion("1.19"))
name := strings.ToLower(t.Name())
// Create container with up to-date-API
container.Run(ctx, t, request.NewAPIClient(t), container.WithName(name),
container.WithCmd("true"),
func(c *container.TestContainerConfig) {
c.HostConfig.Resources.CpusetCpus = "0"
},
)
poll.WaitOn(t, container.IsInState(ctx, apiClient, name, "exited"), poll.WithDelay(100*time.Millisecond))
_, body, err := apiClient.ContainerInspectWithRaw(ctx, name, false)
assert.NilError(t, err)
var inspectJSON map[string]interface{}
err = json.Unmarshal(body, &inspectJSON)
assert.NilError(t, err, "unable to unmarshal body for version 1.19: %s", err)
config, ok := inspectJSON["Config"]
assert.Check(t, is.Equal(true, ok), "Unable to find 'Config'")
cfg := config.(map[string]interface{})
_, ok = cfg["Cpuset"]
assert.Check(t, is.Equal(true, ok), "API version 1.19 expected to include Cpuset in 'Config'")
}
func TestInspectAnnotations(t *testing.T) { func TestInspectAnnotations(t *testing.T) {
ctx := setupTest(t) ctx := setupTest(t)
apiClient := request.NewAPIClient(t) apiClient := request.NewAPIClient(t)

View file

@ -6,7 +6,6 @@ import (
"time" "time"
containertypes "github.com/docker/docker/api/types/container" containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container" "github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/testutil" "github.com/docker/docker/testutil"
"github.com/docker/docker/testutil/request" "github.com/docker/docker/testutil/request"
@ -133,15 +132,6 @@ func TestKillStoppedContainer(t *testing.T) {
assert.Assert(t, is.Contains(err.Error(), "is not running")) assert.Assert(t, is.Contains(err.Error(), "is not running"))
} }
func TestKillStoppedContainerAPIPre120(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "Windows only supports 1.25 or later")
ctx := setupTest(t)
apiClient := request.NewAPIClient(t, client.WithVersion("1.19"))
id := container.Create(ctx, t, apiClient)
err := apiClient.ContainerKill(ctx, id, "SIGKILL")
assert.NilError(t, err)
}
func TestKillDifferentUserContainer(t *testing.T) { func TestKillDifferentUserContainer(t *testing.T) {
// TODO Windows: Windows does not yet support -u (Feb 2016). // TODO Windows: Windows does not yet support -u (Feb 2016).
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "User containers (container.Config.User) are not yet supported on %q platform", testEnv.DaemonInfo.OSType) skip.If(t, testEnv.DaemonInfo.OSType == "windows", "User containers (container.Config.User) are not yet supported on %q platform", testEnv.DaemonInfo.OSType)

View file

@ -31,7 +31,6 @@ func TestInfoBinaryCommits(t *testing.T) {
func TestInfoAPIVersioned(t *testing.T) { func TestInfoAPIVersioned(t *testing.T) {
ctx := testutil.StartSpan(baseContext, t) ctx := testutil.StartSpan(baseContext, t)
// Windows only supports 1.25 or later
res, body, err := req.Get(ctx, "/v1.24/info") res, body, err := req.Get(ctx, "/v1.24/info")
assert.NilError(t, err) assert.NilError(t, err)

View file

@ -27,11 +27,6 @@ func (r ContainerDecoder) DecodeConfig(src io.Reader) (*container.Config, *conta
return decodeContainerConfig(src, si) return decodeContainerConfig(src, si)
} }
// DecodeHostConfig makes ContainerDecoder to implement httputils.ContainerDecoder
func (r ContainerDecoder) DecodeHostConfig(src io.Reader) (*container.HostConfig, error) {
return decodeHostConfig(src)
}
// decodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper // decodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
// struct and returns both a Config and a HostConfig struct, and performs some // struct and returns both a Config and a HostConfig struct, and performs some
// validation. Certain parameters need daemon-side validation that cannot be done // validation. Certain parameters need daemon-side validation that cannot be done

View file

@ -26,11 +26,10 @@ func TestDecodeContainerConfig(t *testing.T) {
imgName string imgName string
) )
// FIXME (thaJeztah): update fixtures for more current versions.
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
imgName = "ubuntu" imgName = "ubuntu"
fixtures = []f{ fixtures = []f{
{"fixtures/unix/container_config_1_14.json", strslice.StrSlice{}},
{"fixtures/unix/container_config_1_17.json", strslice.StrSlice{"bash"}},
{"fixtures/unix/container_config_1_19.json", strslice.StrSlice{"bash"}}, {"fixtures/unix/container_config_1_19.json", strslice.StrSlice{"bash"}},
} }
} else { } else {

View file

@ -1,30 +0,0 @@
{
"Hostname":"",
"Domainname": "",
"User":"",
"Memory": 1000,
"MemorySwap":0,
"CpuShares": 512,
"Cpuset": "0,1",
"AttachStdin":false,
"AttachStdout":true,
"AttachStderr":true,
"PortSpecs":null,
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":null,
"Cmd":[
"bash"
],
"Image":"ubuntu",
"Volumes":{
"/tmp": {}
},
"WorkingDir":"",
"NetworkDisabled": false,
"ExposedPorts":{
"22/tcp": {}
},
"RestartPolicy": { "Name": "always" }
}

View file

@ -1,50 +0,0 @@
{
"Hostname": "",
"Domainname": "",
"User": "",
"Memory": 1000,
"MemorySwap": 0,
"CpuShares": 512,
"Cpuset": "0,1",
"AttachStdin": false,
"AttachStdout": true,
"AttachStderr": true,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": [
"date"
],
"Entrypoint": "bash",
"Image": "ubuntu",
"Volumes": {
"/tmp": {}
},
"WorkingDir": "",
"NetworkDisabled": false,
"MacAddress": "12:34:56:78:9a:bc",
"ExposedPorts": {
"22/tcp": {}
},
"SecurityOpt": [""],
"HostConfig": {
"Binds": ["/tmp:/tmp"],
"Links": ["redis3:redis"],
"LxcConf": {"lxc.utsname":"docker"},
"PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
"PublishAllPorts": false,
"Privileged": false,
"ReadonlyRootfs": false,
"Dns": ["8.8.8.8"],
"DnsSearch": [""],
"DnsOptions": [""],
"ExtraHosts": null,
"VolumesFrom": ["parent", "other:ro"],
"CapAdd": ["NET_ADMIN"],
"CapDrop": ["MKNOD"],
"RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
"NetworkMode": "bridge",
"Devices": []
}
}

View file

@ -1,18 +0,0 @@
{
"Binds": ["/tmp:/tmp"],
"ContainerIDFile": "",
"LxcConf": [],
"Privileged": false,
"PortBindings": {
"80/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "49153"
}
]
},
"Links": ["/name:alias"],
"PublishAllPorts": false,
"CapAdd": ["NET_ADMIN"],
"CapDrop": ["MKNOD"]
}

View file

@ -1,30 +0,0 @@
{
"Binds": ["/tmp:/tmp"],
"Links": ["redis3:redis"],
"LxcConf": {"lxc.utsname":"docker"},
"Memory": 0,
"MemorySwap": 0,
"CpuShares": 512,
"CpuPeriod": 100000,
"CpusetCpus": "0,1",
"CpusetMems": "0,1",
"BlkioWeight": 300,
"OomKillDisable": false,
"PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
"PublishAllPorts": false,
"Privileged": false,
"ReadonlyRootfs": false,
"Dns": ["8.8.8.8"],
"DnsSearch": [""],
"ExtraHosts": null,
"VolumesFrom": ["parent", "other:ro"],
"CapAdd": ["NET_ADMIN"],
"CapDrop": ["MKNOD"],
"RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
"NetworkMode": "bridge",
"Devices": [],
"Ulimits": [{}],
"LogConfig": { "Type": "json-file", "Config": {} },
"SecurityOpt": [""],
"CgroupParent": ""
}

View file

@ -1,23 +1,12 @@
package runconfig // import "github.com/docker/docker/runconfig" package runconfig // import "github.com/docker/docker/runconfig"
import ( import (
"io"
"strings" "strings"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
) )
// DecodeHostConfig creates a HostConfig based on the specified Reader.
// It assumes the content of the reader will be JSON, and decodes it.
func decodeHostConfig(src io.Reader) (*container.HostConfig, error) {
var w ContainerConfigWrapper
if err := loadJSON(src, &w); err != nil {
return nil, err
}
return w.getHostConfig(), nil
}
// SetDefaultNetModeIfBlank changes the NetworkMode in a HostConfig structure // SetDefaultNetModeIfBlank changes the NetworkMode in a HostConfig structure
// to default if it is not populated. This ensures backwards compatibility after // to default if it is not populated. This ensures backwards compatibility after
// the validation of the network mode was moved from the docker CLI to the // the validation of the network mode was moved from the docker CLI to the

View file

@ -3,51 +3,12 @@
package runconfig // import "github.com/docker/docker/runconfig" package runconfig // import "github.com/docker/docker/runconfig"
import ( import (
"bytes"
"fmt"
"os"
"testing" "testing"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/sysinfo" "github.com/docker/docker/pkg/sysinfo"
"gotest.tools/v3/assert"
) )
func TestDecodeHostConfig(t *testing.T) {
fixtures := []struct {
file string
}{
{"fixtures/unix/container_hostconfig_1_14.json"},
{"fixtures/unix/container_hostconfig_1_19.json"},
}
for _, f := range fixtures {
b, err := os.ReadFile(f.file)
if err != nil {
t.Fatal(err)
}
c, err := decodeHostConfig(bytes.NewReader(b))
if err != nil {
t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
}
assert.Check(t, !c.Privileged)
if l := len(c.Binds); l != 1 {
t.Fatalf("Expected 1 bind, found %d\n", l)
}
if len(c.CapAdd) != 1 && c.CapAdd[0] != "NET_ADMIN" {
t.Fatalf("Expected CapAdd NET_ADMIN, got %v", c.CapAdd)
}
if len(c.CapDrop) != 1 && c.CapDrop[0] != "NET_ADMIN" {
t.Fatalf("Expected CapDrop NET_ADMIN, got %v", c.CapDrop)
}
}
}
func TestValidateResources(t *testing.T) { func TestValidateResources(t *testing.T) {
type resourceTest struct { type resourceTest struct {
ConfigCPURealtimePeriod int64 ConfigCPURealtimePeriod int64