answer/pkg/agents/console/container.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
}