Browse code

Merge pull request #23348 from vdemeester/migrate-cp-to-cobra

Migrate cp command to cobra

Alexander Morozov authored on 2016/06/15 02:49:22
Showing 5 changed files
... ...
@@ -3,7 +3,6 @@ package client
3 3
 // Command returns a cli command handler if one exists
4 4
 func (cli *DockerCli) Command(name string) func(...string) error {
5 5
 	return map[string]func(...string) error{
6
-		"cp":      cli.CmdCp,
7 6
 		"exec":    cli.CmdExec,
8 7
 		"info":    cli.CmdInfo,
9 8
 		"inspect": cli.CmdInspect,
10 9
new file mode 100644
... ...
@@ -0,0 +1,302 @@
0
+package container
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"os"
6
+	"path/filepath"
7
+	"strings"
8
+
9
+	"golang.org/x/net/context"
10
+
11
+	"github.com/docker/docker/api/client"
12
+	"github.com/docker/docker/cli"
13
+	"github.com/docker/docker/pkg/archive"
14
+	"github.com/docker/docker/pkg/system"
15
+	"github.com/docker/engine-api/types"
16
+	"github.com/spf13/cobra"
17
+)
18
+
19
+type copyOptions struct {
20
+	source      string
21
+	destination string
22
+	followLink  bool
23
+}
24
+
25
+type copyDirection int
26
+
27
+const (
28
+	fromContainer copyDirection = (1 << iota)
29
+	toContainer
30
+	acrossContainers = fromContainer | toContainer
31
+)
32
+
33
+type cpConfig struct {
34
+	followLink bool
35
+}
36
+
37
+// NewCopyCommand creates a new `docker cp` command
38
+func NewCopyCommand(dockerCli *client.DockerCli) *cobra.Command {
39
+	var opts copyOptions
40
+
41
+	cmd := &cobra.Command{
42
+		Use: `cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
43
+	docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH`,
44
+		Short: "Copy files/folders between a container and the local filesystem",
45
+		Long: strings.Join([]string{
46
+			"\nUse '-' as the source to read a tar archive from stdin\n",
47
+			"and extract it to a directory destination in a container.\n",
48
+			"Use '-' as the destination to stream a tar archive of a\n",
49
+			"container source to stdout.",
50
+		}, ""),
51
+		Args: cli.ExactArgs(2),
52
+		RunE: func(cmd *cobra.Command, args []string) error {
53
+			if args[0] == "" {
54
+				return fmt.Errorf("source can not be empty")
55
+			}
56
+			if args[1] == "" {
57
+				return fmt.Errorf("destination can not be empty")
58
+			}
59
+			opts.source = args[0]
60
+			opts.destination = args[1]
61
+			return runCopy(dockerCli, opts)
62
+		},
63
+	}
64
+
65
+	flags := cmd.Flags()
66
+
67
+	flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
68
+
69
+	return cmd
70
+}
71
+
72
+func runCopy(dockerCli *client.DockerCli, opts copyOptions) error {
73
+	srcContainer, srcPath := splitCpArg(opts.source)
74
+	dstContainer, dstPath := splitCpArg(opts.destination)
75
+
76
+	var direction copyDirection
77
+	if srcContainer != "" {
78
+		direction |= fromContainer
79
+	}
80
+	if dstContainer != "" {
81
+		direction |= toContainer
82
+	}
83
+
84
+	cpParam := &cpConfig{
85
+		followLink: opts.followLink,
86
+	}
87
+
88
+	ctx := context.Background()
89
+
90
+	switch direction {
91
+	case fromContainer:
92
+		return copyFromContainer(ctx, dockerCli, srcContainer, srcPath, dstPath, cpParam)
93
+	case toContainer:
94
+		return copyToContainer(ctx, dockerCli, srcPath, dstContainer, dstPath, cpParam)
95
+	case acrossContainers:
96
+		// Copying between containers isn't supported.
97
+		return fmt.Errorf("copying between containers is not supported")
98
+	default:
99
+		// User didn't specify any container.
100
+		return fmt.Errorf("must specify at least one container source")
101
+	}
102
+}
103
+
104
+func statContainerPath(ctx context.Context, dockerCli *client.DockerCli, containerName, path string) (types.ContainerPathStat, error) {
105
+	return dockerCli.Client().ContainerStatPath(ctx, containerName, path)
106
+}
107
+
108
+func resolveLocalPath(localPath string) (absPath string, err error) {
109
+	if absPath, err = filepath.Abs(localPath); err != nil {
110
+		return
111
+	}
112
+
113
+	return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
114
+}
115
+
116
+func copyFromContainer(ctx context.Context, dockerCli *client.DockerCli, srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) {
117
+	if dstPath != "-" {
118
+		// Get an absolute destination path.
119
+		dstPath, err = resolveLocalPath(dstPath)
120
+		if err != nil {
121
+			return err
122
+		}
123
+	}
124
+
125
+	// if client requests to follow symbol link, then must decide target file to be copied
126
+	var rebaseName string
127
+	if cpParam.followLink {
128
+		srcStat, err := statContainerPath(ctx, dockerCli, srcContainer, srcPath)
129
+
130
+		// If the destination is a symbolic link, we should follow it.
131
+		if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
132
+			linkTarget := srcStat.LinkTarget
133
+			if !system.IsAbs(linkTarget) {
134
+				// Join with the parent directory.
135
+				srcParent, _ := archive.SplitPathDirEntry(srcPath)
136
+				linkTarget = filepath.Join(srcParent, linkTarget)
137
+			}
138
+
139
+			linkTarget, rebaseName = archive.GetRebaseName(srcPath, linkTarget)
140
+			srcPath = linkTarget
141
+		}
142
+
143
+	}
144
+
145
+	content, stat, err := dockerCli.Client().CopyFromContainer(ctx, srcContainer, srcPath)
146
+	if err != nil {
147
+		return err
148
+	}
149
+	defer content.Close()
150
+
151
+	if dstPath == "-" {
152
+		// Send the response to STDOUT.
153
+		_, err = io.Copy(os.Stdout, content)
154
+
155
+		return err
156
+	}
157
+
158
+	// Prepare source copy info.
159
+	srcInfo := archive.CopyInfo{
160
+		Path:       srcPath,
161
+		Exists:     true,
162
+		IsDir:      stat.Mode.IsDir(),
163
+		RebaseName: rebaseName,
164
+	}
165
+
166
+	preArchive := content
167
+	if len(srcInfo.RebaseName) != 0 {
168
+		_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
169
+		preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
170
+	}
171
+	// See comments in the implementation of `archive.CopyTo` for exactly what
172
+	// goes into deciding how and whether the source archive needs to be
173
+	// altered for the correct copy behavior.
174
+	return archive.CopyTo(preArchive, srcInfo, dstPath)
175
+}
176
+
177
+func copyToContainer(ctx context.Context, dockerCli *client.DockerCli, srcPath, dstContainer, dstPath string, cpParam *cpConfig) (err error) {
178
+	if srcPath != "-" {
179
+		// Get an absolute source path.
180
+		srcPath, err = resolveLocalPath(srcPath)
181
+		if err != nil {
182
+			return err
183
+		}
184
+	}
185
+
186
+	// In order to get the copy behavior right, we need to know information
187
+	// about both the source and destination. The API is a simple tar
188
+	// archive/extract API but we can use the stat info header about the
189
+	// destination to be more informed about exactly what the destination is.
190
+
191
+	// Prepare destination copy info by stat-ing the container path.
192
+	dstInfo := archive.CopyInfo{Path: dstPath}
193
+	dstStat, err := statContainerPath(ctx, dockerCli, dstContainer, dstPath)
194
+
195
+	// If the destination is a symbolic link, we should evaluate it.
196
+	if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
197
+		linkTarget := dstStat.LinkTarget
198
+		if !system.IsAbs(linkTarget) {
199
+			// Join with the parent directory.
200
+			dstParent, _ := archive.SplitPathDirEntry(dstPath)
201
+			linkTarget = filepath.Join(dstParent, linkTarget)
202
+		}
203
+
204
+		dstInfo.Path = linkTarget
205
+		dstStat, err = statContainerPath(ctx, dockerCli, dstContainer, linkTarget)
206
+	}
207
+
208
+	// Ignore any error and assume that the parent directory of the destination
209
+	// path exists, in which case the copy may still succeed. If there is any
210
+	// type of conflict (e.g., non-directory overwriting an existing directory
211
+	// or vice versa) the extraction will fail. If the destination simply did
212
+	// not exist, but the parent directory does, the extraction will still
213
+	// succeed.
214
+	if err == nil {
215
+		dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
216
+	}
217
+
218
+	var (
219
+		content         io.Reader
220
+		resolvedDstPath string
221
+	)
222
+
223
+	if srcPath == "-" {
224
+		// Use STDIN.
225
+		content = os.Stdin
226
+		resolvedDstPath = dstInfo.Path
227
+		if !dstInfo.IsDir {
228
+			return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath))
229
+		}
230
+	} else {
231
+		// Prepare source copy info.
232
+		srcInfo, err := archive.CopyInfoSourcePath(srcPath, cpParam.followLink)
233
+		if err != nil {
234
+			return err
235
+		}
236
+
237
+		srcArchive, err := archive.TarResource(srcInfo)
238
+		if err != nil {
239
+			return err
240
+		}
241
+		defer srcArchive.Close()
242
+
243
+		// With the stat info about the local source as well as the
244
+		// destination, we have enough information to know whether we need to
245
+		// alter the archive that we upload so that when the server extracts
246
+		// it to the specified directory in the container we get the desired
247
+		// copy behavior.
248
+
249
+		// See comments in the implementation of `archive.PrepareArchiveCopy`
250
+		// for exactly what goes into deciding how and whether the source
251
+		// archive needs to be altered for the correct copy behavior when it is
252
+		// extracted. This function also infers from the source and destination
253
+		// info which directory to extract to, which may be the parent of the
254
+		// destination that the user specified.
255
+		dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
256
+		if err != nil {
257
+			return err
258
+		}
259
+		defer preparedArchive.Close()
260
+
261
+		resolvedDstPath = dstDir
262
+		content = preparedArchive
263
+	}
264
+
265
+	options := types.CopyToContainerOptions{
266
+		AllowOverwriteDirWithFile: false,
267
+	}
268
+
269
+	return dockerCli.Client().CopyToContainer(ctx, dstContainer, resolvedDstPath, content, options)
270
+}
271
+
272
+// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
273
+// in a valid LOCALPATH, like `file:name.txt`. We can resolve this ambiguity by
274
+// requiring a LOCALPATH with a `:` to be made explicit with a relative or
275
+// absolute path:
276
+// 	`/path/to/file:name.txt` or `./file:name.txt`
277
+//
278
+// This is apparently how `scp` handles this as well:
279
+// 	http://www.cyberciti.biz/faq/rsync-scp-file-name-with-colon-punctuation-in-it/
280
+//
281
+// We can't simply check for a filepath separator because container names may
282
+// have a separator, e.g., "host0/cname1" if container is in a Docker cluster,
283
+// so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
284
+// client, a `:` could be part of an absolute Windows path, in which case it
285
+// is immediately proceeded by a backslash.
286
+func splitCpArg(arg string) (container, path string) {
287
+	if system.IsAbs(arg) {
288
+		// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
289
+		return "", arg
290
+	}
291
+
292
+	parts := strings.SplitN(arg, ":", 2)
293
+
294
+	if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
295
+		// Either there's no `:` in the arg
296
+		// OR it's an explicit local relative path like `./file:name.txt`.
297
+		return "", arg
298
+	}
299
+
300
+	return parts[0], parts[1]
301
+}
0 302
deleted file mode 100644
... ...
@@ -1,297 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-	"os"
7
-	"path/filepath"
8
-	"strings"
9
-
10
-	"golang.org/x/net/context"
11
-
12
-	Cli "github.com/docker/docker/cli"
13
-	"github.com/docker/docker/pkg/archive"
14
-	flag "github.com/docker/docker/pkg/mflag"
15
-	"github.com/docker/docker/pkg/system"
16
-	"github.com/docker/engine-api/types"
17
-)
18
-
19
-type copyDirection int
20
-
21
-const (
22
-	fromContainer copyDirection = (1 << iota)
23
-	toContainer
24
-	acrossContainers = fromContainer | toContainer
25
-)
26
-
27
-type cpConfig struct {
28
-	followLink bool
29
-}
30
-
31
-// CmdCp copies files/folders to or from a path in a container.
32
-//
33
-// When copying from a container, if DEST_PATH is '-' the data is written as a
34
-// tar archive file to STDOUT.
35
-//
36
-// When copying to a container, if SRC_PATH is '-' the data is read as a tar
37
-// archive file from STDIN, and the destination CONTAINER:DEST_PATH, must specify
38
-// a directory.
39
-//
40
-// Usage:
41
-// 	docker cp CONTAINER:SRC_PATH DEST_PATH|-
42
-// 	docker cp SRC_PATH|- CONTAINER:DEST_PATH
43
-func (cli *DockerCli) CmdCp(args ...string) error {
44
-	cmd := Cli.Subcmd(
45
-		"cp",
46
-		[]string{"CONTAINER:SRC_PATH DEST_PATH|-", "SRC_PATH|- CONTAINER:DEST_PATH"},
47
-		strings.Join([]string{
48
-			Cli.DockerCommands["cp"].Description,
49
-			"\nUse '-' as the source to read a tar archive from stdin\n",
50
-			"and extract it to a directory destination in a container.\n",
51
-			"Use '-' as the destination to stream a tar archive of a\n",
52
-			"container source to stdout.",
53
-		}, ""),
54
-		true,
55
-	)
56
-
57
-	followLink := cmd.Bool([]string{"L", "-follow-link"}, false, "Always follow symbol link in SRC_PATH")
58
-
59
-	cmd.Require(flag.Exact, 2)
60
-	cmd.ParseFlags(args, true)
61
-
62
-	if cmd.Arg(0) == "" {
63
-		return fmt.Errorf("source can not be empty")
64
-	}
65
-	if cmd.Arg(1) == "" {
66
-		return fmt.Errorf("destination can not be empty")
67
-	}
68
-
69
-	srcContainer, srcPath := splitCpArg(cmd.Arg(0))
70
-	dstContainer, dstPath := splitCpArg(cmd.Arg(1))
71
-
72
-	var direction copyDirection
73
-	if srcContainer != "" {
74
-		direction |= fromContainer
75
-	}
76
-	if dstContainer != "" {
77
-		direction |= toContainer
78
-	}
79
-
80
-	cpParam := &cpConfig{
81
-		followLink: *followLink,
82
-	}
83
-
84
-	ctx := context.Background()
85
-
86
-	switch direction {
87
-	case fromContainer:
88
-		return cli.copyFromContainer(ctx, srcContainer, srcPath, dstPath, cpParam)
89
-	case toContainer:
90
-		return cli.copyToContainer(ctx, srcPath, dstContainer, dstPath, cpParam)
91
-	case acrossContainers:
92
-		// Copying between containers isn't supported.
93
-		return fmt.Errorf("copying between containers is not supported")
94
-	default:
95
-		// User didn't specify any container.
96
-		return fmt.Errorf("must specify at least one container source")
97
-	}
98
-}
99
-
100
-// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
101
-// in a valid LOCALPATH, like `file:name.txt`. We can resolve this ambiguity by
102
-// requiring a LOCALPATH with a `:` to be made explicit with a relative or
103
-// absolute path:
104
-// 	`/path/to/file:name.txt` or `./file:name.txt`
105
-//
106
-// This is apparently how `scp` handles this as well:
107
-// 	http://www.cyberciti.biz/faq/rsync-scp-file-name-with-colon-punctuation-in-it/
108
-//
109
-// We can't simply check for a filepath separator because container names may
110
-// have a separator, e.g., "host0/cname1" if container is in a Docker cluster,
111
-// so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
112
-// client, a `:` could be part of an absolute Windows path, in which case it
113
-// is immediately proceeded by a backslash.
114
-func splitCpArg(arg string) (container, path string) {
115
-	if system.IsAbs(arg) {
116
-		// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
117
-		return "", arg
118
-	}
119
-
120
-	parts := strings.SplitN(arg, ":", 2)
121
-
122
-	if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
123
-		// Either there's no `:` in the arg
124
-		// OR it's an explicit local relative path like `./file:name.txt`.
125
-		return "", arg
126
-	}
127
-
128
-	return parts[0], parts[1]
129
-}
130
-
131
-func (cli *DockerCli) statContainerPath(ctx context.Context, containerName, path string) (types.ContainerPathStat, error) {
132
-	return cli.client.ContainerStatPath(ctx, containerName, path)
133
-}
134
-
135
-func resolveLocalPath(localPath string) (absPath string, err error) {
136
-	if absPath, err = filepath.Abs(localPath); err != nil {
137
-		return
138
-	}
139
-
140
-	return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
141
-}
142
-
143
-func (cli *DockerCli) copyFromContainer(ctx context.Context, srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) {
144
-	if dstPath != "-" {
145
-		// Get an absolute destination path.
146
-		dstPath, err = resolveLocalPath(dstPath)
147
-		if err != nil {
148
-			return err
149
-		}
150
-	}
151
-
152
-	// if client requests to follow symbol link, then must decide target file to be copied
153
-	var rebaseName string
154
-	if cpParam.followLink {
155
-		srcStat, err := cli.statContainerPath(ctx, srcContainer, srcPath)
156
-
157
-		// If the destination is a symbolic link, we should follow it.
158
-		if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
159
-			linkTarget := srcStat.LinkTarget
160
-			if !system.IsAbs(linkTarget) {
161
-				// Join with the parent directory.
162
-				srcParent, _ := archive.SplitPathDirEntry(srcPath)
163
-				linkTarget = filepath.Join(srcParent, linkTarget)
164
-			}
165
-
166
-			linkTarget, rebaseName = archive.GetRebaseName(srcPath, linkTarget)
167
-			srcPath = linkTarget
168
-		}
169
-
170
-	}
171
-
172
-	content, stat, err := cli.client.CopyFromContainer(ctx, srcContainer, srcPath)
173
-	if err != nil {
174
-		return err
175
-	}
176
-	defer content.Close()
177
-
178
-	if dstPath == "-" {
179
-		// Send the response to STDOUT.
180
-		_, err = io.Copy(os.Stdout, content)
181
-
182
-		return err
183
-	}
184
-
185
-	// Prepare source copy info.
186
-	srcInfo := archive.CopyInfo{
187
-		Path:       srcPath,
188
-		Exists:     true,
189
-		IsDir:      stat.Mode.IsDir(),
190
-		RebaseName: rebaseName,
191
-	}
192
-
193
-	preArchive := content
194
-	if len(srcInfo.RebaseName) != 0 {
195
-		_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
196
-		preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
197
-	}
198
-	// See comments in the implementation of `archive.CopyTo` for exactly what
199
-	// goes into deciding how and whether the source archive needs to be
200
-	// altered for the correct copy behavior.
201
-	return archive.CopyTo(preArchive, srcInfo, dstPath)
202
-}
203
-
204
-func (cli *DockerCli) copyToContainer(ctx context.Context, srcPath, dstContainer, dstPath string, cpParam *cpConfig) (err error) {
205
-	if srcPath != "-" {
206
-		// Get an absolute source path.
207
-		srcPath, err = resolveLocalPath(srcPath)
208
-		if err != nil {
209
-			return err
210
-		}
211
-	}
212
-
213
-	// In order to get the copy behavior right, we need to know information
214
-	// about both the source and destination. The API is a simple tar
215
-	// archive/extract API but we can use the stat info header about the
216
-	// destination to be more informed about exactly what the destination is.
217
-
218
-	// Prepare destination copy info by stat-ing the container path.
219
-	dstInfo := archive.CopyInfo{Path: dstPath}
220
-	dstStat, err := cli.statContainerPath(ctx, dstContainer, dstPath)
221
-
222
-	// If the destination is a symbolic link, we should evaluate it.
223
-	if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
224
-		linkTarget := dstStat.LinkTarget
225
-		if !system.IsAbs(linkTarget) {
226
-			// Join with the parent directory.
227
-			dstParent, _ := archive.SplitPathDirEntry(dstPath)
228
-			linkTarget = filepath.Join(dstParent, linkTarget)
229
-		}
230
-
231
-		dstInfo.Path = linkTarget
232
-		dstStat, err = cli.statContainerPath(ctx, dstContainer, linkTarget)
233
-	}
234
-
235
-	// Ignore any error and assume that the parent directory of the destination
236
-	// path exists, in which case the copy may still succeed. If there is any
237
-	// type of conflict (e.g., non-directory overwriting an existing directory
238
-	// or vice versa) the extraction will fail. If the destination simply did
239
-	// not exist, but the parent directory does, the extraction will still
240
-	// succeed.
241
-	if err == nil {
242
-		dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
243
-	}
244
-
245
-	var (
246
-		content         io.Reader
247
-		resolvedDstPath string
248
-	)
249
-
250
-	if srcPath == "-" {
251
-		// Use STDIN.
252
-		content = os.Stdin
253
-		resolvedDstPath = dstInfo.Path
254
-		if !dstInfo.IsDir {
255
-			return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath))
256
-		}
257
-	} else {
258
-		// Prepare source copy info.
259
-		srcInfo, err := archive.CopyInfoSourcePath(srcPath, cpParam.followLink)
260
-		if err != nil {
261
-			return err
262
-		}
263
-
264
-		srcArchive, err := archive.TarResource(srcInfo)
265
-		if err != nil {
266
-			return err
267
-		}
268
-		defer srcArchive.Close()
269
-
270
-		// With the stat info about the local source as well as the
271
-		// destination, we have enough information to know whether we need to
272
-		// alter the archive that we upload so that when the server extracts
273
-		// it to the specified directory in the container we get the desired
274
-		// copy behavior.
275
-
276
-		// See comments in the implementation of `archive.PrepareArchiveCopy`
277
-		// for exactly what goes into deciding how and whether the source
278
-		// archive needs to be altered for the correct copy behavior when it is
279
-		// extracted. This function also infers from the source and destination
280
-		// info which directory to extract to, which may be the parent of the
281
-		// destination that the user specified.
282
-		dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
283
-		if err != nil {
284
-			return err
285
-		}
286
-		defer preparedArchive.Close()
287
-
288
-		resolvedDstPath = dstDir
289
-		content = preparedArchive
290
-	}
291
-
292
-	options := types.CopyToContainerOptions{
293
-		AllowOverwriteDirWithFile: false,
294
-	}
295
-
296
-	return cli.client.CopyToContainer(ctx, dstContainer, resolvedDstPath, content, options)
297
-}
... ...
@@ -44,6 +44,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
44 44
 		swarm.NewSwarmCommand(dockerCli),
45 45
 		container.NewAttachCommand(dockerCli),
46 46
 		container.NewCommitCommand(dockerCli),
47
+		container.NewCopyCommand(dockerCli),
47 48
 		container.NewCreateCommand(dockerCli),
48 49
 		container.NewDiffCommand(dockerCli),
49 50
 		container.NewExportCommand(dockerCli),
... ...
@@ -8,7 +8,6 @@ type Command struct {
8 8
 
9 9
 // DockerCommandUsage lists the top level docker commands and their short usage
10 10
 var DockerCommandUsage = []Command{
11
-	{"cp", "Copy files/folders between a container and the local filesystem"},
12 11
 	{"exec", "Run a command in a running container"},
13 12
 	{"info", "Display system-wide information"},
14 13
 	{"inspect", "Return low-level information on a container, image or task"},