From d8823f4b596fe49713d3d72d808f02e704404775 Mon Sep 17 00:00:00 2001 From: Aidan Delaney Date: Wed, 10 Aug 2022 16:57:17 +0100 Subject: [PATCH 1/4] Distribute binaries if requested Add BP_MAVEN_COMMAND and RunBuild toggle in the build plan to control whether to allow distribution-only. --- maven/build.go | 107 +++++++++++++++++++++---------------- maven/build_test.go | 61 +++++++++++++++++++++ maven/detect.go | 29 ++++++---- maven/distribution.go | 1 + maven/mvnd_distribution.go | 1 + 5 files changed, 143 insertions(+), 56 deletions(-) diff --git a/maven/build.go b/maven/build.go index 860fff2..4968664 100644 --- a/maven/build.go +++ b/maven/build.go @@ -38,6 +38,10 @@ import ( "github.com/paketo-buildpacks/libpak/bindings" ) +const ( + RunBuild = "RunBuild" +) + type Build struct { Logger bard.Logger ApplicationFactory ApplicationFactory @@ -69,8 +73,20 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { } dc.Logger = b.Logger + pr := libpak.PlanEntryResolver{ + Plan: context.Plan, + } + runBuild := true + entry, ok, err := pr.Resolve(PlanEntryMaven) + if ok && err == nil { + if runBuildValue, ok := entry.Metadata[RunBuild].(bool); ok { + runBuild = runBuildValue + } + } + + bpMavenCommand, _ := cr.Resolve(BpMavenCommand) command := "" - if cr.ResolveBool("BP_MAVEN_DAEMON_ENABLED") { + if cr.ResolveBool("BP_MAVEN_DAEMON_ENABLED") || bpMavenCommand == "mvnd" { dep, err := dr.Resolve("mvnd", "") if err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) @@ -84,7 +100,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { command = filepath.Join(context.Layers.Path, dist.Name(), "bin", "mvnd") } else { command = filepath.Join(context.Application.Path, "mvnw") - if _, err := os.Stat(command); os.IsNotExist(err) { + if _, err := os.Stat(command); os.IsNotExist(err) || bpMavenCommand == "mvn" { dep, err := dr.Resolve("maven", "") if err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) @@ -118,57 +134,58 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { c.Logger = b.Logger result.Layers = append(result.Layers, c) - args, err := libbs.ResolveArguments("BP_MAVEN_BUILD_ARGUMENTS", cr) - if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to resolve build arguments\n%w", err) - } + if runBuild { + args, err := libbs.ResolveArguments("BP_MAVEN_BUILD_ARGUMENTS", cr) + if err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve build arguments\n%w", err) + } - pomFile, userSet := cr.Resolve("BP_MAVEN_POM_FILE") - if userSet { - args = append([]string{"--file", pomFile}, args...) - } + pomFile, userSet := cr.Resolve("BP_MAVEN_POM_FILE") + if userSet { + args = append([]string{"--file", pomFile}, args...) + } - if !b.TTY && !contains(args, []string{"-B", "--batch-mode"}) { - // terminal is not tty, and the user did not set batch mode; let's set it - args = append([]string{"--batch-mode"}, args...) - } + if !b.TTY && !contains(args, []string{"-B", "--batch-mode"}) { + // terminal is not tty, and the user did not set batch mode; let's set it + args = append([]string{"--batch-mode"}, args...) + } - md := map[string]interface{}{} - if binding, ok, err := bindings.ResolveOne(context.Platform.Bindings, bindings.OfType("maven")); err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to resolve binding\n%w", err) - } else if ok { - args, err = handleMavenSettings(binding, args, md) - if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to process maven settings from binding\n%w", err) + md := map[string]interface{}{} + if binding, ok, err := bindings.ResolveOne(context.Platform.Bindings, bindings.OfType("maven")); err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve binding\n%w", err) + } else if ok { + args, err = handleMavenSettings(binding, args, md) + if err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to process maven settings from binding\n%w", err) + } } - } - art := libbs.ArtifactResolver{ - ArtifactConfigurationKey: "BP_MAVEN_BUILT_ARTIFACT", - ConfigurationResolver: cr, - ModuleConfigurationKey: "BP_MAVEN_BUILT_MODULE", - InterestingFileDetector: libbs.JARInterestingFileDetector{}, - } + art := libbs.ArtifactResolver{ + ArtifactConfigurationKey: "BP_MAVEN_BUILT_ARTIFACT", + ConfigurationResolver: cr, + ModuleConfigurationKey: "BP_MAVEN_BUILT_MODULE", + InterestingFileDetector: libbs.JARInterestingFileDetector{}, + } - bomScanner := sbom.NewSyftCLISBOMScanner(context.Layers, effect.NewExecutor(), b.Logger) + bomScanner := sbom.NewSyftCLISBOMScanner(context.Layers, effect.NewExecutor(), b.Logger) + + a, err := b.ApplicationFactory.NewApplication( + md, + args, + art, + c, + command, + result.BOM, + context.Application.Path, + bomScanner, + ) + if err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to create application layer\n%w", err) + } - a, err := b.ApplicationFactory.NewApplication( - md, - args, - art, - c, - command, - result.BOM, - context.Application.Path, - bomScanner, - ) - if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to create application layer\n%w", err) + a.Logger = b.Logger + result.Layers = append(result.Layers, a) } - - a.Logger = b.Logger - result.Layers = append(result.Layers, a) - return result, nil } diff --git a/maven/build_test.go b/maven/build_test.go index a612e6f..496e44a 100644 --- a/maven/build_test.go +++ b/maven/build_test.go @@ -448,6 +448,67 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { _, err := mavenBuild.Build(ctx) Expect(err).NotTo(HaveOccurred()) }) + + context("distribute binaries only", func() { + it.Before(func() { + entry := libcnb.BuildpackPlanEntry{ + Name: maven.PlanEntryMaven, + Metadata: map[string]interface{}{ + maven.RunBuild: false, + }, + } + ctx.Plan.Entries = append(ctx.Plan.Entries, entry) + }) + + it("contributes distribution for API 0.7+", func() { + ctx.Buildpack.Metadata["dependencies"] = []map[string]interface{}{ + { + "id": "maven", + "version": "1.1.1", + "stacks": []interface{}{"test-stack-id"}, + "cpes": []string{"cpe:2.3:a:apache:maven:3.8.3:*:*:*:*:*:*:*"}, + "purl": "pkg:generic/apache-maven@3.8.3", + }, + } + ctx.StackID = "test-stack-id" + + result, err := mavenBuild.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + + Expect(result.Layers).To(HaveLen(2)) + Expect(result.Layers[0].Name()).To(Equal("maven")) + Expect(result.Layers[1].Name()).To(Equal("cache")) + + Expect(result.BOM.Entries).To(HaveLen(1)) + Expect(result.BOM.Entries[0].Name).To(Equal("maven")) + Expect(result.BOM.Entries[0].Build).To(BeTrue()) + Expect(result.BOM.Entries[0].Launch).To(BeFalse()) + }) + + it("contributes distribution for API <=0.6", func() { + ctx.Buildpack.Metadata["dependencies"] = []map[string]interface{}{ + { + "id": "maven", + "version": "1.1.1", + "stacks": []interface{}{"test-stack-id"}, + }, + } + ctx.StackID = "test-stack-id" + ctx.Buildpack.API = "0.6" + + result, err := mavenBuild.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + + Expect(result.Layers).To(HaveLen(2)) + Expect(result.Layers[0].Name()).To(Equal("maven")) + Expect(result.Layers[1].Name()).To(Equal("cache")) + + Expect(result.BOM.Entries).To(HaveLen(1)) + Expect(result.BOM.Entries[0].Name).To(Equal("maven")) + Expect(result.BOM.Entries[0].Build).To(BeTrue()) + Expect(result.BOM.Entries[0].Launch).To(BeFalse()) + }) + }) } type FakeApplicationFactory struct{} diff --git a/maven/detect.go b/maven/detect.go index d259fb5..50832e1 100644 --- a/maven/detect.go +++ b/maven/detect.go @@ -32,6 +32,7 @@ const ( PlanEntryJVMApplicationPackage = "jvm-application-package" PlanEntryJDK = "jdk" PlanEntrySyft = "syft" + BpMavenCommand = "BP_MAVEN_COMMAND" ) type Detect struct{} @@ -43,16 +44,7 @@ func (Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) return libcnb.DetectResult{}, err } - pomFile, _ := cr.Resolve("BP_MAVEN_POM_FILE") - file := filepath.Join(context.Application.Path, pomFile) - _, err = os.Stat(file) - if os.IsNotExist(err) { - return libcnb.DetectResult{Pass: false}, nil - } else if err != nil { - return libcnb.DetectResult{}, fmt.Errorf("unable to determine if %s exists\n%w", file, err) - } - - return libcnb.DetectResult{ + result := libcnb.DetectResult{ Pass: true, Plans: []libcnb.BuildPlan{ { @@ -67,5 +59,20 @@ func (Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) }, }, }, - }, nil + } + + if binary, _ := cr.Resolve(BpMavenCommand); binary == "mvn" || binary == "mvnd" { + return result, nil + } + + pomFile, _ := cr.Resolve("BP_MAVEN_POM_FILE") + file := filepath.Join(context.Application.Path, pomFile) + _, err = os.Stat(file) + if os.IsNotExist(err) { + return libcnb.DetectResult{Pass: false}, nil + } else if err != nil { + return libcnb.DetectResult{}, fmt.Errorf("unable to determine if %s exists\n%w", file, err) + } + + return result, nil } diff --git a/maven/distribution.go b/maven/distribution.go index 7c085a5..73de645 100644 --- a/maven/distribution.go +++ b/maven/distribution.go @@ -33,6 +33,7 @@ type Distribution struct { func NewDistribution(dependency libpak.BuildpackDependency, cache libpak.DependencyCache) (Distribution, libcnb.BOMEntry) { contributor, entry := libpak.NewDependencyLayer(dependency, cache, libcnb.LayerTypes{ + Build: true, Cache: true, }) return Distribution{LayerContributor: contributor}, entry diff --git a/maven/mvnd_distribution.go b/maven/mvnd_distribution.go index e5c20e9..3b1ce72 100644 --- a/maven/mvnd_distribution.go +++ b/maven/mvnd_distribution.go @@ -33,6 +33,7 @@ type MvndDistribution struct { func NewMvndDistribution(dependency libpak.BuildpackDependency, cache libpak.DependencyCache) (MvndDistribution, libcnb.BOMEntry) { contributor, entry := libpak.NewDependencyLayer(dependency, cache, libcnb.LayerTypes{ + Build: true, Cache: true, }) return MvndDistribution{LayerContributor: contributor}, entry From 9df261ed94795522b55671d7314ea0c237baecf4 Mon Sep 17 00:00:00 2001 From: Aidan Delaney Date: Fri, 12 Aug 2022 09:19:14 +0100 Subject: [PATCH 2/4] Factor install out of Build Provide install functionality that can be re-used by Build. --- buildpack.toml | 6 ++ maven/build.go | 131 ++++++++++++++++++++++++++++---------------- maven/build_test.go | 95 ++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+), 48 deletions(-) diff --git a/buildpack.toml b/buildpack.toml index 68d0477..66b679b 100644 --- a/buildpack.toml +++ b/buildpack.toml @@ -60,6 +60,12 @@ api = "0.7" default = "false" description = "use maven daemon" name = "BP_MAVEN_DAEMON_ENABLED" + + [[metadata.configurations]] + build = true + default = "false" + description = "distribute maven binary" + name = "BP_MAVEN_COMMAND" [[metadata.dependencies]] cpes = ["cpe:2.3:a:apache:maven:3.8.6:*:*:*:*:*:*:*"] diff --git a/maven/build.go b/maven/build.go index 4968664..86a5293 100644 --- a/maven/build.go +++ b/maven/build.go @@ -42,6 +42,13 @@ const ( RunBuild = "RunBuild" ) +var ( + artifacts = map[string]string{ + "mvn": "maven", + "mvnd": "mvnd", + } +) + type Build struct { Logger bard.Logger ApplicationFactory ApplicationFactory @@ -53,26 +60,39 @@ type ApplicationFactory interface { cache libbs.Cache, command string, bom *libcnb.BOM, applicationPath string, bomScanner sbom.SBOMScanner) (libbs.Application, error) } -func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { - b.Logger.Title(context.Buildpack) - result := libcnb.NewBuildResult() - - cr, err := libpak.NewConfigurationResolver(context.Buildpack, &b.Logger) - if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to create configuration resolver\n%w", err) - } - +func install(b Build, context libcnb.BuildContext, artifact string) (string, libcnb.LayerContributor, libcnb.BOMEntry, error) { dr, err := libpak.NewDependencyResolver(context) if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to create dependency resolver\n%w", err) + return "", nil, libcnb.BOMEntry{}, fmt.Errorf("unable to create dependency resolver\n%w", err) } dc, err := libpak.NewDependencyCache(context) if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to create dependency cache\n%w", err) + return "", nil, libcnb.BOMEntry{}, fmt.Errorf("unable to create dependency cache\n%w", err) } dc.Logger = b.Logger + dep, err := dr.Resolve(artifact, "") + if err != nil { + return "", nil, libcnb.BOMEntry{}, fmt.Errorf("unable to find dependency\n%w", err) + } + + if artifact == "maven" { + dist, be := NewDistribution(dep, dc) + dist.Logger = b.Logger + command := filepath.Join(context.Layers.Path, dist.Name(), "bin", "mvn") + return command, dist, be, nil + } + dist, be := NewDistribution(dep, dc) + dist.Logger = b.Logger + command := filepath.Join(context.Layers.Path, dist.Name(), "bin", "mvnd") + return command, dist, be, nil +} + +func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { + b.Logger.Title(context.Buildpack) + result := libcnb.NewBuildResult() + pr := libpak.PlanEntryResolver{ Plan: context.Plan, } @@ -84,45 +104,24 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { } } - bpMavenCommand, _ := cr.Resolve(BpMavenCommand) - command := "" - if cr.ResolveBool("BP_MAVEN_DAEMON_ENABLED") || bpMavenCommand == "mvnd" { - dep, err := dr.Resolve("mvnd", "") - if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) - } - - dist, be := NewMvndDistribution(dep, dc) - dist.Logger = b.Logger - result.Layers = append(result.Layers, dist) - result.BOM.Entries = append(result.BOM.Entries, be) - - command = filepath.Join(context.Layers.Path, dist.Name(), "bin", "mvnd") - } else { - command = filepath.Join(context.Application.Path, "mvnw") - if _, err := os.Stat(command); os.IsNotExist(err) || bpMavenCommand == "mvn" { - dep, err := dr.Resolve("maven", "") - if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) - } - - dist, be := NewDistribution(dep, dc) - dist.Logger = b.Logger - result.Layers = append(result.Layers, dist) - result.BOM.Entries = append(result.BOM.Entries, be) + cr, err := libpak.NewConfigurationResolver(context.Buildpack, &b.Logger) + if err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to create configuration resolver\n%w", err) + } - command = filepath.Join(context.Layers.Path, dist.Name(), "bin", "mvn") - } else if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to stat %s\n%w", command, err) - } else { - if err := os.Chmod(command, 0755); err != nil { - b.Logger.Bodyf("WARNING: unable to chmod %s:\n%s", command, err) - } + bpMavenCommand, _ := cr.Resolve(BpMavenCommand) + // no install requested and no build requested + if bpMavenCommand == "" && runBuild == false { + return libcnb.BuildResult{}, nil + } - if err = b.CleanMvnWrapper(command); err != nil { - b.Logger.Bodyf("WARNING: unable to clean mvnw file: %s\n%s", command, err) - } + if bpMavenCommand == "mvn" || bpMavenCommand == "mvnd" { + cmd, layer, bomEntry, err := install(b, context, artifacts[bpMavenCommand]) + if cmd == "" { + return libcnb.BuildResult{}, fmt.Errorf("unable to install dependency\n%w", err) } + result.Layers = append(result.Layers, layer) + result.BOM.Entries = append(result.BOM.Entries, bomEntry) } u, err := user.Current() @@ -132,9 +131,42 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { c := libbs.Cache{Path: filepath.Join(u.HomeDir, ".m2")} c.Logger = b.Logger - result.Layers = append(result.Layers, c) if runBuild { + command := "" + if cr.ResolveBool("BP_MAVEN_DAEMON_ENABLED") && bpMavenCommand != "mvnd" { + cmd, layer, bomEntry, err := install(b, context, "mvnd") + if err != nil { + return libcnb.BuildResult{}, err + } + result.Layers = append(result.Layers, layer) + result.BOM.Entries = append(result.BOM.Entries, bomEntry) + command = cmd + } else { + command = filepath.Join(context.Application.Path, "mvnw") + if _, err := os.Stat(command); os.IsNotExist(err) && bpMavenCommand != "mvn" { + cmd, layer, bomEntry, err := install(b, context, "maven") + if err != nil { + return libcnb.BuildResult{}, err + } + result.Layers = append(result.Layers, layer) + result.BOM.Entries = append(result.BOM.Entries, bomEntry) + command = cmd + } else if err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to stat %s\n%w", command, err) + } else { + if err := os.Chmod(command, 0755); err != nil { + b.Logger.Bodyf("WARNING: unable to chmod %s:\n%s", command, err) + } + + if err = b.CleanMvnWrapper(command); err != nil { + b.Logger.Bodyf("WARNING: unable to clean mvnw file: %s\n%s", command, err) + } + } + } + + result.Layers = append(result.Layers, c) + args, err := libbs.ResolveArguments("BP_MAVEN_BUILD_ARGUMENTS", cr) if err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to resolve build arguments\n%w", err) @@ -185,7 +217,10 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { a.Logger = b.Logger result.Layers = append(result.Layers, a) + } else { + result.Layers = append(result.Layers, c) } + return result, nil } diff --git a/maven/build_test.go b/maven/build_test.go index 496e44a..f6b12b3 100644 --- a/maven/build_test.go +++ b/maven/build_test.go @@ -458,6 +458,12 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { }, } ctx.Plan.Entries = append(ctx.Plan.Entries, entry) + os.Setenv("BP_MAVEN_COMMAND", "mvn") + }) + + it.After(func() { + os.Unsetenv("BP_MAVEN_COMMAND") + _, ctx.Plan.Entries = ctx.Plan.Entries[len(ctx.Plan.Entries)-1], ctx.Plan.Entries[:len(ctx.Plan.Entries)-1] }) it("contributes distribution for API 0.7+", func() { @@ -509,6 +515,95 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(result.BOM.Entries[0].Launch).To(BeFalse()) }) }) + + context("distribute binary build with another", func() { + it.Before(func() { + entry := libcnb.BuildpackPlanEntry{ + Name: maven.PlanEntryMaven, + Metadata: map[string]interface{}{ + maven.RunBuild: true, + }, + } + ctx.Plan.Entries = append(ctx.Plan.Entries, entry) + os.Setenv("BP_MAVEN_COMMAND", "mvn") + os.Setenv("BP_MAVEN_DAEMON_ENABLED", "1") + }) + + it.After(func() { + os.Unsetenv("BP_MAVEN_COMMAND") + os.Unsetenv("BP_MAVEN_DAEMON_ENABLED") + _, ctx.Plan.Entries = ctx.Plan.Entries[len(ctx.Plan.Entries)-1], ctx.Plan.Entries[:len(ctx.Plan.Entries)-1] + }) + + it("contributes distribution for API 0.7+", func() { + ctx.Buildpack.Metadata["dependencies"] = []map[string]interface{}{ + { + "id": "maven", + "version": "1.1.1", + "stacks": []interface{}{"test-stack-id"}, + "cpes": []string{"cpe:2.3:a:apache:maven:3.8.3:*:*:*:*:*:*:*"}, + "purl": "pkg:generic/apache-maven@3.8.3", + }, + { + "id": "mvnd", + "version": "1.1.1", + "stacks": []interface{}{"test-stack-id"}, + }, + } + ctx.StackID = "test-stack-id" + + result, err := mavenBuild.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + + Expect(result.Layers).To(HaveLen(4)) + Expect(result.Layers[0].Name()).To(Equal("maven")) + Expect(result.Layers[1].Name()).To(Equal("mvnd")) + Expect(result.Layers[2].Name()).To(Equal("cache")) + Expect(result.Layers[3].Name()).To(Equal("application")) + + Expect(result.BOM.Entries).To(HaveLen(2)) + Expect(result.BOM.Entries[0].Name).To(Equal("maven")) + Expect(result.BOM.Entries[0].Build).To(BeTrue()) + Expect(result.BOM.Entries[0].Launch).To(BeFalse()) + Expect(result.BOM.Entries[1].Name).To(Equal("mvnd")) + Expect(result.BOM.Entries[1].Build).To(BeTrue()) + Expect(result.BOM.Entries[1].Launch).To(BeFalse()) + }) + + it("contributes distribution for API <=0.6", func() { + ctx.Buildpack.Metadata["dependencies"] = []map[string]interface{}{ + { + "id": "maven", + "version": "1.1.1", + "stacks": []interface{}{"test-stack-id"}, + }, + { + "id": "mvnd", + "version": "1.1.1", + "stacks": []interface{}{"test-stack-id"}, + }, + } + ctx.StackID = "test-stack-id" + ctx.Buildpack.API = "0.6" + + result, err := mavenBuild.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + + Expect(result.Layers).To(HaveLen(4)) + Expect(result.Layers[0].Name()).To(Equal("maven")) + Expect(result.Layers[1].Name()).To(Equal("mvnd")) + Expect(result.Layers[2].Name()).To(Equal("cache")) + Expect(result.Layers[3].Name()).To(Equal("application")) + + Expect(result.BOM.Entries).To(HaveLen(2)) + Expect(result.BOM.Entries[0].Name).To(Equal("maven")) + Expect(result.BOM.Entries[0].Build).To(BeTrue()) + Expect(result.BOM.Entries[0].Launch).To(BeFalse()) + Expect(result.BOM.Entries[1].Name).To(Equal("mvnd")) + Expect(result.BOM.Entries[1].Build).To(BeTrue()) + Expect(result.BOM.Entries[1].Launch).To(BeFalse()) + }) + }) } type FakeApplicationFactory struct{} From ec074d84c3974bbb16e9e6e01b248a7ae8c2d814 Mon Sep 17 00:00:00 2001 From: Aidan Delaney Date: Wed, 24 Aug 2022 10:11:31 +0100 Subject: [PATCH 3/4] Provide maven if no pom.xml is found If no maven project is found, allow subsequent buildpacks to request that the maven buildpack makes either `mvn` or `mvnd` available. --- maven/build.go | 23 ++++++++++------------- maven/build_test.go | 6 ++---- maven/detect.go | 28 ++++++++++++++++++---------- maven/detect_test.go | 16 ++++++++++++++-- 4 files changed, 44 insertions(+), 29 deletions(-) diff --git a/maven/build.go b/maven/build.go index 86a5293..04a571e 100644 --- a/maven/build.go +++ b/maven/build.go @@ -39,16 +39,10 @@ import ( ) const ( + Command = "Command" RunBuild = "RunBuild" ) -var ( - artifacts = map[string]string{ - "mvn": "maven", - "mvnd": "mvnd", - } -) - type Build struct { Logger bard.Logger ApplicationFactory ApplicationFactory @@ -103,20 +97,23 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { runBuild = runBuildValue } } + mavenCommand := "" + if command, ok := entry.Metadata[Command].(string); ok { + mavenCommand = command + } cr, err := libpak.NewConfigurationResolver(context.Buildpack, &b.Logger) if err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to create configuration resolver\n%w", err) } - bpMavenCommand, _ := cr.Resolve(BpMavenCommand) // no install requested and no build requested - if bpMavenCommand == "" && runBuild == false { + if mavenCommand == "" && !runBuild { return libcnb.BuildResult{}, nil } - if bpMavenCommand == "mvn" || bpMavenCommand == "mvnd" { - cmd, layer, bomEntry, err := install(b, context, artifacts[bpMavenCommand]) + if mavenCommand == "maven" || mavenCommand == "mvnd" { + cmd, layer, bomEntry, err := install(b, context, mavenCommand) if cmd == "" { return libcnb.BuildResult{}, fmt.Errorf("unable to install dependency\n%w", err) } @@ -134,7 +131,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { if runBuild { command := "" - if cr.ResolveBool("BP_MAVEN_DAEMON_ENABLED") && bpMavenCommand != "mvnd" { + if cr.ResolveBool("BP_MAVEN_DAEMON_ENABLED") && mavenCommand != "mvnd" { cmd, layer, bomEntry, err := install(b, context, "mvnd") if err != nil { return libcnb.BuildResult{}, err @@ -144,7 +141,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { command = cmd } else { command = filepath.Join(context.Application.Path, "mvnw") - if _, err := os.Stat(command); os.IsNotExist(err) && bpMavenCommand != "mvn" { + if _, err := os.Stat(command); os.IsNotExist(err) && mavenCommand != "maven" { cmd, layer, bomEntry, err := install(b, context, "maven") if err != nil { return libcnb.BuildResult{}, err diff --git a/maven/build_test.go b/maven/build_test.go index f6b12b3..906c80d 100644 --- a/maven/build_test.go +++ b/maven/build_test.go @@ -455,14 +455,13 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Name: maven.PlanEntryMaven, Metadata: map[string]interface{}{ maven.RunBuild: false, + maven.Command: "maven", }, } ctx.Plan.Entries = append(ctx.Plan.Entries, entry) - os.Setenv("BP_MAVEN_COMMAND", "mvn") }) it.After(func() { - os.Unsetenv("BP_MAVEN_COMMAND") _, ctx.Plan.Entries = ctx.Plan.Entries[len(ctx.Plan.Entries)-1], ctx.Plan.Entries[:len(ctx.Plan.Entries)-1] }) @@ -522,15 +521,14 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Name: maven.PlanEntryMaven, Metadata: map[string]interface{}{ maven.RunBuild: true, + maven.Command: "maven", }, } ctx.Plan.Entries = append(ctx.Plan.Entries, entry) - os.Setenv("BP_MAVEN_COMMAND", "mvn") os.Setenv("BP_MAVEN_DAEMON_ENABLED", "1") }) it.After(func() { - os.Unsetenv("BP_MAVEN_COMMAND") os.Unsetenv("BP_MAVEN_DAEMON_ENABLED") _, ctx.Plan.Entries = ctx.Plan.Entries[len(ctx.Plan.Entries)-1], ctx.Plan.Entries[:len(ctx.Plan.Entries)-1] }) diff --git a/maven/detect.go b/maven/detect.go index 50832e1..891688d 100644 --- a/maven/detect.go +++ b/maven/detect.go @@ -32,7 +32,6 @@ const ( PlanEntryJVMApplicationPackage = "jvm-application-package" PlanEntryJDK = "jdk" PlanEntrySyft = "syft" - BpMavenCommand = "BP_MAVEN_COMMAND" ) type Detect struct{} @@ -44,35 +43,44 @@ func (Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) return libcnb.DetectResult{}, err } - result := libcnb.DetectResult{ + provide_maven := libcnb.DetectResult{ Pass: true, Plans: []libcnb.BuildPlan{ { Provides: []libcnb.BuildPlanProvide{ - {Name: PlanEntryJVMApplicationPackage}, {Name: PlanEntryMaven}, }, Requires: []libcnb.BuildPlanRequire{ - {Name: PlanEntrySyft}, - {Name: PlanEntryJDK}, {Name: PlanEntryMaven}, }, }, }, } - if binary, _ := cr.Resolve(BpMavenCommand); binary == "mvn" || binary == "mvnd" { - return result, nil - } - pomFile, _ := cr.Resolve("BP_MAVEN_POM_FILE") file := filepath.Join(context.Application.Path, pomFile) _, err = os.Stat(file) if os.IsNotExist(err) { - return libcnb.DetectResult{Pass: false}, nil + return provide_maven, nil } else if err != nil { return libcnb.DetectResult{}, fmt.Errorf("unable to determine if %s exists\n%w", file, err) } + result := libcnb.DetectResult{ + Pass: true, + Plans: []libcnb.BuildPlan{ + { + Provides: []libcnb.BuildPlanProvide{ + {Name: PlanEntryJVMApplicationPackage}, + {Name: PlanEntryMaven}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: PlanEntrySyft}, + {Name: PlanEntryJDK}, + {Name: PlanEntryMaven}, + }, + }, + }, + } return result, nil } diff --git a/maven/detect_test.go b/maven/detect_test.go index a46fbb1..e9b8413 100644 --- a/maven/detect_test.go +++ b/maven/detect_test.go @@ -48,9 +48,21 @@ func testDetect(t *testing.T, context spec.G, it spec.S) { Expect(os.RemoveAll(ctx.Application.Path)).To(Succeed()) }) - it("fails without pom.xml", func() { + it("provides maven without pom.xml", func() { os.Setenv("BP_MAVEN_POM_FILE", "pom.xml") - Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{})) + Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{ + Pass: true, + Plans: []libcnb.BuildPlan{ + { + Provides: []libcnb.BuildPlanProvide{ + {Name: maven.PlanEntryMaven}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: maven.PlanEntryMaven}, + }, + }, + }, + })) }) it("passes with pom.xml", func() { From bbb0d6fd8328279181a2b236732d249b8ba5690f Mon Sep 17 00:00:00 2001 From: Aidan Delaney Date: Fri, 26 Aug 2022 13:24:47 +0100 Subject: [PATCH 4/4] Override MAVEN_OPTS with maven settings If `mvn` or `mvnd` are installed for use by subsequent buildpacks, then set MAVEN_OPTS to any configured maven settings. This ensures that subsequent buildpacks use the same `--settings` and `-Dsettings.security`. --- maven/build.go | 31 ++++++++++++++++++------------- maven/distribution.go | 5 +++++ maven/distribution_test.go | 2 ++ maven/mvnd_distribution.go | 5 +++++ maven/mvnd_distribution_test.go | 2 ++ 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/maven/build.go b/maven/build.go index 04a571e..335a622 100644 --- a/maven/build.go +++ b/maven/build.go @@ -54,7 +54,7 @@ type ApplicationFactory interface { cache libbs.Cache, command string, bom *libcnb.BOM, applicationPath string, bomScanner sbom.SBOMScanner) (libbs.Application, error) } -func install(b Build, context libcnb.BuildContext, artifact string) (string, libcnb.LayerContributor, libcnb.BOMEntry, error) { +func install(b Build, context libcnb.BuildContext, artifact string, securityArgs []string) (string, libcnb.LayerContributor, libcnb.BOMEntry, error) { dr, err := libpak.NewDependencyResolver(context) if err != nil { return "", nil, libcnb.BOMEntry{}, fmt.Errorf("unable to create dependency resolver\n%w", err) @@ -73,11 +73,13 @@ func install(b Build, context libcnb.BuildContext, artifact string) (string, lib if artifact == "maven" { dist, be := NewDistribution(dep, dc) + dist.SecurityArgs = securityArgs dist.Logger = b.Logger command := filepath.Join(context.Layers.Path, dist.Name(), "bin", "mvn") return command, dist, be, nil } dist, be := NewDistribution(dep, dc) + dist.SecurityArgs = securityArgs dist.Logger = b.Logger command := filepath.Join(context.Layers.Path, dist.Name(), "bin", "mvnd") return command, dist, be, nil @@ -112,8 +114,19 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { return libcnb.BuildResult{}, nil } + md := map[string]interface{}{} + securityArgs := []string{} + if binding, ok, err := bindings.ResolveOne(context.Platform.Bindings, bindings.OfType("maven")); err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve binding\n%w", err) + } else if ok { + securityArgs, err = handleMavenSettings(binding, securityArgs, md) + if err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to process maven settings from binding\n%w", err) + } + } + if mavenCommand == "maven" || mavenCommand == "mvnd" { - cmd, layer, bomEntry, err := install(b, context, mavenCommand) + cmd, layer, bomEntry, err := install(b, context, mavenCommand, securityArgs) if cmd == "" { return libcnb.BuildResult{}, fmt.Errorf("unable to install dependency\n%w", err) } @@ -132,7 +145,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { if runBuild { command := "" if cr.ResolveBool("BP_MAVEN_DAEMON_ENABLED") && mavenCommand != "mvnd" { - cmd, layer, bomEntry, err := install(b, context, "mvnd") + cmd, layer, bomEntry, err := install(b, context, "mvnd", securityArgs) if err != nil { return libcnb.BuildResult{}, err } @@ -142,7 +155,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { } else { command = filepath.Join(context.Application.Path, "mvnw") if _, err := os.Stat(command); os.IsNotExist(err) && mavenCommand != "maven" { - cmd, layer, bomEntry, err := install(b, context, "maven") + cmd, layer, bomEntry, err := install(b, context, "maven", securityArgs) if err != nil { return libcnb.BuildResult{}, err } @@ -179,15 +192,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { args = append([]string{"--batch-mode"}, args...) } - md := map[string]interface{}{} - if binding, ok, err := bindings.ResolveOne(context.Platform.Bindings, bindings.OfType("maven")); err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to resolve binding\n%w", err) - } else if ok { - args, err = handleMavenSettings(binding, args, md) - if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to process maven settings from binding\n%w", err) - } - } + args = append(securityArgs, args...) art := libbs.ArtifactResolver{ ArtifactConfigurationKey: "BP_MAVEN_BUILT_ARTIFACT", diff --git a/maven/distribution.go b/maven/distribution.go index 73de645..c02b3b1 100644 --- a/maven/distribution.go +++ b/maven/distribution.go @@ -19,6 +19,7 @@ package maven import ( "fmt" "os" + "strings" "github.com/buildpacks/libcnb" "github.com/paketo-buildpacks/libpak" @@ -29,6 +30,7 @@ import ( type Distribution struct { LayerContributor libpak.DependencyLayerContributor Logger bard.Logger + SecurityArgs []string } func NewDistribution(dependency libpak.BuildpackDependency, cache libpak.DependencyCache) (Distribution, libcnb.BOMEntry) { @@ -48,6 +50,9 @@ func (d Distribution) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { return libcnb.Layer{}, fmt.Errorf("unable to expand Maven\n%w", err) } + if len(d.SecurityArgs) > 0 { + layer.BuildEnvironment.Override("MAVEN_OPTS", strings.Join(d.SecurityArgs, " ")) + } return layer, nil }) } diff --git a/maven/distribution_test.go b/maven/distribution_test.go index e8f0604..8eae9c5 100644 --- a/maven/distribution_test.go +++ b/maven/distribution_test.go @@ -56,6 +56,7 @@ func testDistribution(t *testing.T, context spec.G, it spec.S) { dc := libpak.DependencyCache{CachePath: "testdata"} d, _ := maven.NewDistribution(dep, dc) + d.SecurityArgs = []string{"test", "test"} layer, err := ctx.Layers.Layer("test-layer") Expect(err).NotTo(HaveOccurred()) @@ -64,6 +65,7 @@ func testDistribution(t *testing.T, context spec.G, it spec.S) { Expect(layer.Cache).To(BeTrue()) Expect(filepath.Join(layer.Path, "fixture-marker")).To(BeARegularFile()) + Expect(layer.BuildEnvironment["MAVEN_OPTS.override"]).To(Equal("test test")) }) } diff --git a/maven/mvnd_distribution.go b/maven/mvnd_distribution.go index 3b1ce72..b8f186a 100644 --- a/maven/mvnd_distribution.go +++ b/maven/mvnd_distribution.go @@ -19,6 +19,7 @@ package maven import ( "fmt" "os" + "strings" "github.com/buildpacks/libcnb" "github.com/paketo-buildpacks/libpak" @@ -29,6 +30,7 @@ import ( type MvndDistribution struct { LayerContributor libpak.DependencyLayerContributor Logger bard.Logger + SecurityArgs []string } func NewMvndDistribution(dependency libpak.BuildpackDependency, cache libpak.DependencyCache) (MvndDistribution, libcnb.BOMEntry) { @@ -48,6 +50,9 @@ func (d MvndDistribution) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { return libcnb.Layer{}, fmt.Errorf("unable to expand Maven\n%w", err) } + if len(d.SecurityArgs) > 0 { + layer.BuildEnvironment.Override("MAVEN_OPTS", strings.Join(d.SecurityArgs, " ")) + } return layer, nil }) } diff --git a/maven/mvnd_distribution_test.go b/maven/mvnd_distribution_test.go index a4c496c..8e6137a 100644 --- a/maven/mvnd_distribution_test.go +++ b/maven/mvnd_distribution_test.go @@ -56,6 +56,7 @@ func testMvndDistribution(t *testing.T, context spec.G, it spec.S) { dc := libpak.DependencyCache{CachePath: "testdata"} d, _ := maven.NewMvndDistribution(dep, dc) + d.SecurityArgs = []string{"test", "test"} layer, err := ctx.Layers.Layer("test-layer") Expect(err).NotTo(HaveOccurred()) @@ -64,6 +65,7 @@ func testMvndDistribution(t *testing.T, context spec.G, it spec.S) { Expect(layer.Cache).To(BeTrue()) Expect(filepath.Join(layer.Path, "fixture-marker")).To(BeARegularFile()) + Expect(layer.BuildEnvironment["MAVEN_OPTS.override"]).To(Equal("test test")) }) }