package tools import ( "context" "io" "net/netip" "strings" "time" "github.com/moby/go-archive" "github.com/moby/moby/api/pkg/stdcopy" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/network" "github.com/moby/term" "github.com/moby/moby/client" "github.com/moby/moby/client/pkg/jsonmessage" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) func Docker() *docker { ctx := context.Background() return &docker{ ctx: ctx, } } type ( docker struct { ctx context.Context } dockerImage struct { docker *docker auth string } dockerContainer struct { docker *docker id string name string config *container.Config hostconfig *container.HostConfig network *network.NetworkingConfig } ) func (t *docker) client() (*client.Client, error) { cli, err := client.New(client.FromEnv) if err != nil { return nil, err } return cli, nil } func (t *docker) ContainerList() ([]*dockerContainer, error) { cli, err := t.client() if err != nil { return nil, err } defer cli.Close() containers, err := cli.ContainerList(context.Background(), client.ContainerListOptions{All: true}) if err != nil { return nil, err } res := []*dockerContainer{} for _, ctr := range containers.Items { c, err := t.ContainerGetByID(ctr.ID) if err != nil { return nil, err } res = append(res, c) } return res, nil } func (t *docker) ContainerGetByID(id string) (*dockerContainer, error) { cli, err := t.client() if err != nil { return nil, err } defer cli.Close() c, err := cli.ContainerInspect(context.Background(), id, client.ContainerInspectOptions{}) if err != nil { return nil, err } return &dockerContainer{ docker: t, id: id, name: strings.TrimPrefix(c.Container.Name, "/"), config: c.Container.Config, hostconfig: c.Container.HostConfig, network: &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{}, }, }, nil } func (t *docker) ContainerGetByName(name string) (*dockerContainer, error) { cli, err := t.client() if err != nil { return nil, err } defer cli.Close() filters := client.Filters{} filters.Add("name", name) containers, err := cli.ContainerList(context.Background(), client.ContainerListOptions{ Filters: filters, }) if err != nil { return nil, err } var id string for _, ctr := range containers.Items { for _, n := range ctr.Names { if strings.TrimPrefix(n, "/") == name { id = ctr.ID } } } return t.ContainerGetByID(id) } func (t *docker) ContainerNew(name string) *dockerContainer { return &dockerContainer{ name: name, docker: t, config: &container.Config{ ExposedPorts: network.PortSet{}, }, hostconfig: &container.HostConfig{ AutoRemove: false, }, network: &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{}, }, } } func (t *dockerContainer) ID() string { return t.id } func (t *dockerContainer) Name() string { return t.name } func (t *dockerContainer) Config() *container.Config { return t.config } func (t *dockerContainer) HostConfig() *container.HostConfig { return t.hostconfig } func (t *dockerContainer) NetworkConfig() *network.NetworkingConfig { return t.network } func (t *dockerContainer) Exec() {} func (t *dockerContainer) Run(destOut io.Writer, destErr io.Writer, timeout time.Duration) (int64, error) { err := t.Create() if err != nil { return -1, err } err = t.Start() if err != nil { return -1, err } res, err := t.Wait(timeout) errs := t.Stop() if errs != nil { destErr.Write([]byte(errs.Error())) } errs = t.Logs(destOut, destErr) if errs != nil { destErr.Write([]byte(errs.Error())) } errs = t.Remove() if errs != nil { destErr.Write([]byte(errs.Error())) } return res, err } func (t *dockerContainer) Logs(destOut io.Writer, destErr io.Writer) error { cli, err := t.docker.client() if err != nil { return err } defer cli.Close() res, err := cli.ContainerLogs(t.docker.ctx, t.id, client.ContainerLogsOptions{ ShowStdout: true, ShowStderr: true, // Details: true, }) if err != nil { return err } defer res.Close() _, err = stdcopy.StdCopy(destOut, destErr, res) if err != nil { return err } return nil } func (t *dockerContainer) AddPort(containerPort, address, hostPort string) error { cp, err := network.ParsePort(containerPort) if err != nil { return err } addr, err := netip.ParseAddr(address) if err != nil { return err } if t.hostconfig.PortBindings == nil { t.hostconfig.PortBindings = network.PortMap{} } t.hostconfig.PortBindings[cp] = []network.PortBinding{ { HostPort: hostPort, HostIP: addr, }, } return nil } func (t *dockerContainer) State() (container.ContainerState, error) { cli, err := t.docker.client() if err != nil { return "", err } c, err := cli.ContainerInspect(context.Background(), t.id, client.ContainerInspectOptions{}) if err != nil { return "", err } return c.Container.State.Status, nil } func (t *dockerContainer) Wait(timeout time.Duration) (int64, error) { cli, err := t.docker.client() if err != nil { return -1, err } defer cli.Close() ctx, _ := context.WithTimeout(t.docker.ctx, timeout) wait := cli.ContainerWait(ctx, t.id, client.ContainerWaitOptions{}) select { case err := <-wait.Error: if err != nil { return -1, err } case res := <-wait.Result: return res.StatusCode, nil } return -1, nil } func (t *dockerContainer) Create() error { cli, err := t.docker.client() if err != nil { return err } defer cli.Close() create, err := cli.ContainerCreate(context.Background(), client.ContainerCreateOptions{ Name: t.name, Config: t.config, HostConfig: t.hostconfig, NetworkingConfig: t.network, Platform: &v1.Platform{}, }) if err != nil { return err } t.id = create.ID return nil } func (t *dockerContainer) Start() error { cli, err := t.docker.client() if err != nil { return err } defer cli.Close() _, err = cli.ContainerStart(t.docker.ctx, t.id, client.ContainerStartOptions{}) if err != nil { return err } return nil } func (t *dockerContainer) Stop() error { cli, err := t.docker.client() if err != nil { return err } defer cli.Close() _, err = cli.ContainerStop(t.docker.ctx, t.id, client.ContainerStopOptions{}) if err != nil { return err } return nil } func (t *dockerContainer) Remove() error { cli, err := t.docker.client() if err != nil { return err } defer cli.Close() _, err = cli.ContainerRemove(t.docker.ctx, t.id, client.ContainerRemoveOptions{ // RemoveLinks: true, RemoveVolumes: true, Force: true, }) if err != nil { return err } t.id = "" return nil } func (t *docker) Image() *dockerImage { return &dockerImage{ docker: t, } } // RegistryAuth is the base64 encoded credentials for the registry func (t *dockerImage) Auth(RegistryAuth string) { t.auth = RegistryAuth } func (t *dockerImage) Build(tag, path, dockerfile string) error { cli, err := t.docker.client() if err != nil { return err } defer cli.Close() tar, err := archive.TarWithOptions(path, &archive.TarOptions{}) if err != nil { return err } opts := client.ImageBuildOptions{ Dockerfile: dockerfile, Tags: []string{tag}, Remove: true, } buildResponse, err := cli.ImageBuild(t.docker.ctx, tar, opts) if err != nil { return err } defer buildResponse.Body.Close() if log != nil { fd, isTerminal := term.GetFdInfo(log) return jsonmessage.DisplayJSONMessagesStream(buildResponse.Body, log, fd, isTerminal, nil) } return nil } func (t *dockerImage) Tag(source, target string) error { cli, err := t.docker.client() if err != nil { return err } defer cli.Close() _, err = cli.ImageTag(t.docker.ctx, client.ImageTagOptions{ Source: source, Target: target, }) if err != nil { return err } return nil } func (t *dockerImage) Push(image string) error { cli, err := t.docker.client() if err != nil { return err } defer cli.Close() res, err := cli.ImagePush(t.docker.ctx, image, client.ImagePushOptions{ RegistryAuth: t.auth, }) if err != nil { return err } defer res.Close() if log != nil { fd, isTerminal := term.GetFdInfo(log) return jsonmessage.DisplayJSONMessagesStream(res, log, fd, isTerminal, nil) } return nil } func (t *dockerImage) Remove(image string) error { cli, err := t.docker.client() if err != nil { return err } defer cli.Close() _, err = cli.ImageRemove(t.docker.ctx, image, client.ImageRemoveOptions{}) if err != nil { return err } return nil } func (t *dockerImage) Pull(image string, timeout time.Duration) error { cli, err := t.docker.client() if err != nil { return err } defer cli.Close() ctx, _ := context.WithTimeout(t.docker.ctx, timeout) res, err := cli.ImagePull(ctx, image, client.ImagePullOptions{ RegistryAuth: t.auth, }) if err != nil { return err } defer res.Close() if log != nil { fd, isTerminal := term.GetFdInfo(log) return jsonmessage.DisplayJSONMessagesStream(res, log, fd, isTerminal, nil) } else { err = res.Wait(ctx) if err != nil { return err } } return nil }