diff --git a/application.go b/application.go index ffa8aba..3aaa8cf 100644 --- a/application.go +++ b/application.go @@ -18,14 +18,12 @@ package libcnb // Application is the user contributed application to build. type Application struct { - // Path is the path to the application. Path string } // Label represents an image label. type Label struct { - // Key is the key of the label. Key string `toml:"key"` @@ -35,7 +33,6 @@ type Label struct { // Process represents metadata about a type of command that can be run. type Process struct { - // Type is the type of the process. Type string `toml:"type"` @@ -62,7 +59,6 @@ type Slice struct { // LaunchTOML represents the contents of launch.toml. type LaunchTOML struct { - // Labels is the collection of image labels contributed by the buildpack. Labels []Label `toml:"labels"` @@ -71,44 +67,20 @@ type LaunchTOML struct { // Slices is the collection of slices contributed by the buildpack. Slices []Slice `toml:"slices"` - - // BOM is a collection of entries for the bill of materials. - BOM []BOMEntry `toml:"bom"` } func (l LaunchTOML) isEmpty() bool { - return len(l.Labels) == 0 && len(l.Processes) == 0 && len(l.Slices) == 0 && len(l.BOM) == 0 + return len(l.Labels) == 0 && len(l.Processes) == 0 && len(l.Slices) == 0 } // BuildTOML represents the contents of build.toml. type BuildTOML struct { - // BOM contains the build-time bill of materials. - BOM []BOMEntry `toml:"bom"` - // Unmet is a collection of buildpack plan entries that should be passed through to subsequent providers. Unmet []UnmetPlanEntry } func (b BuildTOML) isEmpty() bool { - return len(b.BOM) == 0 && len(b.Unmet) == 0 -} - -// BOMEntry contains a bill of materials entry. -type BOMEntry struct { - // Name represents the name of the entry. - Name string `toml:"name"` - - // Metadata is the metadata of the entry. Optional. - Metadata map[string]interface{} `toml:"metadata,omitempty"` - - // Launch indicates whether the given entry is included in app image. If launch is true the entry - // will be added to the app image Bill of Materials. Launch should be true if the entry describes - // the contents of a launch layer or app layer. - Launch bool `toml:"-"` - - // Build indicates whether the given entry is available at build time. If build is true the entry - // will be added to the build Bill of Materials. - Build bool `toml:"-"` + return len(b.Unmet) == 0 } // Store represents the contents of store.toml diff --git a/build.go b/build.go index 12e89b2..f16a949 100644 --- a/build.go +++ b/build.go @@ -32,7 +32,6 @@ import ( // BuildContext contains the inputs to build. type BuildContext struct { - // Application is application to build. Application Application @@ -57,9 +56,6 @@ type BuildContext struct { // BuildResult contains the results of detection. type BuildResult struct { - // BOM contains entries to be appended to the app image Bill of Materials and/or build Bill of Materials. - BOM *BOM - // Labels are the image labels contributed by the buildpack. Labels []Label @@ -80,16 +76,10 @@ type BuildResult struct { Unmet []UnmetPlanEntry } -// BOM contains all Bill of Materials entries -type BOM struct { - Entries []BOMEntry -} - // NewBuildResult creates a new BuildResult instance, initializing empty fields. func NewBuildResult() BuildResult { return BuildResult{ PersistentMetadata: make(map[string]interface{}), - BOM: &BOM{}, } } @@ -100,8 +90,8 @@ func (b BuildResult) String() string { } return fmt.Sprintf( - "{BOM: %+v, Labels:%+v Layers:%s PersistentMetadata:%+v Processes:%+v Slices:%+v, Unmet:%+v}", - b.BOM, b.Labels, l, b.PersistentMetadata, b.PersistentMetadata, b.Slices, b.Unmet, + "{Labels:%+v Layers:%s PersistentMetadata:%+v Processes:%+v Slices:%+v, Unmet:%+v}", + b.Labels, l, b.PersistentMetadata, b.PersistentMetadata, b.Slices, b.Unmet, ) } @@ -283,23 +273,10 @@ func Build(build BuildFunc, options ...Option) { } } - var launchBOM, buildBOM []BOMEntry - if result.BOM != nil { - for _, entry := range result.BOM.Entries { - if entry.Launch { - launchBOM = append(launchBOM, entry) - } - if entry.Build { - buildBOM = append(buildBOM, entry) - } - } - } - launch := LaunchTOML{ Labels: result.Labels, Processes: result.Processes, Slices: result.Slices, - BOM: launchBOM, } if !launch.isEmpty() { @@ -322,7 +299,6 @@ func Build(build BuildFunc, options ...Option) { buildTOML := BuildTOML{ Unmet: result.Unmet, - BOM: buildBOM, } if !buildTOML.isEmpty() { diff --git a/build_test.go b/build_test.go index 1908c71..eaf9a8d 100644 --- a/build_test.go +++ b/build_test.go @@ -451,17 +451,6 @@ version = "1.1.1" it("writes launch.toml", func() { buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) { return libcnb.BuildResult{ - BOM: &libcnb.BOM{Entries: []libcnb.BOMEntry{ - { - Name: "test-launch-bom-entry", - Metadata: map[string]interface{}{"test-key": "test-value"}, - Launch: true, - }, - { - Name: "test-build-bom-entry", - Metadata: map[string]interface{}{"test-key": "test-value"}, - }, - }}, Labels: []libcnb.Label{ { Key: "test-key", @@ -508,13 +497,6 @@ version = "1.1.1" Paths: []string{"test-path"}, }, }, - BOM: []libcnb.BOMEntry{ - { - Name: "test-launch-bom-entry", - Metadata: map[string]interface{}{"test-key": "test-value"}, - Launch: true, - }, - }, })) }) @@ -568,18 +550,6 @@ version = "1.1.1" it("writes build.toml", func() { buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) { return libcnb.BuildResult{ - BOM: &libcnb.BOM{Entries: []libcnb.BOMEntry{ - { - Name: "test-build-bom-entry", - Metadata: map[string]interface{}{"test-key": "test-value"}, - Build: true, - }, - { - Name: "test-launch-bom-entry", - Metadata: map[string]interface{}{"test-key": "test-value"}, - Build: false, - }, - }}, Unmet: []libcnb.UnmetPlanEntry{ { Name: "test-entry", @@ -595,13 +565,6 @@ version = "1.1.1" Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "build.toml"))) Expect(tomlWriter.Calls[0].Arguments[1]).To(Equal(libcnb.BuildTOML{ - BOM: []libcnb.BOMEntry{ - { - Name: "test-build-bom-entry", - Metadata: map[string]interface{}{"test-key": "test-value"}, - Build: true, - }, - }, Unmet: []libcnb.UnmetPlanEntry{ { Name: "test-entry", diff --git a/layer.go b/layer.go index c6688e6..b63d896 100644 --- a/layer.go +++ b/layer.go @@ -28,7 +28,6 @@ import ( // Exec represents the exec.d layer location type Exec struct { - // Path is the path to the exec.d directory. Path string } @@ -68,9 +67,21 @@ func (p Profile) ProcessAddf(processType string, name string, format string, a . p.Addf(filepath.Join(processType, name), format, a...) } +// BOMFormat indicates the format of the SBOM entry +type SBOMFormat int + +const ( + CycloneDXJSON SBOMFormat = iota + SPDXJSON + SyftJSON +) + +func (b SBOMFormat) String() string { + return []string{"cdx.json", "spdx.json", "syft.json"}[b] +} + // Contribute represents a layer managed by the buildpack. type Layer struct { - // LayerTypes indicates the type of layer LayerTypes `toml:"types"` @@ -99,6 +110,11 @@ type Layer struct { Exec Exec `toml:"-"` } +// SBOMPath returns the path to the layer specific SBOM File +func (l Layer) SBOMPath(bt SBOMFormat) string { + return filepath.Join(filepath.Dir(l.Path), fmt.Sprintf("%s.sbom.%s", l.Name, bt)) +} + // LayerTypes describes which types apply to a given layer. A layer may have any combination of Launch, Build, and // Cache types. type LayerTypes struct { @@ -114,7 +130,6 @@ type LayerTypes struct { // Layers represents the layers part of the specification. type Layers struct { - // Path is the layers filesystem location. Path string } @@ -150,3 +165,13 @@ func (l *Layers) Layer(name string) (Layer, error) { return layer, nil } + +// BOMBuildPath returns the full path to the build SBoM file for the buildpack +func (l Layers) BuildSBOMPath(bt SBOMFormat) string { + return filepath.Join(l.Path, fmt.Sprintf("build.sbom.%s", bt)) +} + +// BOMLaunchPath returns the full path to the launch SBoM file for the buildpack +func (l Layers) LaunchSBOMPath(bt SBOMFormat) string { + return filepath.Join(l.Path, fmt.Sprintf("launch.sbom.%s", bt)) +} diff --git a/layer_test.go b/layer_test.go index 5b15ee2..0d6d6e2 100644 --- a/layer_test.go +++ b/layer_test.go @@ -110,6 +110,18 @@ func testLayer(t *testing.T, context spec.G, it spec.S) { Expect(l.Profile).To(Equal(libcnb.Profile{})) }) + it("generates BOM paths", func() { + l, err := layers.Layer("test-name") + Expect(err).NotTo(HaveOccurred()) + + Expect(l.Path).To(Equal(filepath.Join(path, "test-name"))) + Expect(layers.BuildSBOMPath(libcnb.CycloneDXJSON)).To(Equal(filepath.Join(path, "build.sbom.cdx.json"))) + Expect(layers.BuildSBOMPath(libcnb.SPDXJSON)).To(Equal(filepath.Join(path, "build.sbom.spdx.json"))) + Expect(layers.BuildSBOMPath(libcnb.SyftJSON)).To(Equal(filepath.Join(path, "build.sbom.syft.json"))) + Expect(layers.LaunchSBOMPath(libcnb.SyftJSON)).To(Equal(filepath.Join(path, "launch.sbom.syft.json"))) + Expect(l.SBOMPath(libcnb.SyftJSON)).To(Equal(filepath.Join(path, "test-name.sbom.syft.json"))) + }) + it("reads existing 0.5 metadata", func() { Expect(ioutil.WriteFile( filepath.Join(path, "test-name.toml"),