Skip to content

Commit 28411cf

Browse files
authored
Replace docker.Xxx helpers with functions on command.Command (#2288)
* DockerCommand.exec accepts io.Writer DockerCommand.exec will write the underlying command stdout and stderr to the provided io.Writer. This allows callers to either capture the entire output or allow long running commands to stream output to a writer that isn't os.Stdout or os.Stderr * upgrade docker/docker github.com/docker/docker needed an upgrade, but this wasn't compatible with a dependency of goreleaser, so I removed it from go tools * Move several docker CLI helpers to the docker interface and implementation Several helper functions in the `docker` package (`ImageInspect`, `ImageExists`, `ContainerInspect`, `ContainerLogsFollow`, and `Pull`) invisibly shelled out to the docker CLI, often with slightly different logic than was the `DockerCommand` struct was doing. This PR moves them to functions on the `DockerCommand` struct as well as adds them to the `command.Command` interface so they can be swapped out with the upcoming engine client. * add docker client integration tests test harness for running integration tests against an actual docker daemon and local registry * port docker.Stop to command.ContainerStop * force alpine test fixture to platform/amd64 * handle Pull errors from both dockerd & containerd different errors are returned when working with a docker registry and OCI registry. This maps both of them to the not found error * fix tests & lint * Pull returns the inspect response and has a force param There's a few places in the code where we check for a local image, and if not found begin a pull, then carry on. I had a bug that allowed pull failures to be swallowed and stale inspect responses to be passed to predict+train. This fixes the issue by ensuring that Pull returns the inspect response of the image that was just pulled, or to skip pulling if the image already exists using a force flag.
1 parent 9cd3059 commit 28411cf

25 files changed

+1090
-1277
lines changed

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ BINDIR = $(PREFIX)/bin
77
INSTALL := install -m 0755
88

99
GO ?= go
10-
GORELEASER := $(GO) tool goreleaser
10+
# GORELEASER := $(GO) tool goreleaser
11+
GORELEASER := $(GO) run github.com/goreleaser/goreleaser/v2@latest
1112
GOIMPORTS := $(GO) tool goimports
1213
GOLINT := $(GO) tool golangci-lint
1314

@@ -67,10 +68,11 @@ clean:
6768

6869
.PHONY: test-go
6970
test-go: pkg/dockerfile/embed/.wheel
70-
$(GO) tool gotestsum -- -timeout 1200s -parallel 5 ./... $(ARGS)
71+
$(GO) tool gotestsum -- -short -timeout 1200s -parallel 5 ./... $(ARGS)
7172

7273
.PHONY: test-integration
7374
test-integration: $(COG_BINARIES)
75+
$(GO) test ./pkg/docker/...
7476
PATH="$(PWD):$(PATH)" $(TOX) -e integration
7577

7678
.PHONY: test-python

go.mod

Lines changed: 62 additions & 266 deletions
Large diffs are not rendered by default.

go.sum

Lines changed: 123 additions & 785 deletions
Large diffs are not rendered by default.

pkg/cli/predict.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ the prediction on that.`,
6969
func cmdPredict(cmd *cobra.Command, args []string) error {
7070
ctx := cmd.Context()
7171

72+
dockerCommand := docker.NewDockerCommand()
73+
7274
imageName := ""
7375
volumes := []docker.Volume{}
7476
gpus := gpusFlag
@@ -112,17 +114,12 @@ func cmdPredict(cmd *cobra.Command, args []string) error {
112114
return fmt.Errorf("Invalid image name '%s'. Did you forget `-i`?", imageName)
113115
}
114116

115-
exists, err := docker.ImageExists(ctx, imageName)
117+
inspectResp, err := dockerCommand.Pull(ctx, imageName, false)
116118
if err != nil {
117-
return fmt.Errorf("Failed to determine if %s exists: %w", imageName, err)
118-
}
119-
if !exists {
120-
console.Infof("Pulling image: %s", imageName)
121-
if err := docker.Pull(ctx, imageName); err != nil {
122-
return fmt.Errorf("Failed to pull %s: %w", imageName, err)
123-
}
119+
return fmt.Errorf("Failed to pull image %q: %w", imageName, err)
124120
}
125-
conf, err := image.GetConfig(ctx, imageName)
121+
122+
conf, err := image.CogConfigFromManifest(ctx, inspectResp)
126123
if err != nil {
127124
return err
128125
}
@@ -136,7 +133,6 @@ func cmdPredict(cmd *cobra.Command, args []string) error {
136133

137134
console.Info("")
138135
console.Infof("Starting Docker image %s and running setup()...", imageName)
139-
dockerCommand := docker.NewDockerCommand()
140136

141137
predictor, err := predict.NewPredictor(ctx, docker.RunOptions{
142138
GPUs: gpus,

pkg/cli/train.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ Otherwise, it will build the model in the current directory and train it.`,
5555
func cmdTrain(cmd *cobra.Command, args []string) error {
5656
ctx := cmd.Context()
5757

58+
dockerCommand := docker.NewDockerCommand()
59+
5860
imageName := ""
5961
volumes := []docker.Volume{}
6062
gpus := gpusFlag
@@ -88,17 +90,12 @@ func cmdTrain(cmd *cobra.Command, args []string) error {
8890
// Use existing image
8991
imageName = args[0]
9092

91-
exists, err := docker.ImageExists(ctx, imageName)
93+
inspectResp, err := dockerCommand.Pull(ctx, imageName, false)
9294
if err != nil {
93-
return fmt.Errorf("Failed to determine if %s exists: %w", imageName, err)
94-
}
95-
if !exists {
96-
console.Infof("Pulling image: %s", imageName)
97-
if err := docker.Pull(ctx, imageName); err != nil {
98-
return fmt.Errorf("Failed to pull %s: %w", imageName, err)
99-
}
95+
return fmt.Errorf("Failed to pull image %q: %w", imageName, err)
10096
}
101-
conf, err := image.GetConfig(ctx, imageName)
97+
98+
conf, err := image.CogConfigFromManifest(ctx, inspectResp)
10299
if err != nil {
103100
return err
104101
}
@@ -112,7 +109,6 @@ func cmdTrain(cmd *cobra.Command, args []string) error {
112109

113110
console.Info("")
114111
console.Infof("Starting Docker image %s...", imageName)
115-
dockerCommand := docker.NewDockerCommand()
116112

117113
predictor, err := predict.NewPredictor(ctx, docker.RunOptions{
118114
GPUs: gpus,

pkg/docker/command/command.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
package command
22

3-
import "context"
3+
import (
4+
"context"
5+
"io"
6+
7+
"github.com/docker/docker/api/types/container"
8+
"github.com/docker/docker/api/types/image"
9+
)
410

511
type Command interface {
6-
Pull(ctx context.Context, ref string) error
12+
// Pull pulls an image from a remote registry and returns the inspect response for the local image.
13+
// If the image already exists, it will return the inspect response for the local image without pulling.
14+
// When force is true, it will always attempt to pull the image.
15+
Pull(ctx context.Context, ref string, force bool) (*image.InspectResponse, error)
716
Push(ctx context.Context, ref string) error
817
LoadUserInformation(ctx context.Context, registryHost string) (*UserInfo, error)
918
CreateTarFile(ctx context.Context, ref string, tmpDir string, tarFile string, folder string) (string, error)
1019
CreateAptTarFile(ctx context.Context, tmpDir string, aptTarFile string, packages ...string) (string, error)
11-
Inspect(ctx context.Context, ref string) (*Manifest, error)
20+
Inspect(ctx context.Context, ref string) (*image.InspectResponse, error)
21+
ImageExists(ctx context.Context, ref string) (bool, error)
22+
ContainerLogs(ctx context.Context, containerID string, w io.Writer) error
23+
ContainerInspect(ctx context.Context, id string) (*container.InspectResponse, error)
24+
ContainerStop(ctx context.Context, containerID string) error
1225
}

pkg/docker/command/errors.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package command
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
)
7+
8+
// NotFoundError represents “object <ref> wasn’t found” inside the Docker engine.
9+
type NotFoundError struct {
10+
// Ref is a unique identifier, such as an image reference, container ID, etc.
11+
Ref string
12+
// Object is the ref type, such as "container", "image", "volume", etc.
13+
Object string
14+
}
15+
16+
func (e *NotFoundError) Error() string {
17+
objType := e.Object
18+
if objType == "" {
19+
objType = "object"
20+
}
21+
return fmt.Sprintf("%s not found: %q", objType, e.Ref)
22+
}
23+
24+
func (e *NotFoundError) Is(target error) bool {
25+
_, ok := target.(*NotFoundError)
26+
return ok
27+
}
28+
29+
func IsNotFoundError(err error) bool {
30+
return errors.Is(err, &NotFoundError{})
31+
}

pkg/docker/container_inspect.go

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)