From 7b8d0f7f55d7a0a23a62d9b4d89cc3e7a4a18040 Mon Sep 17 00:00:00 2001 From: Manu Gupta Date: Mon, 9 Dec 2024 21:44:13 -0800 Subject: [PATCH] 3432: Support --pull flag in nerdctl compose up Signed-off-by: Manu Gupta --- cmd/nerdctl/compose/compose_up.go | 6 +++++ cmd/nerdctl/compose/compose_up_test.go | 35 ++++++++++++++++++++++++++ pkg/composer/create.go | 2 +- pkg/composer/run.go | 2 +- pkg/composer/up.go | 1 + pkg/composer/up_service.go | 7 ++++-- 6 files changed, 49 insertions(+), 4 deletions(-) diff --git a/cmd/nerdctl/compose/compose_up.go b/cmd/nerdctl/compose/compose_up.go index f4670947c3f..02b621d5679 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", "missing", "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_test.go b/cmd/nerdctl/compose/compose_up_test.go index 63bba829fcf..d703178d3ef 100644 --- a/cmd/nerdctl/compose/compose_up_test.go +++ b/cmd/nerdctl/compose/compose_up_test.go @@ -83,3 +83,38 @@ services: assert.NilError(t, err) assert.Equal(t, "hi\n", string(testB)) } + +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", 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", 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) + }) +} 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) }