Browse code

api: remove POST /containers/{id}/copy endpoint (api < v1.23)

This endpoint was deprecated in API v1.20 (Docker Engine v1.8.0) in
commit db9cc91a9ef7dea4c8d85f64578889cec3dd99b2, in favor of the
`PUT /containers/{id}/archive` and `HEAD /containers/{id}/archive`
endpoints, and disabled in API v1.24 (Docker Engine v1.12.0) through
commit 428328908dc529b1678fb3d8b033fb0591a294e3.

This patch removes the endpoint, and the associated `daemon.ContainerCopy`
method in the backend.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2024/01/21 23:02:49
Showing 7 changed files
... ...
@@ -24,7 +24,6 @@ type execBackend interface {
24 24
 // copyBackend includes functions to implement to provide container copy functionality.
25 25
 type copyBackend interface {
26 26
 	ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error)
27
-	ContainerCopy(name string, res string) (io.ReadCloser, error)
28 27
 	ContainerExport(ctx context.Context, name string, out io.Writer) error
29 28
 	ContainerExtractToDir(name, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) error
30 29
 	ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error)
... ...
@@ -56,7 +56,6 @@ func (r *containerRouter) initRoutes() {
56 56
 		router.NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait),
57 57
 		router.NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize),
58 58
 		router.NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach),
59
-		router.NewPostRoute("/containers/{name:.*}/copy", r.postContainersCopy), // Deprecated since 1.8 (API v1.20), errors out since 1.12 (API v1.24)
60 59
 		router.NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate),
61 60
 		router.NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
62 61
 		router.NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),
... ...
@@ -11,49 +11,10 @@ import (
11 11
 
12 12
 	"github.com/docker/docker/api/server/httputils"
13 13
 	"github.com/docker/docker/api/types"
14
-	"github.com/docker/docker/api/types/versions"
15 14
 	gddohttputil "github.com/golang/gddo/httputil"
16 15
 )
17 16
 
18
-type pathError struct{}
19
-
20
-func (pathError) Error() string {
21
-	return "Path cannot be empty"
22
-}
23
-
24
-func (pathError) InvalidParameter() {}
25
-
26
-// postContainersCopy is deprecated in favor of getContainersArchive.
27
-//
28
-// Deprecated since 1.8 (API v1.20), errors out since 1.12 (API v1.24)
29
-func (s *containerRouter) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
30
-	version := httputils.VersionFromContext(ctx)
31
-	if versions.GreaterThanOrEqualTo(version, "1.24") {
32
-		w.WriteHeader(http.StatusNotFound)
33
-		return nil
34
-	}
35
-
36
-	cfg := types.CopyConfig{}
37
-	if err := httputils.ReadJSON(r, &cfg); err != nil {
38
-		return err
39
-	}
40
-
41
-	if cfg.Resource == "" {
42
-		return pathError{}
43
-	}
44
-
45
-	data, err := s.backend.ContainerCopy(vars["name"], cfg.Resource)
46
-	if err != nil {
47
-		return err
48
-	}
49
-	defer data.Close()
50
-
51
-	w.Header().Set("Content-Type", "application/x-tar")
52
-	_, err = io.Copy(w, data)
53
-	return err
54
-}
55
-
56
-// // Encode the stat to JSON, base64 encode, and place in a header.
17
+// setContainerPathStatHeader encodes the stat to JSON, base64 encode, and place in a header.
57 18
 func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error {
58 19
 	statJSON, err := json.Marshal(stat)
59 20
 	if err != nil {
... ...
@@ -8,25 +8,6 @@ import (
8 8
 	"github.com/docker/docker/errdefs"
9 9
 )
10 10
 
11
-// ContainerCopy performs a deprecated operation of archiving the resource at
12
-// the specified path in the container identified by the given name.
13
-func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, error) {
14
-	ctr, err := daemon.GetContainer(name)
15
-	if err != nil {
16
-		return nil, err
17
-	}
18
-
19
-	data, err := daemon.containerCopy(ctr, res)
20
-	if err == nil {
21
-		return data, nil
22
-	}
23
-
24
-	if os.IsNotExist(err) {
25
-		return nil, containerFileNotFound{res, name}
26
-	}
27
-	return nil, errdefs.System(err)
28
-}
29
-
30 11
 // ContainerStatPath stats the filesystem resource at the specified path in the
31 12
 // container identified by the given name.
32 13
 func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) {
... ...
@@ -161,55 +161,6 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path
161 161
 	return nil
162 162
 }
163 163
 
