Skip to content

Commit 20dd3e5

Browse files
Only pull image on container create if the error matches
Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
1 parent b462778 commit 20dd3e5

File tree

3 files changed

+112
-7
lines changed

3 files changed

+112
-7
lines changed

cli/command/container/create.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,23 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
272272
if err != nil {
273273
// Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior.
274274
if errdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing {
275+
missing := reference.FamiliarString(namedRef)
276+
277+
errMessage := err.Error()
278+
errMsgMatch := regexp.MustCompile(`No such image:\s*(.*)$`)
279+
match := errMsgMatch.FindStringSubmatch(errMessage)
280+
281+
if len(match) == 2 {
282+
missing = match[1]
283+
}
284+
275285
if !options.quiet {
276286
// we don't want to write to stdout anything apart from container.ID
277-
fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
287+
fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", missing)
288+
}
289+
290+
if missing != reference.FamiliarString(namedRef) {
291+
return "", err
278292
}
279293

280294
if err := pullAndTagImage(); err != nil {

cli/command/container/create_test.go

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
109109
}, {
110110
PullPolicy: PullImageNever,
111111
ExpectedPulls: 0,
112-
ExpectedErrMsg: "error fake not found",
112+
ExpectedErrMsg: "No such image: does-not-exist-locally:latest",
113113
},
114114
}
115115
for _, tc := range cases {
@@ -127,7 +127,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
127127
defer func() { tc.ResponseCounter++ }()
128128
switch tc.ResponseCounter {
129129
case 0:
130-
return container.CreateResponse{}, fakeNotFound{}
130+
return container.CreateResponse{}, fakeNotFound{image: imageName + ":latest"}
131131
default:
132132
return container.CreateResponse{ID: containerID}, nil
133133
}
@@ -160,6 +160,87 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
160160
}
161161
}
162162

163+
func TestCreateContainerImagePullMissingValidate(t *testing.T) {
164+
const (
165+
containerID = "abcdef"
166+
)
167+
168+
cases := []struct {
169+
ContainerImage string
170+
MissingImage string
171+
ExpectedPulls int
172+
ExpectedErrMsg string
173+
}{
174+
{
175+
ContainerImage: "does-not-exist-locally",
176+
MissingImage: "does-not-exist-locally:latest",
177+
ExpectedPulls: 1,
178+
ExpectedErrMsg: "No such image: does-not-exist-locally:latest",
179+
}, {
180+
ContainerImage: "registry:5000/does-not-exist-locally",
181+
MissingImage: "registry:5000/does-not-exist-locally:latest",
182+
ExpectedPulls: 1,
183+
ExpectedErrMsg: "No such image: registry:5000/does-not-exist-locally",
184+
}, {
185+
ContainerImage: "registry:5000/does-not-exist-locally:tag",
186+
MissingImage: "registry:5000/does-not-exist-locally:tag",
187+
ExpectedPulls: 1,
188+
ExpectedErrMsg: "No such image: registry:5000/does-not-exist-locally:tag",
189+
}, {
190+
ContainerImage: "registry:5000/does-not-exist-locally@sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
191+
MissingImage: "registry:5000/does-not-exist-locally@sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
192+
ExpectedPulls: 1,
193+
ExpectedErrMsg: "No such image: registry:5000/does-not-exist-locally@sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
194+
}, {
195+
ContainerImage: "does-not-exist-locally",
196+
MissingImage: "some-other-image",
197+
ExpectedPulls: 0,
198+
ExpectedErrMsg: "No such image: some-other-image",
199+
},
200+
}
201+
for _, tc := range cases {
202+
t.Run(tc.MissingImage, func(t *testing.T) {
203+
pullCounter := 0
204+
205+
config := &containerConfig{
206+
Config: &container.Config{
207+
Image: tc.ContainerImage,
208+
},
209+
HostConfig: &container.HostConfig{},
210+
}
211+
212+
client := &fakeClient{
213+
createContainerFunc: func(
214+
config *container.Config,
215+
hostConfig *container.HostConfig,
216+
networkingConfig *network.NetworkingConfig,
217+
platform *specs.Platform,
218+
containerName string,
219+
) (container.CreateResponse, error) {
220+
return container.CreateResponse{}, fakeNotFound{image: tc.MissingImage}
221+
},
222+
imageCreateFunc: func(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
223+
defer func() { pullCounter++ }()
224+
return io.NopCloser(strings.NewReader("")), nil
225+
},
226+
infoFunc: func() (system.Info, error) {
227+
return system.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
228+
},
229+
}
230+
fakeCLI := test.NewFakeCli(client)
231+
_, err := createContainer(context.Background(), fakeCLI, config, &createOptions{
232+
name: "name",
233+
platform: runtime.GOOS,
234+
untrusted: true,
235+
pull: PullImageMissing,
236+
})
237+
238+
assert.Check(t, is.ErrorContains(err, tc.ExpectedErrMsg))
239+
assert.Check(t, is.Equal(tc.ExpectedPulls, pullCounter))
240+
})
241+
}
242+
}
243+
163244
func TestCreateContainerImagePullPolicyInvalid(t *testing.T) {
164245
cases := []struct {
165246
PullPolicy string
@@ -378,7 +459,17 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
378459
assert.NilError(t, err)
379460
}
380461

381-
type fakeNotFound struct{}
462+
type fakeNotFound struct {
463+
image string
464+
}
465+
466+
func (f fakeNotFound) NotFound() {}
467+
func (f fakeNotFound) Error() string {
468+
img := "fake"
382469

383-
func (f fakeNotFound) NotFound() {}
384-
func (f fakeNotFound) Error() string { return "error fake not found" }
470+
if f.image != "" {
471+
img = f.image
472+
}
473+
474+
return "No such image: " + img
475+
}

cli/command/container/run_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ func TestRunPullTermination(t *testing.T) {
235235
return container.CreateResponse{}, ctx.Err()
236236
default:
237237
}
238-
return container.CreateResponse{}, fakeNotFound{}
238+
return container.CreateResponse{}, fakeNotFound{image: "foobar:latest"}
239239
},
240240
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
241241
return types.HijackedResponse{}, errors.New("shouldn't try to attach to a container")

0 commit comments

Comments
 (0)