Initial push
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
package dkr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/arescom/docker-migrate/internal/spec"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
)
|
||||
|
||||
// CopyOut streams the contents of a path inside a container as an uncompressed
|
||||
// tar archive.
|
||||
//
|
||||
// This is the single mechanism used for every kind of data location. It works
|
||||
// for named volumes, anonymous volumes and bind mounts alike, because the
|
||||
// daemon resolves the mount and produces the tar itself: no helper image is
|
||||
// needed, the container's own image needs no tar binary, and the container does
|
||||
// not have to be running.
|
||||
//
|
||||
// The archive entries are rooted at the last path segment, matching `docker cp`
|
||||
// semantics. Restoring therefore targets the parent directory; use RestorePath.
|
||||
func (c *Client) CopyOut(ctx context.Context, containerID, srcPath string) (io.ReadCloser, error) {
|
||||
rc, _, err := c.api.CopyFromContainer(ctx, containerID, srcPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %s from %s: %w", srcPath, short(containerID), err)
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
// CopyIn writes an uncompressed tar archive into a path inside a container.
|
||||
func (c *Client) CopyIn(ctx context.Context, containerID, dstPath string, r io.Reader) error {
|
||||
err := c.api.CopyToContainer(ctx, containerID, dstPath, r, container.CopyToContainerOptions{
|
||||
AllowOverwriteDirWithFile: false,
|
||||
CopyUIDGID: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("write %s into %s: %w", dstPath, short(containerID), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestorePath is the directory an archive produced by CopyOut must be extracted
|
||||
// into so that the contents land back at the original destination.
|
||||
func RestorePath(destination string) string {
|
||||
d := path.Dir(strings.TrimSuffix(destination, "/"))
|
||||
if d == "" || d == "." {
|
||||
return "/"
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// SaveImage streams `docker save` output for one or more image references.
|
||||
func (c *Client) SaveImage(ctx context.Context, refs ...string) (io.ReadCloser, error) {
|
||||
rc, err := c.api.ImageSave(ctx, refs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("save image %s: %w", strings.Join(refs, ","), err)
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
// ImageSizeBytes returns the on-disk size of an image, used to estimate how
|
||||
// long a streamed transfer will take.
|
||||
func (c *Client) ImageSizeBytes(ctx context.Context, ref string) int64 {
|
||||
insp, err := c.api.ImageInspect(ctx, ref)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return insp.Size
|
||||
}
|
||||
|
||||
// State returns the current status of a container, e.g. "running".
|
||||
func (c *Client) State(ctx context.Context, id string) (string, error) {
|
||||
j, err := c.api.ContainerInspect(ctx, id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if j.State == nil {
|
||||
return "", errors.New("no state in inspect payload")
|
||||
}
|
||||
return j.State.Status, nil
|
||||
}
|
||||
|
||||
// Stop stops a container and waits for it to settle. A container that is
|
||||
// already stopped is left alone.
|
||||
func (c *Client) Stop(ctx context.Context, id string, timeout time.Duration) error {
|
||||
st, err := c.State(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if st != "running" && st != "restarting" && st != "paused" {
|
||||
return nil
|
||||
}
|
||||
secs := int(timeout.Seconds())
|
||||
if err := c.api.ContainerStop(ctx, id, container.StopOptions{Timeout: &secs}); err != nil {
|
||||
return fmt.Errorf("stop %s: %w", short(id), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts a container.
|
||||
func (c *Client) Start(ctx context.Context, id string) error {
|
||||
if err := c.api.ContainerStart(ctx, id, container.StartOptions{}); err != nil {
|
||||
return fmt.Errorf("start %s: %w", short(id), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VolumeSizes measures every local volume in one daemon round trip. It can be
|
||||
// slow on hosts with a lot of data, so the UI asks for it explicitly rather
|
||||
// than including it in the inventory.
|
||||
func (c *Client) VolumeSizes(ctx context.Context) (map[string]int64, error) {
|
||||
du, err := c.api.DiskUsage(ctx, dockertypes.DiskUsageOptions{
|
||||
Types: []dockertypes.DiskUsageObject{dockertypes.VolumeObject},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("compute disk usage: %w", err)
|
||||
}
|
||||
out := map[string]int64{}
|
||||
for _, v := range du.Volumes {
|
||||
if v == nil || v.UsageData == nil {
|
||||
continue
|
||||
}
|
||||
out[v.Name] = v.UsageData.Size
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MeasureMounts fills in the SizeBytes of every mount it can determine.
|
||||
// Volume sizes come from the daemon; bind mount sizes are measured by walking
|
||||
// the path from inside the container, which works even when the daemon is
|
||||
// remote.
|
||||
func (c *Client) MeasureMounts(ctx context.Context, containers []spec.Container) error {
|
||||
sizes, err := c.VolumeSizes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range containers {
|
||||
for j := range containers[i].Mounts {
|
||||
m := &containers[i].Mounts[j]
|
||||
switch m.Kind {
|
||||
case spec.MountVolume, spec.MountAnonymous:
|
||||
if s, ok := sizes[m.Name]; ok {
|
||||
m.SizeBytes = s
|
||||
}
|
||||
case spec.MountTmpfs:
|
||||
m.SizeBytes = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ = image.InspectResponse{}
|
||||
Reference in New Issue
Block a user