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

More cache cleanups: #397

Merged
merged 1 commit into from
Nov 1, 2018
Merged
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
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