lifecycle/phase/exporter.go

676 lines
23 KiB
Go

package phase
import (
"encoding/json"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/BurntSushi/toml"
"github.com/buildpacks/imgutil"
"github.com/buildpacks/imgutil/local"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/pkg/errors"
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/buildpack"
"github.com/buildpacks/lifecycle/image"
"github.com/buildpacks/lifecycle/internal/fsutil"
"github.com/buildpacks/lifecycle/launch"
"github.com/buildpacks/lifecycle/layers"
"github.com/buildpacks/lifecycle/log"
"github.com/buildpacks/lifecycle/platform"
"github.com/buildpacks/lifecycle/platform/files"
)
type Cache interface {
Exists() bool
Name() string
SetMetadata(metadata platform.CacheMetadata) error
RetrieveMetadata() (platform.CacheMetadata, error)
AddLayerFile(tarPath string, sha string) error
ReuseLayer(sha string) error
RetrieveLayer(sha string) (io.ReadCloser, error)
Commit() error
VerifyLayer(sha string) error
}
type Exporter struct {
Buildpacks []buildpack.GroupElement
LayerFactory LayerFactory
Logger log.Logger
PlatformAPI *api.Version
}
// LayerFactory given a directory on the local filesystem will return a `layers.Layer`
// that can be used to construct an OCI image.
//
//go:generate mockgen -package testmock -destination testmock/layer_factory.go github.com/buildpacks/lifecycle/phase LayerFactory
type LayerFactory interface {
DirLayer(id string, dir string, createdBy string) (layers.Layer, error)
LauncherLayer(path string) (layers.Layer, error)
ProcessTypesLayer(metadata launch.Metadata) (layers.Layer, error)
SliceLayers(dir string, slices []layers.Slice) ([]layers.Layer, error)
TarLayer(withID string, fromTarPath string, createdBy string) (layer layers.Layer, err error)
}
type LauncherConfig struct {
Path string
SBOMDir string
Metadata files.LauncherMetadata
}
type ExportOptions struct {
// WorkingImage is the image to save.
WorkingImage imgutil.Image
// AdditionalNames are additional tags to save to, besides WorkingImage.Name().
AdditionalNames []string
// ExtendedDir is the location of extension-provided layers.
ExtendedDir string
// AppDir is the source directory.
AppDir string
// LayersDir is the location of buildpack-provided layers.
LayersDir string
// OrigMetadata was read from the previous image during the `analyze` phase, and is used to determine if a previously-uploaded layer can be re-used.
OrigMetadata files.LayersMetadata
// LauncherConfig is the launcher config.
LauncherConfig LauncherConfig
// DefaultProcessType is the user-provided default process type.
DefaultProcessType string
// ExecEnv is the target execution environment.
ExecEnv string
// RunImageRef is the run image reference for the layer metadata label.
RunImageRef string
// RunImageForExport is run image metadata for the layer metadata label for Platform API >= 0.12.
RunImageForExport files.RunImageForExport
// Project is project metadata for the project metadata label.
Project files.ProjectMetadata
}
func (e *Exporter) Export(opts ExportOptions) (files.Report, error) {
var err error
defer log.NewMeasurement("Exporter", e.Logger)()
if e.PlatformAPI.AtLeast("0.11") {
if err = e.copyBuildpacksioSBOMs(opts); err != nil {
return files.Report{}, errors.Wrapf(err, "failed to copy buildpacksio SBOMs")
}
}
opts.LayersDir, err = filepath.Abs(opts.LayersDir)
if err != nil {
return files.Report{}, errors.Wrapf(err, "layers dir absolute path")
}
opts.AppDir, err = filepath.Abs(opts.AppDir)
if err != nil {
return files.Report{}, errors.Wrapf(err, "app dir absolute path")
}
meta := files.LayersMetadata{}
meta.RunImage.TopLayer, err = opts.WorkingImage.TopLayer()
if err != nil {
return files.Report{}, errors.Wrap(err, "get run image top layer SHA")
}
meta.RunImage.Reference = opts.RunImageRef
if e.PlatformAPI.AtLeast("0.12") {
meta.RunImage.Image = opts.RunImageForExport.Image
meta.RunImage.Mirrors = opts.RunImageForExport.Mirrors
}
// ensure we always copy the new RunImage into the old stack to preserve old behavior
meta.Stack = &files.Stack{RunImage: opts.RunImageForExport}
buildMD, err := files.Handler.ReadBuildMetadata(launch.GetMetadataFilePath(opts.LayersDir), e.PlatformAPI)
if err != nil {
return files.Report{}, errors.Wrap(err, "read build metadata")
}
// extension-provided layers
if err := e.addExtensionLayers(opts); err != nil {
return files.Report{}, err
}
// buildpack-provided layers
if err := e.addBuildpackLayers(opts, &meta); err != nil {
return files.Report{}, err
}
if e.PlatformAPI.AtLeast("0.8") {
if err := e.addSBOMLaunchLayer(opts, &meta); err != nil {
return files.Report{}, err
}
}
// app layers (split into 1 or more slices)
if err := e.addAppLayers(opts, buildMD.Slices, &meta); err != nil {
return files.Report{}, errors.Wrap(err, "exporting app layers")
}
// launcher layers (launcher binary, launcher config, process symlinks)
if err := e.addLauncherLayers(opts, buildMD, &meta); err != nil {
return files.Report{}, err
}
if err := e.setLabels(opts, meta, buildMD); err != nil {
return files.Report{}, err
}
if err := e.setEnv(opts); err != nil {
return files.Report{}, err
}
e.Logger.Debugf("Setting WORKDIR: '%s'", opts.AppDir)
if err := e.setWorkingDir(opts); err != nil {
return files.Report{}, errors.Wrap(err, "setting workdir")
}
entrypoint, err := e.entrypoint(buildMD.ToLaunchMD(), opts.DefaultProcessType, buildMD.BuildpackDefaultProcessType)
if err != nil {
return files.Report{}, errors.Wrap(err, "determining entrypoint")
}
e.Logger.Debugf("Setting ENTRYPOINT: '%s'", entrypoint)
if err = opts.WorkingImage.SetEntrypoint(entrypoint); err != nil {
return files.Report{}, errors.Wrap(err, "setting entrypoint")
}
if err = opts.WorkingImage.SetCmd(); err != nil { // Note: Command intentionally empty
return files.Report{}, errors.Wrap(err, "setting cmd")
}
report := files.Report{}
report.Build, err = e.makeBuildReport(opts.LayersDir)
if err != nil {
return files.Report{}, err
}
report.Image, err = saveImage(opts.WorkingImage, opts.AdditionalNames, e.Logger)
if err != nil {
return files.Report{}, err
}
return report, nil
}
func SBOMExtensions() []string {
return []string{buildpack.ExtensionCycloneDX, buildpack.ExtensionSPDX, buildpack.ExtensionSyft}
}
func (e *Exporter) copyBuildpacksioSBOMs(opts ExportOptions) error {
targetBuildDir := filepath.Join(opts.LayersDir, "sbom", "build", launch.EscapeID("buildpacksio/lifecycle"))
if err := e.copyDefaultSBOMsForComponent("lifecycle", targetBuildDir); err != nil {
return err
}
targetLaunchDir := filepath.Join(opts.LayersDir, "sbom", "launch", launch.EscapeID("buildpacksio/lifecycle"), "launcher")
switch {
case opts.LauncherConfig.SBOMDir == "" ||
opts.LauncherConfig.SBOMDir == platform.DefaultBuildpacksioSBOMDir:
return e.copyDefaultSBOMsForComponent("launcher", targetLaunchDir)
default:
// if provided a custom launcher SBOM directory, copy all files that look like sboms in that directory
return e.copyLauncherSBOMs(opts.LauncherConfig.SBOMDir, targetLaunchDir)
}
}
func (e *Exporter) copyLauncherSBOMs(srcDir string, dstDir string) error {
sboms, err := fsutil.FilesWithExtensions(srcDir, SBOMExtensions())
if err != nil {
e.Logger.Warnf("Failed to list contents of directory %s", srcDir)
return err
}
if err := os.MkdirAll(dstDir, 0755); err != nil {
return err
}
for _, sbom := range sboms {
dstPath := filepath.Join(dstDir, filepath.Base(sbom))
err = fsutil.Copy(sbom, dstPath)
if err != nil {
e.Logger.Warnf("failed while copying SBOM from %s to %s", sbom, dstPath)
return err
}
}
return nil
}
func (e *Exporter) copyDefaultSBOMsForComponent(component, dstDir string) error {
for _, extension := range SBOMExtensions() {
srcFilename := fmt.Sprintf("%s.%s", component, extension)
srcPath := filepath.Join(platform.DefaultBuildpacksioSBOMDir, srcFilename)
if _, err := os.Stat(srcPath); err != nil {
if os.IsNotExist(err) {
e.Logger.Warnf("Did not find SBOM %s in %s", srcFilename, platform.DefaultBuildpacksioSBOMDir)
continue
} else {
return err
}
}
// create target directory if not exists
if err := os.MkdirAll(dstDir, os.ModePerm); err != nil {
return err
}
dstPath := filepath.Join(dstDir, extension)
e.Logger.Debugf("Copying SBOM %s to %s", srcFilename, dstPath)
if err := fsutil.Copy(srcPath, dstPath); err != nil {
return err
}
}
return nil
}
func (e *Exporter) addExtensionLayers(opts ExportOptions) error {
if !e.PlatformAPI.AtLeast("0.12") || opts.ExtendedDir == "" {
return nil
}
parentPath := filepath.Join(opts.ExtendedDir, "run")
extendedRunImage, extendedRunImagePath, err := image.FromLayoutPath(parentPath)
if err != nil {
return err
}
if extendedRunImage == nil {
return nil
}
allLayers, err := extendedRunImage.Layers()
if err != nil {
return err
}
configFile, err := extendedRunImage.ConfigFile()
if err != nil {
return err
}
history := configFile.History
for idx, l := range allLayers {
layerDigest, blobExists, err := getLayerInfo(l, extendedRunImagePath)
if err != nil {
return fmt.Errorf("failed to get layer info: %w", err)
}
if !blobExists {
// the referenced blob is a run image (base image) layer, not an extension-provided layer
continue
}
h := getHistoryForNonEmptyLayerAtIndex(history, idx)
_, extID := parseHistory(h)
withID := fmt.Sprintf("%s:extended-layer-%d", extID, idx)
fromTarPath := filepath.Join(extendedRunImagePath, "blobs", layerDigest.Algorithm, layerDigest.Hex)
layer, err := e.LayerFactory.TarLayer(withID, fromTarPath, h.CreatedBy)
if err != nil {
return fmt.Errorf("failed to create layer from tar path %s: %w", fromTarPath, err)
}
if _, err = e.addExtensionLayer(opts.WorkingImage, layer); err != nil {
return fmt.Errorf("failed to add or reuse extension layer: %w", err)
}
}
return nil
}
func getLayerInfo(layer v1.Layer, parentDir string) (v1.Hash, bool, error) {
_, err := layer.DiffID()
if err != nil {
if _, ok := err.(*fs.PathError); ok {
return v1.Hash{}, false, nil // the blob doesn't exist
}
return v1.Hash{}, false, err
}
layerDigest, err := layer.Digest()
if err != nil {
return v1.Hash{}, false, err
}
layerPath := filepath.Join(parentDir, "blobs", layerDigest.Algorithm, layerDigest.Hex)
if _, err := os.Stat(layerPath); err != nil {
if os.IsNotExist(err) {
return v1.Hash{}, false, nil // the blob doesn't exist
}
return v1.Hash{}, false, err
}
return layerDigest, true, nil
}
func getHistoryForNonEmptyLayerAtIndex(history []v1.History, idx int) v1.History {
var processed int
for _, h := range history {
if h.EmptyLayer {
continue
}
if processed == idx {
return h
}
processed++
}
return v1.History{}
}
func parseHistory(history v1.History) (string, string) {
r := strings.NewReader(history.CreatedBy)
var (
createdBy, extID string
)
n, err := fmt.Fscanf(r, layers.ExtensionLayerName, &createdBy, &extID)
if err != nil || n != 2 {
return history.CreatedBy, "from extensions"
}
return createdBy, extID
}
func isLocalImage(workingImage imgutil.Image) bool {
if _, ok := workingImage.(*local.Image); ok {
return true
}
return false
}
func (e *Exporter) addBuildpackLayers(opts ExportOptions, meta *files.LayersMetadata) error {
for _, bp := range e.Buildpacks {
bpDir, err := buildpack.ReadLayersDir(opts.LayersDir, bp, e.Logger)
e.Logger.Debugf("Processing buildpack directory: %s", bpDir.Path)
if err != nil {
return errors.Wrapf(err, "reading layers for buildpack '%s'", bp.ID)
}
bpMD := buildpack.LayersMetadata{
ID: bp.ID,
Version: bp.Version,
Layers: map[string]buildpack.LayerMetadata{},
Store: bpDir.Store,
}
for _, fsLayer := range bpDir.FindLayers(buildpack.MadeLaunch) {
fsLayer := fsLayer
e.Logger.Debugf("Processing launch layer: %s", fsLayer.Path())
lmd, err := fsLayer.Read()
if err != nil {
return errors.Wrapf(err, "reading '%s' metadata", fsLayer.Identifier())
}
createdBy := fmt.Sprintf(layers.BuildpackLayerName, fsLayer.Name(), fmt.Sprintf("%s@%s", bp.ID, bp.Version))
if fsLayer.HasLocalContents() {
layer, err := e.LayerFactory.DirLayer(fsLayer.Identifier(), fsLayer.Path(), createdBy)
if err != nil {
return errors.Wrapf(err, "creating layer")
}
origLayerMetadata := opts.OrigMetadata.LayersMetadataFor(bp.ID).Layers[fsLayer.Name()]
lmd.SHA, err = e.addOrReuseBuildpackLayer(opts.WorkingImage, layer, origLayerMetadata.SHA, createdBy)
if err != nil {
return err
}
} else {
if lmd.Cache {
return fmt.Errorf("layer '%s' is cache=true but has no contents", fsLayer.Identifier())
}
origLayerMetadata, ok := opts.OrigMetadata.LayersMetadataFor(bp.ID).Layers[fsLayer.Name()]
if !ok {
return fmt.Errorf("cannot reuse '%s', previous image has no metadata for layer '%s'", fsLayer.Identifier(), fsLayer.Identifier())
}
e.Logger.Infof("Reusing layer '%s'\n", fsLayer.Identifier())
e.Logger.Debugf("Layer '%s' SHA: %s\n", fsLayer.Identifier(), origLayerMetadata.SHA)
if err := opts.WorkingImage.ReuseLayerWithHistory(origLayerMetadata.SHA, v1.History{CreatedBy: createdBy}); err != nil {
return errors.Wrapf(err, "reusing layer: '%s'", fsLayer.Identifier())
}
lmd.SHA = origLayerMetadata.SHA
}
bpMD.Layers[fsLayer.Name()] = lmd
}
meta.Buildpacks = append(meta.Buildpacks, bpMD)
if malformedLayers := bpDir.FindLayers(buildpack.Malformed); len(malformedLayers) > 0 {
ids := make([]string, 0, len(malformedLayers))
for _, ml := range malformedLayers {
ids = append(ids, ml.Identifier())
}
return fmt.Errorf("failed to parse metadata for layers '%s'", ids)
}
}
return nil
}
func (e *Exporter) addLauncherLayers(opts ExportOptions, buildMD *files.BuildMetadata, meta *files.LayersMetadata) error {
launcherLayer, err := e.LayerFactory.LauncherLayer(opts.LauncherConfig.Path)
if err != nil {
return errors.Wrap(err, "creating launcher layers")
}
meta.Launcher.SHA, err = e.addOrReuseBuildpackLayer(opts.WorkingImage, launcherLayer, opts.OrigMetadata.Launcher.SHA, layers.LauncherLayerName)
if err != nil {
return errors.Wrap(err, "exporting launcher configLayer")
}
configLayer, err := e.LayerFactory.DirLayer("buildpacksio/lifecycle:config", filepath.Join(opts.LayersDir, "config"), layers.LauncherConfigLayerName)
if err != nil {
return errors.Wrapf(err, "creating layer '%s'", configLayer.ID)
}
meta.Config.SHA, err = e.addOrReuseBuildpackLayer(opts.WorkingImage, configLayer, opts.OrigMetadata.Config.SHA, layers.LauncherConfigLayerName)
if err != nil {
return errors.Wrap(err, "exporting config layer")
}
if err := e.launcherConfig(opts, buildMD, meta); err != nil {
return err
}
return nil
}
func (e *Exporter) addAppLayers(opts ExportOptions, slices []layers.Slice, meta *files.LayersMetadata) error {
// creating app layers (slices + app dir)
e.Logger.Debugf("Adding app layers")
sliceLayers, err := e.LayerFactory.SliceLayers(opts.AppDir, slices)
if err != nil {
return errors.Wrap(err, "creating app layers")
}
var numberOfReusedLayers int
for _, slice := range sliceLayers {
e.Logger.Debugf("Layer '%s' SHA: %s\n", slice.ID, slice.Digest)
var err error
found := false
for _, previous := range opts.OrigMetadata.App {
if slice.Digest == previous.SHA {
found = true
break
}
}
if found {
err = opts.WorkingImage.ReuseLayerWithHistory(slice.Digest, slice.History)
numberOfReusedLayers++
} else {
err = opts.WorkingImage.AddLayerWithDiffIDAndHistory(slice.TarPath, slice.Digest, slice.History)
}
if err != nil {
return err
}
meta.App = append(meta.App, files.LayerMetadata{SHA: slice.Digest})
}
delta := len(sliceLayers) - numberOfReusedLayers
if numberOfReusedLayers > 0 {
e.Logger.Infof("Reused %d/%d app layer(s)\n", numberOfReusedLayers, len(sliceLayers))
}
if delta != 0 {
e.Logger.Infof("Added %d/%d app layer(s)\n", delta, len(sliceLayers))
}
return nil
}
func (e *Exporter) setLabels(opts ExportOptions, meta files.LayersMetadata, buildMD *files.BuildMetadata) error {
data, err := json.Marshal(meta)
if err != nil {
return errors.Wrap(err, "marshall metadata")
}
e.Logger.Infof("Adding label '%s'", platform.LifecycleMetadataLabel)
if err = opts.WorkingImage.SetLabel(platform.LifecycleMetadataLabel, string(data)); err != nil {
return errors.Wrap(err, "set app image metadata label")
}
buildMD.Launcher = opts.LauncherConfig.Metadata
buildJSON, err := json.Marshal(buildMD)
if err != nil {
return errors.Wrap(err, "parse build metadata")
}
e.Logger.Infof("Adding label '%s'", platform.BuildMetadataLabel)
if err := opts.WorkingImage.SetLabel(platform.BuildMetadataLabel, string(buildJSON)); err != nil {
return errors.Wrap(err, "set build image metadata label")
}
projectJSON, err := json.Marshal(opts.Project)
if err != nil {
return errors.Wrap(err, "parse project metadata")
}
e.Logger.Infof("Adding label '%s'", platform.ProjectMetadataLabel)
if err := opts.WorkingImage.SetLabel(platform.ProjectMetadataLabel, string(projectJSON)); err != nil {
return errors.Wrap(err, "set project metadata label")
}
for _, label := range buildMD.Labels {
e.Logger.Infof("Adding label '%s'", label.Key)
if err := opts.WorkingImage.SetLabel(label.Key, label.Value); err != nil {
return errors.Wrapf(err, "set buildpack-provided label '%s'", label.Key)
}
}
// Note: This is gated by Platform API 0.15, not Buildpack API
// The platform controls the Platform API version and determines when this is available
if e.PlatformAPI.AtLeast("0.15") && opts.ExecEnv != "" {
e.Logger.Infof("Adding label '%s'", platform.ExecEnvLabel)
if err := opts.WorkingImage.SetLabel(platform.ExecEnvLabel, opts.ExecEnv); err != nil {
return errors.Wrap(err, "set execution environment label")
}
}
return nil
}
func (e *Exporter) setEnv(opts ExportOptions) error {
e.Logger.Debugf("Setting %s=%s", platform.EnvLayersDir, opts.LayersDir)
if err := opts.WorkingImage.SetEnv(platform.EnvLayersDir, opts.LayersDir); err != nil {
return errors.Wrapf(err, "set app image env %s", platform.EnvLayersDir)
}
e.Logger.Debugf("Setting %s=%s", platform.EnvAppDir, opts.AppDir)
if err := opts.WorkingImage.SetEnv(platform.EnvAppDir, opts.AppDir); err != nil {
return errors.Wrapf(err, "set app image env %s", platform.EnvAppDir)
}
e.Logger.Debugf("Setting %s=%s", platform.EnvPlatformAPI, e.PlatformAPI.String())
if err := opts.WorkingImage.SetEnv(platform.EnvPlatformAPI, e.PlatformAPI.String()); err != nil {
return errors.Wrapf(err, "set app image env %s", platform.EnvAppDir)
}
e.Logger.Debugf("Setting %s=%s", platform.EnvDeprecationMode, platform.ModeQuiet)
if err := opts.WorkingImage.SetEnv(platform.EnvDeprecationMode, platform.ModeQuiet); err != nil {
return errors.Wrapf(err, "set app image env %s", platform.EnvAppDir)
}
path, err := opts.WorkingImage.Env("PATH")
if err != nil {
return errors.Wrap(err, "failed to get PATH from app image")
}
path = strings.Join([]string{launch.ProcessDir, launch.LifecycleDir, path}, string(os.PathListSeparator))
e.Logger.Debugf("Prepending %s and %s to PATH", launch.ProcessDir, launch.LifecycleDir)
if err := opts.WorkingImage.SetEnv("PATH", path); err != nil {
return errors.Wrap(err, "set app image env PATH")
}
return nil
}
func (e *Exporter) setWorkingDir(opts ExportOptions) error {
return opts.WorkingImage.SetWorkingDir(opts.AppDir)
}
func (e *Exporter) entrypoint(launchMD launch.Metadata, userDefaultProcessType, buildpackDefaultProcessType string) (string, error) {
if userDefaultProcessType != "" {
defaultProcess, ok := launchMD.FindProcessType(userDefaultProcessType)
if !ok {
return "", fmt.Errorf("tried to set %s to default but it doesn't exist", userDefaultProcessType)
}
e.Logger.Infof("Setting default process type '%s'", defaultProcess.Type)
return launch.ProcessPath(defaultProcess.Type), nil
}
if buildpackDefaultProcessType == "" {
e.Logger.Info("no default process type")
return launch.LauncherPath, nil
}
e.Logger.Infof("Setting default process type '%s'", buildpackDefaultProcessType)
return launch.ProcessPath(buildpackDefaultProcessType), nil
}
func (e *Exporter) launcherConfig(opts ExportOptions, buildMD *files.BuildMetadata, meta *files.LayersMetadata) error {
launchMD := launch.Metadata{
Processes: buildMD.Processes,
}
if len(buildMD.Processes) > 0 {
processTypesLayer, err := e.LayerFactory.ProcessTypesLayer(launchMD)
if err != nil {
return errors.Wrapf(err, "creating layer '%s'", processTypesLayer.ID)
}
meta.ProcessTypes.SHA, err = e.addOrReuseBuildpackLayer(opts.WorkingImage, processTypesLayer, opts.OrigMetadata.ProcessTypes.SHA, layers.ProcessTypesLayerName)
if err != nil {
return errors.Wrapf(err, "exporting layer '%s'", processTypesLayer.ID)
}
}
return nil
}
func (e *Exporter) addOrReuseBuildpackLayer(image imgutil.Image, layer layers.Layer, previousSHA, createdBy string) (string, error) {
layer, err := e.LayerFactory.DirLayer(layer.ID, layer.TarPath, createdBy)
if err != nil {
return "", errors.Wrapf(err, "creating layer '%s'", layer.ID)
}
if layer.Digest == previousSHA {
e.Logger.Infof("Reusing layer '%s'\n", layer.ID)
e.Logger.Debugf("Layer '%s' SHA: %s\n", layer.ID, layer.Digest)
return layer.Digest, image.ReuseLayerWithHistory(previousSHA, layer.History)
}
e.Logger.Infof("Adding layer '%s'\n", layer.ID)
e.Logger.Debugf("Layer '%s' SHA: %s\n", layer.ID, layer.Digest)
return layer.Digest, image.AddLayerWithDiffIDAndHistory(layer.TarPath, layer.Digest, layer.History)
}
func (e *Exporter) addExtensionLayer(image imgutil.Image, layer layers.Layer) (string, error) {
return layer.Digest, image.AddLayerWithDiffIDAndHistory(layer.TarPath, layer.Digest, layer.History)
}
func (e *Exporter) makeBuildReport(layersDir string) (files.BuildReport, error) {
if e.PlatformAPI.AtLeast("0.9") {
return files.BuildReport{}, nil
}
var out []buildpack.BOMEntry
for _, bp := range e.Buildpacks {
var bpBuildReport files.BuildReport
bpBuildTOML := filepath.Join(layersDir, launch.EscapeID(bp.ID), "build.toml")
if _, err := toml.DecodeFile(bpBuildTOML, &bpBuildReport); err != nil && !os.IsNotExist(err) {
return files.BuildReport{}, err
}
out = append(out, buildpack.WithBuildpack(bp, bpBuildReport.BOM)...)
}
return files.BuildReport{BOM: out}, nil
}
func (e *Exporter) addSBOMLaunchLayer(opts ExportOptions, meta *files.LayersMetadata) error {
sbomLaunchDir, err := readLayersSBOM(opts.LayersDir, "launch", e.Logger)
if err != nil {
return errors.Wrap(err, "failed to read layers config sbom")
}
if sbomLaunchDir != nil {
layer, err := e.LayerFactory.DirLayer(sbomLaunchDir.Identifier(), sbomLaunchDir.Path(), layers.SBOMLayerName)
if err != nil {
return errors.Wrapf(err, "creating layer")
}
var originalSHA string
if opts.OrigMetadata.BOM != nil {
originalSHA = opts.OrigMetadata.BOM.SHA
}
sha, err := e.addOrReuseBuildpackLayer(opts.WorkingImage, layer, originalSHA, layers.SBOMLayerName)
if err != nil {
return errors.Wrapf(err, "exporting layer '%s'", layer.ID)
}
meta.BOM = &files.LayerMetadata{SHA: sha}
}
return nil
}