diff --git a/API.md b/API.md index 336902616bf5..4afa18b45d9e 100755 --- a/API.md +++ b/API.md @@ -1503,6 +1503,8 @@ publish [?[]string](#?[]string) publishAll [?bool](#?bool) +pull [?string](#?string) + quiet [?bool](#?bool) readonly [?bool](#?bool) diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 1e9092bd66bc..32478bb51da7 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -388,6 +388,10 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { "publish-all", "P", false, "Publish all exposed ports to random ports on the host interface", ) + createFlags.String( + "pull", "missing", + `Pull image before creating ("always"|"missing"|"never") (default "missing")`, + ) createFlags.BoolP( "quiet", "q", false, "Suppress output information when pulling images", diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go index 0eee51e79c72..53f133929b21 100644 --- a/cmd/podman/pull.go +++ b/cmd/podman/pull.go @@ -150,7 +150,7 @@ func pullCmd(c *cliconfig.PullValues) (retError error) { // See https://bugzilla.redhat.com/show_bug.cgi?id=1701922 for background // information. if !c.Bool("all-tags") { - newImage, err := runtime.New(getContext(), imgArg, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, true, nil) + newImage, err := runtime.New(getContext(), imgArg, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) if err != nil { return errors.Wrapf(err, "error pulling image %q", imgArg) } @@ -188,7 +188,7 @@ func pullCmd(c *cliconfig.PullValues) (retError error) { var foundIDs []string foundImage := true for _, name := range names { - newImage, err := runtime.New(getContext(), name, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, true, nil) + newImage, err := runtime.New(getContext(), name, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) if err != nil { logrus.Errorf("error pulling image %q", name) foundImage = false diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 1d35ac17b3b2..5122d37d1c77 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -732,7 +732,7 @@ func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtim registryCreds = creds } dockerRegistryOptions.DockerRegistryCreds = registryCreds - newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, false, &label) + newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, &label, util.PullImageMissing) } else { newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) } diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index e29e6b28e391..094330e245b6 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -83,7 +83,13 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod. } else { return nil, nil, errors.Errorf("error, no input arguments were provided") } - newImage, err := runtime.ImageRuntime().New(ctx, name, rtc.SignaturePolicyPath, GetAuthFile(c.String("authfile")), writer, nil, image.SigningOptions{}, false, nil) + + pullType, err := util.ValidatePullType(c.String("pull")) + if err != nil { + return nil, nil, err + } + + newImage, err := runtime.ImageRuntime().New(ctx, name, rtc.SignaturePolicyPath, GetAuthFile(c.String("authfile")), writer, nil, image.SigningOptions{}, nil, pullType) if err != nil { return nil, nil, err } diff --git a/cmd/podman/shared/intermediate.go b/cmd/podman/shared/intermediate.go index 3479876b4675..c6c32f8a980d 100644 --- a/cmd/podman/shared/intermediate.go +++ b/cmd/podman/shared/intermediate.go @@ -436,6 +436,7 @@ func NewIntermediateLayer(c *cliconfig.PodmanCommand, remote bool) GenericCLIRes m["privileged"] = newCRBool(c, "privileged") m["publish"] = newCRStringSlice(c, "publish") m["publish-all"] = newCRBool(c, "publish-all") + m["pull"] = newCRString(c, "pull") m["quiet"] = newCRBool(c, "quiet") m["read-only"] = newCRBool(c, "read-only") m["read-only-tmpfs"] = newCRBool(c, "read-only-tmpfs") diff --git a/cmd/podman/shared/intermediate_varlink.go b/cmd/podman/shared/intermediate_varlink.go index 4742d49094dd..9dbf839501e6 100644 --- a/cmd/podman/shared/intermediate_varlink.go +++ b/cmd/podman/shared/intermediate_varlink.go @@ -137,6 +137,7 @@ func (g GenericCLIResults) MakeVarlink() iopodman.Create { Privileged: BoolToPtr(g.Find("privileged")), Publish: StringSliceToPtr(g.Find("publish")), PublishAll: BoolToPtr(g.Find("publish-all")), + Pull: StringToPtr(g.Find("pull")), Quiet: BoolToPtr(g.Find("quiet")), Readonly: BoolToPtr(g.Find("read-only")), Readonlytmpfs: BoolToPtr(g.Find("read-only-tmpfs")), @@ -393,6 +394,7 @@ func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults { m["privileged"] = boolFromVarlink(opts.Privileged, "privileged", false) m["publish"] = stringSliceFromVarlink(opts.Publish, "publish", nil) m["publish-all"] = boolFromVarlink(opts.PublishAll, "publish-all", false) + m["pull"] = stringFromVarlink(opts.Pull, "missing", nil) m["quiet"] = boolFromVarlink(opts.Quiet, "quiet", false) m["read-only"] = boolFromVarlink(opts.Readonly, "read-only", false) m["read-only-tmpfs"] = boolFromVarlink(opts.Readonlytmpfs, "read-only-tmpfs", true) diff --git a/cmd/podman/sign.go b/cmd/podman/sign.go index 1333cf441cd6..de289047a975 100644 --- a/cmd/podman/sign.go +++ b/cmd/podman/sign.go @@ -15,6 +15,7 @@ import ( "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/trust" + "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -113,7 +114,7 @@ func signCmd(c *cliconfig.SignValues) error { if err != nil { return err } - newImage, err := runtime.ImageRuntime().New(getContext(), signimage, rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{SignBy: signby}, false, nil) + newImage, err := runtime.ImageRuntime().New(getContext(), signimage, rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{SignBy: signby}, nil, util.PullImageMissing) if err != nil { return errors.Wrapf(err, "error pulling image %s", signimage) } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 4a4c97e999c6..2e7dee94d792 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -347,6 +347,7 @@ type Create ( privileged: ?bool, publish: ?[]string, publishAll: ?bool, + pull: ?string, quiet: ?bool, readonly: ?bool, readonlytmpfs: ?bool, diff --git a/completions/bash/podman b/completions/bash/podman index cf9b31393b22..962c15a951b6 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1845,6 +1845,7 @@ _podman_container_run() { --pids-limit --pod --publish -p + --pull --runtime --rootfs --security-opt diff --git a/contrib/perftest/main.go b/contrib/perftest/main.go index 237f4f6e68c1..f6c90914a3d3 100644 --- a/contrib/perftest/main.go +++ b/contrib/perftest/main.go @@ -103,7 +103,7 @@ func main() { } fmt.Printf("image %s not found locally, fetching from remote registry..\n", *testImageName) - testImage, err = client.ImageRuntime().New(ctx, *testImageName, "", "", writer, &dockerRegistryOptions, image2.SigningOptions{}, false, nil) + testImage, err = client.ImageRuntime().New(ctx, *testImageName, "", "", writer, &dockerRegistryOptions, image2.SigningOptions{}, nil, util.PullImageMissing) if err != nil { logrus.Fatal(err) } diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 6fe224bd408f..1377f2a038b6 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -582,6 +582,15 @@ port to a random port on the host within an *ephemeral port range* defined by `/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host ports and the exposed ports, use `podman port`. +**--pull**=*missing* + +Pull image before creating ("always"|"missing"|"never") (default "missing"). + 'missing': default value, attempt to pull the latest image from the registries listed in registries.conf if a local image does not exist.Raise an error if the image is not in any listed registry and is not present locally. + 'always': Pull the image from the first registry it is found in as listed in registries.conf. Raise an error if not found in the registries, even if the image is present locally. + 'never': do not pull the image from the registry, use only the local version. Raise an error if the image is not present locally. + +Defaults to *missing*. + **--quiet**, **-q** Suppress output information when pulling images diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 4f6c3568b453..2445df5662d9 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -601,6 +601,15 @@ When using -P, podman will bind any exposed port to a random port on the host within an *ephemeral port range* defined by `/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host ports and the exposed ports, use `podman port`. +**--pull**=*missing* + +Pull image before running ("always"|"missing"|"never") (default "missing"). + 'missing': default value, attempt to pull the latest image from the registries listed in registries.conf if a local image does not exist.Raise an error if the image is not in any listed registry and is not present locally. + 'always': Pull the image from the first registry it is found in as listed in registries.conf. Raise an error if not found in the registries, even if the image is present locally. + 'never': do not pull the image from the registry, use only the local version. Raise an error if the image is not present locally. + +Defaults to *missing*. + **--quiet**, **-q** Suppress output information when pulling images diff --git a/libpod/image/image.go b/libpod/image/image.go index 068491f2823f..cb7c390c665c 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -135,7 +135,7 @@ func (ir *Runtime) NewFromLocal(name string) (*Image, error) { // New creates a new image object where the image could be local // or remote -func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *DockerRegistryOptions, signingoptions SigningOptions, forcePull bool, label *string) (*Image, error) { +func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *DockerRegistryOptions, signingoptions SigningOptions, label *string, pullType util.PullType) (*Image, error) { span, _ := opentracing.StartSpanFromContext(ctx, "newImage") span.SetTag("type", "runtime") defer span.Finish() @@ -145,11 +145,13 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile InputName: name, imageruntime: ir, } - if !forcePull { + if pullType != util.PullImageAlways { localImage, err := newImage.getLocalImage() if err == nil { newImage.image = localImage return &newImage, nil + } else if pullType == util.PullImageNever { + return nil, err } } diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index e93ebf797861..5a6d095f613d 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -3,12 +3,13 @@ package image import ( "context" "fmt" - "github.com/containers/libpod/libpod/events" "io" "io/ioutil" "os" "testing" + "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/util" "github.com/containers/storage" "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" @@ -89,9 +90,9 @@ func TestImage_NewFromLocal(t *testing.T) { ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) ir.Eventer = events.NewNullEventer() - bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, false, nil) + bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing) assert.NoError(t, err) - bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, false, nil) + bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing) assert.NoError(t, err) tm, err := makeLocalMatrix(bb, bbglibc) @@ -139,7 +140,7 @@ func TestImage_New(t *testing.T) { // Iterate over the names and delete the image // after the pull for _, img := range names { - newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, false, nil) + newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing) assert.NoError(t, err) assert.NotEqual(t, newImage.ID(), "") err = newImage.Remove(context.Background(), false) @@ -168,7 +169,7 @@ func TestImage_MatchRepoTag(t *testing.T) { ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) ir.Eventer = events.NewNullEventer() - newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, false, nil) + newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, nil, util.PullImageMissing) assert.NoError(t, err) err = newImage.TagImage("foo:latest") assert.NoError(t, err) diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index 24651099fb87..5387eb58756a 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -9,6 +9,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" "github.com/opencontainers/image-spec/specs-go/v1" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" @@ -111,7 +112,7 @@ func (r *Runtime) createInfraContainer(ctx context.Context, p *Pod) (*Container, return nil, define.ErrRuntimeStopped } - newImage, err := r.ImageRuntime().New(ctx, r.config.InfraImage, "", "", nil, nil, image.SigningOptions{}, false, nil) + newImage, err := r.ImageRuntime().New(ctx, r.config.InfraImage, "", "", nil, nil, image.SigningOptions{}, nil, util.PullImageMissing) if err != nil { return nil, err } diff --git a/pkg/adapter/checkpoint_restore.go b/pkg/adapter/checkpoint_restore.go index 1cac86d120a5..15f9e81056d1 100644 --- a/pkg/adapter/checkpoint_restore.go +++ b/pkg/adapter/checkpoint_restore.go @@ -11,6 +11,7 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/errorhandling" + "github.com/containers/libpod/pkg/util" "github.com/containers/storage/pkg/archive" jsoniter "github.com/json-iterator/go" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -112,7 +113,7 @@ func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri return nil, err } - _, err = runtime.ImageRuntime().New(ctx, config.RootfsImageName, rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false, nil) + _, err = runtime.ImageRuntime().New(ctx, config.RootfsImageName, rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, nil, util.PullImageMissing) if err != nil { return nil, err } diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index 2743dfdc6c26..1dd72babf2bd 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -19,6 +19,7 @@ import ( "github.com/containers/libpod/pkg/adapter/shortcuts" ns "github.com/containers/libpod/pkg/namespaces" createconfig "github.com/containers/libpod/pkg/spec" + "github.com/containers/libpod/pkg/util" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/ghodss/yaml" @@ -578,7 +579,7 @@ func (r *LocalRuntime) PlayKubeYAML(ctx context.Context, c *cliconfig.KubePlayVa } for _, container := range podYAML.Spec.Containers { - newImage, err := r.ImageRuntime().New(ctx, container.Image, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, false, nil) + newImage, err := r.ImageRuntime().New(ctx, container.Image, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageMissing) if err != nil { return nil, err } diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index c80d5991e803..ba988aaf74bc 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -24,6 +24,7 @@ import ( "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" "k8s.io/api/core/v1" @@ -132,8 +133,8 @@ func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef type } // New calls into local storage to look for an image in local storage or to pull it -func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, forcePull bool, label *string) (*ContainerImage, error) { - img, err := r.Runtime.ImageRuntime().New(ctx, name, signaturePolicyPath, authfile, writer, dockeroptions, signingoptions, forcePull, label) +func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, label *string, pullType util.PullType) (*ContainerImage, error) { + img, err := r.Runtime.ImageRuntime().New(ctx, name, signaturePolicyPath, authfile, writer, dockeroptions, signingoptions, label, pullType) if err != nil { return nil, err } diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 0cafbb2aa916..420c9d0bb495 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -26,6 +26,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" "github.com/opencontainers/go-digest" @@ -272,7 +273,7 @@ func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef type } // New calls into local storage to look for an image in local storage or to pull it -func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, forcePull bool, label *string) (*ContainerImage, error) { +func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, label *string, pullType util.PullType) (*ContainerImage, error) { var iid string if label != nil { return nil, errors.New("the remote client function does not support checking a remote image for a label") diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 520e41438ad6..3f73639e73d0 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -356,3 +356,32 @@ func OpenExclusiveFile(path string) (*os.File, error) { } return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) } + +// PullType whether to pull new image +type PullType int + +const ( + // PullImageAlways always try to pull new image when create or run + PullImageAlways PullType = iota + // PullImageMissing pulls image if it is not locally + PullImageMissing + // PullImageNever will never pull new image + PullImageNever +) + +// ValidatePullType check if the pullType from CLI is valid and returns the valid enum type +// if the value from CLI is invalid returns the error +func ValidatePullType(pullType string) (PullType, error) { + switch pullType { + case "always": + return PullImageAlways, nil + case "missing": + return PullImageMissing, nil + case "never": + return PullImageNever, nil + case "": + return PullImageMissing, nil + default: + return PullImageMissing, errors.Errorf("invalid pull type %q", pullType) + } +} diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index b5a711dfdaba..fe7f11b4d847 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -658,7 +658,7 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error { imageID = newImage[0].ID() } } else { - newImage, err := i.Runtime.ImageRuntime().New(getContext(), name, "", "", output, &dockerRegistryOptions, so, false, nil) + newImage, err := i.Runtime.ImageRuntime().New(getContext(), name, "", "", output, &dockerRegistryOptions, so, nil, util.PullImageMissing) if err != nil { foundError = true c <- errors.Wrapf(err, "unable to pull %s", name) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 25d0c339009d..2918cce78539 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -231,4 +231,14 @@ var _ = Describe("Podman create", func() { Expect(ctrJSON[0].Config.Cmd[0]).To(Equal("redis-server")) Expect(ctrJSON[0].Config.Entrypoint).To(Equal("docker-entrypoint.sh")) }) + + It("podman create --pull", func() { + session := podmanTest.PodmanNoCache([]string{"create", "--pull", "never", "--name=foo", "nginx"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + + session = podmanTest.PodmanNoCache([]string{"create", "--pull", "always", "--name=foo", "nginx"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To((Equal(0))) + }) })