164
-func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) {
165
-	container.Lock()
166
-
167
-	defer func() {
168
-		if err != nil {
169
-			// Wait to unlock the container until the archive is fully read
170
-			// (see the ReadCloseWrapper func below) or if there is an error
171
-			// before that occurs.
172
-			container.Unlock()
173
-		}
174
-	}()
175
-
176
-	cfs, err := daemon.openContainerFS(container)
177
-	if err != nil {
178
-		return nil, err
179
-	}
180
-	defer func() {
181
-		if err != nil {
182
-			cfs.Close()
183
-		}
184
-	}()
185
-
186
-	err = cfs.RunInFS(context.TODO(), func() error {
187
-		_, err := os.Stat(resource)
188
-		return err
189
-	})
190
-	if err != nil {
191
-		return nil, err
192
-	}
193
-
194
-	tb, err := archive.NewTarballer(resource, &archive.TarOptions{
195
-		Compression: archive.Uncompressed,
196
-	})
197
-	if err != nil {
198
-		return nil, err
199
-	}
200
-
201
-	cfs.GoInFS(context.TODO(), tb.Do)
202
-	archv := tb.Reader()
203
-	reader := ioutils.NewReadCloserWrapper(archv, func() error {
204
-		err := archv.Close()
205
-		_ = cfs.Close()
206
-		container.Unlock()
207
-		return err
208
-	})
209
-	daemon.LogContainerEvent(container, events.ActionCopy)
210
-	return reader, nil
211
-}
212
-
213 164
 // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
214 165
 // cannot be in a read-only volume. If it  is not in a volume, the container
215 166
 // cannot be configured with a read-only rootfs.
... ...
@@ -958,81 +958,6 @@ func (s *DockerAPISuite) TestContainerAPICopyNotExistsAnyMore(c *testing.T) {
958 958
 	assert.Equal(c, res.StatusCode, http.StatusNotFound)
959 959
 }
960 960
 
961
-func (s *DockerAPISuite) TestContainerAPICopyPre124(c *testing.T) {
962
-	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
963
-	const name = "test-container-api-copy"
964
-	cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
965
-
966
-	postData := types.CopyConfig{
967
-		Resource: "/test.txt",
968
-	}
969
-
970
-	res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
971
-	assert.NilError(c, err)
972
-	assert.Equal(c, res.StatusCode, http.StatusOK)
973
-
974
-	found := false
975
-	for tarReader := tar.NewReader(body); ; {
976
-		h, err := tarReader.Next()
977
-		if err != nil {
978
-			if err == io.EOF {
979
-				break
980
-			}
981
-			c.Fatal(err)
982
-		}
983
-		if h.Name == "test.txt" {
984
-			found = true
985
-			break
986
-		}
987
-	}
988
-	assert.Assert(c, found)
989
-}
990
-
991
-func (s *DockerAPISuite) TestContainerAPICopyResourcePathEmptyPre124(c *testing.T) {
992
-	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
993
-	const name = "test-container-api-copy-resource-empty"
994
-	cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
995
-
996
-	postData := types.CopyConfig{
997
-		Resource: "",
998
-	}
999
-
1000
-	res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
1001
-	assert.NilError(c, err)
1002
-	assert.Equal(c, res.StatusCode, http.StatusBadRequest)
1003
-	b, err := request.ReadBody(body)
1004
-	assert.NilError(c, err)
1005
-	assert.Assert(c, is.Regexp("^Path cannot be empty\n$", string(b)))
1006
-}
1007
-
1008
-func (s *DockerAPISuite) TestContainerAPICopyResourcePathNotFoundPre124(c *testing.T) {
1009
-	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
1010
-	const name = "test-container-api-copy-resource-not-found"
1011
-	cli.DockerCmd(c, "run", "--name", name, "busybox")
1012
-
1013
-	postData := types.CopyConfig{
1014
-		Resource: "/notexist",
1015
-	}
1016
-
1017
-	res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
1018
-	assert.NilError(c, err)
1019
-	assert.Equal(c, res.StatusCode, http.StatusNotFound)
1020
-	b, err := request.ReadBody(body)
1021
-	assert.NilError(c, err)
1022
-	assert.Assert(c, is.Regexp("^Could not find the file /notexist in container "+name+"\n$", string(b)))
1023
-}
1024
-
1025
-func (s *DockerAPISuite) TestContainerAPICopyContainerNotFoundPr124(c *testing.T) {
1026
-	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
1027
-	postData := types.CopyConfig{
1028
-		Resource: "/something",
1029
-	}
1030
-
1031
-	res, _, err := request.Post(testutil.GetContext(c), "/v1.23/containers/notexists/copy", request.JSONBody(postData))
1032
-	assert.NilError(c, err)
1033
-	assert.Equal(c, res.StatusCode, http.StatusNotFound)
1034
-}
1035
-
1036 961
 func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) {
1037 962
 	id := runSleepingContainer(c)
1038 963
 	cli.WaitRun(c, id)
... ...
@@ -29,7 +29,6 @@ func TestContainerInvalidJSON(t *testing.T) {
29 29
 	if runtime.GOOS != "windows" {
30 30
 		endpoints = append(
31 31
 			endpoints,
32
-			"/v1.23/containers/foobar/copy",  // deprecated since 1.8 (API v1.20), errors out since 1.12 (API v1.24)
33 32
 			"/v1.23/containers/foobar/start", // accepts a body on API < v1.24
34 33
 		)
35 34
 	}