116 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			116 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package console
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"log/slog"
 | |
| 
 | |
| 	"github.com/docker/docker/api/types/container"
 | |
| 	"github.com/docker/docker/api/types/network"
 | |
| 	"github.com/docker/docker/client"
 | |
| 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 | |
| )
 | |
| 
 | |
| type Container struct {
 | |
| 	client *client.Client
 | |
| 	id     string
 | |
| }
 | |
| 
 | |
| func (c Container) Start(ctx context.Context) error {
 | |
| 	return c.client.ContainerStart(ctx, c.id, container.StartOptions{})
 | |
| }
 | |
| 
 | |
| func (c Container) Close(ctx context.Context) error {
 | |
| 	timeout := 10
 | |
| 	if err := c.client.ContainerStop(ctx, c.id, container.StopOptions{
 | |
| 		Timeout: &timeout,
 | |
| 	}); err != nil {
 | |
| 		// If stop fails, force kill
 | |
| 		if err := c.client.ContainerKill(ctx, c.id, "SIGKILL"); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Remove container and volumes
 | |
| 	removeOptions := container.RemoveOptions{
 | |
| 		RemoveVolumes: true,
 | |
| 		Force:         true,
 | |
| 	}
 | |
| 
 | |
| 	return c.client.ContainerRemove(ctx, c.id, removeOptions)
 | |
| }
 | |
| 
 | |
| func (c Container) execUserCmd(ctx context.Context, user string, cmd string) (string, error) {
 | |
| 	slog.Info("executing command", "user", user, "cmd", cmd)
 | |
| 	var cmdStr = []string{"/bin/bash", "-c"}
 | |
| 
 | |
| 	cmdStr = append(cmdStr, cmd)
 | |
| 
 | |
| 	slog.Info("executing command", "user", user, "cmd", fmt.Sprintf("%#v", cmdStr))
 | |
| 	exec, err := c.client.ContainerExecCreate(ctx, c.id, container.ExecOptions{
 | |
| 		Cmd:          cmdStr,
 | |
| 		User:         user,
 | |
| 		AttachStdout: true,
 | |
| 		AttachStderr: true,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	resp, err := c.client.ContainerExecAttach(ctx, exec.ID, container.ExecAttachOptions{})
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	defer resp.Close()
 | |
| 
 | |
| 	output, err := io.ReadAll(resp.Reader)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	// Wait for command to finish and get exit code
 | |
| 	inspectResp, err := c.client.ContainerExecInspect(ctx, exec.ID)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	slog.Info("command finished", "output", string(output), "err", err, "resp", inspectResp)
 | |
| 
 | |
| 	if inspectResp.ExitCode != 0 {
 | |
| 		return string(output), fmt.Errorf("command exited with code %d", inspectResp.ExitCode)
 | |
| 	}
 | |
| 
 | |
| 	return string(output), nil
 | |
| }
 | |
| func (c Container) Execute(ctx context.Context, cmd string) (string, error) {
 | |
| 	return c.execUserCmd(ctx, "", cmd)
 | |
| }
 | |
| 
 | |
| func (c Container) ExecuteAs(ctx context.Context, user string, cmd string) (string, error) {
 | |
| 	return c.execUserCmd(ctx, user, cmd)
 | |
| }
 | |
| 
 | |
| func (c Container) Sudo(ctx context.Context, cmd string) (string, error) {
 | |
| 	return c.execUserCmd(ctx, "root", cmd)
 | |
| }
 | |
| 
 | |
| type ContainerConfig struct {
 | |
| 	Config     *container.Config
 | |
| 	HostConfig *container.HostConfig
 | |
| 	NetConfig  *network.NetworkingConfig
 | |
| 	Platform   *v1.Platform
 | |
| 	Name       string
 | |
| }
 | |
| 
 | |
| func CreateContainer(ctx context.Context, cl *client.Client, cfg ContainerConfig) (*Container, error) {
 | |
| 	resp, err := cl.ContainerCreate(ctx, cfg.Config, cfg.HostConfig, cfg.NetConfig, cfg.Platform, cfg.Name)
 | |
| 
 | |
| 	slog.Info("creating container", "resp", resp, "err", err)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &Container{client: cl, id: resp.ID}, nil
 | |
| }
 |