Skip to content

Commit

Permalink
More cache cleanups: (#397)
Browse files Browse the repository at this point in the history
- move the layer cache to an interface
- refactor the DockerCommand implementations to support Cached and non-cached implementations.
  • Loading branch information
dlorenc authored Nov 1, 2018
1 parent 7afb11c commit 52a6ce6
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 61 deletions.
20 changes: 15 additions & 5 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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
Expand Down
5 changes: 2 additions & 3 deletions pkg/commands/base_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 4 additions & 3 deletions pkg/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
33 changes: 31 additions & 2 deletions pkg/commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
83 changes: 35 additions & 48 deletions pkg/executor/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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
}
}
Expand All @@ -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,
},
},
)
Expand Down

0 comments on commit 52a6ce6

Please sign in to comment.