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

Write sbom.legacy.json files for newer platform api #825

Merged
merged 5 commits into from
Mar 21, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
32 changes: 23 additions & 9 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package lifecycle

import (
"fmt"
"io"
goio "io"
"os"
"path/filepath"
"sort"
Expand All @@ -12,7 +12,8 @@ import (
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/buildpack"
"github.com/buildpacks/lifecycle/env"
io2 "github.com/buildpacks/lifecycle/internal/io"
"github.com/buildpacks/lifecycle/internal/encoding"
"github.com/buildpacks/lifecycle/internal/io"
"github.com/buildpacks/lifecycle/launch"
"github.com/buildpacks/lifecycle/layers"
"github.com/buildpacks/lifecycle/platform"
Expand Down Expand Up @@ -42,7 +43,7 @@ type Builder struct {
Platform Platform
Group buildpack.Group
Plan platform.BuildPlan
Out, Err io.Writer
Out, Err goio.Writer
Logger Logger
BuildpackStore BuildpackStore
}
Expand All @@ -62,7 +63,8 @@ func (b *Builder) Build() (*platform.BuildMetadata, error) {

processMap := newProcessMap()
plan := b.Plan
var bom []buildpack.BOMEntry
var buildBOM []buildpack.BOMEntry
var launchBOM []buildpack.BOMEntry
var bomFiles []buildpack.BOMFile
var slices []layers.Slice
var labels []buildpack.Label
Expand All @@ -89,7 +91,8 @@ func (b *Builder) Build() (*platform.BuildMetadata, error) {
b.Logger.Debug("Updating buildpack processes")
updateDefaultProcesses(br.Processes, api.MustParse(bp.API), b.Platform.API())

bom = append(bom, br.BOM...)
buildBOM = append(buildBOM, br.BuildBOM...)
launchBOM = append(launchBOM, br.LaunchBOM...)
bomFiles = append(bomFiles, br.BOMFiles...)
labels = append(labels, br.Labels...)
plan = plan.Filter(br.MetRequires)
Expand All @@ -107,8 +110,8 @@ func (b *Builder) Build() (*platform.BuildMetadata, error) {

if b.Platform.API().LessThan("0.4") {
config.Logger.Debug("Updating BOM entries")
for i := range bom {
bom[i].ConvertMetadataToVersion()
for i := range launchBOM {
launchBOM[i].ConvertMetadataToVersion()
}
}

Expand All @@ -120,12 +123,23 @@ func (b *Builder) Build() (*platform.BuildMetadata, error) {
}
}

if b.Platform.API().AtLeast("0.9") {
b.Logger.Debug("Creating sBOM files for legacy BOM")
if err := encoding.WriteJSON(filepath.Join(b.LayersDir, "sbom", "launch", "sbom.legacy.json"), launchBOM); err != nil {
return nil, errors.Wrap(err, "encoding launch bom")
}
if err := encoding.WriteJSON(filepath.Join(b.LayersDir, "sbom", "build", "sbom.legacy.json"), buildBOM); err != nil {
return nil, errors.Wrap(err, "encoding build bom")
}
launchBOM = []buildpack.BOMEntry{}
}

b.Logger.Debug("Listing processes")
procList := processMap.list()

b.Logger.Debug("Finished build")
return &platform.BuildMetadata{
BOM: bom,
BOM: launchBOM,
Buildpacks: b.Group.Group,
Labels: labels,
Processes: procList,
Expand Down Expand Up @@ -172,7 +186,7 @@ func (b *Builder) copyBOMFiles(layersDir string, bomFiles []buildpack.BOMFile) e
return err
}

return io2.Copy(bomFile.Path, filepath.Join(targetDir, name))
return io.Copy(bomFile.Path, filepath.Join(targetDir, name))
}
)

Expand Down
148 changes: 137 additions & 11 deletions builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lifecycle_test

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
Expand Down Expand Up @@ -295,7 +296,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {

when("build metadata", func() {
when("bom", func() {
it("should aggregate BOM from each buildpack", func() {
it("omits bom and saves the aggregated legacy boms to <layers>/sbom/", func() {
builder.Group.Group = []buildpack.GroupBuildpack{
{ID: "A", Version: "v1", API: "0.5", Homepage: "Buildpack A Homepage"},
{ID: "B", Version: "v2", API: "0.2"},
Expand All @@ -304,10 +305,19 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
bpA := testmock.NewMockBuildpack(mockCtrl)
buildpackStore.EXPECT().Lookup("A", "v1").Return(bpA, nil)
bpA.EXPECT().Build(gomock.Any(), config, gomock.Any()).Return(buildpack.BuildResult{
BOM: []buildpack.BOMEntry{
BuildBOM: []buildpack.BOMEntry{
{
Require: buildpack.Require{
Name: "dep1",
Name: "build-dep1",
Metadata: map[string]interface{}{"version": "v1"},
},
Buildpack: buildpack.GroupBuildpack{ID: "A", Version: "v1"},
},
},
LaunchBOM: []buildpack.BOMEntry{
{
Require: buildpack.Require{
Name: "launch-dep1",
Metadata: map[string]interface{}{"version": "v1"},
},
Buildpack: buildpack.GroupBuildpack{ID: "A", Version: "v1"},
Expand All @@ -317,10 +327,19 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
bpB := testmock.NewMockBuildpack(mockCtrl)
buildpackStore.EXPECT().Lookup("B", "v2").Return(bpB, nil)
bpB.EXPECT().Build(gomock.Any(), config, gomock.Any()).Return(buildpack.BuildResult{
BOM: []buildpack.BOMEntry{
BuildBOM: []buildpack.BOMEntry{
{
Require: buildpack.Require{
Name: "dep2",
Name: "build-dep2",
Metadata: map[string]interface{}{"version": "v1"},
},
Buildpack: buildpack.GroupBuildpack{ID: "B", Version: "v2"},
},
},
LaunchBOM: []buildpack.BOMEntry{
{
Require: buildpack.Require{
Name: "launch-dep2",
Metadata: map[string]interface{}{"version": "v1"},
},
Buildpack: buildpack.GroupBuildpack{ID: "B", Version: "v2"},
Expand All @@ -332,26 +351,59 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
if err != nil {
t.Fatalf("Unexpected error:\n%s\n", err)
}
if s := cmp.Diff(metadata.BOM, []buildpack.BOMEntry{
if s := cmp.Diff(metadata.BOM, []buildpack.BOMEntry{}); s != "" {
t.Fatalf("Unexpected:\n%s\n", s)
}

t.Log("saves the aggregated legacy launch bom to <layers>/sbom/launch/sbom.legacy.json")
var foundLaunch []buildpack.BOMEntry
launchContents, err := ioutil.ReadFile(filepath.Join(builder.LayersDir, "sbom", "launch", "sbom.legacy.json"))
h.AssertNil(t, err)
h.AssertNil(t, json.Unmarshal(launchContents, &foundLaunch))
expectedLaunch := []buildpack.BOMEntry{
{
Require: buildpack.Require{
Name: "dep1",
Name: "launch-dep1",
Version: "",
Metadata: map[string]interface{}{"version": string("v1")},
},
Buildpack: buildpack.GroupBuildpack{ID: "A", Version: "v1"},
},
{
Require: buildpack.Require{
Name: "dep2",
Name: "launch-dep2",
Version: "",
Metadata: map[string]interface{}{"version": string("v1")},
},
Buildpack: buildpack.GroupBuildpack{ID: "B", Version: "v2"},
},
}
h.AssertEq(t, foundLaunch, expectedLaunch)

t.Log("saves the aggregated legacy build bom to <layers>/sbom/build/sbom.legacy.json")
var foundBuild []buildpack.BOMEntry
buildContents, err := ioutil.ReadFile(filepath.Join(builder.LayersDir, "sbom", "build", "sbom.legacy.json"))
h.AssertNil(t, err)
h.AssertNil(t, json.Unmarshal(buildContents, &foundBuild))
expectedBuild := []buildpack.BOMEntry{
{
Require: buildpack.Require{
Name: "build-dep1",
Version: "",
Metadata: map[string]interface{}{"version": string("v1")},
},
Buildpack: buildpack.GroupBuildpack{ID: "A", Version: "v1"},
},
{
Require: buildpack.Require{
Name: "build-dep2",
Version: "",
Metadata: map[string]interface{}{"version": string("v1")},
},
Buildpack: buildpack.GroupBuildpack{ID: "B", Version: "v2"},
},
}); s != "" {
t.Fatalf("Unexpected:\n%s\n", s)
}
h.AssertEq(t, foundBuild, expectedBuild)
})
})

Expand Down Expand Up @@ -835,7 +887,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
bpA := testmock.NewMockBuildpack(mockCtrl)
buildpackStore.EXPECT().Lookup("A", "v1").Return(bpA, nil)
bpA.EXPECT().Build(gomock.Any(), config, gomock.Any()).Return(buildpack.BuildResult{
BOM: []buildpack.BOMEntry{
LaunchBOM: []buildpack.BOMEntry{
{
Require: buildpack.Require{
Name: "dep1",
Expand Down Expand Up @@ -966,6 +1018,80 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
})
})
})

when("platform api < 0.9", func() {
it.Before(func() {
builder.Platform = platform.NewPlatform("0.8")
})
when("build metadata", func() {
when("bom", func() {
it("returns the aggregated boms from each buildpack", func() {
builder.Group.Group = []buildpack.GroupBuildpack{
{ID: "A", Version: "v1", API: "0.5", Homepage: "Buildpack A Homepage"},
{ID: "B", Version: "v2", API: "0.2"},
}

bpA := testmock.NewMockBuildpack(mockCtrl)
buildpackStore.EXPECT().Lookup("A", "v1").Return(bpA, nil)
bpA.EXPECT().Build(gomock.Any(), config, gomock.Any()).Return(buildpack.BuildResult{
LaunchBOM: []buildpack.BOMEntry{
{
Require: buildpack.Require{
Name: "dep1",
Metadata: map[string]interface{}{"version": "v1"},
},
Buildpack: buildpack.GroupBuildpack{ID: "A", Version: "v1"},
},
},
}, nil)
bpB := testmock.NewMockBuildpack(mockCtrl)
buildpackStore.EXPECT().Lookup("B", "v2").Return(bpB, nil)
bpB.EXPECT().Build(gomock.Any(), config, gomock.Any()).Return(buildpack.BuildResult{
LaunchBOM: []buildpack.BOMEntry{
{
Require: buildpack.Require{
Name: "dep2",
Metadata: map[string]interface{}{"version": "v1"},
},
Buildpack: buildpack.GroupBuildpack{ID: "B", Version: "v2"},
},
},
}, nil)

metadata, err := builder.Build()
if err != nil {
t.Fatalf("Unexpected error:\n%s\n", err)
}
if s := cmp.Diff(metadata.BOM, []buildpack.BOMEntry{
{
Require: buildpack.Require{
Name: "dep1",
Version: "",
Metadata: map[string]interface{}{"version": string("v1")},
},
Buildpack: buildpack.GroupBuildpack{ID: "A", Version: "v1"},
},
{
Require: buildpack.Require{
Name: "dep2",
Version: "",
Metadata: map[string]interface{}{"version": string("v1")},
},
Buildpack: buildpack.GroupBuildpack{ID: "B", Version: "v2"},
},
}); s != "" {
t.Fatalf("Unexpected:\n%s\n", s)
}

t.Log("it does not save the aggregated legacy launch bom to <layers>/sbom/launch/sbom.legacy.json")
h.AssertPathDoesNotExist(t, filepath.Join(builder.LayersDir, "sbom", "launch", "sbom.legacy.json"))

t.Log("it does not save the aggregated legacy build bom to <layers>/sbom/build/sbom.legacy.json")
h.AssertPathDoesNotExist(t, filepath.Join(builder.LayersDir, "sbom", "build", "sbom.legacy.json"))
})
})
})
})
})
}

Expand Down
21 changes: 13 additions & 8 deletions buildpack/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ type BuildConfig struct {
}

type BuildResult struct {
BOM []BOMEntry
BuildBOM []BOMEntry
LaunchBOM []BOMEntry
BOMFiles []BOMFile
Labels []Label
MetRequires []string
Expand Down Expand Up @@ -264,7 +265,7 @@ func (b *Descriptor) readOutputFiles(bpLayersDir, bpPlanPath string, bpPlanIn Pl
}

// set BOM and MetRequires
br.BOM, err = bomValidator.ValidateBOM(bpFromBpInfo, bpPlanOut.toBOM())
br.LaunchBOM, err = bomValidator.ValidateBOM(bpFromBpInfo, bpPlanOut.toBOM())
if err != nil {
return BuildResult{}, err
}
Expand All @@ -284,20 +285,24 @@ func (b *Descriptor) readOutputFiles(bpLayersDir, bpPlanPath string, bpPlanIn Pl
}
} else {
// read build.toml
var bpBuild BuildTOML
var buildTOML BuildTOML
buildPath := filepath.Join(bpLayersDir, "build.toml")
if _, err := toml.DecodeFile(buildPath, &bpBuild); err != nil && !os.IsNotExist(err) {
if _, err := toml.DecodeFile(buildPath, &buildTOML); err != nil && !os.IsNotExist(err) {
return BuildResult{}, err
}
if _, err := bomValidator.ValidateBOM(bpFromBpInfo, bpBuild.BOM); err != nil {
if _, err := bomValidator.ValidateBOM(bpFromBpInfo, buildTOML.BOM); err != nil {
return BuildResult{}, err
}
br.BuildBOM, err = bomValidator.ValidateBOM(bpFromBpInfo, buildTOML.BOM)
if err != nil {
return BuildResult{}, err
}

// set MetRequires
if err := validateUnmet(bpBuild.Unmet, bpPlanIn); err != nil {
if err := validateUnmet(buildTOML.Unmet, bpPlanIn); err != nil {
return BuildResult{}, err
}
br.MetRequires = names(bpPlanIn.filter(bpBuild.Unmet).Entries)
br.MetRequires = names(bpPlanIn.filter(buildTOML.Unmet).Entries)

// set BOM files
br.BOMFiles, err = b.processBOMFiles(bpLayersDir, bpFromBpInfo, bpLayers, logger)
Expand All @@ -313,7 +318,7 @@ func (b *Descriptor) readOutputFiles(bpLayersDir, bpPlanPath string, bpPlanIn Pl
}

// set BOM
br.BOM, err = bomValidator.ValidateBOM(bpFromBpInfo, launchTOML.BOM)
br.LaunchBOM, err = bomValidator.ValidateBOM(bpFromBpInfo, launchTOML.BOM)
if err != nil {
return BuildResult{}, err
}
Expand Down
Loading