From 713b3e414ac77c52b85cf1a42115deb02236df31 Mon Sep 17 00:00:00 2001 From: Daniel Mikusa Date: Fri, 9 Apr 2021 13:29:02 -0400 Subject: [PATCH 1/4] Allow users to provide a custom gradle.properties file When a binding of type or kind equals `gradle` is present, the contents of that file are copied to `$GRADLE_USER_HOME/gradle.properties`. This is picked up by Gradle when it runs and allows the user to pass additional configuration to Gradle. For example, this can be used for providing credentials to a remote Maven repository. --- README.md | 5 +++ cmd/main/main.go | 1 + gradle/build.go | 74 ++++++++++++++++++++++++++++++++-- gradle/build_test.go | 96 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 168 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4f4732b..1326da4 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ The buildpack will do the following: ## Bindings The buildpack optionally accepts the following bindings: +### Type: `gradle` +|Secret | Description +|-----|-------------- +|`gradle.properties` | If present, the contents of the file are copied to `$GRADLE_USER_HOME/gradle.properties` which is [picked up by gradle and merged](https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties) when it runs. + ### Type: `dependency-mapping` |Key | Value | Description |----------------------|---------|------------ diff --git a/cmd/main/main.go b/cmd/main/main.go index 081ccfe..458f6bf 100644 --- a/cmd/main/main.go +++ b/cmd/main/main.go @@ -32,6 +32,7 @@ func main() { gradle.Build{ ApplicationFactory: libbs.NewApplicationFactory(), Logger: bard.NewLogger(os.Stdout), + HomeDirectoryResolver: gradle.OSHomeDirectoryResolver{}, }, ) } diff --git a/gradle/build.go b/gradle/build.go index 969ca3a..9af9a54 100644 --- a/gradle/build.go +++ b/gradle/build.go @@ -17,7 +17,12 @@ package gradle import ( + "crypto/sha256" + "encoding/hex" "fmt" + "github.com/paketo-buildpacks/libpak/bindings" + "io" + "io/ioutil" "os" "os/user" "path/filepath" @@ -31,6 +36,7 @@ import ( type Build struct { Logger bard.Logger ApplicationFactory ApplicationFactory + HomeDirectoryResolver HomeDirectoryResolver } type ApplicationFactory interface { @@ -38,6 +44,21 @@ type ApplicationFactory interface { cache libbs.Cache, command string, bom *libcnb.BOM, applicationPath string) (libbs.Application, error) } +type HomeDirectoryResolver interface { + Location() (string, error) +} + +type OSHomeDirectoryResolver struct {} + +func (p OSHomeDirectoryResolver) Location() (string, error) { + u, err := user.Current() + if err != nil { + return "", fmt.Errorf("unable to determine user home directory\n%w", err) + } + + return u.HomeDir, nil +} + func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { b.Logger.Title(context.Buildpack) result := libcnb.NewBuildResult() @@ -79,12 +100,13 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { } } - u, err := user.Current() + homeDir, err := b.HomeDirectoryResolver.Location() if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to determine user home directory\n%w", err) + return libcnb.BuildResult{}, fmt.Errorf("home directory resolution failure\n%w", err) } + gradleHome := filepath.Join(homeDir, ".gradle") - c := libbs.Cache{Path: filepath.Join(u.HomeDir, ".gradle")} + c := libbs.Cache{Path: gradleHome} c.Logger = b.Logger result.Layers = append(result.Layers, c) @@ -93,6 +115,16 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { return libcnb.BuildResult{}, fmt.Errorf("unable to resolve build arguments\n%w", err) } + md := map[string]interface{}{} + if binding, ok, err := bindings.ResolveOne(context.Platform.Bindings, bindings.OfType("gradle")); err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve binding\n%w", err) + } else if ok { + err = handleGradleSettings(binding, gradleHome, md) + if err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to process maven settings from binding\n%w", err) + } + } + art := libbs.ArtifactResolver{ ArtifactConfigurationKey: "BP_GRADLE_BUILT_ARTIFACT", ConfigurationResolver: cr, @@ -101,7 +133,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { } a, err := b.ApplicationFactory.NewApplication( - map[string]interface{}{}, + md, args, art, c, @@ -117,3 +149,37 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { return result, nil } + +func handleGradleSettings(binding libcnb.Binding, gradleHome string, md map[string]interface{}) error { + path, ok := binding.SecretFilePath("gradle.properties") + if !ok { + return nil + } + + gradleProperties, err := ioutil.ReadFile(path) + if err != nil { + return fmt.Errorf("error reading gradle.properties\n%w", err) + } + + err = os.MkdirAll(gradleHome, 0755) + if err != nil { + return fmt.Errorf("cannot make gradle home\n%w", err) + } + + err = ioutil.WriteFile(filepath.Join(gradleHome, "gradle.properties"), gradleProperties, 0644) + if err != nil { + return fmt.Errorf("error writing gradle.properties\n%w", err) + } + + hasher := sha256.New() + propertiesFile, err := os.Open(path) + if err != nil { + return fmt.Errorf("unable to open gradle.properties\n%w", err) + } + if _, err := io.Copy(hasher, propertiesFile); err != nil { + return fmt.Errorf("error hashing gradle.properties\n%w", err) + } + + md["gradle-properties-sha256"] = hex.EncodeToString(hasher.Sum(nil)) + return nil +} \ No newline at end of file diff --git a/gradle/build_test.go b/gradle/build_test.go index e2b04d0..2378f53 100644 --- a/gradle/build_test.go +++ b/gradle/build_test.go @@ -17,6 +17,7 @@ package gradle_test import ( + "github.com/paketo-buildpacks/libpak" "io/ioutil" "os" "path/filepath" @@ -37,6 +38,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { ctx libcnb.BuildContext gradleBuild gradle.Build + homeDir string ) it.Before(func() { @@ -45,17 +47,31 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { ctx.Application.Path, err = ioutil.TempDir("", "build-application") Expect(err).NotTo(HaveOccurred()) + ctx.Buildpack.Metadata = map[string]interface{}{ + "configurations": []map[string]interface{}{ + {"name": "BP_GRADLE_BUILD_ARGUMENTS", "default": "test-argument"}, + }, + } + ctx.Layers.Path, err = ioutil.TempDir("", "build-layers") Expect(err).NotTo(HaveOccurred()) + homeDir, err = ioutil.TempDir("", "home-dir") + Expect(err).NotTo(HaveOccurred()) + gradleBuild = gradle.Build{ ApplicationFactory: &FakeApplicationFactory{}, + HomeDirectoryResolver: FakeHomeDirectoryResolver{path: homeDir}, } }) it.After(func() { Expect(os.RemoveAll(ctx.Application.Path)).To(Succeed()) Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) + + homeDir,err := gradleBuild.HomeDirectoryResolver.Location() + Expect(err).ToNot(HaveOccurred()) + Expect(os.RemoveAll(homeDir)).To(Succeed()) }) it("does not contribute distribution if wrapper exists", func() { @@ -101,18 +117,90 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(result.BOM.Entries[0].Build).To(BeTrue()) Expect(result.BOM.Entries[0].Launch).To(BeFalse()) }) + + + context("gradle properties bindings exists", func() { + var result libcnb.BuildResult + + it.Before(func() { + var err error + ctx.StackID = "test-stack-id" + ctx.Platform.Path, err = ioutil.TempDir("", "gradle-test-platform") + Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "gradlew"), []byte{}, 0644)).To(Succeed()) + ctx.Platform.Bindings = libcnb.Bindings{ + { + Name: "some-gradle", + Type: "gradle", + Secret: map[string]string{"gradle.properties": "gradle-settings-content"}, + Path: filepath.Join(ctx.Platform.Path, "bindings", "some-gradle"), // TODO: is this what get's hashed? + }, + } + gradleSettingsPath, ok := ctx.Platform.Bindings[0].SecretFilePath("gradle.properties") + Expect(os.MkdirAll(filepath.Dir(gradleSettingsPath), 0777)).To(Succeed()) + Expect(ok).To(BeTrue()) + Expect(ioutil.WriteFile( + gradleSettingsPath, + []byte("gradle-settings-content"), + 0644, + )).To(Succeed()) + + result, err = gradleBuild.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Layers).To(HaveLen(2)) + }) + + it.After(func() { + Expect(os.RemoveAll(ctx.Platform.Path)).To(Succeed()) + }) + + it("provides gradle.properties under $GRADLE_USER_HOME", func() { + gradlePropertiesPath := filepath.Join(homeDir, ".gradle", "gradle.properties") + Expect(gradlePropertiesPath).To(BeARegularFile()) + + data, err := ioutil.ReadFile(gradlePropertiesPath) + Expect(err).ToNot(HaveOccurred()) + Expect(string(data)).To(Equal("gradle-settings-content")) + }) + + it("adds the hash of gradle.properties to the layer metadata", func() { + md := result.Layers[1].(libbs.Application).LayerContributor.ExpectedMetadata + mdMap, ok := md.(map[string]interface{}) + Expect(ok).To(BeTrue()) + // expected: sha256 of the string "gradle-settings-content" + expected := "e6fdb059bdd9e59cec36afd5fb39c1e5b3c83694253b61c359701b4097520da4" + Expect(mdMap["gradle-properties-sha256"]).To(Equal(expected)) + }) + }) } type FakeApplicationFactory struct{} func (f *FakeApplicationFactory) NewApplication( - _ map[string]interface{}, - _ []string, + additionalMetdata map[string]interface{}, + argugments []string, _ libbs.ArtifactResolver, - _ libbs.Cache, + cache libbs.Cache, command string, _ *libcnb.BOM, _ string, ) (libbs.Application, error) { - return libbs.Application{Command: command}, nil + contributor := libpak.NewLayerContributor( + "Compiled Application", + additionalMetdata, + libcnb.LayerTypes{Cache: true}, + ) + return libbs.Application{ + LayerContributor: contributor, + Arguments: argugments, + Command: command, + Cache: cache, + }, nil +} + +type FakeHomeDirectoryResolver struct{ + path string } + +func (f FakeHomeDirectoryResolver) Location() (string, error) { + return f.path, nil +} \ No newline at end of file From a7675beab2bae4ce774b6fe368297e27ec843a25 Mon Sep 17 00:00:00 2001 From: Daniel Mikusa Date: Tue, 13 Apr 2021 10:10:07 -0400 Subject: [PATCH 2/4] Fix some formatting issues & use a symlink to the binding file instead of copying it into the layer --- gradle/build.go | 37 ++++++++++++++++------------- gradle/build_test.go | 55 +++++++++++++++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/gradle/build.go b/gradle/build.go index 9af9a54..e6c1d22 100644 --- a/gradle/build.go +++ b/gradle/build.go @@ -20,13 +20,13 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "github.com/paketo-buildpacks/libpak/bindings" "io" - "io/ioutil" "os" "os/user" "path/filepath" + "github.com/paketo-buildpacks/libpak/bindings" + "github.com/buildpacks/libcnb" "github.com/paketo-buildpacks/libbs" "github.com/paketo-buildpacks/libpak" @@ -34,8 +34,8 @@ import ( ) type Build struct { - Logger bard.Logger - ApplicationFactory ApplicationFactory + Logger bard.Logger + ApplicationFactory ApplicationFactory HomeDirectoryResolver HomeDirectoryResolver } @@ -48,7 +48,7 @@ type HomeDirectoryResolver interface { Location() (string, error) } -type OSHomeDirectoryResolver struct {} +type OSHomeDirectoryResolver struct{} func (p OSHomeDirectoryResolver) Location() (string, error) { u, err := user.Current() @@ -102,7 +102,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { homeDir, err := b.HomeDirectoryResolver.Location() if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("home directory resolution failure\n%w", err) + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve home directory\n%w", err) } gradleHome := filepath.Join(homeDir, ".gradle") @@ -156,19 +156,24 @@ func handleGradleSettings(binding libcnb.Binding, gradleHome string, md map[stri return nil } - gradleProperties, err := ioutil.ReadFile(path) + err := os.MkdirAll(gradleHome, 0755) if err != nil { - return fmt.Errorf("error reading gradle.properties\n%w", err) + return fmt.Errorf("unable to make gradle home\n%w", err) } - err = os.MkdirAll(gradleHome, 0755) - if err != nil { - return fmt.Errorf("cannot make gradle home\n%w", err) - } + gradlePropertiesPath := filepath.Join(gradleHome, "gradle.properties") + if err := os.Symlink(path, gradlePropertiesPath); os.IsExist(err) { + err = os.Remove(gradlePropertiesPath) + if err != nil { + return fmt.Errorf("unable to remove old symlink for gradle.properties\n%w", err) + } - err = ioutil.WriteFile(filepath.Join(gradleHome, "gradle.properties"), gradleProperties, 0644) - if err != nil { - return fmt.Errorf("error writing gradle.properties\n%w", err) + err = os.Symlink(path, gradlePropertiesPath) + if err != nil { + return fmt.Errorf("unable to create symlink for gradle.properties on retry\n%w", err) + } + } else if err != nil { + return fmt.Errorf("unable to symlink bound gradle.properties\n%w", err) } hasher := sha256.New() @@ -182,4 +187,4 @@ func handleGradleSettings(binding libcnb.Binding, gradleHome string, md map[stri md["gradle-properties-sha256"] = hex.EncodeToString(hasher.Sum(nil)) return nil -} \ No newline at end of file +} diff --git a/gradle/build_test.go b/gradle/build_test.go index 2378f53..7f6a047 100644 --- a/gradle/build_test.go +++ b/gradle/build_test.go @@ -17,12 +17,13 @@ package gradle_test import ( - "github.com/paketo-buildpacks/libpak" "io/ioutil" "os" "path/filepath" "testing" + "github.com/paketo-buildpacks/libpak" + "github.com/buildpacks/libcnb" . "github.com/onsi/gomega" "github.com/sclevine/spec" @@ -60,7 +61,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(err).NotTo(HaveOccurred()) gradleBuild = gradle.Build{ - ApplicationFactory: &FakeApplicationFactory{}, + ApplicationFactory: &FakeApplicationFactory{}, HomeDirectoryResolver: FakeHomeDirectoryResolver{path: homeDir}, } }) @@ -69,8 +70,6 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(os.RemoveAll(ctx.Application.Path)).To(Succeed()) Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) - homeDir,err := gradleBuild.HomeDirectoryResolver.Location() - Expect(err).ToNot(HaveOccurred()) Expect(os.RemoveAll(homeDir)).To(Succeed()) }) @@ -118,21 +117,22 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(result.BOM.Entries[0].Launch).To(BeFalse()) }) - context("gradle properties bindings exists", func() { - var result libcnb.BuildResult + var bindingPath string it.Before(func() { var err error ctx.StackID = "test-stack-id" ctx.Platform.Path, err = ioutil.TempDir("", "gradle-test-platform") + Expect(err).NotTo(HaveOccurred()) Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "gradlew"), []byte{}, 0644)).To(Succeed()) + bindingPath = filepath.Join(ctx.Platform.Path, "bindings", "some-gradle") ctx.Platform.Bindings = libcnb.Bindings{ { Name: "some-gradle", Type: "gradle", Secret: map[string]string{"gradle.properties": "gradle-settings-content"}, - Path: filepath.Join(ctx.Platform.Path, "bindings", "some-gradle"), // TODO: is this what get's hashed? + Path: bindingPath, }, } gradleSettingsPath, ok := ctx.Platform.Bindings[0].SecretFilePath("gradle.properties") @@ -143,10 +143,6 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { []byte("gradle-settings-content"), 0644, )).To(Succeed()) - - result, err = gradleBuild.Build(ctx) - Expect(err).NotTo(HaveOccurred()) - Expect(result.Layers).To(HaveLen(2)) }) it.After(func() { @@ -154,6 +150,10 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { }) it("provides gradle.properties under $GRADLE_USER_HOME", func() { + result, err := gradleBuild.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Layers).To(HaveLen(2)) + gradlePropertiesPath := filepath.Join(homeDir, ".gradle", "gradle.properties") Expect(gradlePropertiesPath).To(BeARegularFile()) @@ -162,7 +162,36 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(string(data)).To(Equal("gradle-settings-content")) }) + it("recreates symlink for gradle.properties under $GRADLE_USER_HOME", func() { + gradlePropertiesPath := filepath.Join(homeDir, ".gradle", "gradle.properties") + Expect(os.MkdirAll(filepath.Dir(gradlePropertiesPath), 0755)).ToNot(HaveOccurred()) + err := os.Symlink("/dev/null", gradlePropertiesPath) + Expect(err).NotTo(HaveOccurred()) + Expect(gradlePropertiesPath).To(BeAnExistingFile()) + + result, err := gradleBuild.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Layers).To(HaveLen(2)) + + info, err := os.Lstat(gradlePropertiesPath) + Expect(err).NotTo(HaveOccurred()) + Expect(info.Mode()&os.ModeSymlink != 0).To(BeTrue()) // is symlink bit set + + target, err := os.Readlink(gradlePropertiesPath) + Expect(err).NotTo(HaveOccurred()) + // symlink should point to our binding, not /dev/null + Expect(target).To(Equal(filepath.Join(bindingPath, "gradle.properties"))) + + data, err := ioutil.ReadFile(gradlePropertiesPath) + Expect(err).ToNot(HaveOccurred()) + Expect(string(data)).To(Equal("gradle-settings-content")) + }) + it("adds the hash of gradle.properties to the layer metadata", func() { + result, err := gradleBuild.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Layers).To(HaveLen(2)) + md := result.Layers[1].(libbs.Application).LayerContributor.ExpectedMetadata mdMap, ok := md.(map[string]interface{}) Expect(ok).To(BeTrue()) @@ -197,10 +226,10 @@ func (f *FakeApplicationFactory) NewApplication( }, nil } -type FakeHomeDirectoryResolver struct{ +type FakeHomeDirectoryResolver struct { path string } func (f FakeHomeDirectoryResolver) Location() (string, error) { return f.path, nil -} \ No newline at end of file +} From 231d985eec68a1550383ebb9b168f9955ade205b Mon Sep 17 00:00:00 2001 From: Daniel Mikusa Date: Wed, 14 Apr 2021 13:47:49 -0400 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Emily Casey --- gradle/build.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/build.go b/gradle/build.go index e6c1d22..01c39c1 100644 --- a/gradle/build.go +++ b/gradle/build.go @@ -121,7 +121,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { } else if ok { err = handleGradleSettings(binding, gradleHome, md) if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to process maven settings from binding\n%w", err) + return libcnb.BuildResult{}, fmt.Errorf("unable to process gradle properties from binding\n%w", err) } } @@ -150,7 +150,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { return result, nil } -func handleGradleSettings(binding libcnb.Binding, gradleHome string, md map[string]interface{}) error { +func handleGradleProperties(binding libcnb.Binding, gradleHome string, md map[string]interface{}) error { path, ok := binding.SecretFilePath("gradle.properties") if !ok { return nil From 6b5dd1f49f28e1f1db62869f62675d73aa55e3ad Mon Sep 17 00:00:00 2001 From: Daniel Mikusa Date: Thu, 15 Apr 2021 15:20:36 -0400 Subject: [PATCH 4/4] Moved creation of symlink into a layer contributor that is added prior to the layer where gradle runs, but after the cache layer. This allows the cache code to run and initialize the symlink for the cache, then this layer contributes next, adding the symlink into the cache directory and finally gradle runs. --- cmd/main/main.go | 4 +- gradle/build.go | 53 ++---------- gradle/build_test.go | 84 +++--------------- gradle/gradle_properties.go | 42 +++++++++ gradle/gradle_properties_test.go | 143 +++++++++++++++++++++++++++++++ gradle/init_test.go | 1 + 6 files changed, 204 insertions(+), 123 deletions(-) create mode 100644 gradle/gradle_properties.go create mode 100644 gradle/gradle_properties_test.go diff --git a/cmd/main/main.go b/cmd/main/main.go index 458f6bf..efa21f1 100644 --- a/cmd/main/main.go +++ b/cmd/main/main.go @@ -30,8 +30,8 @@ func main() { libpak.Main( gradle.Detect{}, gradle.Build{ - ApplicationFactory: libbs.NewApplicationFactory(), - Logger: bard.NewLogger(os.Stdout), + ApplicationFactory: libbs.NewApplicationFactory(), + Logger: bard.NewLogger(os.Stdout), HomeDirectoryResolver: gradle.OSHomeDirectoryResolver{}, }, ) diff --git a/gradle/build.go b/gradle/build.go index 01c39c1..8564cf3 100644 --- a/gradle/build.go +++ b/gradle/build.go @@ -17,10 +17,7 @@ package gradle import ( - "crypto/sha256" - "encoding/hex" "fmt" - "io" "os" "os/user" "path/filepath" @@ -115,14 +112,13 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { return libcnb.BuildResult{}, fmt.Errorf("unable to resolve build arguments\n%w", err) } - md := map[string]interface{}{} if binding, ok, err := bindings.ResolveOne(context.Platform.Bindings, bindings.OfType("gradle")); err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to resolve binding\n%w", err) } else if ok { - err = handleGradleSettings(binding, gradleHome, md) - if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to process gradle properties from binding\n%w", err) - } + result.Layers = append(result.Layers, PropertiesFile{ + binding, + gradleHome, + }) } art := libbs.ArtifactResolver{ @@ -133,7 +129,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { } a, err := b.ApplicationFactory.NewApplication( - md, + map[string]interface{}{}, args, art, c, @@ -149,42 +145,3 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { return result, nil } - -func handleGradleProperties(binding libcnb.Binding, gradleHome string, md map[string]interface{}) error { - path, ok := binding.SecretFilePath("gradle.properties") - if !ok { - return nil - } - - err := os.MkdirAll(gradleHome, 0755) - if err != nil { - return fmt.Errorf("unable to make gradle home\n%w", err) - } - - gradlePropertiesPath := filepath.Join(gradleHome, "gradle.properties") - if err := os.Symlink(path, gradlePropertiesPath); os.IsExist(err) { - err = os.Remove(gradlePropertiesPath) - if err != nil { - return fmt.Errorf("unable to remove old symlink for gradle.properties\n%w", err) - } - - err = os.Symlink(path, gradlePropertiesPath) - if err != nil { - return fmt.Errorf("unable to create symlink for gradle.properties on retry\n%w", err) - } - } else if err != nil { - return fmt.Errorf("unable to symlink bound gradle.properties\n%w", err) - } - - hasher := sha256.New() - propertiesFile, err := os.Open(path) - if err != nil { - return fmt.Errorf("unable to open gradle.properties\n%w", err) - } - if _, err := io.Copy(hasher, propertiesFile); err != nil { - return fmt.Errorf("error hashing gradle.properties\n%w", err) - } - - md["gradle-properties-sha256"] = hex.EncodeToString(hasher.Sum(nil)) - return nil -} diff --git a/gradle/build_test.go b/gradle/build_test.go index 7f6a047..4b1e9c0 100644 --- a/gradle/build_test.go +++ b/gradle/build_test.go @@ -22,8 +22,6 @@ import ( "path/filepath" "testing" - "github.com/paketo-buildpacks/libpak" - "github.com/buildpacks/libcnb" . "github.com/onsi/gomega" "github.com/sclevine/spec" @@ -48,12 +46,6 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { ctx.Application.Path, err = ioutil.TempDir("", "build-application") Expect(err).NotTo(HaveOccurred()) - ctx.Buildpack.Metadata = map[string]interface{}{ - "configurations": []map[string]interface{}{ - {"name": "BP_GRADLE_BUILD_ARGUMENTS", "default": "test-argument"}, - }, - } - ctx.Layers.Path, err = ioutil.TempDir("", "build-layers") Expect(err).NotTo(HaveOccurred()) @@ -69,7 +61,6 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { it.After(func() { Expect(os.RemoveAll(ctx.Application.Path)).To(Succeed()) Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) - Expect(os.RemoveAll(homeDir)).To(Succeed()) }) @@ -131,16 +122,16 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { { Name: "some-gradle", Type: "gradle", - Secret: map[string]string{"gradle.properties": "gradle-settings-content"}, + Secret: map[string]string{"gradle.properties": "gradle-properties-content"}, Path: bindingPath, }, } - gradleSettingsPath, ok := ctx.Platform.Bindings[0].SecretFilePath("gradle.properties") - Expect(os.MkdirAll(filepath.Dir(gradleSettingsPath), 0777)).To(Succeed()) + gradlePropertiesPath, ok := ctx.Platform.Bindings[0].SecretFilePath("gradle.properties") + Expect(os.MkdirAll(filepath.Dir(gradlePropertiesPath), 0777)).To(Succeed()) Expect(ok).To(BeTrue()) Expect(ioutil.WriteFile( - gradleSettingsPath, - []byte("gradle-settings-content"), + gradlePropertiesPath, + []byte("gradle-properties-content"), 0644, )).To(Succeed()) }) @@ -152,52 +143,9 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { it("provides gradle.properties under $GRADLE_USER_HOME", func() { result, err := gradleBuild.Build(ctx) Expect(err).NotTo(HaveOccurred()) - Expect(result.Layers).To(HaveLen(2)) - - gradlePropertiesPath := filepath.Join(homeDir, ".gradle", "gradle.properties") - Expect(gradlePropertiesPath).To(BeARegularFile()) - data, err := ioutil.ReadFile(gradlePropertiesPath) - Expect(err).ToNot(HaveOccurred()) - Expect(string(data)).To(Equal("gradle-settings-content")) - }) - - it("recreates symlink for gradle.properties under $GRADLE_USER_HOME", func() { - gradlePropertiesPath := filepath.Join(homeDir, ".gradle", "gradle.properties") - Expect(os.MkdirAll(filepath.Dir(gradlePropertiesPath), 0755)).ToNot(HaveOccurred()) - err := os.Symlink("/dev/null", gradlePropertiesPath) - Expect(err).NotTo(HaveOccurred()) - Expect(gradlePropertiesPath).To(BeAnExistingFile()) - - result, err := gradleBuild.Build(ctx) - Expect(err).NotTo(HaveOccurred()) - Expect(result.Layers).To(HaveLen(2)) - - info, err := os.Lstat(gradlePropertiesPath) - Expect(err).NotTo(HaveOccurred()) - Expect(info.Mode()&os.ModeSymlink != 0).To(BeTrue()) // is symlink bit set - - target, err := os.Readlink(gradlePropertiesPath) - Expect(err).NotTo(HaveOccurred()) - // symlink should point to our binding, not /dev/null - Expect(target).To(Equal(filepath.Join(bindingPath, "gradle.properties"))) - - data, err := ioutil.ReadFile(gradlePropertiesPath) - Expect(err).ToNot(HaveOccurred()) - Expect(string(data)).To(Equal("gradle-settings-content")) - }) - - it("adds the hash of gradle.properties to the layer metadata", func() { - result, err := gradleBuild.Build(ctx) - Expect(err).NotTo(HaveOccurred()) - Expect(result.Layers).To(HaveLen(2)) - - md := result.Layers[1].(libbs.Application).LayerContributor.ExpectedMetadata - mdMap, ok := md.(map[string]interface{}) - Expect(ok).To(BeTrue()) - // expected: sha256 of the string "gradle-settings-content" - expected := "e6fdb059bdd9e59cec36afd5fb39c1e5b3c83694253b61c359701b4097520da4" - Expect(mdMap["gradle-properties-sha256"]).To(Equal(expected)) + Expect(result.Layers).To(HaveLen(3)) + Expect(result.Layers[1].Name()).To(Equal("gradle-properties")) }) }) } @@ -205,25 +153,15 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { type FakeApplicationFactory struct{} func (f *FakeApplicationFactory) NewApplication( - additionalMetdata map[string]interface{}, - argugments []string, + _ map[string]interface{}, + _ []string, _ libbs.ArtifactResolver, - cache libbs.Cache, + _ libbs.Cache, command string, _ *libcnb.BOM, _ string, ) (libbs.Application, error) { - contributor := libpak.NewLayerContributor( - "Compiled Application", - additionalMetdata, - libcnb.LayerTypes{Cache: true}, - ) - return libbs.Application{ - LayerContributor: contributor, - Arguments: argugments, - Command: command, - Cache: cache, - }, nil + return libbs.Application{Command: command}, nil } type FakeHomeDirectoryResolver struct { diff --git a/gradle/gradle_properties.go b/gradle/gradle_properties.go new file mode 100644 index 0000000..f2bbf1d --- /dev/null +++ b/gradle/gradle_properties.go @@ -0,0 +1,42 @@ +package gradle + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/buildpacks/libcnb" +) + +type PropertiesFile struct { + Binding libcnb.Binding + GradleHome string +} + +func (p PropertiesFile) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { + path, ok := p.Binding.SecretFilePath("gradle.properties") + if !ok { + return layer, nil + } + + gradlePropertiesPath := filepath.Join(p.GradleHome, "gradle.properties") + if err := os.Symlink(path, gradlePropertiesPath); os.IsExist(err) { + err = os.Remove(gradlePropertiesPath) + if err != nil { + return layer, fmt.Errorf("unable to remove old symlink for gradle.properties\n%w", err) + } + + err = os.Symlink(path, gradlePropertiesPath) + if err != nil { + return layer, fmt.Errorf("unable to create symlink for gradle.properties on retry\n%w", err) + } + } else if err != nil { + return layer, fmt.Errorf("unable to symlink bound gradle.properties\n%w", err) + } + + return layer, nil +} + +func (p PropertiesFile) Name() string { + return "gradle-properties" +} diff --git a/gradle/gradle_properties_test.go b/gradle/gradle_properties_test.go new file mode 100644 index 0000000..c5377d5 --- /dev/null +++ b/gradle/gradle_properties_test.go @@ -0,0 +1,143 @@ +package gradle_test + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/buildpacks/libcnb" + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + + "github.com/paketo-buildpacks/gradle/gradle" +) + +func testGradleProperties(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + + ctx libcnb.BuildContext + gradleProps gradle.PropertiesFile + gradleLayer libcnb.Layer + gradleHome string + gradleTargetPropsPath string + bindingPath string + homeDir string + ) + + it.Before(func() { + var err error + + ctx.Platform.Path, err = ioutil.TempDir("", "gradle-test-platform") + Expect(err).NotTo(HaveOccurred()) + + ctx.Application.Path, err = ioutil.TempDir("", "build-application") + Expect(err).NotTo(HaveOccurred()) + + ctx.Layers.Path, err = ioutil.TempDir("", "build-layers") + Expect(err).NotTo(HaveOccurred()) + + homeDir, err = ioutil.TempDir("", "home-dir") + Expect(err).NotTo(HaveOccurred()) + + gradleHome = filepath.Join(homeDir, ".gradle") + gradleTargetPropsPath = filepath.Join(gradleHome, "gradle.properties") + }) + + it.After(func() { + Expect(os.RemoveAll(ctx.Platform.Path)).To(Succeed()) + Expect(os.RemoveAll(ctx.Application.Path)).To(Succeed()) + Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) + Expect(os.RemoveAll(homeDir)).To(Succeed()) + }) + + context("no binding is present", func() { + it("does nothing ", func() { + layer, err := gradleProps.Contribute(gradleLayer) + Expect(err).NotTo(HaveOccurred()) + Expect(layer).To(Equal(gradleLayer)) + + Expect(gradleHome).ToNot(BeADirectory()) + Expect(gradleTargetPropsPath).ToNot(BeAnExistingFile()) + }) + }) + + context("a binding is present", func() { + it.Before(func() { + var err error + + bindingPath = filepath.Join(ctx.Platform.Path, "bindings", "some-gradle") + ctx.Platform.Bindings = libcnb.Bindings{ + { + Name: "some-gradle", + Type: "gradle", + Secret: map[string]string{"gradle.properties": "gradle-properties-content"}, + Path: bindingPath, + }, + } + gradleSrcPropsPath, ok := ctx.Platform.Bindings[0].SecretFilePath("gradle.properties") + Expect(os.MkdirAll(filepath.Dir(gradleSrcPropsPath), 0777)).To(Succeed()) + Expect(ok).To(BeTrue()) + Expect(ioutil.WriteFile( + gradleSrcPropsPath, + []byte("gradle-properties-content"), + 0644, + )).To(Succeed()) + + // normally done by cache layer + Expect(os.MkdirAll(gradleHome, 0755)).ToNot(HaveOccurred()) + + gradleLayer, err = ctx.Layers.Layer("gradle-properties") + Expect(err).NotTo(HaveOccurred()) + + gradleProps = gradle.PropertiesFile{ + Binding: ctx.Platform.Bindings[0], + GradleHome: gradleHome, + } + }) + + it("creates a symlink for gradle.properties under $GRADLE_USER_HOME", func() { + layer, err := gradleProps.Contribute(gradleLayer) + Expect(err).NotTo(HaveOccurred()) + Expect(layer).To(Equal(gradleLayer)) + + info, err := os.Lstat(gradleTargetPropsPath) + Expect(err).NotTo(HaveOccurred()) + Expect(info.Mode()&os.ModeSymlink != 0).To(BeTrue()) // is symlink bit set + + target, err := os.Readlink(gradleTargetPropsPath) + Expect(err).NotTo(HaveOccurred()) + Expect(target).To(Equal(filepath.Join(bindingPath, "gradle.properties"))) + + data, err := ioutil.ReadFile(gradleTargetPropsPath) + Expect(err).ToNot(HaveOccurred()) + Expect(string(data)).To(Equal("gradle-properties-content")) + }) + + it("recreates symlink for gradle.properties under $GRADLE_USER_HOME", func() { + Expect(os.MkdirAll(filepath.Dir(gradleTargetPropsPath), 0755)).ToNot(HaveOccurred()) + err := os.Symlink("/dev/null", gradleTargetPropsPath) + Expect(err).NotTo(HaveOccurred()) + Expect(gradleTargetPropsPath).To(BeAnExistingFile()) + + layer, err := gradleProps.Contribute(gradleLayer) + Expect(err).NotTo(HaveOccurred()) + Expect(layer).To(Equal(gradleLayer)) + + info, err := os.Lstat(gradleTargetPropsPath) + Expect(err).NotTo(HaveOccurred()) + Expect(info.Mode()&os.ModeSymlink != 0).To(BeTrue()) // is symlink bit set + + target, err := os.Readlink(gradleTargetPropsPath) + Expect(err).NotTo(HaveOccurred()) + // symlink should point to our binding, not /dev/null + Expect(target).To(Equal(filepath.Join(bindingPath, "gradle.properties"))) + + data, err := ioutil.ReadFile(gradleTargetPropsPath) + Expect(err).ToNot(HaveOccurred()) + Expect(string(data)).To(Equal("gradle-properties-content")) + }) + + }) +} diff --git a/gradle/init_test.go b/gradle/init_test.go index a576b55..399d085 100644 --- a/gradle/init_test.go +++ b/gradle/init_test.go @@ -28,5 +28,6 @@ func TestUnit(t *testing.T) { suite("Build", testBuild) suite("Detect", testDetect) suite("Distribution", testDistribution) + suite("Properties", testGradleProperties) suite.Run(t) }