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

Refactor lifecycle/cmd/restorer to move logic out of main and into phase package #1338

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
18 changes: 9 additions & 9 deletions cmd/lifecycle/creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,15 @@ func (c *createCmd) Exec() error {
plan files.Plan
)
cmd.DefaultLogger.Phase("ANALYZING")
analyzerFactory := phase.NewConnectedFactory(
connectedFactory := phase.NewConnectedFactory(
c.PlatformAPI,
&cmd.BuildpackAPIVerifier{},
NewCacheHandler(c.keychain),
files.NewHandler(),
image.NewHandler(c.docker, c.keychain, c.LayoutDir, c.UseLayout, c.InsecureRegistries),
image.NewRegistryHandler(c.keychain, c.InsecureRegistries),
)
analyzer, err := analyzerFactory.NewAnalyzer(c.Inputs(), cmd.DefaultLogger)
analyzer, err := connectedFactory.NewAnalyzer(c.Inputs(), cmd.DefaultLogger)
if err != nil {
return unwrapErrorFailWithMessage(err, "initialize analyzer")
}
Expand All @@ -146,13 +146,13 @@ func (c *createCmd) Exec() error {

// Detect
cmd.DefaultLogger.Phase("DETECTING")
detectorFactory := phase.NewHermeticFactory(
hermeticFactory := phase.NewHermeticFactory(
c.PlatformAPI,
&cmd.BuildpackAPIVerifier{},
files.NewHandler(),
dirStore,
)
detector, err := detectorFactory.NewDetector(c.Inputs(), cmd.DefaultLogger)
detector, err := hermeticFactory.NewDetector(c.Inputs(), cmd.DefaultLogger)
if err != nil {
return unwrapErrorFailWithMessage(err, "initialize detector")
}
Expand All @@ -171,12 +171,12 @@ func (c *createCmd) Exec() error {
// Restore
if !c.SkipLayers || c.PlatformAPI.AtLeast("0.10") {
cmd.DefaultLogger.Phase("RESTORING")
restoreCmd := &restoreCmd{
Platform: c.Platform,
keychain: c.keychain,
restorer, err := connectedFactory.NewRestorer(c.Inputs(), cmd.DefaultLogger, buildpack.Group{})
if err != nil {
return unwrapErrorFailWithMessage(err, "initialize restorer")
}
if err := restoreCmd.restore(analyzedMD.LayersMetadata, group, cacheStore); err != nil {
return err
if err = restorer.RestoreCache(); err != nil {
return cmd.FailErrCode(err, c.CodeFor(platform.RestoreError), "restore")
}
}

Expand Down
209 changes: 14 additions & 195 deletions cmd/lifecycle/restorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@ package main
import (
"errors"
"fmt"
"os"
"path/filepath"

"github.com/buildpacks/imgutil"
"github.com/buildpacks/imgutil/layout"
"github.com/buildpacks/imgutil/layout/sparse"
"github.com/buildpacks/imgutil/remote"
"github.com/docker/docker/client"
"github.com/google/go-containerregistry/pkg/authn"

Expand All @@ -18,16 +12,12 @@ import (
"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/cmd/lifecycle/cli"
"github.com/buildpacks/lifecycle/image"
"github.com/buildpacks/lifecycle/internal/encoding"
"github.com/buildpacks/lifecycle/internal/layer"
"github.com/buildpacks/lifecycle/phase"
"github.com/buildpacks/lifecycle/platform"
"github.com/buildpacks/lifecycle/platform/files"
"github.com/buildpacks/lifecycle/priv"
)

const kanikoDir = "/kaniko"

type restoreCmd struct {
*platform.Platform

Expand Down Expand Up @@ -95,196 +85,25 @@ func (r *restoreCmd) Privileges() error {
}

func (r *restoreCmd) Exec() error {
group, err := files.Handler.ReadGroup(r.GroupPath)
if err != nil {
return err
}
if err = verifyBuildpackApis(group); err != nil {
return err
}

var analyzedMD files.Analyzed
if analyzedMD, err = files.Handler.ReadAnalyzed(r.AnalyzedPath, cmd.DefaultLogger); err == nil {
if r.supportsBuildImageExtension() && r.BuildImageRef != "" {
cmd.DefaultLogger.Debugf("Pulling builder image metadata for %s...", r.BuildImageRef)
remoteBuildImage, err := r.pullSparse(r.BuildImageRef)
if err != nil {
return cmd.FailErr(err, fmt.Sprintf("pull builder image %s", r.BuildImageRef))
}
digestRef, err := remoteBuildImage.Identifier()
if err != nil {
return cmd.FailErr(err, "get digest reference for builder image")
}
analyzedMD.BuildImage = &files.ImageIdentifier{Reference: digestRef.String()}
cmd.DefaultLogger.Debugf("Adding build image info to analyzed metadata: ")
cmd.DefaultLogger.Debugf(encoding.ToJSONMaybe(analyzedMD.BuildImage))
}
var (
runImage imgutil.Image
)
runImageName := analyzedMD.RunImageImage() // FIXME: if we have a digest reference available in `Reference` (e.g., in the non-daemon case) we should use it
if r.supportsRunImageExtension() && needsPulling(analyzedMD.RunImage) {
cmd.DefaultLogger.Debugf("Pulling run image metadata for %s...", runImageName)
runImage, err = r.pullSparse(runImageName)
if err != nil {
return cmd.FailErr(err, fmt.Sprintf("pull run image %s", runImageName))
}
// update analyzed metadata, even if we only needed to pull the image metadata, because
// the extender needs a digest reference in analyzed.toml,
// and daemon images will only have a daemon image ID
if err = r.updateAnalyzedMD(&analyzedMD, runImage); err != nil {
return cmd.FailErr(err, "update analyzed metadata")
}
} else if r.needsUpdating(analyzedMD.RunImage, group) {
cmd.DefaultLogger.Debugf("Updating run image info in analyzed metadata...")
h := image.NewHandler(r.docker, r.keychain, r.LayoutDir, r.UseLayout, r.InsecureRegistries)
runImage, err = h.InitImage(runImageName)
if err != nil || !runImage.Found() {
return cmd.FailErr(err, fmt.Sprintf("get run image %s", runImageName))
}
if err = r.updateAnalyzedMD(&analyzedMD, runImage); err != nil {
return cmd.FailErr(err, "update analyzed metadata")
}
}
if err = files.Handler.WriteAnalyzed(r.AnalyzedPath, &analyzedMD, cmd.DefaultLogger); err != nil {
return cmd.FailErr(err, "write analyzed metadata")
}
} else {
cmd.DefaultLogger.Warnf("Not using analyzed data, usable file not found: %s", err)
}

cacheStore, err := initCache(r.CacheImageRef, r.CacheDir, r.keychain, r.PlatformAPI.LessThan("0.13"))
if err != nil {
return err
}
return r.restore(analyzedMD.LayersMetadata, group, cacheStore)
}

func (r *restoreCmd) updateAnalyzedMD(analyzedMD *files.Analyzed, runImage imgutil.Image) error {
if r.PlatformAPI.LessThan("0.10") {
return nil
}
digestRef, err := runImage.Identifier()
if err != nil {
return cmd.FailErr(err, "get digest reference for run image")
}
var targetData *files.TargetMetadata
if r.PlatformAPI.AtLeast("0.12") {
targetData, err = platform.GetTargetMetadata(runImage)
if err != nil {
return cmd.FailErr(err, "read target data from run image")
}
}
cmd.DefaultLogger.Debugf("Run image info in analyzed metadata was: ")
cmd.DefaultLogger.Debugf(encoding.ToJSONMaybe(analyzedMD.RunImage))
analyzedMD.RunImage.Reference = digestRef.String()
analyzedMD.RunImage.TargetMetadata = targetData
cmd.DefaultLogger.Debugf("Run image info in analyzed metadata is: ")
cmd.DefaultLogger.Debugf(encoding.ToJSONMaybe(analyzedMD.RunImage))
return nil
}

func needsPulling(runImage *files.RunImage) bool {
if runImage == nil {
// sanity check to prevent panic, should be unreachable
return false
}
return runImage.Extend
}

func (r *restoreCmd) needsUpdating(runImage *files.RunImage, group buildpack.Group) bool {
if r.PlatformAPI.LessThan("0.10") {
return false
}
if !group.HasExtensions() {
return false
}
if runImage == nil {
// sanity check to prevent panic, should be unreachable
return false
}
if isPopulated(runImage.TargetMetadata) {
return false
}
return true
}

func isPopulated(metadata *files.TargetMetadata) bool {
return metadata != nil && metadata.OS != ""
}

func (r *restoreCmd) supportsBuildImageExtension() bool {
return r.PlatformAPI.AtLeast("0.10")
}

func (r *restoreCmd) supportsRunImageExtension() bool {
return r.PlatformAPI.AtLeast("0.12") && !r.UseLayout // FIXME: add layout support as part of https://github.com/buildpacks/lifecycle/issues/1102
}

func (r *restoreCmd) supportsTargetData() bool {
return r.PlatformAPI.AtLeast("0.12")
}

func (r *restoreCmd) pullSparse(imageRef string) (imgutil.Image, error) {
baseCacheDir := filepath.Join(kanikoDir, "cache", "base")
if err := os.MkdirAll(baseCacheDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create cache directory: %w", err)
}

var opts []imgutil.ImageOption
opts = append(opts, append(image.GetInsecureOptions(r.InsecureRegistries), remote.FromBaseImage(imageRef))...)

// get remote image
remoteImage, err := remote.NewImage(imageRef, r.keychain, opts...)
if err != nil {
return nil, fmt.Errorf("failed to initialize remote image: %w", err)
}
if !remoteImage.Found() {
return nil, fmt.Errorf("failed to get remote image")
}
// check for usable kaniko dir
if _, err := os.Stat(kanikoDir); err != nil {
if !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to read kaniko directory: %w", err)
}
return nil, nil
}
// save to disk
h, err := remoteImage.UnderlyingImage().Digest()
if err != nil {
return nil, fmt.Errorf("failed to get remote image digest: %w", err)
}
path := filepath.Join(baseCacheDir, h.String())
cmd.DefaultLogger.Debugf("Saving image metadata to %s...", path)
sparseImage, err := sparse.NewImage(
path,
remoteImage.UnderlyingImage(),
layout.WithMediaTypes(imgutil.DefaultTypes),
factory := phase.NewConnectedFactory(
r.PlatformAPI,
&cmd.BuildpackAPIVerifier{},
NewCacheHandler(r.keychain),
files.Handler,
image.NewHandler(r.docker, r.keychain, r.LayoutDir, r.UseLayout, r.InsecureRegistries),
image.NewRegistryHandler(r.keychain, r.InsecureRegistries),
)
restorer, err := factory.NewRestorer(r.Inputs(), cmd.DefaultLogger, buildpack.Group{})
if err != nil {
return nil, fmt.Errorf("failed to initialize sparse image: %w", err)
return unwrapErrorFailWithMessage(err, "initialize restorer")
}
if err = sparseImage.Save(); err != nil {
return nil, fmt.Errorf("failed to save sparse image: %w", err)
if err = restorer.RestoreAnalyzed(); err != nil {
return cmd.FailErrCode(err, r.CodeFor(platform.RestoreError), "restore")
}
return remoteImage, nil
}

func (r *restoreCmd) restore(layerMetadata files.LayersMetadata, group buildpack.Group, cacheStore phase.Cache) error {
restorer := &phase.Restorer{
LayersDir: r.LayersDir,
Buildpacks: group.Group,
Logger: cmd.DefaultLogger,
PlatformAPI: r.PlatformAPI,
LayerMetadataRestorer: layer.NewDefaultMetadataRestorer(r.LayersDir, r.SkipLayers, cmd.DefaultLogger),
LayersMetadata: layerMetadata,
SBOMRestorer: layer.NewSBOMRestorer(layer.SBOMRestorerOpts{
LayersDir: r.LayersDir,
Logger: cmd.DefaultLogger,
Nop: r.SkipLayers,
}, r.PlatformAPI),
if err = files.Handler.WriteAnalyzed(r.AnalyzedPath, &restorer.AnalyzedMD, cmd.DefaultLogger); err != nil {
return cmd.FailErr(err, "write analyzed metadata")
}
if err := restorer.Restore(cacheStore); err != nil {
if err = restorer.RestoreCache(); err != nil {
return cmd.FailErrCode(err, r.CodeFor(platform.RestoreError), "restore")
}
return nil
Expand Down
17 changes: 14 additions & 3 deletions image/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
//go:generate mockgen -package testmock -destination ../phase/testmock/image_handler.go github.com/buildpacks/lifecycle/image Handler
type Handler interface {
InitImage(imageRef string) (imgutil.Image, error)
InitRemoteImage(imageRef string) (imgutil.Image, error)
Kind() string
}

Expand All @@ -22,15 +23,25 @@ type Handler interface {
// - WHEN a docker client is provided then it returns a LocalHandler
// - WHEN an auth.Keychain is provided then it returns a RemoteHandler
// - Otherwise nil is returned
func NewHandler(docker client.CommonAPIClient, keychain authn.Keychain, layoutDir string, useLayout bool, insecureRegistries []string) Handler {
func NewHandler(
docker client.CommonAPIClient,
keychain authn.Keychain,
layoutDir string,
useLayout bool,
insecureRegistries []string,
) Handler {
if layoutDir != "" && useLayout {
return &LayoutHandler{
layoutDir: layoutDir,
layoutDir: layoutDir,
keychain: keychain,
insecureRegistries: insecureRegistries,
}
}
if docker != nil {
return &LocalHandler{
docker: docker,
docker: docker,
keychain: keychain,
insecureRegistries: insecureRegistries,
}
}
if keychain != nil {
Expand Down
25 changes: 24 additions & 1 deletion image/layout_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ import (

"github.com/buildpacks/imgutil"
"github.com/buildpacks/imgutil/layout"
"github.com/buildpacks/imgutil/remote"
"github.com/google/go-containerregistry/pkg/authn"
v1 "github.com/google/go-containerregistry/pkg/v1"
)

const LayoutKind = "layout"

type LayoutHandler struct {
layoutDir string
layoutDir string
keychain authn.Keychain
insecureRegistries []string
}

func (h *LayoutHandler) InitImage(imageRef string) (imgutil.Image, error) {
Expand All @@ -29,6 +33,25 @@ func (h *LayoutHandler) InitImage(imageRef string) (imgutil.Image, error) {
return layout.NewImage(path, layout.FromBaseImagePath(path))
}

// InitRemoteImage TODO
func (h *LayoutHandler) InitRemoteImage(imageRef string) (imgutil.Image, error) {
if imageRef == "" {
return nil, nil
}

options := []imgutil.ImageOption{
remote.FromBaseImage(imageRef),
}

options = append(options, GetInsecureOptions(h.insecureRegistries)...)

return remote.NewImage(
imageRef,
h.keychain,
options...,
)
}

func (h *LayoutHandler) Kind() string {
return LayoutKind
}
Expand Down
Loading
Loading