add unit tests for caching run and copy
This commit is contained in:
		
							parent
							
								
									f8507eb323
								
							
						
					
					
						commit
						2aa481c15e
					
				|  | @ -17,6 +17,7 @@ limitations under the License. | ||||||
| package commands | package commands | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 
 | 
 | ||||||
|  | @ -158,6 +159,7 @@ func (c *CopyCommand) CacheCommand(img v1.Image) DockerCommand { | ||||||
| 	return &CachingCopyCommand{ | 	return &CachingCopyCommand{ | ||||||
| 		img:       img, | 		img:       img, | ||||||
| 		cmd:       c.cmd, | 		cmd:       c.cmd, | ||||||
|  | 		extractFn: util.ExtractFile, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -170,16 +172,23 @@ type CachingCopyCommand struct { | ||||||
| 	img            v1.Image | 	img            v1.Image | ||||||
| 	extractedFiles []string | 	extractedFiles []string | ||||||
| 	cmd            *instructions.CopyCommand | 	cmd            *instructions.CopyCommand | ||||||
|  | 	extractFn      util.ExtractFunction | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (cr *CachingCopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { | func (cr *CachingCopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { | ||||||
| 	logrus.Infof("Found cached layer, extracting to filesystem") | 	logrus.Infof("Found cached layer, extracting to filesystem") | ||||||
| 	var err error | 	var err error | ||||||
| 	cr.extractedFiles, err = util.GetFSFromImage(RootDir, cr.img) | 
 | ||||||
|  | 	if cr.img == nil { | ||||||
|  | 		return errors.New(fmt.Sprintf("cached command image is nil %v", cr.String())) | ||||||
|  | 	} | ||||||
|  | 	cr.extractedFiles, err = util.GetFSFromImage(RootDir, cr.img, cr.extractFn) | ||||||
|  | 
 | ||||||
| 	logrus.Infof("extractedFiles: %s", cr.extractedFiles) | 	logrus.Infof("extractedFiles: %s", cr.extractedFiles) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.Wrap(err, "extracting fs from image") | 		return errors.Wrap(err, "extracting fs from image") | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -188,6 +197,9 @@ func (cr *CachingCopyCommand) FilesToSnapshot() []string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (cr *CachingCopyCommand) String() string { | func (cr *CachingCopyCommand) String() string { | ||||||
|  | 	if cr.cmd == nil { | ||||||
|  | 		return "nil command" | ||||||
|  | 	} | ||||||
| 	return cr.cmd.String() | 	return cr.cmd.String() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ limitations under the License. | ||||||
| package commands | package commands | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"archive/tar" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | @ -215,3 +216,143 @@ func Test_resolveIfSymlink(t *testing.T) { | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func Test_CachingCopyCommand_ExecuteCommand(t *testing.T) { | ||||||
|  | 	tarContent, err := prepareTarFixture([]string{"foo.txt"}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("couldn't prepare tar fixture %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	config := &v1.Config{} | ||||||
|  | 	buildArgs := &dockerfile.BuildArgs{} | ||||||
|  | 
 | ||||||
|  | 	type testCase struct { | ||||||
|  | 		desctiption    string | ||||||
|  | 		expectLayer    bool | ||||||
|  | 		expectErr      bool | ||||||
|  | 		count          *int | ||||||
|  | 		expectedCount  int | ||||||
|  | 		command        *CachingCopyCommand | ||||||
|  | 		extractedFiles []string | ||||||
|  | 		contextFiles   []string | ||||||
|  | 	} | ||||||
|  | 	testCases := []testCase{ | ||||||
|  | 		func() testCase { | ||||||
|  | 			c := &CachingCopyCommand{ | ||||||
|  | 				img: fakeImage{ | ||||||
|  | 					ImageLayers: []v1.Layer{ | ||||||
|  | 						fakeLayer{TarContent: tarContent}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				cmd: &instructions.CopyCommand{ | ||||||
|  | 					SourcesAndDest: []string{ | ||||||
|  | 						"foo.txt", "foo.txt", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			count := 0 | ||||||
|  | 			tc := testCase{ | ||||||
|  | 				desctiption:    "with valid image and valid layer", | ||||||
|  | 				count:          &count, | ||||||
|  | 				expectedCount:  1, | ||||||
|  | 				expectLayer:    true, | ||||||
|  | 				extractedFiles: []string{"/foo.txt"}, | ||||||
|  | 				contextFiles:   []string{"foo.txt"}, | ||||||
|  | 			} | ||||||
|  | 			c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error { | ||||||
|  | 				*tc.count++ | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 			tc.command = c | ||||||
|  | 			return tc | ||||||
|  | 		}(), | ||||||
|  | 		func() testCase { | ||||||
|  | 			c := &CachingCopyCommand{} | ||||||
|  | 			tc := testCase{ | ||||||
|  | 				desctiption: "with no image", | ||||||
|  | 				expectErr:   true, | ||||||
|  | 			} | ||||||
|  | 			c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error { | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 			tc.command = c | ||||||
|  | 			return tc | ||||||
|  | 		}(), | ||||||
|  | 		func() testCase { | ||||||
|  | 			c := &CachingCopyCommand{ | ||||||
|  | 				img: fakeImage{}, | ||||||
|  | 			} | ||||||
|  | 			tc := testCase{ | ||||||
|  | 				desctiption: "with image containing no layers", | ||||||
|  | 			} | ||||||
|  | 			c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error { | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 			tc.command = c | ||||||
|  | 			return tc | ||||||
|  | 		}(), | ||||||
|  | 		func() testCase { | ||||||
|  | 			c := &CachingCopyCommand{ | ||||||
|  | 				img: fakeImage{ | ||||||
|  | 					ImageLayers: []v1.Layer{ | ||||||
|  | 						fakeLayer{}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error { | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 			tc := testCase{ | ||||||
|  | 				desctiption: "with image one layer which has no tar content", | ||||||
|  | 				expectErr:   false, // this one probably should fail but doesn't because of how ExecuteCommand and util.GetFSFromLayers are implemented - cvgw- 2019-11-25
 | ||||||
|  | 				expectLayer: true, | ||||||
|  | 			} | ||||||
|  | 			tc.command = c | ||||||
|  | 			return tc | ||||||
|  | 		}(), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tc := range testCases { | ||||||
|  | 		t.Run(tc.desctiption, func(t *testing.T) { | ||||||
|  | 			c := tc.command | ||||||
|  | 			err := c.ExecuteCommand(config, buildArgs) | ||||||
|  | 			if !tc.expectErr && err != nil { | ||||||
|  | 				t.Errorf("Expected err to be nil but was %v", err) | ||||||
|  | 			} else if tc.expectErr && err == nil { | ||||||
|  | 				t.Error("Expected err but was nil") | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if tc.count != nil { | ||||||
|  | 				if *tc.count != tc.expectedCount { | ||||||
|  | 					t.Errorf("Expected extractFn to be called %v times but was called %v times", tc.expectedCount, *tc.count) | ||||||
|  | 				} | ||||||
|  | 				for _, file := range tc.extractedFiles { | ||||||
|  | 					match := false | ||||||
|  | 					cFiles := c.FilesToSnapshot() | ||||||
|  | 					for _, cFile := range cFiles { | ||||||
|  | 						if file == cFile { | ||||||
|  | 							match = true | ||||||
|  | 							break | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					if !match { | ||||||
|  | 						t.Errorf("Expected extracted files to include %v but did not %v", file, cFiles) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				// CachingCopyCommand does not override BaseCommand
 | ||||||
|  | 				// FilesUseFromContext so this will always return an empty slice and no error
 | ||||||
|  | 				// This seems like it might be a bug as it results in CopyCommands and CachingCopyCommands generating different cache keys - cvgw - 2019-11-27
 | ||||||
|  | 				cmdFiles, err := c.FilesUsedFromContext( | ||||||
|  | 					config, buildArgs, | ||||||
|  | 				) | ||||||
|  | 				if err != nil { | ||||||
|  | 					t.Errorf("failed to get files used from context from command") | ||||||
|  | 				} | ||||||
|  | 				if len(cmdFiles) != 0 { | ||||||
|  | 					t.Errorf("expected files used from context to be empty but was not") | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,88 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | // used for testing in the commands package
 | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 
 | ||||||
|  | 	v1 "github.com/google/go-containerregistry/pkg/v1" | ||||||
|  | 	"github.com/google/go-containerregistry/pkg/v1/types" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type fakeLayer struct { | ||||||
|  | 	TarContent []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f fakeLayer) Digest() (v1.Hash, error) { | ||||||
|  | 	return v1.Hash{}, nil | ||||||
|  | } | ||||||
|  | func (f fakeLayer) DiffID() (v1.Hash, error) { | ||||||
|  | 	return v1.Hash{}, nil | ||||||
|  | } | ||||||
|  | func (f fakeLayer) Compressed() (io.ReadCloser, error) { | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | func (f fakeLayer) Uncompressed() (io.ReadCloser, error) { | ||||||
|  | 	return ioutil.NopCloser(bytes.NewReader(f.TarContent)), nil | ||||||
|  | } | ||||||
|  | func (f fakeLayer) Size() (int64, error) { | ||||||
|  | 	return 0, nil | ||||||
|  | } | ||||||
|  | func (f fakeLayer) MediaType() (types.MediaType, error) { | ||||||
|  | 	return "", nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type fakeImage struct { | ||||||
|  | 	ImageLayers []v1.Layer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f fakeImage) Layers() ([]v1.Layer, error) { | ||||||
|  | 	return f.ImageLayers, nil | ||||||
|  | } | ||||||
|  | func (f fakeImage) MediaType() (types.MediaType, error) { | ||||||
|  | 	return "", nil | ||||||
|  | } | ||||||
|  | func (f fakeImage) Size() (int64, error) { | ||||||
|  | 	return 0, nil | ||||||
|  | } | ||||||
|  | func (f fakeImage) ConfigName() (v1.Hash, error) { | ||||||
|  | 	return v1.Hash{}, nil | ||||||
|  | } | ||||||
|  | func (f fakeImage) ConfigFile() (*v1.ConfigFile, error) { | ||||||
|  | 	return &v1.ConfigFile{}, nil | ||||||
|  | } | ||||||
|  | func (f fakeImage) RawConfigFile() ([]byte, error) { | ||||||
|  | 	return []byte{}, nil | ||||||
|  | } | ||||||
|  | func (f fakeImage) Digest() (v1.Hash, error) { | ||||||
|  | 	return v1.Hash{}, nil | ||||||
|  | } | ||||||
|  | func (f fakeImage) Manifest() (*v1.Manifest, error) { | ||||||
|  | 	return &v1.Manifest{}, nil | ||||||
|  | } | ||||||
|  | func (f fakeImage) RawManifest() ([]byte, error) { | ||||||
|  | 	return []byte{}, nil | ||||||
|  | } | ||||||
|  | func (f fakeImage) LayerByDigest(v1.Hash) (v1.Layer, error) { | ||||||
|  | 	return fakeLayer{}, nil | ||||||
|  | } | ||||||
|  | func (f fakeImage) LayerByDiffID(v1.Hash) (v1.Layer, error) { | ||||||
|  | 	return fakeLayer{}, nil | ||||||
|  | } | ||||||
|  | @ -166,6 +166,7 @@ func (r *RunCommand) CacheCommand(img v1.Image) DockerCommand { | ||||||
| 	return &CachingRunCommand{ | 	return &CachingRunCommand{ | ||||||
| 		img:       img, | 		img:       img, | ||||||
| 		cmd:       r.cmd, | 		cmd:       r.cmd, | ||||||
|  | 		extractFn: util.ExtractFile, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -186,15 +187,21 @@ type CachingRunCommand struct { | ||||||
| 	img            v1.Image | 	img            v1.Image | ||||||
| 	extractedFiles []string | 	extractedFiles []string | ||||||
| 	cmd            *instructions.RunCommand | 	cmd            *instructions.RunCommand | ||||||
|  | 	extractFn      util.ExtractFunction | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (cr *CachingRunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { | func (cr *CachingRunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { | ||||||
| 	logrus.Infof("Found cached layer, extracting to filesystem") | 	logrus.Infof("Found cached layer, extracting to filesystem") | ||||||
| 	var err error | 	var err error | ||||||
| 	cr.extractedFiles, err = util.GetFSFromImage(constants.RootDir, cr.img) | 
 | ||||||
|  | 	if cr.img == nil { | ||||||
|  | 		return errors.New(fmt.Sprintf("command image is nil %v", cr.String())) | ||||||
|  | 	} | ||||||
|  | 	cr.extractedFiles, err = util.GetFSFromImage(constants.RootDir, cr.img, cr.extractFn) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.Wrap(err, "extracting fs from image") | 		return errors.Wrap(err, "extracting fs from image") | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -203,5 +210,8 @@ func (cr *CachingRunCommand) FilesToSnapshot() []string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (cr *CachingRunCommand) String() string { | func (cr *CachingRunCommand) String() string { | ||||||
|  | 	if cr.cmd == nil { | ||||||
|  | 		return "nil command" | ||||||
|  | 	} | ||||||
| 	return cr.cmd.String() | 	return cr.cmd.String() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -16,10 +16,19 @@ limitations under the License. | ||||||
| package commands | package commands | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"archive/tar" | ||||||
|  | 	"bytes" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
| 	"os/user" | 	"os/user" | ||||||
|  | 	"path/filepath" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/GoogleContainerTools/kaniko/pkg/dockerfile" | ||||||
| 	"github.com/GoogleContainerTools/kaniko/testutil" | 	"github.com/GoogleContainerTools/kaniko/testutil" | ||||||
|  | 	v1 "github.com/google/go-containerregistry/pkg/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func Test_addDefaultHOME(t *testing.T) { | func Test_addDefaultHOME(t *testing.T) { | ||||||
|  | @ -120,3 +129,187 @@ func Test_addDefaultHOME(t *testing.T) { | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func prepareTarFixture(fileNames []string) ([]byte, error) { | ||||||
|  | 	dir, err := ioutil.TempDir("/tmp", "tar-fixture") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	content := ` | ||||||
|  | Meow meow meow meow | ||||||
|  | meow meow meow meow | ||||||
|  | ` | ||||||
|  | 	for _, name := range fileNames { | ||||||
|  | 		if err := ioutil.WriteFile(filepath.Join(dir, name), []byte(content), 0777); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	writer := bytes.NewBuffer([]byte{}) | ||||||
|  | 	tw := tar.NewWriter(writer) | ||||||
|  | 	defer tw.Close() | ||||||
|  | 	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if info.IsDir() { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		hdr, err := tar.FileInfoHeader(info, "") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if err := tw.WriteHeader(hdr); err != nil { | ||||||
|  | 			log.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		body, err := ioutil.ReadFile(path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if _, err := tw.Write(body); err != nil { | ||||||
|  | 			log.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return writer.Bytes(), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Test_CachingRunCommand_ExecuteCommand(t *testing.T) { | ||||||
|  | 	tarContent, err := prepareTarFixture([]string{"foo.txt"}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("couldn't prepare tar fixture %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	config := &v1.Config{} | ||||||
|  | 	buildArgs := &dockerfile.BuildArgs{} | ||||||
|  | 
 | ||||||
|  | 	type testCase struct { | ||||||
|  | 		desctiption    string | ||||||
|  | 		expectLayer    bool | ||||||
|  | 		expectErr      bool | ||||||
|  | 		count          *int | ||||||
|  | 		expectedCount  int | ||||||
|  | 		command        *CachingRunCommand | ||||||
|  | 		extractedFiles []string | ||||||
|  | 		contextFiles   []string | ||||||
|  | 	} | ||||||
|  | 	testCases := []testCase{ | ||||||
|  | 		func() testCase { | ||||||
|  | 			c := &CachingRunCommand{ | ||||||
|  | 				img: fakeImage{ | ||||||
|  | 					ImageLayers: []v1.Layer{ | ||||||
|  | 						fakeLayer{TarContent: tarContent}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			count := 0 | ||||||
|  | 			tc := testCase{ | ||||||
|  | 				desctiption:    "with valid image and valid layer", | ||||||
|  | 				count:          &count, | ||||||
|  | 				expectedCount:  1, | ||||||
|  | 				expectLayer:    true, | ||||||
|  | 				extractedFiles: []string{"/foo.txt"}, | ||||||
|  | 				contextFiles:   []string{"foo.txt"}, | ||||||
|  | 			} | ||||||
|  | 			c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error { | ||||||
|  | 				*tc.count++ | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 			tc.command = c | ||||||
|  | 			return tc | ||||||
|  | 		}(), | ||||||
|  | 		func() testCase { | ||||||
|  | 			c := &CachingRunCommand{} | ||||||
|  | 			tc := testCase{ | ||||||
|  | 				desctiption: "with no image", | ||||||
|  | 				expectErr:   true, | ||||||
|  | 			} | ||||||
|  | 			c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error { | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 			tc.command = c | ||||||
|  | 			return tc | ||||||
|  | 		}(), | ||||||
|  | 		func() testCase { | ||||||
|  | 			c := &CachingRunCommand{ | ||||||
|  | 				img: fakeImage{}, | ||||||
|  | 			} | ||||||
|  | 			tc := testCase{ | ||||||
|  | 				desctiption: "with image containing no layers", | ||||||
|  | 			} | ||||||
|  | 			c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error { | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 			tc.command = c | ||||||
|  | 			return tc | ||||||
|  | 		}(), | ||||||
|  | 		func() testCase { | ||||||
|  | 			c := &CachingRunCommand{ | ||||||
|  | 				img: fakeImage{ | ||||||
|  | 					ImageLayers: []v1.Layer{ | ||||||
|  | 						fakeLayer{}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error { | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 			tc := testCase{ | ||||||
|  | 				desctiption: "with image one layer which has no tar content", | ||||||
|  | 				expectErr:   false, // this one probably should fail but doesn't because of how ExecuteCommand and util.GetFSFromLayers are implemented - cvgw- 2019-11-25
 | ||||||
|  | 				expectLayer: true, | ||||||
|  | 			} | ||||||
|  | 			tc.command = c | ||||||
|  | 			return tc | ||||||
|  | 		}(), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tc := range testCases { | ||||||
|  | 		t.Run(tc.desctiption, func(t *testing.T) { | ||||||
|  | 			c := tc.command | ||||||
|  | 			err := c.ExecuteCommand(config, buildArgs) | ||||||
|  | 			if !tc.expectErr && err != nil { | ||||||
|  | 				t.Errorf("Expected err to be nil but was %v", err) | ||||||
|  | 			} else if tc.expectErr && err == nil { | ||||||
|  | 				t.Error("Expected err but was nil") | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if tc.count != nil { | ||||||
|  | 				if *tc.count != tc.expectedCount { | ||||||
|  | 					t.Errorf("Expected extractFn to be called %v times but was called %v times", 1, *tc.count) | ||||||
|  | 				} | ||||||
|  | 				for _, file := range tc.extractedFiles { | ||||||
|  | 					match := false | ||||||
|  | 					cmdFiles := c.extractedFiles | ||||||
|  | 					for _, f := range cmdFiles { | ||||||
|  | 						if file == f { | ||||||
|  | 							match = true | ||||||
|  | 							break | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					if !match { | ||||||
|  | 						t.Errorf("Expected extracted files to include %v but did not %v", file, cmdFiles) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// CachingRunCommand does not override BaseCommand
 | ||||||
|  | 				// FilesUseFromContext so this will always return an empty slice and no error
 | ||||||
|  | 				// This seems like it might be a bug as it results in RunCommands and CachingRunCommands generating different cache keys - cvgw - 2019-11-27
 | ||||||
|  | 				cmdFiles, err := c.FilesUsedFromContext( | ||||||
|  | 					config, buildArgs, | ||||||
|  | 				) | ||||||
|  | 				if err != nil { | ||||||
|  | 					t.Errorf("failed to get files used from context from command") | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if len(cmdFiles) != 0 { | ||||||
|  | 					t.Errorf("expected files used from context to be empty but was not") | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -211,10 +211,13 @@ func (s *stageBuilder) optimize(compositeKey CompositeCache, cfg v1.Config) erro | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return errors.Wrap(err, "failed to hash composite key") | 			return errors.Wrap(err, "failed to hash composite key") | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
| 		logrus.Debugf("optimize: cache key for command %v %v", command.String(), ck) | 		logrus.Debugf("optimize: cache key for command %v %v", command.String(), ck) | ||||||
| 		s.finalCacheKey = ck | 		s.finalCacheKey = ck | ||||||
|  | 
 | ||||||
| 		if command.ShouldCacheOutput() && !stopCache { | 		if command.ShouldCacheOutput() && !stopCache { | ||||||
| 			img, err := s.layerCache.RetrieveLayer(ck) | 			img, err := s.layerCache.RetrieveLayer(ck) | ||||||
|  | 
 | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				logrus.Debugf("Failed to retrieve layer: %s", err) | 				logrus.Debugf("Failed to retrieve layer: %s", err) | ||||||
| 				logrus.Infof("No cached layer found for cmd %s", command.String()) | 				logrus.Infof("No cached layer found for cmd %s", command.String()) | ||||||
|  | @ -247,6 +250,7 @@ func (s *stageBuilder) build() error { | ||||||
| 	} else { | 	} else { | ||||||
| 		compositeKey = NewCompositeCache(s.baseImageDigest) | 		compositeKey = NewCompositeCache(s.baseImageDigest) | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	compositeKey.AddKey(s.opts.BuildArgs...) | 	compositeKey.AddKey(s.opts.BuildArgs...) | ||||||
| 
 | 
 | ||||||
| 	// Apply optimizations to the instructions.
 | 	// Apply optimizations to the instructions.
 | ||||||
|  | @ -269,21 +273,26 @@ func (s *stageBuilder) build() error { | ||||||
| 
 | 
 | ||||||
| 	if shouldUnpack { | 	if shouldUnpack { | ||||||
| 		t := timing.Start("FS Unpacking") | 		t := timing.Start("FS Unpacking") | ||||||
| 		if _, err := util.GetFSFromImage(constants.RootDir, s.image); err != nil { | 
 | ||||||
|  | 		if _, err := util.GetFSFromImage(constants.RootDir, s.image, util.ExtractFile); err != nil { | ||||||
| 			return errors.Wrap(err, "failed to get filesystem from image") | 			return errors.Wrap(err, "failed to get filesystem from image") | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
| 		timing.DefaultRun.Stop(t) | 		timing.DefaultRun.Stop(t) | ||||||
| 	} else { | 	} else { | ||||||
| 		logrus.Info("Skipping unpacking as no commands require it.") | 		logrus.Info("Skipping unpacking as no commands require it.") | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	if err := util.DetectFilesystemWhitelist(constants.WhitelistPath); err != nil { | 	if err := util.DetectFilesystemWhitelist(constants.WhitelistPath); err != nil { | ||||||
| 		return errors.Wrap(err, "failed to check filesystem whitelist") | 		return errors.Wrap(err, "failed to check filesystem whitelist") | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	// Take initial snapshot
 | 	// Take initial snapshot
 | ||||||
| 	t := timing.Start("Initial FS snapshot") | 	t := timing.Start("Initial FS snapshot") | ||||||
| 	if err := s.snapshotter.Init(); err != nil { | 	if err := s.snapshotter.Init(); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	timing.DefaultRun.Stop(t) | 	timing.DefaultRun.Stop(t) | ||||||
| 
 | 
 | ||||||
| 	cacheGroup := errgroup.Group{} | 	cacheGroup := errgroup.Group{} | ||||||
|  | @ -327,6 +336,9 @@ func (s *stageBuilder) build() error { | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return errors.Wrap(err, "failed to hash composite key") | 			return errors.Wrap(err, "failed to hash composite key") | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
|  | 		logrus.Debugf("build: cache key for command %v %v", command.String(), ck) | ||||||
|  | 
 | ||||||
| 		// Push layer to cache (in parallel) now along with new config file
 | 		// Push layer to cache (in parallel) now along with new config file
 | ||||||
| 		if s.opts.Cache && command.ShouldCacheOutput() { | 		if s.opts.Cache && command.ShouldCacheOutput() { | ||||||
| 			cacheGroup.Go(func() error { | 			cacheGroup.Go(func() error { | ||||||
|  | @ -641,7 +653,7 @@ func extractImageToDependencyDir(name string, image v1.Image) error { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	logrus.Debugf("trying to extract to %s", dependencyDir) | 	logrus.Debugf("trying to extract to %s", dependencyDir) | ||||||
| 	_, err := util.GetFSFromImage(dependencyDir, image) | 	_, err := util.GetFSFromImage(dependencyDir, image, util.ExtractFile) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -69,9 +69,17 @@ var volumes = []string{} | ||||||
| 
 | 
 | ||||||
| var excluded []string | var excluded []string | ||||||
| 
 | 
 | ||||||
|  | type ExtractFunction func(string, *tar.Header, io.Reader) error | ||||||
|  | 
 | ||||||
| // GetFSFromImage extracts the layers of img to root
 | // GetFSFromImage extracts the layers of img to root
 | ||||||
| // It returns a list of all files extracted
 | // It returns a list of all files extracted
 | ||||||
| func GetFSFromImage(root string, img v1.Image) ([]string, error) { | func GetFSFromImage(root string, img v1.Image, extract ExtractFunction) ([]string, error) { | ||||||
|  | 	if extract == nil { | ||||||
|  | 		return nil, errors.New("must supply an extract function") | ||||||
|  | 	} | ||||||
|  | 	if img == nil { | ||||||
|  | 		return nil, errors.New("image cannot be nil") | ||||||
|  | 	} | ||||||
| 	if err := DetectFilesystemWhitelist(constants.WhitelistPath); err != nil { | 	if err := DetectFilesystemWhitelist(constants.WhitelistPath); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -114,7 +122,7 @@ func GetFSFromImage(root string, img v1.Image) ([]string, error) { | ||||||
| 				} | 				} | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			if err := extractFile(root, hdr, tr); err != nil { | 			if err := extract(root, hdr, tr); err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			extractedFiles = append(extractedFiles, filepath.Join(root, filepath.Clean(hdr.Name))) | 			extractedFiles = append(extractedFiles, filepath.Join(root, filepath.Clean(hdr.Name))) | ||||||
|  | @ -179,7 +187,7 @@ func unTar(r io.Reader, dest string) ([]string, error) { | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 		if err := extractFile(dest, hdr, tr); err != nil { | 		if err := ExtractFile(dest, hdr, tr); err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 		extractedFiles = append(extractedFiles, dest) | 		extractedFiles = append(extractedFiles, dest) | ||||||
|  | @ -187,7 +195,7 @@ func unTar(r io.Reader, dest string) ([]string, error) { | ||||||
| 	return extractedFiles, nil | 	return extractedFiles, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func extractFile(dest string, hdr *tar.Header, tr io.Reader) error { | func ExtractFile(dest string, hdr *tar.Header, tr io.Reader) error { | ||||||
| 	path := filepath.Join(dest, filepath.Clean(hdr.Name)) | 	path := filepath.Join(dest, filepath.Clean(hdr.Name)) | ||||||
| 	base := filepath.Base(path) | 	base := filepath.Base(path) | ||||||
| 	dir := filepath.Dir(path) | 	dir := filepath.Dir(path) | ||||||
|  |  | ||||||
|  | @ -659,7 +659,7 @@ func TestExtractFile(t *testing.T) { | ||||||
| 			defer os.RemoveAll(r) | 			defer os.RemoveAll(r) | ||||||
| 
 | 
 | ||||||
| 			for _, hdr := range tc.hdrs { | 			for _, hdr := range tc.hdrs { | ||||||
| 				if err := extractFile(r, hdr, bytes.NewReader(tc.contents)); err != nil { | 				if err := ExtractFile(r, hdr, bytes.NewReader(tc.contents)); err != nil { | ||||||
| 					t.Fatal(err) | 					t.Fatal(err) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue