// Run process insider a docker container package docker import ( "context" "errors" "fmt" "io" "net/netip" "os" "github.com/docker/docker/pkg/stdcopy" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/network" "github.com/moby/moby/client" "sirherobrine23.com.br/go-bds/exec/v2/process" ) // Register default client docker var _ = process.RegisterProcess(NewProc, "docker") type DockerContainer struct { DockerClient *client.Client ContainerName string Image string Platform string // Docker platform to run image Ports []network.Port Volumes []string containerID string statusExit *container.WaitResponse } // Return new docker exec func NewDocker(client *client.Client) *DockerContainer { return &DockerContainer{ Image: "debian:latest", DockerClient: client, Volumes: []string{}, Ports: []network.Port{}, } } // Create Docker client connection and return new DockerContainer with "debian:latest" image func NewProc() (*DockerContainer, error) { client, err := client.New(client.FromEnv) if err != nil { return nil, err } return NewDocker(client), nil } // Append port on start conteiner func (docker *DockerContainer) AddPort(networkType string, local, remote uint16) { switch networkType { case "udp", "udp4", "udp6": docker.Ports = append(docker.Ports, network.MustParsePort(fmt.Sprintf("%d:%d/udp", remote, local))) case "tcp", "tcp4", "tcp6": docker.Ports = append(docker.Ports, network.MustParsePort(fmt.Sprintf("%d:%d/tcp", remote, local))) default: docker.Ports = append(docker.Ports, network.MustParsePort(fmt.Sprintf("%d:%d", remote, local))) } } // Get container addresses func (docker DockerContainer) ContainerAddr() ([]netip.Addr, error) { if docker.containerID == "" { return nil, process.ErrNoProcess } // Get container info info, err := docker.DockerClient.ContainerInspect(context.Background(), docker.containerID, client.ContainerInspectOptions{}) if err != nil { return nil, err } // IP Addresses addr := []netip.Addr{} if info.Container.NetworkSettings != nil { if network := info.Container.NetworkSettings; len(info.Container.NetworkSettings.Networks) == 0 { for _, network := range network.Networks { if network.IPAddress.String() != "" { addr = append(addr, network.IPAddress) } if network.GlobalIPv6Address.String() != "" { addr = append(addr, network.GlobalIPv6Address) } } } } return addr, nil } func (docker *DockerContainer) Kill() error { if docker.containerID == "" { return process.ErrNoProcess } _, err := docker.DockerClient.ContainerKill(context.Background(), docker.containerID, client.ContainerKillOptions{Signal: "SIGKILL"}) return err } func (docker *DockerContainer) Wait() error { if docker.containerID == "" { return process.ErrNoProcess } wait := docker.DockerClient.ContainerWait(context.Background(), docker.containerID, client.ContainerWaitOptions{Condition: container.WaitConditionNextExit}) select { case err := <-wait.Error: if err != nil { return err } case status := <-wait.Result: docker.statusExit = &status if status.Error != nil { return errors.New(status.Error.Message) } else if status.StatusCode != 0 { return fmt.Errorf("exit code %d", status.StatusCode) } } return nil } func (docker *DockerContainer) ExitCode() (int, error) { if docker.statusExit != nil { return int(docker.statusExit.StatusCode), nil } else if docker.containerID == "" { return -1, process.ErrNoProcess } wait := docker.DockerClient.ContainerWait(context.Background(), docker.containerID, client.ContainerWaitOptions{Condition: container.WaitConditionNextExit}) select { case err := <-wait.Error: if err != nil { return -1, err } case status := <-wait.Result: docker.statusExit = &status if status.Error != nil { return -1, errors.New(status.Error.Message) } else if status.StatusCode != 0 { return -1, fmt.Errorf("exit code %d", status.StatusCode) } } return int(docker.statusExit.StatusCode), nil } func (docker *DockerContainer) Close() error { if docker.containerID == "" { return process.ErrNoProcess } else if _, err := docker.DockerClient.ContainerStop(context.Background(), docker.containerID, client.ContainerStopOptions{Signal: "SIGTERM"}); err != nil { return err } return docker.Wait() } func (docker *DockerContainer) Signal(signal os.Signal) error { if docker.containerID == "" { return process.ErrNoProcess } else if _, err := docker.DockerClient.ContainerStop(context.Background(), docker.containerID, client.ContainerStopOptions{Signal: signal.String()}); err != nil { return err } return nil } func (w *DockerContainer) AttachStdin(r io.Reader) error { if w.containerID != "" { hijackedResponse, err := w.DockerClient.ContainerAttach(context.Background(), w.containerID, client.ContainerAttachOptions{ Stream: true, Stdin: true, }) if err != nil { return err } go io.Copy(hijackedResponse.Conn, r) } return process.ErrNoProcess } func (w *DockerContainer) AttachStdout(w2 io.Writer) error { if w.containerID != "" { log, err := w.DockerClient.ContainerLogs(context.Background(), w.containerID, client.ContainerLogsOptions{Follow: true, ShowStdout: true, Tail: "5"}) if err != nil { return err } go stdcopy.StdCopy(w2, nil, log) } return process.ErrNoProcess } func (w *DockerContainer) AttachStderr(w2 io.Writer) error { if w.containerID != "" { log, err := w.DockerClient.ContainerLogs(context.Background(), w.containerID, client.ContainerLogsOptions{Follow: true, ShowStderr: true, Tail: "5"}) if err != nil { return err } go stdcopy.StdCopy(nil, w2, log) } return process.ErrNoProcess } func (docker *DockerContainer) Start(options *process.Exec) error { if docker.DockerClient == nil { var err error if docker.DockerClient, err = client.NewClientWithOpts(client.FromEnv); err != nil { return err } } // Return error if container started if docker.containerID != "" { return process.ErrNoCallStart } ctx := context.Background() dVolumes, dPorts := map[string]struct{}{}, network.PortSet{} for _, vol := range docker.Volumes { dVolumes[vol] = struct{}{} } for _, port := range docker.Ports { p, err := network.ParsePort(fmt.Sprintf("%s/%s", port.Port(), port.Proto())) if err != nil { return err } dPorts[p] = struct{}{} } config := container.Config{ AttachStdin: true, AttachStdout: true, AttachStderr: true, Tty: true, OpenStdin: true, Env: options.Environment.ToSlice(), Cmd: []string{}, Entrypoint: options.Arguments, Image: docker.Image, WorkingDir: options.Cwd, Volumes: dVolumes, ExposedPorts: dPorts, } host := container.HostConfig{ AutoRemove: true, Tmpfs: map[string]string{ "/tmp": "rw,nosuid", }, } containerCreated, err := docker.DockerClient.ContainerCreate(ctx, client.ContainerCreateOptions{ Config: &config, HostConfig: &host, Name: docker.ContainerName, }) if docker.containerID = containerCreated.ID; err != nil { return err } else if _, err = docker.DockerClient.ContainerStart(ctx, docker.containerID, client.ContainerStartOptions{}); err != nil { return err } if options.Context != nil { ctx := options.Context go func() { <-ctx.Done() docker.Signal(os.Interrupt) }() } return nil }