Refactor Exec Method to Enhance Container Execution and Output Handling
- Implemented a readiness check to ensure the container is running before executing commands, improving reliability. - Updated the method to create an exec instance and attach to it, allowing for better management of command execution. - Enhanced output handling by reading from the exec response, ensuring context-aware output retrieval. - Added logic to wait for exec completion and retrieve the exit code, improving feedback on command execution status.
This commit is contained in:
parent
08e7ed36a3
commit
1ece4b0f48
1 changed files with 75 additions and 14 deletions
|
|
@ -325,18 +325,62 @@ func (m *Manager) Exec(ctx context.Context, name string, cmd []string, opts *Exe
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, err := m.Stream(ctx, name, cmd, opts)
|
// Ensure container is running
|
||||||
if err != nil {
|
if err := m.ensureRunning(ctx, name); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
|
||||||
|
// Get container
|
||||||
|
c, ok := m.containers.Load(name)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrContainerNotFound
|
||||||
|
}
|
||||||
|
cont := c.(*Container)
|
||||||
|
|
||||||
|
// Update last used time
|
||||||
|
cont.LastUsedAt = time.Now()
|
||||||
|
|
||||||
|
// Default options
|
||||||
|
if opts.WorkDir == "" {
|
||||||
|
opts.WorkDir = "/workspace"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create exec instance
|
||||||
|
execConfig := container.ExecOptions{
|
||||||
|
Cmd: cmd,
|
||||||
|
WorkingDir: opts.WorkDir,
|
||||||
|
Env: mapToSlice(opts.Env),
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
AttachStdin: opts.Stdin != nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
execResp, err := m.dockerClient.ContainerExecCreate(ctx, cont.ID, execConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create exec: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach to exec
|
||||||
|
attachResp, err := m.dockerClient.ContainerExecAttach(ctx, execResp.ID, container.ExecStartOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to attach to exec: %w", err)
|
||||||
|
}
|
||||||
|
defer attachResp.Close()
|
||||||
|
|
||||||
|
// Handle stdin if provided
|
||||||
|
if opts.Stdin != nil {
|
||||||
|
go func() {
|
||||||
|
io.Copy(attachResp.Conn, opts.Stdin)
|
||||||
|
attachResp.CloseWrite()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// Read output with context awareness
|
// Read output with context awareness
|
||||||
outputCh := make(chan []byte, 1)
|
outputCh := make(chan []byte, 1)
|
||||||
errCh := make(chan error, 1)
|
errCh := make(chan error, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
output, err := io.ReadAll(reader)
|
output, err := io.ReadAll(attachResp.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
return
|
return
|
||||||
|
|
@ -344,23 +388,40 @@ func (m *Manager) Exec(ctx context.Context, name string, cmd []string, opts *Exe
|
||||||
outputCh <- output
|
outputCh <- output
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var output []byte
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
case err := <-errCh:
|
case err := <-errCh:
|
||||||
return nil, fmt.Errorf("failed to read output: %w", err)
|
return nil, fmt.Errorf("failed to read output: %w", err)
|
||||||
case output := <-outputCh:
|
case output = <-outputCh:
|
||||||
|
// Output received
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for exec to complete and get exit code
|
||||||
|
var exitCode int
|
||||||
|
for i := 0; i < 100; i++ { // Max 10 seconds wait
|
||||||
|
inspect, err := m.dockerClient.ContainerExecInspect(ctx, execResp.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to inspect exec: %w", err)
|
||||||
|
}
|
||||||
|
if !inspect.Running {
|
||||||
|
exitCode = inspect.ExitCode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
// Parse Docker multiplexed stream
|
// Parse Docker multiplexed stream
|
||||||
// TODO: Properly demux stdout/stderr from Docker stream
|
// TODO: Properly demux stdout/stderr from Docker stream
|
||||||
stdout := string(output)
|
stdout := string(output)
|
||||||
|
|
||||||
return &ExecResult{
|
return &ExecResult{
|
||||||
ExitCode: 0,
|
ExitCode: exitCode,
|
||||||
Stdout: stdout,
|
Stdout: stdout,
|
||||||
Stderr: "",
|
Stderr: "",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts a stopped container
|
// Start starts a stopped container
|
||||||
func (m *Manager) Start(ctx context.Context, name string) error {
|
func (m *Manager) Start(ctx context.Context, name string) error {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue