Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to provide a custom gradle.properties file #60

Merged
merged 7 commits into from
Apr 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
|----------------------|---------|------------
Expand Down
5 changes: 3 additions & 2 deletions cmd/main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ 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{},
},
)
}
38 changes: 33 additions & 5 deletions gradle/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,40 @@ import (
"os/user"
"path/filepath"

"github.com/paketo-buildpacks/libpak/bindings"

"github.com/buildpacks/libcnb"
"github.com/paketo-buildpacks/libbs"
"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/bard"
)

type Build struct {
Logger bard.Logger
ApplicationFactory ApplicationFactory
Logger bard.Logger
ApplicationFactory ApplicationFactory
HomeDirectoryResolver HomeDirectoryResolver
}

type ApplicationFactory interface {
NewApplication(additionalMetadata map[string]interface{}, arguments []string, artifactResolver libbs.ArtifactResolver,
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()
Expand Down Expand Up @@ -79,12 +97,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("unable to resolve home directory\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)

Expand All @@ -93,6 +112,15 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
return libcnb.BuildResult{}, fmt.Errorf("unable to resolve build arguments\n%w", err)
}

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 {
result.Layers = append(result.Layers, PropertiesFile{
binding,
gradleHome,
})
}

art := libbs.ArtifactResolver{
ArtifactConfigurationKey: "BP_GRADLE_BUILT_ARTIFACT",
ConfigurationResolver: cr,
Expand Down
57 changes: 56 additions & 1 deletion gradle/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {

ctx libcnb.BuildContext
gradleBuild gradle.Build
homeDir string
)

it.Before(func() {
Expand All @@ -48,14 +49,19 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
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{},
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())
Expect(os.RemoveAll(homeDir)).To(Succeed())
})

it("does not contribute distribution if wrapper exists", func() {
Expand Down Expand Up @@ -101,6 +107,47 @@ 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 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-properties-content"},
Path: bindingPath,
},
}
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(
gradlePropertiesPath,
[]byte("gradle-properties-content"),
0644,
)).To(Succeed())
})

it.After(func() {
Expect(os.RemoveAll(ctx.Platform.Path)).To(Succeed())
})

it("provides gradle.properties under $GRADLE_USER_HOME", func() {
result, err := gradleBuild.Build(ctx)
Expect(err).NotTo(HaveOccurred())

Expect(result.Layers).To(HaveLen(3))
Expect(result.Layers[1].Name()).To(Equal("gradle-properties"))
})
})
}

type FakeApplicationFactory struct{}
Expand All @@ -116,3 +163,11 @@ func (f *FakeApplicationFactory) NewApplication(
) (libbs.Application, error) {
return libbs.Application{Command: command}, nil
}

type FakeHomeDirectoryResolver struct {
path string
}

func (f FakeHomeDirectoryResolver) Location() (string, error) {
return f.path, nil
}
42 changes: 42 additions & 0 deletions gradle/gradle_properties.go
Original file line number Diff line number Diff line change
@@ -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) {
dmikusa marked this conversation as resolved.
Show resolved Hide resolved
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)
dmikusa marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return layer, fmt.Errorf("unable to remove old symlink for gradle.properties\n%w", err)
dmikusa marked this conversation as resolved.
Show resolved Hide resolved
}

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"
}
143 changes: 143 additions & 0 deletions gradle/gradle_properties_test.go
Original file line number Diff line number Diff line change
@@ -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"))
})

})
}
1 change: 1 addition & 0 deletions gradle/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ func TestUnit(t *testing.T) {
suite("Build", testBuild)
suite("Detect", testDetect)
suite("Distribution", testDistribution)
suite("Properties", testGradleProperties)
suite.Run(t)
}