lifecycle/phase/analyzer.go

148 lines
4.5 KiB
Go

package phase
import (
"github.com/buildpacks/imgutil"
"github.com/pkg/errors"
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/image"
"github.com/buildpacks/lifecycle/internal/layer"
"github.com/buildpacks/lifecycle/log"
"github.com/buildpacks/lifecycle/platform"
"github.com/buildpacks/lifecycle/platform/files"
)
// Analyzer reads metadata from the previous image (if it exists) and the run image,
// and additionally restores the SBOM layer from the previous image for use later in the build.
type Analyzer struct {
PreviousImage imgutil.Image
RunImage imgutil.Image
Logger log.Logger
SBOMRestorer layer.SBOMRestorer
PlatformAPI *api.Version
}
// NewAnalyzer configures a new Analyzer according to the provided Platform API version.
func (f *ConnectedFactory) NewAnalyzer(inputs platform.LifecycleInputs, logger log.Logger) (*Analyzer, error) {
analyzer := &Analyzer{
Logger: logger,
SBOMRestorer: &layer.NopSBOMRestorer{},
PlatformAPI: f.platformAPI,
}
if err := f.ensureRegistryAccess(inputs); err != nil {
return nil, err
}
if f.platformAPI.AtLeast("0.8") && !inputs.SkipLayers {
analyzer.SBOMRestorer = &layer.DefaultSBOMRestorer{
LayersDir: inputs.LayersDir,
Logger: logger,
}
}
var err error
if analyzer.PreviousImage, err = f.getPreviousImage(inputs.PreviousImageRef, inputs.LaunchCacheDir, logger); err != nil {
return nil, err
}
if analyzer.RunImage, err = f.getRunImage(inputs.RunImageRef); err != nil {
return nil, err
}
return analyzer, nil
}
// Analyze fetches the layers metadata from the previous image and writes analyzed.toml.
func (a *Analyzer) Analyze() (files.Analyzed, error) {
defer log.NewMeasurement("Analyzer", a.Logger)()
var (
err error
appMeta files.LayersMetadata
previousImageRef string
runImageRef string
)
appMeta, previousImageRef, err = a.retrieveAppMetadata()
if err != nil {
return files.Analyzed{}, err
}
if sha := bomSHA(appMeta); sha != "" {
if err = a.SBOMRestorer.RestoreFromPrevious(a.PreviousImage, sha); err != nil {
return files.Analyzed{}, errors.Wrap(err, "retrieving launch SBOM layer")
}
}
var (
atm *files.TargetMetadata
runImageName string
)
if a.RunImage != nil {
runImageRef, err = a.getImageIdentifier(a.RunImage)
if err != nil {
return files.Analyzed{}, errors.Wrap(err, "identifying run image")
}
if a.PlatformAPI.AtLeast("0.12") {
runImageName = a.RunImage.Name()
atm, err = platform.GetTargetMetadata(a.RunImage)
if err != nil {
return files.Analyzed{}, errors.Wrap(err, "unpacking metadata from image")
}
if atm.OS == "" {
return files.Analyzed{}, errors.New("failed to find OS in run image config")
}
}
}
return files.Analyzed{
PreviousImage: &files.ImageIdentifier{
Reference: previousImageRef,
},
RunImage: &files.RunImage{
Reference: runImageRef, // the image identifier, e.g. "s0m3d1g3st" (the image identifier) when exporting to a daemon, or "some.registry/some-repo@sha256:s0m3d1g3st" when exporting to a registry
TargetMetadata: atm,
Image: runImageName, // the provided tag, e.g., "some.registry/some-repo:some-tag" if supported by the platform
},
LayersMetadata: appMeta,
}, nil
}
func (a *Analyzer) getImageIdentifier(image imgutil.Image) (string, error) {
if !image.Found() {
a.Logger.Infof("Image with name %q not found", image.Name())
return "", nil
}
identifier, err := image.Identifier()
if err != nil {
return "", err
}
a.Logger.Debugf("Found image with identifier %q", identifier.String())
return identifier.String(), nil
}
func bomSHA(appMeta files.LayersMetadata) string {
if appMeta.BOM == nil {
return ""
}
return appMeta.BOM.SHA
}
func (a *Analyzer) retrieveAppMetadata() (files.LayersMetadata, string, error) {
if a.PreviousImage == nil {
return files.LayersMetadata{}, "", nil
}
previousImageRef, err := a.getImageIdentifier(a.PreviousImage)
if err != nil {
return files.LayersMetadata{}, "", errors.Wrap(err, "identifying previous image")
}
if a.PreviousImage.Found() && !a.PreviousImage.Valid() {
a.Logger.Infof("Ignoring image %q because it was corrupt", a.PreviousImage.Name())
return files.LayersMetadata{}, "", nil
}
var appMeta files.LayersMetadata
// continue even if the label cannot be decoded
if err = image.DecodeLabel(a.PreviousImage, platform.LifecycleMetadataLabel, &appMeta); err != nil {
return files.LayersMetadata{}, "", nil
}
return appMeta, previousImageRef, nil
}