143 lines
4.0 KiB
Go
143 lines
4.0 KiB
Go
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||
|
|
||
|
package command
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"decl/internal/containerlog"
|
||
|
"github.com/docker/docker/api/types"
|
||
|
"github.com/docker/docker/api/types/strslice"
|
||
|
"github.com/docker/docker/api/types/container"
|
||
|
"github.com/docker/docker/api/types/filters"
|
||
|
"time"
|
||
|
"log/slog"
|
||
|
)
|
||
|
|
||
|
type ContainerExecClient interface {
|
||
|
ContainerExecAttach(ctx context.Context, execID string, config container.ExecAttachOptions) (types.HijackedResponse, error)
|
||
|
ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (types.IDResponse, error)
|
||
|
ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error)
|
||
|
ContainerExecStart(ctx context.Context, execID string, config container.ExecStartOptions) (error)
|
||
|
ContainerList(context.Context, container.ListOptions) ([]types.Container, error)
|
||
|
Close() error
|
||
|
}
|
||
|
|
||
|
type ContainerCommandProvider struct {
|
||
|
containerID string
|
||
|
execID string
|
||
|
ExitCode int
|
||
|
container.ExecOptions
|
||
|
response types.HijackedResponse
|
||
|
pipes *containerlog.StreamReader
|
||
|
Stdin io.Reader
|
||
|
apiClient ContainerExecClient `json:"-" yaml:"-"`
|
||
|
}
|
||
|
|
||
|
// ref could be a resource, but it just needs to implement the ExecResource interface
|
||
|
func NewContainerProvider(cmd *Command, value any) (p *ContainerCommandProvider) {
|
||
|
if args, err := cmd.Template(value); err == nil {
|
||
|
p = &ContainerCommandProvider {
|
||
|
ExecOptions: container.ExecOptions {
|
||
|
Cmd: strslice.StrSlice(append([]string{cmd.Path}, args...)),
|
||
|
AttachStdin: true,
|
||
|
AttachStdout: true,
|
||
|
AttachStderr: true,
|
||
|
},
|
||
|
}
|
||
|
p.containerID = p.ResolveId(context.Background(), cmd.TargetRef)
|
||
|
slog.Info("command.NewContainerProvider", "command", cmd.Path, "args", args, "target", cmd.TargetRef, "container", p.containerID)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *ContainerCommandProvider) ResolveId(ctx context.Context, ref CommandTargetRef) (containerID string) {
|
||
|
name := ref.Name()
|
||
|
filterArgs := filters.NewArgs()
|
||
|
filterArgs.Add("name", "/"+name)
|
||
|
containers, listErr := c.apiClient.ContainerList(ctx, container.ListOptions{
|
||
|
All: true,
|
||
|
Filters: filterArgs,
|
||
|
})
|
||
|
|
||
|
if listErr != nil {
|
||
|
panic(listErr)
|
||
|
}
|
||
|
|
||
|
for _, container := range containers {
|
||
|
for _, containerName := range container.Names {
|
||
|
if containerName == "/"+name {
|
||
|
containerID = container.ID
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *ContainerCommandProvider) Start() (err error) {
|
||
|
var execIDResponse types.IDResponse
|
||
|
ctx := context.Background()
|
||
|
if execIDResponse, err = c.apiClient.ContainerExecCreate(ctx, c.containerID, c.ExecOptions); err == nil {
|
||
|
c.execID = execIDResponse.ID
|
||
|
if c.execID == "" {
|
||
|
return fmt.Errorf("Failed creating a container exec ID")
|
||
|
}
|
||
|
|
||
|
execStartCheck := types.ExecStartCheck{
|
||
|
Tty: false,
|
||
|
}
|
||
|
|
||
|
if c.response, err = c.apiClient.ContainerExecAttach(ctx, c.execID, execStartCheck); err == nil {
|
||
|
c.pipes = containerlog.NewStreamReader(c.response.Conn)
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *ContainerCommandProvider) Wait() (err error) {
|
||
|
var containerDetails container.ExecInspect
|
||
|
ctx := context.Background()
|
||
|
for {
|
||
|
// copy Stdin to the connection
|
||
|
if c.Stdin != nil {
|
||
|
io.Copy(c.response.Conn, c.Stdin)
|
||
|
}
|
||
|
|
||
|
if containerDetails, err = c.apiClient.ContainerExecInspect(ctx, c.execID); err != nil || ! containerDetails.Running {
|
||
|
c.ExitCode = containerDetails.ExitCode
|
||
|
break
|
||
|
} else {
|
||
|
time.Sleep(500 * time.Millisecond)
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *ContainerCommandProvider) SetCmdEnv(env []string) {
|
||
|
c.Env = append(os.Environ(), env...)
|
||
|
}
|
||
|
|
||
|
func (c *ContainerCommandProvider) SetStdin(r io.Reader) {
|
||
|
c.Stdin = r
|
||
|
}
|
||
|
|
||
|
func (c *ContainerCommandProvider) StdinPipe() (io.WriteCloser, error) {
|
||
|
return c.response.Conn, nil
|
||
|
}
|
||
|
|
||
|
func (c *ContainerCommandProvider) StdoutPipe() (io.ReadCloser, error) {
|
||
|
return c.pipes.StdoutPipe(), nil
|
||
|
}
|
||
|
|
||
|
func (c *ContainerCommandProvider) StderrPipe() (io.ReadCloser, error) {
|
||
|
return c.pipes.StderrPipe(), nil
|
||
|
}
|
||
|
|
||
|
func (c *ContainerCommandProvider) Close() (err error) {
|
||
|
err = c.response.CloseWrite()
|
||
|
c.response.Close()
|
||
|
return
|
||
|
}
|