In order to get the copy behavior right, we need to know information about both the source and destination. The API is a simple tar archive/extract API but we can use the stat info header about the destination to be more informed about exactly what the destination is.
(ctx context.Context, dockerCLI command.Cli, copyConfig cpConfig)
| 353 | // archive/extract API but we can use the stat info header about the |
| 354 | // destination to be more informed about exactly what the destination is. |
| 355 | func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpConfig) error { |
| 356 | srcPath := copyConfig.sourcePath |
| 357 | dstPath := copyConfig.destPath |
| 358 | |
| 359 | if srcPath != "-" { |
| 360 | // Get an absolute source path. |
| 361 | p, err := resolveLocalPath(srcPath) |
| 362 | if err != nil { |
| 363 | return err |
| 364 | } |
| 365 | srcPath = p |
| 366 | } |
| 367 | |
| 368 | apiClient := dockerCLI.Client() |
| 369 | // Prepare destination copy info by stat-ing the container path. |
| 370 | dstInfo := archive.CopyInfo{Path: dstPath} |
| 371 | if dst, err := apiClient.ContainerStatPath(ctx, copyConfig.container, client.ContainerStatPathOptions{Path: dstPath}); err == nil { |
| 372 | // If the destination is a symbolic link, we should evaluate it. |
| 373 | if dst.Stat.Mode&os.ModeSymlink != 0 { |
| 374 | linkTarget := dst.Stat.LinkTarget |
| 375 | if !isAbs(linkTarget) { |
| 376 | // Join with the parent directory. |
| 377 | dstParent, _ := archive.SplitPathDirEntry(dstPath) |
| 378 | linkTarget = filepath.Join(dstParent, linkTarget) |
| 379 | } |
| 380 | |
| 381 | dstInfo.Path = linkTarget |
| 382 | dst, err = apiClient.ContainerStatPath(ctx, copyConfig.container, client.ContainerStatPathOptions{Path: linkTarget}) |
| 383 | } |
| 384 | // Validate the destination path |
| 385 | if err == nil { |
| 386 | if err := command.ValidateOutputPathFileMode(dst.Stat.Mode); err != nil { |
| 387 | return fmt.Errorf(`destination "%s:%s" must be a directory or a regular file: %w`, copyConfig.container, dstPath, err) |
| 388 | } |
| 389 | dstInfo.Exists, dstInfo.IsDir = true, dst.Stat.Mode.IsDir() |
| 390 | } |
| 391 | |
| 392 | // Ignore any error and assume that the parent directory of the destination |
| 393 | // path exists, in which case the copy may still succeed. If there is any |
| 394 | // type of conflict (e.g., non-directory overwriting an existing directory |
| 395 | // or vice versa) the extraction will fail. If the destination simply did |
| 396 | // not exist, but the parent directory does, the extraction will still |
| 397 | // succeed. |
| 398 | _ = err // Intentionally ignore stat errors (see above) |
| 399 | } |
| 400 | |
| 401 | var ( |
| 402 | content io.ReadCloser |
| 403 | resolvedDstPath string |
| 404 | copiedSize int64 |
| 405 | contentSize int64 |
| 406 | sizeErr error |
| 407 | ) |
| 408 | |
| 409 | if srcPath == "-" { |
| 410 | content = os.Stdin |
| 411 | resolvedDstPath = dstInfo.Path |
| 412 | sizeErr = errors.New("content size not available for stdin") |
no test coverage detected
searching dependent graphs…