func (d *docker) Copy(id string, files []*types.File) error {
	t, err := filesToTar(files)
	if err != nil {
		return fmt.Errorf("packing files to TAR archive: %w", err)
	}
	return d.cli.CopyToContainer(d.ctx, id, "/", t, dockertypes.CopyToContainerOptions{})
}
Copy takes map of files and their content and copies it to the container using TAR archive.

TODO Add support for base64 encoded content to support copying binary files.
package docker
import (
	"archive/tar"
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"time"
	dockertypes "github.com/docker/docker/api/types"
	containertypes "github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/mount"
	networktypes "github.com/docker/docker/api/types/network"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/flexkube/libflexkube/internal/util"
	"github.com/flexkube/libflexkube/pkg/container/runtime"
	"github.com/flexkube/libflexkube/pkg/container/types"
	"github.com/flexkube/libflexkube/pkg/defaults"
)
const (
	stopTimeout = 30 * time.Second
)
type Config struct {
	Host string `json:"host,omitempty"`
}
type dockerClient interface {
	ContainerCreate(
		ctx context.Context,
		config *containertypes.Config,
		hostConfig *containertypes.HostConfig,
		networkingConfig *networktypes.NetworkingConfig,
		platform *v1.Platform,
		containerName string,
	) (containertypes.ContainerCreateCreatedBody, error)
	ContainerStart(ctx context.Context, container string, options dockertypes.ContainerStartOptions) error
	ContainerStop(ctx context.Context, container string, timeout *time.Duration) error
	ContainerInspect(ctx context.Context, container string) (dockertypes.ContainerJSON, error)
	ContainerRemove(ctx context.Context, container string, options dockertypes.ContainerRemoveOptions) error
	CopyFromContainer(
		ctx context.Context,
		container,
		srcPath string,
	) (io.ReadCloser, dockertypes.ContainerPathStat, error)
	CopyToContainer(
		ctx context.Context,
		container,
		path string,
		content io.Reader,
		options dockertypes.CopyToContainerOptions,
	) error
	ContainerStatPath(ctx context.Context, container, path string) (dockertypes.ContainerPathStat, error)
	ImageList(ctx context.Context, options dockertypes.ImageListOptions) ([]dockertypes.ImageSummary, error)
	ImagePull(ctx context.Context, ref string, options dockertypes.ImagePullOptions) (io.ReadCloser, error)
}
type docker struct {
	ctx context.Context
	cli dockerClient
}
func (c *Config) SetAddress(s string) {
	c.Host = s
}
func (c *Config) GetAddress() string {
	if c != nil && c.Host != "" {
		return c.Host
	}
	return client.DefaultDockerHost
}
func (c *Config) New() (runtime.Runtime, error) {
	cli, err := c.getDockerClient()
	if err != nil {
		return nil, fmt.Errorf("creating Docker client: %w", err)
	}
	return &docker{
		ctx: context.Background(),
		cli: cli,
	}, nil
}
func (c *Config) getDockerClient() (*client.Client, error) {
	opts := []client.Opt{
		client.WithVersion(defaults.DockerAPIVersion),
	}
	if c != nil && c.Host != "" {
		opts = append(opts, client.WithHost(c.Host))
	}
	return client.NewClientWithOpts(opts...)
}
func (d *docker) pullImageIfNotPresent(image string) error {
	id, err := d.imageID(image)
	if err != nil {
		return fmt.Errorf("checking for image presence: %w", err)
	}
	if id != "" {
		return nil
	}
	return d.pullImage(image)
}
func buildPorts(ports []types.PortMap) (nat.PortMap, nat.PortSet, error) {
	portBindings := nat.PortMap{}
	exposedPorts := nat.PortSet{}
	for _, ip := range ports {
		port, err := nat.NewPort(ip.Protocol, strconv.Itoa(ip.Port))
		if err != nil {
			return nil, nil, fmt.Errorf("mapping ports: %w", err)
		}
		if _, exists := portBindings[port]; !exists {
			portBindings[port] = []nat.PortBinding{}
		}
		portBindings[port] = append(portBindings[port], nat.PortBinding{
			HostIP:   ip.IP,
			HostPort: strconv.Itoa(ip.Port),
		})
		exposedPorts[port] = struct{}{}
	}
	return portBindings, exposedPorts, nil
}
func mounts(m []types.Mount) []mount.Mount {
	mounts := []mount.Mount{}
	for _, m := range m {
		mounts = append(mounts, mount.Mount{
			Type:   "bind",
			Source: m.Source,
			Target: m.Target,
			BindOptions: &mount.BindOptions{
				Propagation: mount.Propagation(m.Propagation),
			},
		})
	}
	return mounts
}
func convertContainerConfig(config *types.ContainerConfig) (*containertypes.Config, *containertypes.HostConfig, error) {
	portBindings, exposedPorts, err := buildPorts(config.Ports)
	if err != nil {
		return nil, nil, fmt.Errorf("building ports: %w", err)
	}
	u := config.User
	if config.Group != "" {
		u = fmt.Sprintf("%s:%s", config.User, config.Group)
	}
	env := []string{}
	for k, v := range config.Env {
		env = append(env, fmt.Sprintf("%s=%s", k, v))
	}
	dockerConfig := containertypes.Config{
		Image:        config.Image,
		Cmd:          config.Args,
		Entrypoint:   config.Entrypoint,
		ExposedPorts: exposedPorts,
		User:         u,
		Env:          env,
	}
	hostConfig := containertypes.HostConfig{
		Mounts:       mounts(config.Mounts),
		PortBindings: portBindings,
		Privileged:   config.Privileged,
		NetworkMode:  containertypes.NetworkMode(config.NetworkMode),
		PidMode:      containertypes.PidMode(config.PidMode),
		IpcMode:      containertypes.IpcMode(config.IpcMode),
		RestartPolicy: containertypes.RestartPolicy{
			Name: "unless-stopped",
		},
	}
	return &dockerConfig, &hostConfig, nil
}
func (d *docker) Create(config *types.ContainerConfig) (string, error) {
	if err := d.pullImageIfNotPresent(config.Image); err != nil {
		return "", fmt.Errorf("pulling image: %w", err)
	}
	dockerConfig, hostConfig, err := convertContainerConfig(config)
	if err != nil {
		return "", fmt.Errorf("converting container config to Docker configuration: %w", err)
	}
	c, err := d.cli.ContainerCreate(d.ctx, dockerConfig, hostConfig, &networktypes.NetworkingConfig{}, nil, config.Name)
	if err != nil {
		return "", fmt.Errorf("creating container: %w", err)
	}
	return c.ID, nil
}
func (d *docker) Start(id string) error {
	return d.cli.ContainerStart(d.ctx, id, dockertypes.ContainerStartOptions{})
}
func (d *docker) Stop(id string) error {
	timeout := stopTimeout
	return d.cli.ContainerStop(d.ctx, id, &timeout)
}
func (d *docker) Status(id string) (types.ContainerStatus, error) {
	s := types.ContainerStatus{
		ID: id,
	}
	status, err := d.cli.ContainerInspect(d.ctx, id)
	if err != nil {
		if client.IsErrNotFound(err) {
			s.ID = ""
			return s, nil
		}
		return s, fmt.Errorf("inspecting container: %w", err)
	}
	s.Status = status.State.Status
	return s, nil
}
func (d *docker) Delete(id string) error {
	return d.cli.ContainerRemove(d.ctx, id, dockertypes.ContainerRemoveOptions{})
}