Skip to content

Commit e4c7473

Browse files
authored
Add support for local Docker images (#1114)
1 parent d0001e7 commit e4c7473

File tree

4 files changed

+53
-27
lines changed

4 files changed

+53
-27
lines changed

pkg/lib/configreader/validators.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"time"
2323

2424
"github.com/cortexlabs/cortex/pkg/lib/aws"
25+
"github.com/cortexlabs/cortex/pkg/lib/docker"
2526
"github.com/cortexlabs/cortex/pkg/lib/files"
2627
"github.com/cortexlabs/cortex/pkg/lib/urls"
2728
)
@@ -155,12 +156,7 @@ func ValidateImageVersion(image, cortexVersion string) (string, error) {
155156
return image, nil
156157
}
157158

158-
var tag string
159-
160-
if colonIndex := strings.LastIndex(image, ":"); colonIndex != -1 {
161-
tag = image[colonIndex+1:]
162-
}
163-
159+
tag := docker.ExtractImageTag(image)
164160
// in docker, missing tag implies "latest"
165161
if tag == "" {
166162
tag = "latest"

pkg/lib/docker/docker.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/cortexlabs/cortex/pkg/lib/exit"
3434
"github.com/cortexlabs/cortex/pkg/lib/parallel"
3535
"github.com/cortexlabs/cortex/pkg/lib/print"
36+
"github.com/cortexlabs/cortex/pkg/lib/slices"
3637
dockertypes "github.com/docker/docker/api/types"
3738
dockerclient "github.com/docker/docker/client"
3839
"github.com/docker/docker/pkg/jsonmessage"
@@ -142,17 +143,8 @@ func PullImage(image string, encodedAuthConfig string, pullVerbosity PullVerbosi
142143
return false, err
143144
}
144145

145-
existingImages, err := dockerClient.ImageList(context.Background(), dockertypes.ImageListOptions{})
146-
if err != nil {
147-
return false, WrapDockerError(err)
148-
}
149-
150-
for _, existingImage := range existingImages {
151-
for _, tag := range existingImage.RepoTags {
152-
if tag == image {
153-
return false, nil
154-
}
155-
}
146+
if err := CheckLocalImageAccessible(dockerClient, image); err == nil {
147+
return false, nil
156148
}
157149

158150
pullOutput, err := dockerClient.ImagePull(context.Background(), image, dockertypes.ImagePullOptions{
@@ -245,3 +237,29 @@ func CheckImageAccessible(dockerClient *Client, dockerImage, authConfig string)
245237
}
246238
return nil
247239
}
240+
241+
func CheckLocalImageAccessible(dockerClient *Client, dockerImage string) error {
242+
images, err := dockerClient.ImageList(context.Background(), dockertypes.ImageListOptions{})
243+
if err != nil {
244+
return WrapDockerError(err)
245+
}
246+
247+
// in docker, missing tag implies "latest"
248+
if ExtractImageTag(dockerImage) == "" {
249+
dockerImage = fmt.Sprintf("%s:latest", dockerImage)
250+
}
251+
252+
for _, image := range images {
253+
if slices.HasString(image.RepoTags, dockerImage) {
254+
return nil
255+
}
256+
}
257+
return ErrorImageInaccessible(dockerImage, nil)
258+
}
259+
260+
func ExtractImageTag(dockerImage string) string {
261+
if colonIndex := strings.LastIndex(dockerImage, ":"); colonIndex != -1 {
262+
return dockerImage[colonIndex+1:]
263+
}
264+
return ""
265+
}

pkg/lib/docker/errors.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,14 @@ func ErrorDockerPermissions(err error) error {
5757
}
5858

5959
func ErrorImageInaccessible(image string, cause error) error {
60-
dockerErrMsg := errors.Message(cause)
60+
message := fmt.Sprintf("%s is not accessible", image)
61+
if cause != nil {
62+
message += "\n" + errors.Message(cause) // add \n because docker client errors are
63+
}
64+
6165
return errors.WithStack(&errors.Error{
6266
Kind: ErrImageInaccessible,
63-
Message: fmt.Sprintf("%s is not accessible\n%s", image, dockerErrMsg), // add \n because docker client errors are verbose
67+
Message: message,
6468
Cause: cause,
6569
})
6670
}

pkg/types/spec/validations.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ func validatePredictor(predictor *userconfig.Predictor, projectFiles ProjectFile
471471
if err := validateTensorFlowPredictor(predictor, providerType, projectFiles, awsClient); err != nil {
472472
return err
473473
}
474-
if err := validateDockerImagePath(predictor.TensorFlowServingImage, awsClient); err != nil {
474+
if err := validateDockerImagePath(predictor.TensorFlowServingImage, providerType, awsClient); err != nil {
475475
return errors.Wrap(err, userconfig.TensorFlowServingImageKey)
476476
}
477477
case userconfig.ONNXPredictorType:
@@ -480,7 +480,7 @@ func validatePredictor(predictor *userconfig.Predictor, projectFiles ProjectFile
480480
}
481481
}
482482

483-
if err := validateDockerImagePath(predictor.Image, awsClient); err != nil {
483+
if err := validateDockerImagePath(predictor.Image, providerType, awsClient); err != nil {
484484
return errors.Wrap(err, userconfig.ImageKey)
485485
}
486486

@@ -828,14 +828,26 @@ func FindDuplicateNames(apis []userconfig.API) []userconfig.API {
828828
return nil
829829
}
830830

831-
func validateDockerImagePath(image string, awsClient *aws.Client) error {
831+
func validateDockerImagePath(image string, providerType types.ProviderType, awsClient *aws.Client) error {
832832
if consts.DefaultImagePathsSet.Has(image) {
833833
return nil
834834
}
835835
if _, err := cr.ValidateImageVersion(image, consts.CortexVersion); err != nil {
836836
return err
837837
}
838838

839+
dockerClient, err := docker.GetDockerClient()
840+
if err != nil {
841+
return err
842+
}
843+
844+
if providerType == types.LocalProviderType {
845+
// short circuit if the image is already available locally
846+
if err := docker.CheckLocalImageAccessible(dockerClient, image); err == nil {
847+
return nil
848+
}
849+
}
850+
839851
dockerAuth := docker.NoAuth
840852
if regex.IsValidECRURL(image) {
841853
if awsClient.IsAnonymous {
@@ -871,13 +883,9 @@ func validateDockerImagePath(image string, awsClient *aws.Client) error {
871883
}
872884
}
873885

874-
dockerClient, err := docker.GetDockerClient()
875-
if err != nil {
876-
return err
877-
}
878-
879886
if err := docker.CheckImageAccessible(dockerClient, image, dockerAuth); err != nil {
880887
return err
881888
}
889+
882890
return nil
883891
}

0 commit comments

Comments
 (0)