diff --git a/cmd/nerdctl/compose/compose_build_linux_test.go b/cmd/nerdctl/compose/compose_build_linux_test.go index 79ef29a178b..5f864cc1ca8 100644 --- a/cmd/nerdctl/compose/compose_build_linux_test.go +++ b/cmd/nerdctl/compose/compose_build_linux_test.go @@ -39,6 +39,7 @@ func TestComposeBuild(t *testing.T) { // Make sure we shard the image name to something unique to the test to avoid conflicts with other tests imageSvc0 := data.Identifier("svc0") imageSvc1 := data.Identifier("svc1") + imageSvc2 := data.Identifier("svc2") // We are not going to run them, so, ports conflicts should not matter here dockerComposeYAML := fmt.Sprintf(` @@ -51,7 +52,13 @@ services: svc1: build: . image: %s -`, imageSvc0, imageSvc1) + svc2: + image: %s + build: + context: . + dockerfile_inline: | + FROM %s +`, imageSvc0, imageSvc1, imageSvc2, testutil.CommonImage) data.Temp().Save(dockerComposeYAML, "compose.yaml") data.Temp().Save(dockerfile, "Dockerfile") @@ -59,6 +66,7 @@ services: data.Labels().Set("composeYaml", data.Temp().Path("compose.yaml")) data.Labels().Set("imageSvc0", imageSvc0) data.Labels().Set("imageSvc1", imageSvc1) + data.Labels().Set("imageSvc2", imageSvc2) } testCase.SubTests = []*test.Case{ @@ -76,22 +84,41 @@ services: Output: expect.All( expect.Contains(data.Labels().Get("imageSvc0")), expect.DoesNotContain(data.Labels().Get("imageSvc1")), + expect.DoesNotContain(data.Labels().Get("imageSvc2")), + ), + } + }, + }, + { + Description: "build svc2", + NoParallel: true, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("compose", "-f", data.Labels().Get("composeYaml"), "build", "svc2") + }, + + Command: test.Command("images"), + + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: expect.All( + expect.Contains(data.Labels().Get("imageSvc2")), + expect.DoesNotContain(data.Labels().Get("imageSvc1")), ), } }, }, { - Description: "build svc0 and svc1", + Description: "build svc0, svc1, svc2", NoParallel: true, Setup: func(data test.Data, helpers test.Helpers) { - helpers.Ensure("compose", "-f", data.Labels().Get("composeYaml"), "build", "svc0", "svc1") + helpers.Ensure("compose", "-f", data.Labels().Get("composeYaml"), "build", "svc0", "svc1", "svc2") }, Command: test.Command("images"), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - Output: expect.Contains(data.Labels().Get("imageSvc0"), data.Labels().Get("imageSvc1")), + Output: expect.Contains(data.Labels().Get("imageSvc0"), data.Labels().Get("imageSvc1"), data.Labels().Get("imageSvc2")), } }, }, @@ -122,7 +149,7 @@ services: testCase.Cleanup = func(data test.Data, helpers test.Helpers) { if data.Labels().Get("imageSvc0") != "" { - helpers.Anyhow("rmi", data.Labels().Get("imageSvc0"), data.Labels().Get("imageSvc1")) + helpers.Anyhow("rmi", data.Labels().Get("imageSvc0"), data.Labels().Get("imageSvc1"), data.Labels().Get("imageSvc2")) } } diff --git a/pkg/composer/serviceparser/build.go b/pkg/composer/serviceparser/build.go index d797d0690ba..05290cc7a55 100644 --- a/pkg/composer/serviceparser/build.go +++ b/pkg/composer/serviceparser/build.go @@ -19,6 +19,7 @@ package serviceparser import ( "errors" "fmt" + "os" "path/filepath" "strings" @@ -34,7 +35,7 @@ import ( func parseBuildConfig(c *types.BuildConfig, project *types.Project, imageName string) (*Build, error) { if unknown := reflectutil.UnknownNonEmptyFields(c, - "Context", "Dockerfile", "Args", "CacheFrom", "Target", "Labels", "Secrets", "AdditionalContexts", + "Context", "Dockerfile", "Args", "CacheFrom", "Target", "Labels", "Secrets", "DockerfileInline", "AdditionalContexts", ); len(unknown) > 0 { log.L.Warnf("Ignoring: build: %+v", unknown) } @@ -86,6 +87,20 @@ func parseBuildConfig(c *types.BuildConfig, project *types.Project, imageName st } } + if c.DockerfileInline != "" { + // if DockerfileInline is specified, write it to a temporary file + // and use -f flag to use that docker file with project's ctxdir + tmpFile, err := os.CreateTemp("", fmt.Sprintf("%s.Dockerfile", imageName)) + if err != nil { + return nil, fmt.Errorf("failed to create temp file for DockerfileInline: %w", err) + } + defer tmpFile.Close() + if _, err := tmpFile.Write([]byte(c.DockerfileInline)); err != nil { + return nil, fmt.Errorf("failed to write DockerfileInline: %w", err) + } + b.BuildArgs = append(b.BuildArgs, "-f="+tmpFile.Name()) + } + for _, s := range c.Secrets { fileRef := types.FileReferenceConfig(s) diff --git a/pkg/composer/serviceparser/build_test.go b/pkg/composer/serviceparser/build_test.go index 34af7143aec..f71c2298e1e 100644 --- a/pkg/composer/serviceparser/build_test.go +++ b/pkg/composer/serviceparser/build_test.go @@ -17,7 +17,9 @@ package serviceparser import ( + "fmt" "runtime" + "strings" "testing" "gotest.tools/v3/assert" @@ -54,6 +56,12 @@ services: target: tgt_secret - simple_secret - absolute_secret + baz: + image: bazimg + build: + context: ./bazctx + dockerfile_inline: | + FROM random secrets: src_secret: file: test_secret1 @@ -95,4 +103,23 @@ secrets: assert.Assert(t, in(bar.Build.BuildArgs, "--secret=id=tgt_secret,src="+secretPath+"/test_secret1")) assert.Assert(t, in(bar.Build.BuildArgs, "--secret=id=simple_secret,src="+secretPath+"/test_secret2")) assert.Assert(t, in(bar.Build.BuildArgs, "--secret=id=absolute_secret,src=/tmp/absolute_secret")) + + bazSvc, err := project.GetService("baz") + assert.NilError(t, err) + + baz, err := Parse(project, bazSvc) + assert.NilError(t, err) + + t.Logf("baz: %+v", baz) + t.Logf("baz.Build.BuildArgs: %+v", baz.Build.BuildArgs) + // incase of dockerfile_inline, assert -f flag is overridden to point to a temporary file that ends with + // .Dockerfile + assert.Assert(t, func() bool { + for _, arg := range baz.Build.BuildArgs { + if strings.HasPrefix(arg, "-f=") && strings.Contains(arg, fmt.Sprintf("/%s.Dockerfile", baz.Image)) { + return true + } + } + return false + }()) }