diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index fca66e631b..af4c7f1228 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -31,9 +31,16 @@ import ( "github.com/sirupsen/logrus" ) -// RetrieveLayer checks the specified cache for a layer with the tag :cacheKey -func RetrieveLayer(opts *config.KanikoOptions, cacheKey string) (v1.Image, error) { - cache, err := Destination(opts, cacheKey) +type LayerCache interface { + RetrieveLayer(string) (v1.Image, error) +} + +type RegistryCache struct { + Opts *config.KanikoOptions +} + +func (rc *RegistryCache) RetrieveLayer(ck string) (v1.Image, error) { + cache, err := Destination(rc.Opts, ck) if err != nil { return nil, errors.Wrap(err, "getting cache destination") } @@ -52,8 +59,11 @@ func RetrieveLayer(opts *config.KanikoOptions, cacheKey string) (v1.Image, error if err != nil { return nil, err } - _, err = img.Layers() - return img, err + // Force the manifest to be populated + if _, err := img.RawManifest(); err != nil { + return nil, err + } + return img, nil } // Destination returns the repo where the layer should be stored diff --git a/pkg/commands/base_command.go b/pkg/commands/base_command.go index d2a746ee6f..90ee3e4451 100644 --- a/pkg/commands/base_command.go +++ b/pkg/commands/base_command.go @@ -22,11 +22,10 @@ import ( ) type BaseCommand struct { - cache bool } -func (b *BaseCommand) CacheCommand() bool { - return b.cache +func (b *BaseCommand) CacheCommand(v1.Image) DockerCommand { + return nil } func (b *BaseCommand) FilesToSnapshot() []string { diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index cbb960e684..9ca6e97ae3 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -24,6 +24,8 @@ import ( "github.com/sirupsen/logrus" ) +type CurrentCacheKey func() (string, error) + type DockerCommand interface { // ExecuteCommand is responsible for: // 1. Making required changes to the filesystem (ex. copying files for ADD/COPY or setting ENV variables) @@ -34,9 +36,8 @@ type DockerCommand interface { String() string // A list of files to snapshot, empty for metadata commands or nil if we don't know FilesToSnapshot() []string - // Return true if this command should be true - // Currently only true for RUN - CacheCommand() bool + // Return a cache-aware implementation of this command, if it exists. + CacheCommand(v1.Image) DockerCommand // Return true if this command depends on the build context. FilesUsedFromContext(*v1.Config, *dockerfile.BuildArgs) ([]string, error) diff --git a/pkg/commands/run.go b/pkg/commands/run.go index d8957786cc..0290e6f7c2 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -153,6 +153,35 @@ func (r *RunCommand) FilesToSnapshot() []string { } // CacheCommand returns true since this command should be cached -func (r *RunCommand) CacheCommand() bool { - return true +func (r *RunCommand) CacheCommand(img v1.Image) DockerCommand { + + return &CachingRunCommand{ + img: img, + cmd: r.cmd, + } +} + +type CachingRunCommand struct { + BaseCommand + img v1.Image + extractedFiles []string + cmd *instructions.RunCommand +} + +func (cr *CachingRunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { + logrus.Infof("Found cached layer, extracting to filesystem") + var err error + cr.extractedFiles, err = util.GetFSFromImage(constants.RootDir, cr.img) + if err != nil { + return errors.Wrap(err, "extracting fs from image") + } + return nil +} + +func (cr *CachingRunCommand) FilesToSnapshot() []string { + return cr.extractedFiles +} + +func (cr *CachingRunCommand) String() string { + return cr.cmd.String() } diff --git a/pkg/executor/build.go b/pkg/executor/build.go index 29a1cf314a..d0a7d932d1 100644 --- a/pkg/executor/build.go +++ b/pkg/executor/build.go @@ -86,33 +86,6 @@ func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage) (*sta }, nil } -// extractCachedLayer will extract the cached layer and append it to the config file -func (s *stageBuilder) extractCachedLayer(layer v1.Image, createdBy string) error { - logrus.Infof("Found cached layer, extracting to filesystem") - extractedFiles, err := util.GetFSFromImage(constants.RootDir, layer) - if err != nil { - return errors.Wrap(err, "extracting fs from image") - } - if _, err := s.snapshotter.TakeSnapshot(extractedFiles); err != nil { - return err - } - logrus.Infof("Appending cached layer to base image") - l, err := layer.Layers() - if err != nil { - return errors.Wrap(err, "getting cached layer from image") - } - s.image, err = mutate.Append(s.image, - mutate.Addendum{ - Layer: l[0], - History: v1.History{ - Author: constants.Author, - CreatedBy: createdBy, - }, - }, - ) - return err -} - func (s *stageBuilder) build() error { // Unpack file system to root if _, err := util.GetFSFromImage(constants.RootDir, s.image); err != nil { @@ -136,6 +109,32 @@ func (s *stageBuilder) build() error { cmds = append(cmds, command) } + layerCache := &cache.RegistryCache{ + Opts: s.opts, + } + if s.opts.Cache { + // Possibly replace commands with their cached implementations. + for i, command := range cmds { + if command == nil { + continue + } + ck, err := compositeKey.Hash() + if err != nil { + return err + } + img, err := layerCache.RetrieveLayer(ck) + if err != nil { + logrus.Infof("No cached layer found for cmd %s", command.String()) + break + } + + if cacheCmd := command.CacheCommand(img); cacheCmd != nil { + logrus.Infof("Using caching version of cmd: %s", command.String()) + cmds[i] = cacheCmd + } + } + } + args := dockerfile.NewBuildArgs(s.opts.BuildArgs) for index, command := range cmds { if command == nil { @@ -157,22 +156,6 @@ func (s *stageBuilder) build() error { } logrus.Info(command.String()) - ck, err := compositeKey.Hash() - if err != nil { - return err - } - - if command.CacheCommand() && s.opts.Cache { - image, err := cache.RetrieveLayer(s.opts, ck) - if err == nil { - if err := s.extractCachedLayer(image, command.String()); err != nil { - return errors.Wrap(err, "extracting cached layer") - } - continue - } - logrus.Info("No cached layer found, executing command...") - } - if err := command.ExecuteCommand(&s.cf.Config, args); err != nil { return err } @@ -196,7 +179,11 @@ func (s *stageBuilder) build() error { if err != nil { return err } - if err := s.saveSnapshot(command, ck, contents); err != nil { + ck, err := compositeKey.Hash() + if err != nil { + return err + } + if err := s.saveSnapshot(command.String(), ck, contents); err != nil { return err } } @@ -228,7 +215,7 @@ func (s *stageBuilder) shouldTakeSnapshot(index int, files []string) bool { return true } -func (s *stageBuilder) saveSnapshot(command commands.DockerCommand, ck string, contents []byte) error { +func (s *stageBuilder) saveSnapshot(createdBy string, ck string, contents []byte) error { if contents == nil { logrus.Info("No files were changed, appending empty layer to config. No layer added to image.") return nil @@ -242,8 +229,8 @@ func (s *stageBuilder) saveSnapshot(command commands.DockerCommand, ck string, c return err } // Push layer to cache now along with new config file - if command.CacheCommand() && s.opts.Cache { - if err := pushLayerToCache(s.opts, ck, layer, command.String()); err != nil { + if s.opts.Cache { + if err := pushLayerToCache(s.opts, ck, layer, createdBy); err != nil { return err } } @@ -252,7 +239,7 @@ func (s *stageBuilder) saveSnapshot(command commands.DockerCommand, ck string, c Layer: layer, History: v1.History{ Author: constants.Author, - CreatedBy: command.String(), + CreatedBy: createdBy, }, }, )