Skip to content

Commit 22cd418

Browse files
zmackiecpuguy83
authored andcommitted
Adds flag modifying pull behavior for running and creating containers
- Follows the proposal on issue [#34394](moby/moby#34394) - Maintains current behavior as default (Pull image if missing) - Adds tristate flag allowing modification (PullMissing, PullAlways, PullNever) Signed-off-by: Zander Mackie <zmackie@gmail.com>
1 parent 3273c2e commit 22cd418

3 files changed

Lines changed: 105 additions & 17 deletions

File tree

cli/command/container/create.go

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,18 @@ import (
2222
"github.com/spf13/pflag"
2323
)
2424

25+
// Pull constants
26+
const (
27+
PullImageAlways = "always"
28+
PullImageMissing = "missing" // Default (matches previous bahevior)
29+
PullImageNever = "never"
30+
)
31+
2532
type createOptions struct {
2633
name string
2734
platform string
2835
untrusted bool
36+
pull string // alway, missing, never
2937
}
3038

3139
// NewCreateCommand creates a new cobra.Command for `docker create`
@@ -50,6 +58,8 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
5058
flags.SetInterspersed(false)
5159

5260
flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
61+
flags.StringVar(&opts.pull, "pull", PullImageMissing,
62+
`Pull image before creating ("`+PullImageAlways+`"|"`+PullImageMissing+`"|"`+PullImageNever+`")`)
5363

5464
// Add an explicit help that doesn't have a `-h` to prevent the conflict
5565
// with hostname
@@ -175,6 +185,7 @@ func newCIDFile(path string) (*cidFile, error) {
175185
return &cidFile{path: path, file: f}, nil
176186
}
177187

188+
// nolint: gocyclo
178189
func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, opts *createOptions) (*container.ContainerCreateCreatedBody, error) {
179190
config := containerConfig.Config
180191
hostConfig := containerConfig.HostConfig
@@ -213,31 +224,59 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig
213224
}
214225

215226
//create the container
216-
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name)
227+
var response container.ContainerCreateCreatedBody
228+
if opts.pull == PullImageMissing { // Pull image only if it does not exist locally. Default.
229+
response, err = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name)
217230

218-
//if image not found try to pull it
219-
if err != nil {
220-
if apiclient.IsErrNotFound(err) && namedRef != nil {
221-
fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
231+
//if image not found try to pull it
232+
if err != nil {
233+
if apiclient.IsErrNotFound(err) && namedRef != nil {
234+
fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
222235

223-
// we don't want to write to stdout anything apart from container.ID
224-
if err := pullImage(ctx, dockerCli, config.Image, opts.platform, stderr); err != nil {
225-
return nil, err
226-
}
227-
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
228-
if err := image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef); err != nil {
236+
// we don't want to write to stdout anything apart from container.ID
237+
if err := pullImage(ctx, dockerCli, config.Image, opts.platform, stderr); err != nil {
229238
return nil, err
230239
}
240+
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
241+
if err := image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef); err != nil {
242+
return nil, err
243+
}
244+
}
245+
// Retry
246+
var retryErr error
247+
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name)
248+
if retryErr != nil {
249+
return nil, retryErr
250+
}
251+
} else {
252+
return nil, err
231253
}
232-
// Retry
233-
var retryErr error
234-
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name)
235-
if retryErr != nil {
236-
return nil, retryErr
254+
}
255+
256+
} else if opts.pull == PullImageAlways { // Always try and pull the image.
257+
if err := pullImage(ctx, dockerCli, config.Image, opts.platform, stderr); err != nil {
258+
return nil, err
259+
}
260+
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
261+
if err := image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef); err != nil {
262+
return nil, err
237263
}
238-
} else {
264+
}
265+
response, err = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name)
266+
if err != nil {
239267
return nil, err
240268
}
269+
270+
} else if opts.pull == PullImageNever { // Never try and pull the image
271+
response, err = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name)
272+
273+
if err != nil {
274+
if apiclient.IsErrNotFound(err) && namedRef != nil {
275+
fmt.Fprintf(stderr, "Unable to find image '%s' locally\nWill not pull due to '%s'", reference.FamiliarString(namedRef), opts.pull)
276+
}
277+
}
278+
} else { // We got something weird
279+
return nil, errors.Errorf("Unknown pull option : %s", opts.pull)
241280
}
242281

243282
for _, warning := range response.Warnings {

cli/command/container/create_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ func TestCreateContainerPullsImageIfMissing(t *testing.T) {
116116
name: "name",
117117
platform: runtime.GOOS,
118118
untrusted: true,
119+
pull: PullImageMissing,
119120
})
120121
assert.NilError(t, err)
121122
expected := container.ContainerCreateCreatedBody{ID: containerID}
@@ -124,6 +125,52 @@ func TestCreateContainerPullsImageIfMissing(t *testing.T) {
124125
assert.Check(t, is.Contains(stderr, "Unable to find image 'does-not-exist-locally:latest' locally"))
125126
}
126127

128+
func TestCreateContainerNeverPullsImage(t *testing.T) {
129+
imageName := "does-not-exist-locally"
130+
responseCounter := 0
131+
132+
client := &fakeClient{
133+
createContainerFunc: func(
134+
config *container.Config,
135+
hostConfig *container.HostConfig,
136+
networkingConfig *network.NetworkingConfig,
137+
containerName string,
138+
) (container.ContainerCreateCreatedBody, error) {
139+
defer func() { responseCounter++ }()
140+
switch responseCounter {
141+
case 0:
142+
return container.ContainerCreateCreatedBody{}, fakeNotFound{}
143+
default:
144+
return container.ContainerCreateCreatedBody{}, errors.New("unexpected")
145+
}
146+
},
147+
imageCreateFunc: func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
148+
return ioutil.NopCloser(strings.NewReader("")), nil
149+
},
150+
infoFunc: func() (types.Info, error) {
151+
return types.Info{IndexServerAddress: "http://indexserver"}, nil
152+
},
153+
}
154+
cli := test.NewFakeCli(client)
155+
config := &containerConfig{
156+
Config: &container.Config{
157+
Image: imageName,
158+
},
159+
HostConfig: &container.HostConfig{},
160+
}
161+
body, err := createContainer(context.Background(), cli, config, &createOptions{
162+
name: "name",
163+
platform: runtime.GOOS,
164+
untrusted: true,
165+
pull: PullImageNever,
166+
})
167+
assert.NilError(t, err)
168+
expected := container.ContainerCreateCreatedBody{}
169+
assert.Check(t, is.DeepEqual(expected, *body))
170+
stderr := cli.ErrBuffer().String()
171+
assert.Check(t, is.Contains(stderr, "Unable to find image 'does-not-exist-locally:latest' locally"))
172+
}
173+
127174
func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
128175
testCases := []struct {
129176
name string

cli/command/container/run.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
5656
flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process")
5757
flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
5858
flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
59+
flags.StringVar(&opts.createOptions.pull, "pull", PullImageMissing,
60+
`Pull image before running ("`+PullImageAlways+`"|"`+PullImageMissing+`"|"`+PullImageNever+`")`)
5961

6062
// Add an explicit help that doesn't have a `-h` to prevent the conflict
6163
// with hostname

0 commit comments

Comments
 (0)