Signed-off-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
287 lines
7.3 KiB
Go
287 lines
7.3 KiB
Go
// Run process insider a docker container
|
|
package docker
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/netip"
|
|
"os"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/pkg/stdcopy"
|
|
"github.com/docker/go-connections/nat"
|
|
"sirherobrine23.com.br/go-bds/exec/exec"
|
|
)
|
|
|
|
// Register default client docker
|
|
var _ = exec.Register("docker", NewProc)
|
|
|
|
type DockerContainer struct {
|
|
DockerClient *client.Client
|
|
|
|
ContainerName string
|
|
Image string
|
|
Platform string // Docker platform to run image
|
|
Ports []nat.Port
|
|
Volumes []string
|
|
|
|
containerID string
|
|
statusExit *container.WaitResponse
|
|
}
|
|
|
|
// Return new docker exec
|
|
func NewDocker(client *client.Client) *DockerContainer {
|
|
return &DockerContainer{
|
|
DockerClient: client,
|
|
Image: "debian:latest",
|
|
Volumes: []string{},
|
|
Ports: []nat.Port{},
|
|
}
|
|
}
|
|
|
|
// Create Docker client connection and return new DockerContainer with "debian:latest" image
|
|
func NewProc() (*DockerContainer, error) {
|
|
client, err := client.NewClientWithOpts(client.FromEnv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewDocker(client), nil
|
|
}
|
|
|
|
// Append port on start conteiner
|
|
func (docker *DockerContainer) AddPort(network string, local, remote uint16) {
|
|
switch network {
|
|
case "udp", "udp4", "udp6":
|
|
docker.Ports = append(docker.Ports, nat.Port(fmt.Sprintf("%d:%d/udp", remote, local)))
|
|
case "tcp", "tcp4", "tcp6":
|
|
docker.Ports = append(docker.Ports, nat.Port(fmt.Sprintf("%d:%d/tcp", remote, local)))
|
|
default:
|
|
docker.Ports = append(docker.Ports, nat.Port(fmt.Sprintf("%d:%d", remote, local)))
|
|
}
|
|
}
|
|
|
|
// Get container addresses
|
|
func (docker DockerContainer) ContainerAddr() ([]netip.Addr, error) {
|
|
if docker.containerID == "" {
|
|
return nil, exec.ErrNoProcess
|
|
}
|
|
|
|
// Get container info
|
|
info, err := docker.DockerClient.ContainerInspect(context.Background(), docker.containerID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// IP Addresses
|
|
addr := []netip.Addr{}
|
|
if info.NetworkSettings != nil {
|
|
if network := info.NetworkSettings; len(info.NetworkSettings.Networks) == 0 {
|
|
if network.IPAddress != "" {
|
|
ipv4Addr, err := netip.ParseAddr(network.IPAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addr = append(addr, ipv4Addr)
|
|
}
|
|
|
|
if network.GlobalIPv6Address != "" {
|
|
ipv6Addr, err := netip.ParseAddr(network.GlobalIPv6Address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addr = append(addr, ipv6Addr)
|
|
}
|
|
}
|
|
|
|
for _, network := range info.NetworkSettings.Networks {
|
|
if network.IPAddress != "" {
|
|
ipv4Addr, err := netip.ParseAddr(network.IPAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addr = append(addr, ipv4Addr)
|
|
}
|
|
|
|
if network.GlobalIPv6Address != "" {
|
|
ipv6Addr, err := netip.ParseAddr(network.GlobalIPv6Address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addr = append(addr, ipv6Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
return addr, nil
|
|
}
|
|
|
|
func (docker *DockerContainer) Kill() error {
|
|
if docker.containerID == "" {
|
|
return exec.ErrNoProcess
|
|
}
|
|
return docker.DockerClient.ContainerKill(context.Background(), docker.containerID, "SIGKILL")
|
|
}
|
|
|
|
func (docker *DockerContainer) Wait() error {
|
|
if docker.containerID == "" {
|
|
return exec.ErrNoProcess
|
|
}
|
|
wait, err := docker.DockerClient.ContainerWait(context.Background(), docker.containerID, container.WaitConditionNextExit)
|
|
select {
|
|
case err := <-err:
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case status := <-wait:
|
|
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, exec.ErrNoProcess
|
|
}
|
|
|
|
wait, err := docker.DockerClient.ContainerWait(context.Background(), docker.containerID, container.WaitConditionNextExit)
|
|
select {
|
|
case err := <-err:
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
case status := <-wait:
|
|
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 exec.ErrNoProcess
|
|
} else if err := docker.DockerClient.ContainerStop(context.Background(), docker.containerID, container.StopOptions{Signal: "SIGTERM"}); err != nil {
|
|
return err
|
|
}
|
|
return docker.Wait()
|
|
}
|
|
|
|
func (docker *DockerContainer) Signal(signal os.Signal) error {
|
|
if docker.containerID == "" {
|
|
return exec.ErrNoProcess
|
|
} else if err := docker.DockerClient.ContainerStop(context.Background(), docker.containerID, container.StopOptions{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, container.AttachOptions{
|
|
Stream: true,
|
|
Stdin: true,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go io.Copy(hijackedResponse.Conn, r)
|
|
}
|
|
return exec.ErrNoProcess
|
|
}
|
|
func (w *DockerContainer) AttachStdout(w2 io.Writer) error {
|
|
if w.containerID != "" {
|
|
log, err := w.DockerClient.ContainerLogs(context.Background(), w.containerID, container.LogsOptions{Follow: true, ShowStdout: true, Tail: "5"})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go stdcopy.StdCopy(w2, nil, log)
|
|
}
|
|
return exec.ErrNoProcess
|
|
}
|
|
func (w *DockerContainer) AttachStderr(w2 io.Writer) error {
|
|
if w.containerID != "" {
|
|
log, err := w.DockerClient.ContainerLogs(context.Background(), w.containerID, container.LogsOptions{Follow: true, ShowStderr: true, Tail: "5"})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go stdcopy.StdCopy(nil, w2, log)
|
|
}
|
|
return exec.ErrNoProcess
|
|
}
|
|
|
|
func (docker *DockerContainer) Start(options *exec.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 exec.ErrNoCallStart
|
|
}
|
|
|
|
ctx := context.Background()
|
|
dVolumes, dPorts := map[string]struct{}{}, nat.PortSet{}
|
|
for _, vol := range docker.Volumes {
|
|
dVolumes[vol] = struct{}{}
|
|
}
|
|
for _, port := range docker.Ports {
|
|
dPorts[port] = 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, &config, &host, nil, nil, docker.ContainerName)
|
|
if docker.containerID = containerCreated.ID; err != nil {
|
|
return err
|
|
} else if err = docker.DockerClient.ContainerStart(ctx, docker.containerID, container.StartOptions{}); err != nil {
|
|
return err
|
|
}
|
|
|
|
if options.Context != nil {
|
|
ctx := options.Context
|
|
go func() {
|
|
<-ctx.Done()
|
|
docker.Signal(os.Interrupt)
|
|
}()
|
|
}
|
|
|
|
return nil
|
|
}
|