Skip to content

Commit

Permalink
Merge pull request #54 from dmikusa-pivotal/api_05_compat
Browse files Browse the repository at this point in the history
Modifications to enable libcnb to be compatible with both versions 0.5 and 0.6 of the API
  • Loading branch information
ekcasey authored Apr 20, 2021
2 parents 478b931 + f26c239 commit 902eaf0
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 18 deletions.
25 changes: 22 additions & 3 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,9 @@ func Build(builder Builder, options ...Option) {
}
logger.Debugf("Buildpack: %+v", ctx.Buildpack)

if strings.TrimSpace(ctx.Buildpack.API) != "0.6" {
config.exitHandler.Error(errors.New("this version of libcnb is only compatible with buildpack API 0.6"))
API := strings.TrimSpace(ctx.Buildpack.API)
if API != "0.5" && API != "0.6" {
config.exitHandler.Error(errors.New("this version of libcnb is only compatible with buildpack APIs 0.5 and 0.6"))
return
}

Expand Down Expand Up @@ -273,7 +274,16 @@ func Build(builder Builder, options ...Option) {

file = filepath.Join(ctx.Layers.Path, fmt.Sprintf("%s.toml", layer.Name))
logger.Debugf("Writing layer metadata: %s <= %+v", file, layer)
if err = config.tomlWriter.Write(file, layer); err != nil {
var toWrite interface{} = layer
if API == "0.5" {
toWrite = internal.LayerAPI5{
Build: layer.LayerTypes.Build,
Cache: layer.LayerTypes.Cache,
Launch: layer.LayerTypes.Launch,
Metadata: layer.Metadata,
}
}
if err = config.tomlWriter.Write(file, toWrite); err != nil {
config.exitHandler.Error(fmt.Errorf("unable to write layer metadata %s\n%w", file, err))
return
}
Expand Down Expand Up @@ -315,6 +325,15 @@ func Build(builder Builder, options ...Option) {
if !launch.isEmpty() {
file = filepath.Join(ctx.Layers.Path, "launch.toml")
logger.Debugf("Writing application metadata: %s <= %+v", file, launch)

if API == "0.5" {
for _, process := range launch.Processes {
if process.Default {
logger.Info("WARNING: Launch layer is setting default=true, but that is not supported until API version 0.6. This setting will be ignored.")
}
}
}

if err = config.tomlWriter.Write(file, launch); err != nil {
config.exitHandler.Error(fmt.Errorf("unable to write application metadata %s\n%w", file, err))
return
Expand Down
67 changes: 57 additions & 10 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@
package libcnb_test

import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"text/template"

. "github.com/onsi/gomega"
"github.com/sclevine/spec"
"github.com/stretchr/testify/mock"

"github.com/buildpacks/libcnb"
"github.com/buildpacks/libcnb/internal"
"github.com/buildpacks/libcnb/mocks"
)

Expand All @@ -39,13 +42,15 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
builder *mocks.Builder
buildpackPath string
buildpackPlanPath string
bpTOMLContents string
commandPath string
environmentWriter *mocks.EnvironmentWriter
exitHandler *mocks.ExitHandler
layerContributor *mocks.LayerContributor
layersPath string
platformPath string
tomlWriter *mocks.TOMLWriter
buildpackTOML *template.Template

workingDir string
)
Expand All @@ -64,9 +69,8 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(err).NotTo(HaveOccurred())
Expect(os.Setenv("CNB_BUILDPACK_DIR", buildpackPath)).To(Succeed())

Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
[]byte(`
api = "0.6"
bpTOMLContents = `
api = "{{.APIVersion}}"
[buildpack]
id = "test-id"
Expand Down Expand Up @@ -96,9 +100,15 @@ mixins = ["test-name"]
[metadata]
test-key = "test-value"
`),
0644),
).To(Succeed())
`
buildpackTOML, err = template.New("buildpack.toml").Parse(bpTOMLContents)
Expect(err).ToNot(HaveOccurred())

var b bytes.Buffer
err = buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.6"})
Expect(err).ToNot(HaveOccurred())

Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0644)).To(Succeed())

f, err := ioutil.TempFile("", "build-buildpackplan-path")
Expect(err).NotTo(HaveOccurred())
Expand Down Expand Up @@ -171,11 +181,11 @@ test-key = "test-value"
Expect(os.RemoveAll(platformPath)).To(Succeed())
})

context("buildpack API is not 0.6", func() {
context("buildpack API is not 0.5 or 0.6", func() {
it.Before(func() {
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
[]byte(`
api = "0.5"
api = "0.4"
[buildpack]
id = "test-id"
Expand All @@ -193,7 +203,7 @@ version = "1.1.1"
)

Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
"this version of libcnb is only compatible with buildpack API 0.6",
"this version of libcnb is only compatible with buildpack APIs 0.5 and 0.6",
))
})
})
Expand Down Expand Up @@ -389,7 +399,44 @@ version = "1.1.1"
Expect(environmentWriter.Calls[3].Arguments[1]).To(Equal(map[string]string{"test-profile": "test-value"}))
})

it("writes layer metadata", func() {
it("writes 0.5 layer metadata", func() {
var b bytes.Buffer
err := buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.5"})
Expect(err).ToNot(HaveOccurred())

Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0644)).To(Succeed())

layer := libcnb.Layer{
Name: "test-name",
Path: filepath.Join(layersPath, "test-name"),
LayerTypes: libcnb.LayerTypes{
Build: true,
Cache: true,
Launch: true,
},
Metadata: map[string]interface{}{"test-key": "test-value"},
}
layerContributor.On("Contribute", mock.Anything).Return(layer, nil)
layerContributor.On("Name").Return("test-name")
result := libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}
builder.On("Build", mock.Anything).Return(result, nil)

libcnb.Build(builder,
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
libcnb.WithTOMLWriter(tomlWriter),
)

Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "test-name.toml")))

layer5, ok := tomlWriter.Calls[0].Arguments[1].(internal.LayerAPI5)
Expect(ok).To(BeTrue())
Expect(layer5.Build).To(BeTrue())
Expect(layer5.Cache).To(BeTrue())
Expect(layer5.Launch).To(BeTrue())
Expect(layer5.Metadata).To(Equal(map[string]interface{}{"test-key": "test-value"}))
})

it("writes 0.6 layer metadata", func() {
layer := libcnb.Layer{
Name: "test-name",
Path: filepath.Join(layersPath, "test-name"),
Expand Down
5 changes: 3 additions & 2 deletions detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ func Detect(detector Detector, options ...Option) {
}
logger.Debugf("Buildpack: %+v", ctx.Buildpack)

if strings.TrimSpace(ctx.Buildpack.API) != "0.6" {
config.exitHandler.Error(errors.New("this version of libcnb is only compatible with buildpack API 0.6"))
API := strings.TrimSpace(ctx.Buildpack.API)
if API != "0.5" && API != "0.6" {
config.exitHandler.Error(errors.New("this version of libcnb is only compatible with buildpack API 0.5 and 0.6"))
return
}

Expand Down
4 changes: 2 additions & 2 deletions detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ test-key = "test-value"
Expect(os.RemoveAll(platformPath)).To(Succeed())
})

context("buildpack API is not 0.6", func() {
context("buildpack API is not 0.5 or 0.6", func() {
it.Before(func() {
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
[]byte(`
Expand All @@ -157,7 +157,7 @@ version = "1.1.1"
)

Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
"this version of libcnb is only compatible with buildpack API 0.6",
"this version of libcnb is only compatible with buildpack API 0.5 and 0.6",
))
})
})
Expand Down
18 changes: 18 additions & 0 deletions internal/layer_api_5.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package internal

// LayerAPI5 is used for backwards compatibility with serialization/deserialization of the layer toml
type LayerAPI5 struct {
// bool's are inlined to instead of using libcnb.LayerTypes to break a circular package reference, internal -> libcnb -> internal -> ...

// Build indicates that a layer should be used for builds.
Build bool `toml:"build"`

// Cache indicates that a layer should be cached.
Cache bool `toml:"cache"`

// Launch indicates that a layer should be used for launch.
Launch bool `toml:"launch"`

// Metadata is the metadata associated with the layer.
Metadata map[string]interface{} `toml:"metadata"`
}
13 changes: 13 additions & 0 deletions layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"path/filepath"

"github.com/BurntSushi/toml"
"github.com/buildpacks/libcnb/internal"
)

