diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1d28333..5302b7b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,5 @@ version: 2 updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: daily - labels: - - semver:patch - - type:dependency-upgrade - package-ecosystem: gomod directory: / schedule: diff --git a/.github/pipeline-version b/.github/pipeline-version index a8fdfda..db77e0e 100644 --- a/.github/pipeline-version +++ b/.github/pipeline-version @@ -1 +1 @@ -1.8.1 +1.10.5 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8a9f2bd..8203c9c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: restore-keys: ${{ runner.os }}-go- - uses: actions/setup-go@v2 with: - go-version: "1.15" + go-version: "1.16" - name: Install richgo run: | #!/usr/bin/env bash @@ -37,7 +37,7 @@ jobs: "https://github.com/kyoh86/richgo/releases/download/v${RICHGO_VERSION}/richgo_${RICHGO_VERSION}_linux_amd64.tar.gz" \ | tar -C "${HOME}"/bin -xz richgo env: - RICHGO_VERSION: 0.3.3 + RICHGO_VERSION: 0.3.6 - name: Run Tests run: | #!/usr/bin/env bash diff --git a/.github/workflows/update-pipeline.yml b/.github/workflows/update-pipeline.yml index 9e758c8..6779037 100644 --- a/.github/workflows/update-pipeline.yml +++ b/.github/workflows/update-pipeline.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: "1.15" + go-version: "1.16" - name: Install octo run: | #!/usr/bin/env bash @@ -64,6 +64,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.JAVA_GITHUB_TOKEN }} - uses: peter-evans/create-pull-request@v3 with: + author: ${{ secrets.JAVA_GITHUB_USERNAME }} <${{ secrets.JAVA_GITHUB_USERNAME }}@users.noreply.github.com> body: |- Bumps pipeline from `${{ steps.pipeline.outputs.old-version }}` to `${{ steps.pipeline.outputs.new-version }}`. diff --git a/bard/logger.go b/bard/logger.go index 205b40a..15189bc 100644 --- a/bard/logger.go +++ b/bard/logger.go @@ -76,13 +76,25 @@ func NewLoggerWithOptions(writer io.Writer, options ...Option) Logger { func NewLogger(writer io.Writer) Logger { var options []Option - if _, ok := os.LookupEnv("BP_DEBUG"); ok { - options = append(options, WithDebug(writer)) - } + // check for presence and value of log level environment variable + options = LogLevel(options, writer) return NewLoggerWithOptions(writer, options...) } +func LogLevel(options []Option, writer io.Writer) []Option { + + // Check for older log level env variable + _, dbSet := os.LookupEnv("BP_DEBUG") + + // Then check for common buildpack log level env variable - if either are set to DEBUG/true, enable Debug Writer + if level, ok := os.LookupEnv("BP_LOG_LEVEL"); (ok && strings.ToLower(level) == "debug") || dbSet { + + options = append(options, WithDebug(writer)) + } + return options +} + // Body formats using the default formats for its operands and logs a message to the configured body writer. Spaces // are added between operands when neither is a string. func (l Logger) Body(a ...interface{}) { diff --git a/bard/logger_test.go b/bard/logger_test.go index 1057246..edeeb80 100644 --- a/bard/logger_test.go +++ b/bard/logger_test.go @@ -66,6 +66,21 @@ func testLogger(t *testing.T, context spec.G, it spec.S) { }) }) + context("with BP_LOG_LEVEL set to DEBUG", func() { + it.Before(func() { + Expect(os.Setenv("BP_LOG_LEVEL", "DEBUG")).To(Succeed()) + l = bard.NewLogger(b) + }) + + it.After(func() { + Expect(os.Unsetenv("BP_LOG_LEVEL")).To(Succeed()) + }) + + it("configures debug", func() { + Expect(l.IsDebugEnabled()).To(BeTrue()) + }) + }) + context("with debug disabled", func() { it.Before(func() { l = bard.NewLoggerWithOptions(b) diff --git a/build_test.go b/build_test.go index 5056c77..2e3bde4 100644 --- a/build_test.go +++ b/build_test.go @@ -105,7 +105,10 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { }) it("handles error from Builder", func() { - Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), []byte(`[buildpack] + Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), []byte(` +api = "0.6" + +[buildpack] name = "test-name" version = "test-version"`), 0644)).To(Succeed()) diff --git a/buildpack.go b/buildpack.go index 44f756f..124fb80 100644 --- a/buildpack.go +++ b/buildpack.go @@ -26,7 +26,6 @@ import ( "github.com/Masterminds/semver/v3" "github.com/buildpacks/libcnb" "github.com/heroku/color" - "github.com/mattn/go-shellwords" "github.com/paketo-buildpacks/libpak/bard" ) @@ -85,9 +84,9 @@ type BuildpackDependency struct { Licenses []BuildpackDependencyLicense `toml:"licenses"` } -// AsBuildpackPlanEntry renders the dependency as a BuildpackPlanEntry. -func (b BuildpackDependency) AsBuildpackPlanEntry() libcnb.BuildpackPlanEntry { - return libcnb.BuildpackPlanEntry{ +// AsBOMEntry renders a bill of materials entry describing the dependency. +func (b BuildpackDependency) AsBOMEntry() libcnb.BOMEntry { + return libcnb.BOMEntry{ Name: b.ID, Metadata: map[string]interface{}{ "name": b.Name, @@ -278,15 +277,11 @@ func NewConfigurationResolver(buildpack libcnb.Buildpack, logger *bard.Logger) ( for _, c := range md.Configurations { s, _ := cr.Resolve(c.Name) - p, err := shellwords.Parse(s) - if err != nil { - return ConfigurationResolver{}, fmt.Errorf("unable to parse value\n%w", err) - } e := configurationEntry{ Name: c.Name, Description: c.Description, - Value: strings.Join(p, " "), + Value: s, } if l := len(e.Name); l > nameLength { @@ -442,8 +437,12 @@ func (d *DependencyResolver) Resolve(id string, version string) (BuildpackDepend } func (DependencyResolver) contains(candidates []string, value string) bool { + if len(candidates) == 0 { + return true + } + for _, c := range candidates { - if c == value { + if c == value || c == "*" { return true } } diff --git a/buildpack_test.go b/buildpack_test.go index 7acd6df..3160f85 100644 --- a/buildpack_test.go +++ b/buildpack_test.go @@ -33,7 +33,7 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { Expect = NewWithT(t).Expect ) - it("renders dependency as an BuildpackPlanEntry", func() { + it("renders dependency as a BOMEntry", func() { dependency := libpak.BuildpackDependency{ ID: "test-id", Name: "test-name", @@ -49,7 +49,7 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { }, } - Expect(dependency.AsBuildpackPlanEntry()).To(Equal(libcnb.BuildpackPlanEntry{ + Expect(dependency.AsBOMEntry()).To(Equal(libcnb.BOMEntry{ Name: dependency.ID, Metadata: map[string]interface{}{ "name": dependency.Name, @@ -286,6 +286,68 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { })) }) + it("filters by stack and supports the wildcard stack", func() { + resolver.Dependencies = []libpak.BuildpackDependency{ + { + ID: "test-id", + Name: "test-name", + Version: "1.0", + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}, + }, + { + ID: "test-id", + Name: "test-name", + Version: "1.0", + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"*"}, + }, + } + resolver.StackID = "test-stack-3" + + Expect(resolver.Resolve("test-id", "1.0")).To(Equal(libpak.BuildpackDependency{ + ID: "test-id", + Name: "test-name", + Version: "1.0", + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"*"}, + })) + }) + + it("filters by stack and treats no stacks as the wildcard stack", func() { + resolver.Dependencies = []libpak.BuildpackDependency{ + { + ID: "test-id", + Name: "test-name", + Version: "1.0", + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}, + }, + { + ID: "test-id", + Name: "test-name", + Version: "1.0", + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{}, + }, + } + resolver.StackID = "test-stack-3" + + Expect(resolver.Resolve("test-id", "1.0")).To(Equal(libpak.BuildpackDependency{ + ID: "test-id", + Name: "test-name", + Version: "1.0", + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{}, + })) + }) + it("returns the best dependency", func() { resolver.Dependencies = []libpak.BuildpackDependency{ { diff --git a/dependency_cache.go b/dependency_cache.go index 158d325..200f22f 100644 --- a/dependency_cache.go +++ b/dependency_cache.go @@ -23,6 +23,7 @@ import ( "io" "io/ioutil" "net/http" + "net/url" "os" "path/filepath" "reflect" @@ -187,6 +188,43 @@ func (d *DependencyCache) Artifact(dependency BuildpackDependency, mods ...Reque } func (d DependencyCache) download(uri string, destination string, mods ...RequestModifierFunc) error { + url, err := url.Parse(uri) + if err != nil { + return fmt.Errorf("unable to parse URI %s\n%w", uri, err) + } + + if url.Scheme == "file" { + return d.downloadFile(url.Path, destination, mods...) + } + + return d.downloadHttp(uri, destination, mods...) +} + +func (d DependencyCache) downloadFile(source string, destination string, mods ...RequestModifierFunc) error { + if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil { + return fmt.Errorf("unable to make directory %s\n%w", filepath.Dir(destination), err) + } + + out, err := os.OpenFile(destination, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("unable to open destination file %s\n%w", destination, err) + } + defer out.Close() + + input, err := os.Open(source) + if err != nil { + return fmt.Errorf("unable to open source file %s\n%w", source, err) + } + defer out.Close() + + if _, err := io.Copy(out, input); err != nil { + return fmt.Errorf("unable to copy from %s to %s\n%w", source, destination, err) + } + + return nil +} + +func (d DependencyCache) downloadHttp(uri string, destination string, mods ...RequestModifierFunc) error { req, err := http.NewRequest("GET", uri, nil) if err != nil { return fmt.Errorf("unable to create new GET request for %s\n%w", uri, err) diff --git a/dependency_cache_test.go b/dependency_cache_test.go index 65d91de..1448a9c 100644 --- a/dependency_cache_test.go +++ b/dependency_cache_test.go @@ -223,7 +223,7 @@ func testDependencyCache(t *testing.T, context spec.G, it spec.S) { Expect(ioutil.ReadAll(a)).To(Equal([]byte("test-fixture"))) }) - context("uri is overridden", func() { + context("uri is overridden HTTP", func() { it.Before(func() { dependencyCache.Mappings = map[string]string{ dependency.SHA256: fmt.Sprintf("%s/override-path", server.URL()), @@ -243,6 +243,26 @@ func testDependencyCache(t *testing.T, context spec.G, it spec.S) { }) }) + context("uri is overridden FILE", func() { + it.Before(func() { + sourcePath, err := ioutil.TempDir("", "dependency-source-path") + Expect(err).NotTo(HaveOccurred()) + sourceFile := filepath.Join(sourcePath, "source-file") + Expect(ioutil.WriteFile(sourceFile, []byte("test-fixture"), 0644)).ToNot(HaveOccurred()) + + dependencyCache.Mappings = map[string]string{ + dependency.SHA256: fmt.Sprintf("file://%s", sourceFile), + } + }) + + it("downloads from override filesystem", func() { + a, err := dependencyCache.Artifact(dependency) + Expect(err).NotTo(HaveOccurred()) + + Expect(ioutil.ReadAll(a)).To(Equal([]byte("test-fixture"))) + }) + }) + it("fails with invalid SHA256", func() { server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "invalid-fixture")) diff --git a/detect_test.go b/detect_test.go index a14ab0e..8a92627 100644 --- a/detect_test.go +++ b/detect_test.go @@ -99,7 +99,10 @@ func testDetect(t *testing.T, context spec.G, it spec.S) { }) it("handles error from Detector", func() { - Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), []byte(`[buildpack] + Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), []byte(` +api = "0.6" + +[buildpack] name = "test-name" version = "test-version"`), 0644)).To(Succeed()) diff --git a/go.mod b/go.mod index fb4814c..90ffa75 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,15 @@ module github.com/paketo-buildpacks/libpak go 1.15 require ( - github.com/Masterminds/semver/v3 v3.1.0 - github.com/buildpacks/libcnb v1.18.1 - github.com/creack/pty v1.1.11 + github.com/Masterminds/semver/v3 v3.1.1 + github.com/buildpacks/libcnb v1.22.0 + github.com/creack/pty v1.1.15 github.com/heroku/color v0.0.6 - github.com/imdario/mergo v0.3.11 - github.com/mattn/go-shellwords v1.0.10 - github.com/onsi/gomega v1.10.3 - github.com/pelletier/go-toml v1.8.1 + github.com/imdario/mergo v0.3.12 + github.com/onsi/gomega v1.16.0 + github.com/pelletier/go-toml v1.9.4 github.com/sclevine/spec v1.4.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.7.0 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 ) diff --git a/go.sum b/go.sum index 92b450f..1086376 100644 --- a/go.sum +++ b/go.sum @@ -1,49 +1,57 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= -github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/buildpacks/libcnb v1.18.1 h1:NcAqtyLmSkpcgIOl83GOHY5Mk2ltBFiAI8mmAvavvEs= -github.com/buildpacks/libcnb v1.18.1/go.mod h1:ozKZYold67tlck+1cobg11YhGmJqkQr8bMAH5gSvEvw= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/buildpacks/libcnb v1.22.0 h1:jnQf51ASouvsRv/F3cprStM3SrXxlueVP7MK7b8/km0= +github.com/buildpacks/libcnb v1.22.0/go.mod h1:g++R17SOuahPXTGDj1tbIcLr9csmzHrNVoTsdocdaMc= +github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc= +github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0= github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= -github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= @@ -52,45 +60,70 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/toml_writer.go b/internal/toml_writer.go index 78cca79..a76b6a5 100644 --- a/internal/toml_writer.go +++ b/internal/toml_writer.go @@ -67,7 +67,7 @@ func (t TOMLWriter) Write(path string, value interface{}) error { } switch v := value.(type) { - case libcnb.Launch: + case libcnb.LaunchTOML: if len(v.Slices) > 0 { t.logger.Headerf("%d application slices", len(v.Slices)) } diff --git a/internal/toml_writer_test.go b/internal/toml_writer_test.go index 10d5605..9785008 100644 --- a/internal/toml_writer_test.go +++ b/internal/toml_writer_test.go @@ -87,7 +87,7 @@ other-field = "other-value"`)) }) it("logs []libcnb.Slice", func() { - err := tomlWriter.Write(path, libcnb.Launch{ + err := tomlWriter.Write(path, libcnb.LaunchTOML{ Slices: []libcnb.Slice{ {}, {}, @@ -100,7 +100,7 @@ other-field = "other-value"`)) }) it("logs []libcnb.Label", func() { - err := tomlWriter.Write(path, libcnb.Launch{ + err := tomlWriter.Write(path, libcnb.LaunchTOML{ Labels: []libcnb.Label{ {Key: "test-key-1"}, {Key: "test-key-2"}, @@ -117,7 +117,7 @@ other-field = "other-value"`)) context("[]libcnb.Process", func() { it("aligns process types", func() { - err := tomlWriter.Write(path, libcnb.Launch{ + err := tomlWriter.Write(path, libcnb.LaunchTOML{ Processes: []libcnb.Process{ {Type: "short", Command: "test-command-1"}, {Type: "a-very-long-type", Command: "test-command-2"}, @@ -133,7 +133,7 @@ other-field = "other-value"`)) }) it("appends arguments", func() { - err := tomlWriter.Write(path, libcnb.Launch{ + err := tomlWriter.Write(path, libcnb.LaunchTOML{ Processes: []libcnb.Process{ {Type: "test-type", Command: "test-command", Arguments: []string{"test-arg-1", "test-arg-2"}}, }, @@ -147,7 +147,7 @@ other-field = "other-value"`)) }) it("indicates direct", func() { - err := tomlWriter.Write(path, libcnb.Launch{ + err := tomlWriter.Write(path, libcnb.LaunchTOML{ Processes: []libcnb.Process{ {Type: "test-type", Command: "test-command", Direct: true}, }, diff --git a/layer.go b/layer.go index 6f0918d..1967ec0 100644 --- a/layer.go +++ b/layer.go @@ -21,7 +21,6 @@ import ( "os" "path/filepath" "reflect" - "strings" "github.com/heroku/color" "github.com/pelletier/go-toml" @@ -45,29 +44,25 @@ type LayerContributor struct { // Name is the user readable name of the contribution. Name string + + // ExpectedTypes indicates the types that should be set on the layer. + ExpectedTypes libcnb.LayerTypes } // NewLayerContributor creates a new instance. -func NewLayerContributor(name string, expectedMetadata interface{}) LayerContributor { +func NewLayerContributor(name string, expectedMetadata interface{}, expectedTypes libcnb.LayerTypes) LayerContributor { return LayerContributor{ ExpectedMetadata: expectedMetadata, Name: name, + ExpectedTypes: expectedTypes, } } // LayerFunc is a callback function that is invoked when a layer needs to be contributed. type LayerFunc func() (libcnb.Layer, error) -type LayerFlag uint8 - -const ( - BuildLayer LayerFlag = iota - CacheLayer - LaunchLayer -) - // Contribute is the function to call when implementing your libcnb.LayerContributor. -func (l *LayerContributor) Contribute(layer libcnb.Layer, f LayerFunc, flags ...LayerFlag) (libcnb.Layer, error) { +func (l *LayerContributor) Contribute(layer libcnb.Layer, f LayerFunc) (libcnb.Layer, error) { raw, err := toml.Marshal(l.ExpectedMetadata) if err != nil { return libcnb.Layer{}, fmt.Errorf("unable to encode metadata\n%w", err) @@ -81,8 +76,10 @@ func (l *LayerContributor) Contribute(layer libcnb.Layer, f LayerFunc, flags ... l.Logger.Debugf("Expected metadata: %+v", expected) l.Logger.Debugf("Actual metadata: %+v", layer.Metadata) + // TODO: compare entire layer not just metadata (in case build, launch, or cache have changed) if reflect.DeepEqual(expected, layer.Metadata) { l.Logger.Headerf("%s: %s cached layer", color.BlueString(l.Name), color.GreenString("Reusing")) + layer.LayerTypes = l.ExpectedTypes return layer, nil } @@ -101,17 +98,7 @@ func (l *LayerContributor) Contribute(layer libcnb.Layer, f LayerFunc, flags ... return libcnb.Layer{}, err } - for _, f := range flags { - switch f { - case BuildLayer: - layer.Build = true - case CacheLayer: - layer.Cache = true - case LaunchLayer: - layer.Launch = true - } - } - + layer.LayerTypes = l.ExpectedTypes layer.Metadata = expected return layer, nil @@ -127,8 +114,11 @@ type DependencyLayerContributor struct { // DependencyCache is the cache to use to get the dependency. DependencyCache DependencyCache - // LayerContributor is the contained LayerContributor used for the actual contribution. - LayerContributor LayerContributor + // ExpectedTypes indicates the types that should be set on the layer. + ExpectedTypes libcnb.LayerTypes + + // ExpectedMetadata contains metadata describing the expected layer + ExpectedMetadata interface{} // Logger is the logger to use. Logger bard.Logger @@ -137,29 +127,38 @@ type DependencyLayerContributor struct { RequestModifierFuncs []RequestModifierFunc } -// NewDependencyLayerContributor creates a new instance and adds the dependency to the Buildpack Plan. -func NewDependencyLayerContributor(dependency BuildpackDependency, cache DependencyCache, plan *libcnb.BuildpackPlan) DependencyLayerContributor { +// NewDependencyLayer returns a new DependencyLayerContributor for the given BuildpackDependency and a BOMEntry describing the layer contents. +func NewDependencyLayer(dependency BuildpackDependency, cache DependencyCache, types libcnb.LayerTypes) (DependencyLayerContributor, libcnb.BOMEntry) { c := DependencyLayerContributor{ Dependency: dependency, + ExpectedMetadata: dependency, DependencyCache: cache, - LayerContributor: NewLayerContributor(fmt.Sprintf("%s %s", dependency.Name, dependency.Version), dependency), + ExpectedTypes: types, } - entry := dependency.AsBuildpackPlanEntry() + entry := dependency.AsBOMEntry() entry.Metadata["layer"] = c.LayerName() - plan.Entries = append(plan.Entries, entry) - return c + if types.Launch { + entry.Launch = true + } + if !(types.Launch && !types.Cache && !types.Build) { + // launch-only layers are the only layers NOT guaranteed to be present in the build environment + entry.Build = true + } + + return c, entry } // DependencyLayerFunc is a callback function that is invoked when a dependency needs to be contributed. type DependencyLayerFunc func(artifact *os.File) (libcnb.Layer, error) // Contribute is the function to call whe implementing your libcnb.LayerContributor. -func (d *DependencyLayerContributor) Contribute(layer libcnb.Layer, f DependencyLayerFunc, flags ...LayerFlag) (libcnb.Layer, error) { - d.LayerContributor.Logger = d.Logger +func (d *DependencyLayerContributor) Contribute(layer libcnb.Layer, f DependencyLayerFunc) (libcnb.Layer, error) { + lc := NewLayerContributor(d.Name(), d.ExpectedMetadata, d.ExpectedTypes) + lc.Logger = d.Logger - return d.LayerContributor.Contribute(layer, func() (libcnb.Layer, error) { + return lc.Contribute(layer, func() (libcnb.Layer, error) { artifact, err := d.DependencyCache.Artifact(d.Dependency, d.RequestModifierFuncs...) if err != nil { return libcnb.Layer{}, fmt.Errorf("unable to get dependency %s\n%w", d.Dependency.ID, err) @@ -167,7 +166,7 @@ func (d *DependencyLayerContributor) Contribute(layer libcnb.Layer, f Dependency defer artifact.Close() return f(artifact) - }, flags...) + }) } // LayerName returns the conventional name of the layer for this contributor @@ -175,6 +174,11 @@ func (d *DependencyLayerContributor) LayerName() string { return d.Dependency.ID } +// Name returns the human readable name of the layer +func (d *DependencyLayerContributor) Name() string { + return fmt.Sprintf("%s %s", d.Dependency.Name, d.Dependency.Version) +} + // HelperLayerContributor is a helper for implementing a libcnb.LayerContributor for a buildpack helper application in // order to get consistent logging and avoidance. type HelperLayerContributor struct { @@ -182,8 +186,8 @@ type HelperLayerContributor struct { // Path is the path to the helper application. Path string - // LayerContributor is the contained LayerContributor used for the actual contribution. - LayerContributor LayerContributor + // BuildpackInfo describes the buildpack that provides the helper + BuildpackInfo libcnb.BuildpackInfo // Logger is the logger to use. Logger bard.Logger @@ -192,31 +196,40 @@ type HelperLayerContributor struct { Names []string } -// NewHelperLayerContributor creates a new instance and adds the helper to the Buildpack Plan. -func NewHelperLayerContributor(buildpack libcnb.Buildpack, plan *libcnb.BuildpackPlan, names ...string) HelperLayerContributor { +// NewHelperLayer returns a new HelperLayerContributor and a BOMEntry describing the layer contents. +func NewHelperLayer(buildpack libcnb.Buildpack, names ...string) (HelperLayerContributor, libcnb.BOMEntry) { c := HelperLayerContributor{ - Path: filepath.Join(buildpack.Path, "bin", "helper"), - LayerContributor: NewLayerContributor("Launch Helper", buildpack.Info), - Names: names, + Path: filepath.Join(buildpack.Path, "bin", "helper"), + Names: names, + BuildpackInfo: buildpack.Info, } - plan.Entries = append(plan.Entries, libcnb.BuildpackPlanEntry{ + entry := libcnb.BOMEntry{ Name: "helper", Metadata: map[string]interface{}{ "layer": c.Name(), "names": names, "version": buildpack.Info.Version, }, - }) + Launch: true, + } + + return c, entry +} - return c +// Name returns the conventional name of the layer for this contributor +func (h HelperLayerContributor) Name() string { + return filepath.Base(h.Path) } // Contribute is the function to call whe implementing your libcnb.LayerContributor. func (h HelperLayerContributor) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { - h.LayerContributor.Logger = h.Logger + lc := NewLayerContributor("Launch Helper", h.BuildpackInfo, libcnb.LayerTypes{ + Launch: true, + }) + lc.Logger = h.Logger - return h.LayerContributor.Contribute(layer, func() (libcnb.Layer, error) { + return lc.Contribute(layer, func() (libcnb.Layer, error) { in, err := os.Open(h.Path) if err != nil { return libcnb.Layer{}, fmt.Errorf("unable to open %s\n%w", h.Path, err) @@ -228,7 +241,6 @@ func (h HelperLayerContributor) Contribute(layer libcnb.Layer) (libcnb.Layer, er return libcnb.Layer{}, fmt.Errorf("unable to copy %s to %s", h.Path, out) } - var p []string for _, n := range h.Names { link := layer.Exec.FilePath(n) h.Logger.Bodyf("Creating %s", link) @@ -241,24 +253,8 @@ func (h HelperLayerContributor) Contribute(layer libcnb.Layer) (libcnb.Layer, er if err := os.Symlink(out, link); err != nil { return libcnb.Layer{}, fmt.Errorf("unable to link %s to %s\n%w", out, link, err) } - - p = append(p, - "exec 4<&1", - fmt.Sprintf(`for_export=$(%s 3>&1 >&4) || exit $?`, link), - "exec 4<&-", - "set -a", - `eval "$for_export"`, - "set +a", - ) } - layer.Profile.Add("helper", strings.Join(p, "\n")) - return layer, nil - }, LaunchLayer) -} - -// Name returns the conventional name of the layer for this contributor -func (h HelperLayerContributor) Name() string { - return filepath.Base(h.Path) + }) } diff --git a/layer_test.go b/layer_test.go index f1c92eb..bc10d77 100644 --- a/layer_test.go +++ b/layer_test.go @@ -30,6 +30,7 @@ import ( "github.com/sclevine/spec" "github.com/paketo-buildpacks/libpak" + "github.com/paketo-buildpacks/libpak/bard" ) func testLayer(t *testing.T, context spec.G, it spec.S) { @@ -138,30 +139,154 @@ func testLayer(t *testing.T, context spec.G, it spec.S) { }) it("sets build layer flag", func() { + lc.ExpectedTypes.Build = true layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) { return layer, nil - }, libpak.BuildLayer) + }) Expect(err).NotTo(HaveOccurred()) - Expect(layer.Build).To(BeTrue()) + Expect(layer.LayerTypes.Build).To(BeTrue()) }) it("sets cache layer flag", func() { + lc.ExpectedTypes.Cache = true layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) { return layer, nil - }, libpak.CacheLayer) + }) Expect(err).NotTo(HaveOccurred()) - Expect(layer.Cache).To(BeTrue()) + Expect(layer.LayerTypes.Cache).To(BeTrue()) }) it("sets launch layer flag", func() { + lc.ExpectedTypes.Launch = true + layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) { + return layer, nil + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(layer.LayerTypes.Launch).To(BeTrue()) + }) + + it("sets layer flags regardless of caching behavior (required for 0.6 API)", func() { + lc.ExpectedTypes.Launch = true + lc.ExpectedTypes.Cache = true + lc.ExpectedTypes.Build = true + + layer.Metadata = map[string]interface{}{ + "alpha": "test-alpha", + "bravo": map[string]interface{}{ + "bravo-1": "test-bravo-1", + "bravo-2": "test-bravo-2", + }, + } + + var called bool + layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) { + called = true return layer, nil - }, libpak.LaunchLayer) + }) Expect(err).NotTo(HaveOccurred()) + Expect(called).To(BeFalse()) + + Expect(layer.LayerTypes.Launch).To(BeTrue()) + Expect(layer.LayerTypes.Cache).To(BeTrue()) + Expect(layer.LayerTypes.Build).To(BeTrue()) + }) + }) + + context("NewDependencyLayer", func() { + var dep libpak.BuildpackDependency + + it.Before(func() { + dep = libpak.BuildpackDependency{ + ID: "test-id", + Name: "test-name", + Version: "1.1.1", + URI: "test-uri", + SHA256: "576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1", + Stacks: []string{"test-stack"}, + Licenses: []libpak.BuildpackDependencyLicense{ + { + Type: "test-type", + URI: "test-uri", + }, + }, + } + }) + it("returns a BOM entry for the layer", func() { + _, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{}) + Expect(entry.Name).To(Equal("test-id")) + Expect(entry.Metadata["name"]).To(Equal("test-name")) + Expect(entry.Metadata["version"]).To(Equal("1.1.1")) + Expect(entry.Metadata["uri"]).To(Equal("test-uri")) + Expect(entry.Metadata["sha256"]).To(Equal("576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1")) + Expect(entry.Metadata["licenses"]).To(Equal([]libpak.BuildpackDependencyLicense{ + { + Type: "test-type", + URI: "test-uri", + }, + })) + }) + context("launch layer type", func() { + it("only sets launch on the entry", func() { + _, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{ + Launch: true, + }) + Expect(entry.Launch).To(BeTrue()) + Expect(entry.Build).To(BeFalse()) + }) + }) + + context("launch and build layer type", func() { + it("sets launch and build on the entry", func() { + _, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{ + Launch: true, + Build: true, + }) + Expect(entry.Launch).To(BeTrue()) + Expect(entry.Build).To(BeTrue()) + }) + }) + + context("launch and cache layer type", func() { + it("sets launch and build on the entry", func() { + _, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{ + Launch: true, + Cache: true, + }) + Expect(entry.Launch).To(BeTrue()) + Expect(entry.Build).To(BeTrue()) + }) + }) - Expect(layer.Launch).To(BeTrue()) + context("build layer type", func() { + it("sets build on the entry", func() { + _, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{ + Build: true, + }) + Expect(entry.Launch).To(BeFalse()) + Expect(entry.Build).To(BeTrue()) + }) + }) + + context("cache layer type", func() { + it("sets build on the entry", func() { + _, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{ + Cache: true, + }) + Expect(entry.Launch).To(BeFalse()) + Expect(entry.Build).To(BeTrue()) + }) + }) + + context("no layer types", func() { + it("sets build on the entry", func() { + _, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{}) + Expect(entry.Launch).To(BeFalse()) + Expect(entry.Build).To(BeTrue()) + }) }) }) @@ -193,8 +318,7 @@ func testLayer(t *testing.T, context spec.G, it spec.S) { layer.Metadata = map[string]interface{}{} - dlc.LayerContributor.ExpectedMetadata = dependency - + dlc.ExpectedMetadata = dependency dlc.Dependency = dependency dlc.DependencyCache.CachePath = layer.Path dlc.DependencyCache.DownloadPath = layer.Path @@ -321,23 +445,62 @@ func testLayer(t *testing.T, context spec.G, it spec.S) { })) }) - it("contributes to buildpack plan", func() { - plan := libcnb.BuildpackPlan{} + it("sets layer flags regardless of caching behavior (required for 0.6 API)", func() { + layer.Metadata = map[string]interface{}{ + "id": dependency.ID, + "name": dependency.Name, + "version": dependency.Version, + "uri": dependency.URI, + "sha256": dependency.SHA256, + "stacks": []interface{}{dependency.Stacks[0]}, + "licenses": []map[string]interface{}{ + { + "type": dependency.Licenses[0].Type, + "uri": dependency.Licenses[0].URI, + }, + }, + } + dlc.ExpectedTypes.Launch = true + dlc.ExpectedTypes.Cache = true + dlc.ExpectedTypes.Build = true + + var called bool + + layer, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { + defer artifact.Close() - _ = libpak.NewDependencyLayerContributor(dependency, libpak.DependencyCache{}, &plan) + called = true + return layer, nil + }) + Expect(err).NotTo(HaveOccurred()) - Expect(plan.Entries).To(ContainElement(libcnb.BuildpackPlanEntry{ - Name: dependency.ID, - Metadata: map[string]interface{}{ - "name": dependency.Name, - "version": dependency.Version, - "layer": dependency.ID, - "uri": dependency.URI, - "sha256": dependency.SHA256, - "stacks": dependency.Stacks, - "licenses": dependency.Licenses, + Expect(called).To(BeFalse()) + + Expect(layer.LayerTypes.Launch).To(BeTrue()) + Expect(layer.LayerTypes.Cache).To(BeTrue()) + Expect(layer.LayerTypes.Build).To(BeTrue()) + }) + }) + + context("NewHelperLayer", func() { + it("returns a BOM entry with version equal to buildpack version", func() { + _, entry := libpak.NewHelperLayer(libcnb.Buildpack{ + Info: libcnb.BuildpackInfo{ + Version: "test-version", }, - })) + }, "test-name-1", "test-name-2") + Expect(entry).To(Equal( + libcnb.BOMEntry{ + Name: filepath.Base("helper"), + Metadata: map[string]interface{}{ + "layer": "helper", + "names": []string{"test-name-1", "test-name-2"}, + "version": "test-version", + }, + Launch: true, + Build: false, + }, + )) }) }) @@ -366,9 +529,12 @@ func testLayer(t *testing.T, context spec.G, it spec.S) { file = filepath.Join(file, "helper") Expect(ioutil.WriteFile(file, []byte{}, 0755)).To(Succeed()) - hlc.Path = file - hlc.LayerContributor.ExpectedMetadata = buildpack.Info - hlc.Names = []string{"test-name-1", "test-name-2"} + hlc = libpak.HelperLayerContributor{ + Path: file, + BuildpackInfo: buildpack.Info, + Logger: bard.Logger{}, + Names: []string{"test-name-1", "test-name-2"}, + } }) it.After(func() { @@ -395,28 +561,17 @@ func testLayer(t *testing.T, context spec.G, it spec.S) { file = filepath.Join(layer.Exec.FilePath("test-name-2")) Expect(file).To(BeAnExistingFile()) Expect(os.Readlink(file)).To(Equal(filepath.Join(layer.Path, "helper"))) - - Expect(layer.Profile["helper"]).To(Equal(fmt.Sprintf(`exec 4<&1 -for_export=$(%s 3>&1 >&4) || exit $? -exec 4<&- -set -a -eval "$for_export" -set +a -exec 4<&1 -for_export=$(%s 3>&1 >&4) || exit $? -exec 4<&- -set -a -eval "$for_export" -set +a`, layer.Exec.FilePath("test-name-1"), layer.Exec.FilePath("test-name-2")))) }) it("does not call function with matching metadata", func() { layer.Metadata = map[string]interface{}{ - "id": buildpack.Info.ID, - "name": buildpack.Info.Name, - "version": buildpack.Info.Version, - "homepage": buildpack.Info.Homepage, - "clear-env": buildpack.Info.ClearEnvironment, + "id": buildpack.Info.ID, + "name": buildpack.Info.Name, + "version": buildpack.Info.Version, + "homepage": buildpack.Info.Homepage, + "clear-env": buildpack.Info.ClearEnvironment, + "description": "", + "keywords": []interface{}{}, } _, err := hlc.Contribute(layer) @@ -431,27 +586,37 @@ set +a`, layer.Exec.FilePath("test-name-1"), layer.Exec.FilePath("test-name-2")) Expect(err).NotTo(HaveOccurred()) Expect(layer.Metadata).To(Equal(map[string]interface{}{ - "id": buildpack.Info.ID, - "name": buildpack.Info.Name, - "version": buildpack.Info.Version, - "homepage": buildpack.Info.Homepage, - "clear-env": buildpack.Info.ClearEnvironment, + "id": buildpack.Info.ID, + "name": buildpack.Info.Name, + "version": buildpack.Info.Version, + "homepage": buildpack.Info.Homepage, + "clear-env": buildpack.Info.ClearEnvironment, + "description": "", + "keywords": []interface{}{}, })) }) - it("contributes to buildpack plan", func() { - plan := libcnb.BuildpackPlan{} + it("sets layer flags regardless of caching behavior (required for 0.6 API)", func() { + layer.Metadata = map[string]interface{}{ + "id": buildpack.Info.ID, + "name": buildpack.Info.Name, + "version": buildpack.Info.Version, + "homepage": buildpack.Info.Homepage, + "clear-env": buildpack.Info.ClearEnvironment, + "description": "", + "keywords": []interface{}{}, + } + // Launch is the only one set & always true - _ = libpak.NewHelperLayerContributor(buildpack, &plan, "test-name-1", "test-name-2") + layer, err := hlc.Contribute(layer) + Expect(err).NotTo(HaveOccurred()) - Expect(plan.Entries).To(ContainElement(libcnb.BuildpackPlanEntry{ - Name: filepath.Base("helper"), - Metadata: map[string]interface{}{ - "layer": "helper", - "names": []string{"test-name-1", "test-name-2"}, - "version": buildpack.Info.Version, - }, - })) + Expect(filepath.Join(layer.Exec.FilePath("test-name-1"))).NotTo(BeAnExistingFile()) + Expect(filepath.Join(layer.Exec.FilePath("test-name-2"))).NotTo(BeAnExistingFile()) + + Expect(layer.LayerTypes.Launch).To(BeTrue()) + Expect(layer.LayerTypes.Cache).To(BeFalse()) + Expect(layer.LayerTypes.Build).To(BeFalse()) }) }) } diff --git a/main_test.go b/main_test.go index 9e5ba78..4140d7d 100644 --- a/main_test.go +++ b/main_test.go @@ -66,7 +66,7 @@ func testMain(t *testing.T, context spec.G, it spec.S) { Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), []byte(` -api = "0.0.0" +api = "0.6" [buildpack] id = "test-id" diff --git a/sherpa/file_listing.go b/sherpa/file_listing.go index e1fec08..ba74cad 100644 --- a/sherpa/file_listing.go +++ b/sherpa/file_listing.go @@ -45,6 +45,21 @@ type result struct { value FileEntry } +// NewFileListingHash generates a sha256 hash from the listing of all entries under the roots +func NewFileListingHash(roots ...string) (string, error) { + files, err := NewFileListing(roots...) + if err != nil { + return "", fmt.Errorf("unable to create file listing\n%w", err) + } + + hash := sha256.New() + for _, file := range files { + hash.Write([]byte(file.Path + file.Mode + file.SHA256 + "\n")) + } + + return hex.EncodeToString(hash.Sum(nil)), nil +} + // NewFileListing generates a listing of all entries under the roots. func NewFileListing(roots ...string) ([]FileEntry, error) { entries := make(chan FileEntry) @@ -69,12 +84,21 @@ func NewFileListing(roots ...string) ([]FileEntry, error) { return nil } + if info.IsDir() && info.Name() == ".git" { + return filepath.SkipDir + } + e := FileEntry{ Path: path, Mode: info.Mode().String(), } - if info.IsDir() { + symlinkToDir, err := isSymlinkToDir(path, info) + if err != nil { + return err + } + + if info.IsDir() || symlinkToDir { results <- result{value: e} return nil } @@ -140,3 +164,25 @@ func process(entry FileEntry) (FileEntry, error) { entry.SHA256 = hex.EncodeToString(s.Sum(nil)) return entry, nil } + +func isSymlinkToDir(symlink string, f os.FileInfo) (bool, error) { + if f.Mode().Type() == os.ModeSymlink { + path, err := os.Readlink(symlink) + if err != nil { + return false, fmt.Errorf("unable to read symlink %s\n%w", symlink, err) + } + + if !filepath.IsAbs(path) { + path = filepath.Join(filepath.Dir(symlink), path) + } + + stat, err := os.Stat(path) + if err != nil { + return false, fmt.Errorf("unable to stat file %s\n%w", path, err) + } + + return stat.IsDir(), nil + } + + return false, nil +} diff --git a/sherpa/file_listing_test.go b/sherpa/file_listing_test.go index 50f4d8a..b62163d 100644 --- a/sherpa/file_listing_test.go +++ b/sherpa/file_listing_test.go @@ -17,6 +17,8 @@ package sherpa_test import ( + "crypto/sha256" + "encoding/hex" "io/ioutil" "os" "path/filepath" @@ -47,6 +49,54 @@ func testFileListing(t *testing.T, context spec.G, it spec.S) { }) it("create listing", func() { + Expect(ioutil.WriteFile(filepath.Join(path, "alpha.txt"), []byte{1}, 0644)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(path, "test-directory"), 0755)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(path, "test-directory", "bravo.txt"), []byte{2}, 0644)).To(Succeed()) + + e, err := sherpa.NewFileListing(path) + Expect(err).NotTo(HaveOccurred()) + + Expect(e).To(HaveLen(3)) + }) + + it("create listing skipping .git folder", func() { + Expect(os.MkdirAll(filepath.Join(path, ".git"), 0755)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(path, ".git", "HEAD"), []byte{1}, 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(path, ".git", "config"), []byte{1}, 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(path, "alpha.txt"), []byte{1}, 0644)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(path, "test-directory"), 0755)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(path, "test-directory", "bravo.txt"), []byte{2}, 0644)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(path, "test-directory", ".git"), 0755)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(path, "test-directory", ".git", "config"), []byte{1}, 0644)).To(Succeed()) + + e, err := sherpa.NewFileListing(path) + Expect(err).NotTo(HaveOccurred()) + + Expect(e).To(HaveLen(3)) + }) + + it("create listing as hash with non-regular file", func() { + Expect(ioutil.WriteFile(filepath.Join(path, "alpha.txt"), []byte{1}, 0644)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(path, "test-directory"), 0755)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(path, "test-directory", "bravo.txt"), []byte{2}, 0644)).To(Succeed()) + Expect(os.Symlink(filepath.Join(path, "test-directory"), filepath.Join(path, "symlink-test-dir"))) + Expect(os.Symlink(filepath.Join(path, "test-directory", "bravo.txt"), filepath.Join(path, "symlink-bravo.txt"))) + Expect(os.Symlink("alpha.txt", filepath.Join(path, "symlink-relative.txt"))) + + e, err := sherpa.NewFileListing(path) + Expect(err).NotTo(HaveOccurred()) + + Expect(e).To(HaveLen(6)) + Expect(e[0].Path).To(HaveSuffix("alpha.txt")) + Expect(e[1].Path).To(HaveSuffix("symlink-bravo.txt")) + Expect(e[2].Path).To(HaveSuffix("symlink-relative.txt")) + Expect(e[3].Path).To(HaveSuffix("symlink-test-dir")) + Expect(e[4].Path).To(HaveSuffix("test-directory")) + Expect(e[5].Path).To(HaveSuffix("bravo.txt")) + Expect(e[1].SHA256).To(Equal(e[5].SHA256)) // symlink to file should have hash of target file + }) + + it("create listing and get SHA256", func() { Expect(ioutil.WriteFile(filepath.Join(path, "alpha.txt"), []byte{}, 0644)).To(Succeed()) Expect(os.MkdirAll(filepath.Join(path, "test-directory"), 0755)).To(Succeed()) Expect(ioutil.WriteFile(filepath.Join(path, "test-directory", "bravo.txt"), []byte{}, 0644)).To(Succeed()) @@ -54,6 +104,14 @@ func testFileListing(t *testing.T, context spec.G, it spec.S) { e, err := sherpa.NewFileListing(path) Expect(err).NotTo(HaveOccurred()) - Expect(e).To(HaveLen(3)) + hash := sha256.New() + for _, file := range e { + hash.Write([]byte(file.Path + file.Mode + file.SHA256 + "\n")) + } + + s, err := sherpa.NewFileListingHash(path) + Expect(err).NotTo(HaveOccurred()) + + Expect(s).To(Equal(hex.EncodeToString(hash.Sum(nil)))) }) }