diff --git a/cmd/nerdctl/compose/compose_up.go b/cmd/nerdctl/compose/compose_up.go index f4670947c3f..0cf2d6bccd2 100644 --- a/cmd/nerdctl/compose/compose_up.go +++ b/cmd/nerdctl/compose/compose_up.go @@ -50,6 +50,7 @@ func newComposeUpCommand() *cobra.Command { composeUpCommand.Flags().Bool("force-recreate", false, "Recreate containers even if their configuration and image haven't changed.") composeUpCommand.Flags().Bool("no-recreate", false, "Don't recreate containers if they exist, conflict with --force-recreate.") composeUpCommand.Flags().StringArray("scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.") + composeUpCommand.Flags().String("pull", "", "Pull image before running (\"always\"|\"missing\"|\"never\")") return composeUpCommand } @@ -96,6 +97,10 @@ func composeUpAction(cmd *cobra.Command, services []string) error { if err != nil { return err } + pull, err := cmd.Flags().GetString("pull") + if err != nil { + return err + } removeOrphans, err := cmd.Flags().GetBool("remove-orphans") if err != nil { return err @@ -154,6 +159,7 @@ func composeUpAction(cmd *cobra.Command, services []string) error { QuietPull: quietPull, RemoveOrphans: removeOrphans, Scale: scale, + Pull: pull, ForceRecreate: forceRecreate, NoRecreate: noRecreate, } diff --git a/cmd/nerdctl/compose/compose_up_linux_test.go b/cmd/nerdctl/compose/compose_up_linux_test.go index 6dbd6b7cf34..f25a6f087e1 100644 --- a/cmd/nerdctl/compose/compose_up_linux_test.go +++ b/cmd/nerdctl/compose/compose_up_linux_test.go @@ -579,3 +579,57 @@ services: } c.Assert(expected) } + +func TestComposeUpPull(t *testing.T) { + base := testutil.NewBase(t) + + var dockerComposeYAML = fmt.Sprintf(` +services: + test: + image: %s + command: sh -euxc "echo hi" +`, testutil.CommonImage) + + comp := testutil.NewComposeDir(t, dockerComposeYAML) + defer comp.CleanUp() + + // Cases where pull is required + for _, pull := range []string{"missing", "always"} { + t.Run(fmt.Sprintf("pull=%s", pull), func(t *testing.T) { + base.Cmd("rmi", "-f", testutil.CommonImage).Run() + base.Cmd("images").AssertOutNotContains(testutil.CommonImage) + t.Cleanup(func() { + base.ComposeCmd("-f", comp.YAMLFullPath(), "down").AssertOK() + }) + base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "--pull", pull).AssertOutContains("hi") + }) + } + + t.Run("pull=never, no pull", func(t *testing.T) { + base.Cmd("rmi", "-f", testutil.CommonImage).Run() + base.Cmd("images").AssertOutNotContains(testutil.CommonImage) + t.Cleanup(func() { + base.ComposeCmd("-f", comp.YAMLFullPath(), "down").AssertOK() + }) + base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "--pull", "never").AssertExitCode(1) + }) +} + +func TestComposeUpServiceUpServicePullPolicy(t *testing.T) { + base := testutil.NewBase(t) + + var dockerComposeYAML = fmt.Sprintf(` +services: + test: + image: %s + command: sh -euxc "echo hi" + pull_policy: "never" +`, testutil.CommonImage) + + comp := testutil.NewComposeDir(t, dockerComposeYAML) + defer comp.CleanUp() + + base.Cmd("rmi", "-f", testutil.CommonImage).Run() + base.Cmd("images").AssertOutNotContains(testutil.CommonImage) + base.ComposeCmd("-f", comp.YAMLFullPath(), "up").AssertExitCode(1) +} diff --git a/docs/command-reference.md b/docs/command-reference.md index 628a1e50b9e..42ec4eb706a 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -12,116 +12,117 @@ It does not necessarily mean that the corresponding features are missing in cont -- [Container management](#container-management) - - [:whale: :blue_square: nerdctl run](#whale-blue_square-nerdctl-run) - - [:whale: :blue_square: nerdctl exec](#whale-blue_square-nerdctl-exec) - - [:whale: :blue_square: nerdctl create](#whale-blue_square-nerdctl-create) - - [:whale: nerdctl cp](#whale-nerdctl-cp) - - [:whale: :blue_square: nerdctl ps](#whale-blue_square-nerdctl-ps) - - [:whale: :blue_square: nerdctl inspect](#whale-blue_square-nerdctl-inspect) - - [:whale: nerdctl logs](#whale-nerdctl-logs) - - [:whale: nerdctl port](#whale-nerdctl-port) - - [:whale: nerdctl rm](#whale-nerdctl-rm) - - [:whale: nerdctl stop](#whale-nerdctl-stop) - - [:whale: nerdctl start](#whale-nerdctl-start) - - [:whale: nerdctl restart](#whale-nerdctl-restart) - - [:whale: nerdctl update](#whale-nerdctl-update) - - [:whale: nerdctl wait](#whale-nerdctl-wait) - - [:whale: nerdctl kill](#whale-nerdctl-kill) - - [:whale: nerdctl pause](#whale-nerdctl-pause) - - [:whale: nerdctl unpause](#whale-nerdctl-unpause) - - [:whale: nerdctl rename](#whale-nerdctl-rename) - - [:whale: nerdctl attach](#whale-nerdctl-attach) - - [:whale: nerdctl container prune](#whale-nerdctl-container-prune) - - [:whale: nerdctl diff](#whale-nerdctl-diff) -- [Build](#build) - - [:whale: nerdctl build](#whale-nerdctl-build) - - [:whale: nerdctl commit](#whale-nerdctl-commit) -- [Image management](#image-management) - - [:whale: :blue_square: nerdctl images](#whale-blue_square-nerdctl-images) - - [:whale: :blue_square: nerdctl pull](#whale-blue_square-nerdctl-pull) - - [:whale: nerdctl push](#whale-nerdctl-push) - - [:whale: nerdctl load](#whale-nerdctl-load) - - [:whale: nerdctl save](#whale-nerdctl-save) - - [:whale: nerdctl tag](#whale-nerdctl-tag) - - [:whale: nerdctl rmi](#whale-nerdctl-rmi) - - [:whale: nerdctl image inspect](#whale-nerdctl-image-inspect) - - [:whale: nerdctl image history](#whale-nerdctl-image-history) - - [:whale: nerdctl image prune](#whale-nerdctl-image-prune) - - [:nerd_face: nerdctl image convert](#nerd_face-nerdctl-image-convert) - - [:nerd_face: nerdctl image encrypt](#nerd_face-nerdctl-image-encrypt) - - [:nerd_face: nerdctl image decrypt](#nerd_face-nerdctl-image-decrypt) -- [Registry](#registry) - - [:whale: nerdctl login](#whale-nerdctl-login) - - [:whale: nerdctl logout](#whale-nerdctl-logout) -- [Network management](#network-management) - - [:whale: nerdctl network create](#whale-nerdctl-network-create) - - [:whale: nerdctl network ls](#whale-nerdctl-network-ls) - - [:whale: nerdctl network inspect](#whale-nerdctl-network-inspect) - - [:whale: nerdctl network rm](#whale-nerdctl-network-rm) - - [:whale: nerdctl network prune](#whale-nerdctl-network-prune) -- [Volume management](#volume-management) - - [:whale: nerdctl volume create](#whale-nerdctl-volume-create) - - [:whale: nerdctl volume ls](#whale-nerdctl-volume-ls) - - [:whale: nerdctl volume inspect](#whale-nerdctl-volume-inspect) - - [:whale: nerdctl volume rm](#whale-nerdctl-volume-rm) - - [:whale: nerdctl volume prune](#whale-nerdctl-volume-prune) -- [Namespace management](#namespace-management) - - [:nerd_face: :blue_square: nerdctl namespace create](#nerd_face-blue_square-nerdctl-namespace-create) - - [:nerd_face: :blue_square: nerdctl namespace inspect](#nerd_face-blue_square-nerdctl-namespace-inspect) - - [:nerd_face: :blue_square: nerdctl namespace ls](#nerd_face-blue_square-nerdctl-namespace-ls) - - [:nerd_face: :blue_square: nerdctl namespace remove](#nerd_face-blue_square-nerdctl-namespace-remove) - - [:nerd_face: :blue_square: nerdctl namespace update](#nerd_face-blue_square-nerdctl-namespace-update) -- [AppArmor profile management](#apparmor-profile-management) - - [:nerd_face: nerdctl apparmor inspect](#nerd_face-nerdctl-apparmor-inspect) - - [:nerd_face: nerdctl apparmor load](#nerd_face-nerdctl-apparmor-load) - - [:nerd_face: nerdctl apparmor ls](#nerd_face-nerdctl-apparmor-ls) - - [:nerd_face: nerdctl apparmor unload](#nerd_face-nerdctl-apparmor-unload) -- [Builder management](#builder-management) - - [:whale: nerdctl builder prune](#whale-nerdctl-builder-prune) - - [:nerd_face: nerdctl builder debug](#nerd_face-nerdctl-builder-debug) -- [System](#system) - - [:whale: nerdctl events](#whale-nerdctl-events) - - [:whale: nerdctl info](#whale-nerdctl-info) - - [:whale: nerdctl version](#whale-nerdctl-version) - - [:whale: nerdctl system prune](#whale-nerdctl-system-prune) -- [Stats](#stats) - - [:whale: nerdctl stats](#whale-nerdctl-stats) - - [:whale: nerdctl top](#whale-nerdctl-top) -- [Shell completion](#shell-completion) - - [:nerd_face: nerdctl completion bash](#nerd_face-nerdctl-completion-bash) - - [:nerd_face: nerdctl completion zsh](#nerd_face-nerdctl-completion-zsh) - - [:nerd_face: nerdctl completion fish](#nerd_face-nerdctl-completion-fish) - - [:nerd_face: nerdctl completion powershell](#nerd_face-nerdctl-completion-powershell) -- [Compose](#compose) - - [:whale: nerdctl compose](#whale-nerdctl-compose) - - [:whale: nerdctl compose up](#whale-nerdctl-compose-up) - - [:whale: nerdctl compose logs](#whale-nerdctl-compose-logs) - - [:whale: nerdctl compose build](#whale-nerdctl-compose-build) - - [:whale: nerdctl compose create](#whale-nerdctl-compose-create) - - [:whale: nerdctl compose exec](#whale-nerdctl-compose-exec) - - [:whale: nerdctl compose down](#whale-nerdctl-compose-down) - - [:whale: nerdctl compose images](#whale-nerdctl-compose-images) - - [:whale: nerdctl compose start](#whale-nerdctl-compose-start) - - [:whale: nerdctl compose stop](#whale-nerdctl-compose-stop) - - [:whale: nerdctl compose port](#whale-nerdctl-compose-port) - - [:whale: nerdctl compose ps](#whale-nerdctl-compose-ps) - - [:whale: nerdctl compose pull](#whale-nerdctl-compose-pull) - - [:whale: nerdctl compose push](#whale-nerdctl-compose-push) - - [:whale: nerdctl compose pause](#whale-nerdctl-compose-pause) - - [:whale: nerdctl compose unpause](#whale-nerdctl-compose-unpause) - - [:whale: nerdctl compose config](#whale-nerdctl-compose-config) - - [:whale: nerdctl compose cp](#whale-nerdctl-compose-cp) - - [:whale: nerdctl compose kill](#whale-nerdctl-compose-kill) - - [:whale: nerdctl compose restart](#whale-nerdctl-compose-restart) - - [:whale: nerdctl compose rm](#whale-nerdctl-compose-rm) - - [:whale: nerdctl compose run](#whale-nerdctl-compose-run) - - [:whale: nerdctl compose top](#whale-nerdctl-compose-top) - - [:whale: nerdctl compose version](#whale-nerdctl-compose-version) -- [IPFS management](#ipfs-management) - - [:nerd_face: nerdctl ipfs registry serve](#nerd_face-nerdctl-ipfs-registry-serve) -- [Global flags](#global-flags) -- [Unimplemented Docker commands](#unimplemented-docker-commands) +- [Command reference](#command-reference) + - [Container management](#container-management) + - [:whale: :blue\_square: nerdctl run](#whale-blue_square-nerdctl-run) + - [:whale: :blue\_square: nerdctl exec](#whale-blue_square-nerdctl-exec) + - [:whale: :blue\_square: nerdctl create](#whale-blue_square-nerdctl-create) + - [:whale: nerdctl cp](#whale-nerdctl-cp) + - [:whale: :blue\_square: nerdctl ps](#whale-blue_square-nerdctl-ps) + - [:whale: :blue\_square: nerdctl inspect](#whale-blue_square-nerdctl-inspect) + - [:whale: nerdctl logs](#whale-nerdctl-logs) + - [:whale: nerdctl port](#whale-nerdctl-port) + - [:whale: nerdctl rm](#whale-nerdctl-rm) + - [:whale: nerdctl stop](#whale-nerdctl-stop) + - [:whale: nerdctl start](#whale-nerdctl-start) + - [:whale: nerdctl restart](#whale-nerdctl-restart) + - [:whale: nerdctl update](#whale-nerdctl-update) + - [:whale: nerdctl wait](#whale-nerdctl-wait) + - [:whale: nerdctl kill](#whale-nerdctl-kill) + - [:whale: nerdctl pause](#whale-nerdctl-pause) + - [:whale: nerdctl unpause](#whale-nerdctl-unpause) + - [:whale: nerdctl rename](#whale-nerdctl-rename) + - [:whale: nerdctl attach](#whale-nerdctl-attach) + - [:whale: nerdctl container prune](#whale-nerdctl-container-prune) + - [:whale: nerdctl diff](#whale-nerdctl-diff) + - [Build](#build) + - [:whale: nerdctl build](#whale-nerdctl-build) + - [:whale: nerdctl commit](#whale-nerdctl-commit) + - [Image management](#image-management) + - [:whale: :blue\_square: nerdctl images](#whale-blue_square-nerdctl-images) + - [:whale: :blue\_square: nerdctl pull](#whale-blue_square-nerdctl-pull) + - [:whale: nerdctl push](#whale-nerdctl-push) + - [:whale: nerdctl load](#whale-nerdctl-load) + - [:whale: nerdctl save](#whale-nerdctl-save) + - [:whale: nerdctl tag](#whale-nerdctl-tag) + - [:whale: nerdctl rmi](#whale-nerdctl-rmi) + - [:whale: nerdctl image inspect](#whale-nerdctl-image-inspect) + - [:whale: nerdctl image history](#whale-nerdctl-image-history) + - [:whale: nerdctl image prune](#whale-nerdctl-image-prune) + - [:nerd\_face: nerdctl image convert](#nerd_face-nerdctl-image-convert) + - [:nerd\_face: nerdctl image encrypt](#nerd_face-nerdctl-image-encrypt) + - [:nerd\_face: nerdctl image decrypt](#nerd_face-nerdctl-image-decrypt) + - [Registry](#registry) + - [:whale: nerdctl login](#whale-nerdctl-login) + - [:whale: nerdctl logout](#whale-nerdctl-logout) + - [Network management](#network-management) + - [:whale: nerdctl network create](#whale-nerdctl-network-create) + - [:whale: nerdctl network ls](#whale-nerdctl-network-ls) + - [:whale: nerdctl network inspect](#whale-nerdctl-network-inspect) + - [:whale: nerdctl network rm](#whale-nerdctl-network-rm) + - [:whale: nerdctl network prune](#whale-nerdctl-network-prune) + - [Volume management](#volume-management) + - [:whale: nerdctl volume create](#whale-nerdctl-volume-create) + - [:whale: nerdctl volume ls](#whale-nerdctl-volume-ls) + - [:whale: nerdctl volume inspect](#whale-nerdctl-volume-inspect) + - [:whale: nerdctl volume rm](#whale-nerdctl-volume-rm) + - [:whale: nerdctl volume prune](#whale-nerdctl-volume-prune) + - [Namespace management](#namespace-management) + - [:nerd\_face: :blue\_square: nerdctl namespace create](#nerd_face-blue_square-nerdctl-namespace-create) + - [:nerd\_face: :blue\_square: nerdctl namespace inspect](#nerd_face-blue_square-nerdctl-namespace-inspect) + - [:nerd\_face: :blue\_square: nerdctl namespace ls](#nerd_face-blue_square-nerdctl-namespace-ls) + - [:nerd\_face: :blue\_square: nerdctl namespace remove](#nerd_face-blue_square-nerdctl-namespace-remove) + - [:nerd\_face: :blue\_square: nerdctl namespace update](#nerd_face-blue_square-nerdctl-namespace-update) + - [AppArmor profile management](#apparmor-profile-management) + - [:nerd\_face: nerdctl apparmor inspect](#nerd_face-nerdctl-apparmor-inspect) + - [:nerd\_face: nerdctl apparmor load](#nerd_face-nerdctl-apparmor-load) + - [:nerd\_face: nerdctl apparmor ls](#nerd_face-nerdctl-apparmor-ls) + - [:nerd\_face: nerdctl apparmor unload](#nerd_face-nerdctl-apparmor-unload) + - [Builder management](#builder-management) + - [:whale: nerdctl builder prune](#whale-nerdctl-builder-prune) + - [:nerd\_face: nerdctl builder debug](#nerd_face-nerdctl-builder-debug) + - [System](#system) + - [:whale: nerdctl events](#whale-nerdctl-events) + - [:whale: nerdctl info](#whale-nerdctl-info) + - [:whale: nerdctl version](#whale-nerdctl-version) + - [:whale: nerdctl system prune](#whale-nerdctl-system-prune) + - [Stats](#stats) + - [:whale: nerdctl stats](#whale-nerdctl-stats) + - [:whale: nerdctl top](#whale-nerdctl-top) + - [Shell completion](#shell-completion) + - [:nerd\_face: nerdctl completion bash](#nerd_face-nerdctl-completion-bash) + - [:nerd\_face: nerdctl completion zsh](#nerd_face-nerdctl-completion-zsh) + - [:nerd\_face: nerdctl completion fish](#nerd_face-nerdctl-completion-fish) + - [:nerd\_face: nerdctl completion powershell](#nerd_face-nerdctl-completion-powershell) + - [Compose](#compose) + - [:whale: nerdctl compose](#whale-nerdctl-compose) + - [:whale: nerdctl compose up](#whale-nerdctl-compose-up) + - [:whale: nerdctl compose logs](#whale-nerdctl-compose-logs) + - [:whale: nerdctl compose build](#whale-nerdctl-compose-build) + - [:whale: nerdctl compose create](#whale-nerdctl-compose-create) + - [:whale: nerdctl compose exec](#whale-nerdctl-compose-exec) + - [:whale: nerdctl compose down](#whale-nerdctl-compose-down) + - [:whale: nerdctl compose images](#whale-nerdctl-compose-images) + - [:whale: nerdctl compose start](#whale-nerdctl-compose-start) + - [:whale: nerdctl compose stop](#whale-nerdctl-compose-stop) + - [:whale: nerdctl compose port](#whale-nerdctl-compose-port) + - [:whale: nerdctl compose ps](#whale-nerdctl-compose-ps) + - [:whale: nerdctl compose pull](#whale-nerdctl-compose-pull) + - [:whale: nerdctl compose push](#whale-nerdctl-compose-push) + - [:whale: nerdctl compose pause](#whale-nerdctl-compose-pause) + - [:whale: nerdctl compose unpause](#whale-nerdctl-compose-unpause) + - [:whale: nerdctl compose config](#whale-nerdctl-compose-config) + - [:whale: nerdctl compose cp](#whale-nerdctl-compose-cp) + - [:whale: nerdctl compose kill](#whale-nerdctl-compose-kill) + - [:whale: nerdctl compose restart](#whale-nerdctl-compose-restart) + - [:whale: nerdctl compose rm](#whale-nerdctl-compose-rm) + - [:whale: nerdctl compose run](#whale-nerdctl-compose-run) + - [:whale: nerdctl compose top](#whale-nerdctl-compose-top) + - [:whale: nerdctl compose version](#whale-nerdctl-compose-version) + - [IPFS management](#ipfs-management) + - [:nerd\_face: nerdctl ipfs registry serve](#nerd_face-nerdctl-ipfs-registry-serve) + - [Global flags](#global-flags) + - [Unimplemented Docker commands](#unimplemented-docker-commands) @@ -1413,6 +1414,8 @@ Flags: - :whale: `--remove-orphans`: Remove containers for services not defined in the Compose file - :whale: `--force-recreate`: force Compose to stop and recreate all containers - :whale: `--no-recreate`: force Compose to reuse existing containers +- :whale: `--no-recreate`: force Compose to reuse existing containers +- :whale: `--pull`: Pull image before running ("always"|"missing"|"never") Unimplemented `docker-compose up` (V1) flags: `--no-deps`, `--always-recreate-deps`, `--no-start`, `--abort-on-container-exit`, `--attach-dependencies`, `--timeout`, `--renew-anon-volumes`, `--exit-code-from` diff --git a/pkg/composer/create.go b/pkg/composer/create.go index 25d6872590b..a2ae9180160 100644 --- a/pkg/composer/create.go +++ b/pkg/composer/create.go @@ -119,7 +119,7 @@ func (c *Composer) Create(ctx context.Context, opt CreateOptions, services []str return err } for _, ps := range parsedServices { - if err := c.ensureServiceImage(ctx, ps, !opt.NoBuild, opt.Build, BuildOptions{}, false); err != nil { + if err := c.ensureServiceImage(ctx, ps, !opt.NoBuild, opt.Build, BuildOptions{}, false, ""); err != nil { return err } } diff --git a/pkg/composer/run.go b/pkg/composer/run.go index b2e98ea2b1d..0b3c4c72342 100644 --- a/pkg/composer/run.go +++ b/pkg/composer/run.go @@ -215,7 +215,7 @@ func (c *Composer) runServices(ctx context.Context, parsedServices []*servicepar // TODO: parallelize loop for ensuring images (make sure not to mess up tty) for _, ps := range parsedServices { - if err := c.ensureServiceImage(ctx, ps, !ro.NoBuild, ro.ForceBuild, BuildOptions{}, ro.QuietPull); err != nil { + if err := c.ensureServiceImage(ctx, ps, !ro.NoBuild, ro.ForceBuild, BuildOptions{}, ro.QuietPull, ""); err != nil { return err } } diff --git a/pkg/composer/up.go b/pkg/composer/up.go index b508b8ddc3d..98106c81ae4 100644 --- a/pkg/composer/up.go +++ b/pkg/composer/up.go @@ -42,6 +42,7 @@ type UpOptions struct { ForceRecreate bool NoRecreate bool Scale map[string]int // map of service name to replicas + Pull string } func (opts UpOptions) recreateStrategy() string { diff --git a/pkg/composer/up_service.go b/pkg/composer/up_service.go index 85010f25ce9..f0da7c9b72a 100644 --- a/pkg/composer/up_service.go +++ b/pkg/composer/up_service.go @@ -41,7 +41,7 @@ func (c *Composer) upServices(ctx context.Context, parsedServices []*servicepars // TODO: parallelize loop for ensuring images (make sure not to mess up tty) for _, ps := range parsedServices { - if err := c.ensureServiceImage(ctx, ps, !uo.NoBuild, uo.ForceBuild, BuildOptions{}, uo.QuietPull); err != nil { + if err := c.ensureServiceImage(ctx, ps, !uo.NoBuild, uo.ForceBuild, BuildOptions{}, uo.QuietPull, uo.Pull); err != nil { return err } } @@ -101,7 +101,7 @@ func (c *Composer) upServices(ctx context.Context, parsedServices []*servicepars return nil } -func (c *Composer) ensureServiceImage(ctx context.Context, ps *serviceparser.Service, allowBuild, forceBuild bool, bo BuildOptions, quiet bool) error { +func (c *Composer) ensureServiceImage(ctx context.Context, ps *serviceparser.Service, allowBuild, forceBuild bool, bo BuildOptions, quiet bool, pullModeArg string) error { if ps.Build != nil && allowBuild { if ps.Build.Force || forceBuild { return c.buildServiceImage(ctx, ps.Image, ps.Build, ps.Unparsed.Platform, bo) @@ -117,6 +117,9 @@ func (c *Composer) ensureServiceImage(ctx context.Context, ps *serviceparser.Ser } log.G(ctx).Infof("Ensuring image %s", ps.Image) + if pullModeArg != "" { + return c.EnsureImage(ctx, ps.Image, pullModeArg, ps.Unparsed.Platform, ps, quiet) + } return c.EnsureImage(ctx, ps.Image, ps.PullMode, ps.Unparsed.Platform, ps, quiet) }