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{}