package console_new 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 }