2018-02-05 16:05:59 -05:00
|
|
|
package client // import "github.com/docker/docker/client"
|
2016-09-06 14:46:37 -04:00
|
|
|
|
|
|
|
import (
|
2018-04-19 18:30:59 -04:00
|
|
|
"context"
|
2016-09-06 14:46:37 -04:00
|
|
|
"encoding/base64"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
)
|
|
|
|
|
2021-02-16 10:07:44 -05:00
|
|
|
// ContainerStatPath returns stat information about a path inside the container filesystem.
|
2016-09-06 14:46:37 -04:00
|
|
|
func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (types.ContainerPathStat, error) {
|
|
|
|
query := url.Values{}
|
|
|
|
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
|
|
|
|
|
2017-06-02 08:00:56 -04:00
|
|
|
urlStr := "/containers/" + containerID + "/archive"
|
2016-09-06 14:46:37 -04:00
|
|
|
response, err := cli.head(ctx, urlStr, query, nil)
|
2019-02-11 07:26:12 -05:00
|
|
|
defer ensureReaderClosed(response)
|
2016-09-06 14:46:37 -04:00
|
|
|
if err != nil {
|
2018-01-11 07:15:46 -05:00
|
|
|
return types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+path)
|
2016-09-06 14:46:37 -04:00
|
|
|
}
|
|
|
|
return getContainerPathStatFromHeader(response.header)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CopyToContainer copies content into the container filesystem.
|
2018-01-08 13:29:59 -05:00
|
|
|
// Note that `content` must be a Reader for a TAR archive
|
2018-01-11 07:15:46 -05:00
|
|
|
func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options types.CopyToContainerOptions) error {
|
2016-09-06 14:46:37 -04:00
|
|
|
query := url.Values{}
|
2018-01-11 07:15:46 -05:00
|
|
|
query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API.
|
2016-09-06 14:46:37 -04:00
|
|
|
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
|
|
|
|
if !options.AllowOverwriteDirWithFile {
|
|
|
|
query.Set("noOverwriteDirNonDir", "true")
|
|
|
|
}
|
|
|
|
|
2016-11-14 08:37:08 -05:00
|
|
|
if options.CopyUIDGID {
|
|
|
|
query.Set("copyUIDGID", "true")
|
|
|
|
}
|
|
|
|
|
2018-01-11 07:15:46 -05:00
|
|
|
apiPath := "/containers/" + containerID + "/archive"
|
2016-09-06 14:46:37 -04:00
|
|
|
|
|
|
|
response, err := cli.putRaw(ctx, apiPath, query, content, nil)
|
2019-02-11 07:26:12 -05:00
|
|
|
defer ensureReaderClosed(response)
|
2016-09-06 14:46:37 -04:00
|
|
|
if err != nil {
|
2018-01-11 07:15:46 -05:00
|
|
|
return wrapResponseError(err, response, "container:path", containerID+":"+dstPath)
|
2016-09-06 14:46:37 -04:00
|
|
|
}
|
|
|
|
|
2019-02-09 13:19:22 -05:00
|
|
|
// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
|
2016-09-06 14:46:37 -04:00
|
|
|
if response.statusCode != http.StatusOK {
|
|
|
|
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CopyFromContainer gets the content from the container and returns it as a Reader
|
2018-01-08 13:29:59 -05:00
|
|
|
// for a TAR archive to manipulate it in the host. It's up to the caller to close the reader.
|
2018-01-11 07:15:46 -05:00
|
|
|
func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
2016-09-06 14:46:37 -04:00
|
|
|
query := make(url.Values, 1)
|
|
|
|
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
|
|
|
|
|
2018-01-11 07:15:46 -05:00
|
|
|
apiPath := "/containers/" + containerID + "/archive"
|
2016-09-06 14:46:37 -04:00
|
|
|
response, err := cli.get(ctx, apiPath, query, nil)
|
|
|
|
if err != nil {
|
2018-01-11 07:15:46 -05:00
|
|
|
return nil, types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+srcPath)
|
2016-09-06 14:46:37 -04:00
|
|
|
}
|
|
|
|
|
2019-02-09 13:19:22 -05:00
|
|
|
// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
|
2016-09-06 14:46:37 -04:00
|
|
|
if response.statusCode != http.StatusOK {
|
|
|
|
return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// In order to get the copy behavior right, we need to know information
|
|
|
|
// about both the source and the destination. The response headers include
|
|
|
|
// stat info about the source that we can use in deciding exactly how to
|
|
|
|
// copy it locally. Along with the stat info about the local destination,
|
|
|
|
// we have everything we need to handle the multiple possibilities there
|
|
|
|
// can be when copying a file/dir from one location to another file/dir.
|
|
|
|
stat, err := getContainerPathStatFromHeader(response.header)
|
|
|
|
if err != nil {
|
|
|
|
return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
|
|
|
|
}
|
|
|
|
return response.body, stat, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
|
|
|
|
var stat types.ContainerPathStat
|
|
|
|
|
|
|
|
encodedStat := header.Get("X-Docker-Container-Path-Stat")
|
|
|
|
statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
|
|
|
|
|
|
|
|
err := json.NewDecoder(statDecoder).Decode(&stat)
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("unable to decode container path stat header: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return stat, err
|
|
|
|
}
|