(ctx context.Context, containerID string, cmd []string, in io.Reader)
| 458 | } |
| 459 | |
| 460 | func (t tarDockerClient) Exec(ctx context.Context, containerID string, cmd []string, in io.Reader) error { |
| 461 | execCreateResp, err := t.s.apiClient().ExecCreate(ctx, containerID, client.ExecCreateOptions{ |
| 462 | Cmd: cmd, |
| 463 | AttachStdout: false, |
| 464 | AttachStderr: true, |
| 465 | AttachStdin: in != nil, |
| 466 | TTY: false, |
| 467 | }) |
| 468 | if err != nil { |
| 469 | return err |
| 470 | } |
| 471 | |
| 472 | conn, err := t.s.apiClient().ExecAttach(ctx, execCreateResp.ID, client.ExecAttachOptions{ |
| 473 | TTY: false, |
| 474 | }) |
| 475 | if err != nil { |
| 476 | return err |
| 477 | } |
| 478 | defer conn.Close() |
| 479 | |
| 480 | var eg errgroup.Group |
| 481 | if in != nil { |
| 482 | eg.Go(func() error { |
| 483 | defer func() { |
| 484 | _ = conn.CloseWrite() |
| 485 | }() |
| 486 | _, err := io.Copy(conn.Conn, in) |
| 487 | return err |
| 488 | }) |
| 489 | } |
| 490 | eg.Go(func() error { |
| 491 | _, err := io.Copy(t.s.stdout(), conn.Reader) |
| 492 | return err |
| 493 | }) |
| 494 | |
| 495 | _, err = t.s.apiClient().ExecStart(ctx, execCreateResp.ID, client.ExecStartOptions{ |
| 496 | TTY: false, |
| 497 | Detach: false, |
| 498 | }) |
| 499 | if err != nil { |
| 500 | return err |
| 501 | } |
| 502 | |
| 503 | // although the errgroup is not tied directly to the context, the operations |
| 504 | // in it are reading/writing to the connection, which is tied to the context, |
| 505 | // so they won't block indefinitely |
| 506 | if err := eg.Wait(); err != nil { |
| 507 | return err |
| 508 | } |
| 509 | |
| 510 | execResult, err := t.s.apiClient().ExecInspect(ctx, execCreateResp.ID, client.ExecInspectOptions{}) |
| 511 | if err != nil { |
| 512 | return err |
| 513 | } |
| 514 | if execResult.Running { |
| 515 | return errors.New("process still running") |
| 516 | } |
| 517 | if execResult.ExitCode != 0 { |
nothing calls this directly
no test coverage detected