github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/client/container_copy.go (about)

     1  package client // import "github.com/docker/docker/client"
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/url"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/docker/docker/api/types"
    15  )
    16  
    17  // ContainerStatPath returns Stat information about a path inside the container filesystem.
    18  func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (types.ContainerPathStat, error) {
    19  	query := url.Values{}
    20  	query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
    21  
    22  	urlStr := "/containers/" + containerID + "/archive"
    23  	response, err := cli.head(ctx, urlStr, query, nil)
    24  	defer ensureReaderClosed(response)
    25  	if err != nil {
    26  		return types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+path)
    27  	}
    28  	return getContainerPathStatFromHeader(response.header)
    29  }
    30  
    31  // CopyToContainer copies content into the container filesystem.
    32  // Note that `content` must be a Reader for a TAR archive
    33  func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options types.CopyToContainerOptions) error {
    34  	query := url.Values{}
    35  	query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API.
    36  	// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
    37  	if !options.AllowOverwriteDirWithFile {
    38  		query.Set("noOverwriteDirNonDir", "true")
    39  	}
    40  
    41  	if options.CopyUIDGID {
    42  		query.Set("copyUIDGID", "true")
    43  	}
    44  
    45  	apiPath := "/containers/" + containerID + "/archive"
    46  
    47  	response, err := cli.putRaw(ctx, apiPath, query, content, nil)
    48  	defer ensureReaderClosed(response)
    49  	if err != nil {
    50  		return wrapResponseError(err, response, "container:path", containerID+":"+dstPath)
    51  	}
    52  
    53  	// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
    54  	if response.statusCode != http.StatusOK {
    55  		return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
    56  	}
    57  
    58  	return nil
    59  }
    60  
    61  // CopyFromContainer gets the content from the container and returns it as a Reader
    62  // for a TAR archive to manipulate it in the host. It's up to the caller to close the reader.
    63  func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
    64  	query := make(url.Values, 1)
    65  	query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
    66  
    67  	apiPath := "/containers/" + containerID + "/archive"
    68  	response, err := cli.get(ctx, apiPath, query, nil)
    69  	if err != nil {
    70  		return nil, types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+srcPath)
    71  	}
    72  
    73  	// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
    74  	if response.statusCode != http.StatusOK {
    75  		return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
    76  	}
    77  
    78  	// In order to get the copy behavior right, we need to know information
    79  	// about both the source and the destination. The response headers include
    80  	// stat info about the source that we can use in deciding exactly how to
    81  	// copy it locally. Along with the stat info about the local destination,
    82  	// we have everything we need to handle the multiple possibilities there
    83  	// can be when copying a file/dir from one location to another file/dir.
    84  	stat, err := getContainerPathStatFromHeader(response.header)
    85  	if err != nil {
    86  		return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
    87  	}
    88  	return response.body, stat, err
    89  }
    90  
    91  func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
    92  	var stat types.ContainerPathStat
    93  
    94  	encodedStat := header.Get("X-Docker-Container-Path-Stat")
    95  	statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
    96  
    97  	err := json.NewDecoder(statDecoder).Decode(&stat)
    98  	if err != nil {
    99  		err = fmt.Errorf("unable to decode container path stat header: %s", err)
   100  	}
   101  
   102  	return stat, err
   103  }