// Exec represents the exec.d layer location
Expand Down Expand Up @@ -146,5 +147,17 @@ func (l *Layers) Layer(name string) (Layer, error) {
return Layer{}, fmt.Errorf("unable to decode layer metadata %s\n%w", f, err)
}

if !layer.Build && !layer.Cache && !layer.Launch {
// if all three are false, that could mean we have a API <= 0.5 TOML file
// try parsing the <= 0.5 API format where these were top level attributes
buf := internal.LayerAPI5{}
if _, err := toml.DecodeFile(f, &buf); err != nil && !os.IsNotExist(err) {
return Layer{}, fmt.Errorf("unable to decode layer metadata %s\n%w", f, err)
}
layer.Build = buf.Build
layer.Cache = buf.Cache
layer.Launch = buf.Launch
}

return layer, nil
}
47 changes: 46 additions & 1 deletion layer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,28 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
Expect(l.Profile).To(Equal(libcnb.Profile{}))
})

it("reads existing metadata", func() {
it("reads existing 0.5 metadata", func() {
Expect(ioutil.WriteFile(
filepath.Join(path, "test-name.toml"),
[]byte(`
launch = true
build = false
[metadata]
test-key = "test-value"
`),
0644),
).To(Succeed())

l, err := layers.Layer("test-name")
Expect(err).NotTo(HaveOccurred())

Expect(l.Metadata).To(Equal(map[string]interface{}{"test-key": "test-value"}))
Expect(l.Launch).To(BeTrue())
Expect(l.Build).To(BeFalse())
})

it("reads existing 0.6 metadata", func() {
Expect(ioutil.WriteFile(
filepath.Join(path, "test-name.toml"),
[]byte(`
Expand All @@ -132,5 +153,29 @@ test-key = "test-value"
Expect(l.Launch).To(BeTrue())
Expect(l.Build).To(BeFalse())
})

it("reads existing 0.6 metadata with launch, build and cache all false", func() {
Expect(ioutil.WriteFile(
filepath.Join(path, "test-name.toml"),
[]byte(`
[types]
launch = false
build = false
cache = false
[metadata]
test-key = "test-value"
`),
0644),
).To(Succeed())

l, err := layers.Layer("test-name")
Expect(err).NotTo(HaveOccurred())

Expect(l.Metadata).To(Equal(map[string]interface{}{"test-key": "test-value"}))
Expect(l.Launch).To(BeFalse())
Expect(l.Build).To(BeFalse())
Expect(l.Cache).To(BeFalse())
})
})
}

0 comments on commit 902eaf0

Please sign in to comment.