45067cda33
The wrapResponseError() utility converted some specific errors, but in
doing so, could hide the actual error message returned by the daemon.
In addition, starting with 38e6d474af
,
HTTP status codes were already mapped to their corresponding errdefs
types on the client-side, making this conversion redundant.
This patch removes the wrapResponseError() utility; it's worth noting
that some error-messages will change slightly (as they now return the
error as returned by the daemon), but may cointain more details as
before, and in some cases prevents hiding the actual error.
Before this change:
docker container rm nosuchcontainer
Error: No such container: nosuchcontainer
docker container cp mycontainer:/no/such/path .
Error: No such container:path: mycontainer:/no/such/path
docker container cp ./Dockerfile mycontainer:/no/such/path
Error: No such container:path: mycontainer:/no/such
docker image rm nosuchimage
Error: No such image: nosuchimage
docker network rm nosuchnetwork
Error: No such network: nosuchnetwork
docker volume rm nosuchvolume
Error: No such volume: nosuchvolume
docker plugin rm nosuchplugin
Error: No such plugin: nosuchplugin
docker checkpoint rm nosuchcontainer nosuchcheckpoint
Error response from daemon: No such container: nosuchcontainer
docker checkpoint rm mycontainer nosuchcheckpoint
Error response from daemon: checkpoint nosuchcheckpoint does not exist for container mycontainer
docker service rm nosuchservice
Error: No such service: nosuchservice
docker node rm nosuchnode
Error: No such node: nosuchnode
docker config rm nosuschconfig
Error: No such config: nosuschconfig
docker secret rm nosuchsecret
Error: No such secret: nosuchsecret
After this change:
docker container rm nosuchcontainer
Error response from daemon: No such container: nosuchcontainer
docker container cp mycontainer:/no/such/path .
Error response from daemon: Could not find the file /no/such/path in container mycontainer
docker container cp ./Dockerfile mycontainer:/no/such/path
Error response from daemon: Could not find the file /no/such in container mycontainer
docker image rm nosuchimage
Error response from daemon: No such image: nosuchimage:latest
docker network rm nosuchnetwork
Error response from daemon: network nosuchnetwork not found
docker volume rm nosuchvolume
Error response from daemon: get nosuchvolume: no such volume
docker plugin rm nosuchplugin
Error response from daemon: plugin "nosuchplugin" not found
docker checkpoint rm nosuchcontainer nosuchcheckpoint
Error response from daemon: No such container: nosuchcontainer
docker checkpoint rm mycontainer nosuchcheckpoint
Error response from daemon: checkpoint nosuchcheckpoint does not exist for container mycontainer
docker service rm nosuchservice
Error response from daemon: service nosuchservice not found
docker node rm nosuchnode
Error response from daemon: node nosuchnode not found
docker config rm nosuchconfig
Error response from daemon: config nosuchconfig not found
docker secret rm nosuchsecret
Error response from daemon: secret nosuchsecret not found
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
103 lines
3.8 KiB
Go
103 lines
3.8 KiB
Go
package client // import "github.com/docker/docker/client"
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
)
|
|
|
|
// ContainerStatPath returns stat information about a path inside the container filesystem.
|
|
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.
|
|
|
|
urlStr := "/containers/" + containerID + "/archive"
|
|
response, err := cli.head(ctx, urlStr, query, nil)
|
|
defer ensureReaderClosed(response)
|
|
if err != nil {
|
|
return types.ContainerPathStat{}, err
|
|
}
|
|
return getContainerPathStatFromHeader(response.header)
|
|
}
|
|
|
|
// CopyToContainer copies content into the container filesystem.
|
|
// Note that `content` must be a Reader for a TAR archive
|
|
func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options types.CopyToContainerOptions) error {
|
|
query := url.Values{}
|
|
query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API.
|
|
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
|
|
if !options.AllowOverwriteDirWithFile {
|
|
query.Set("noOverwriteDirNonDir", "true")
|
|
}
|
|
|
|
if options.CopyUIDGID {
|
|
query.Set("copyUIDGID", "true")
|
|
}
|
|
|
|
apiPath := "/containers/" + containerID + "/archive"
|
|
|
|
response, err := cli.putRaw(ctx, apiPath, query, content, nil)
|
|
defer ensureReaderClosed(response)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
|
|
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
|
|
// for a TAR archive to manipulate it in the host. It's up to the caller to close the reader.
|
|
func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
|
query := make(url.Values, 1)
|
|
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
|
|
|
|
apiPath := "/containers/" + containerID + "/archive"
|
|
response, err := cli.get(ctx, apiPath, query, nil)
|
|
if err != nil {
|
|
return nil, types.ContainerPathStat{}, err
|
|
}
|
|
|
|
// TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior
|
|
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
|
|
}
|