Skip to content

Commit 8cd5f9f

Browse files
authored
Add support for imagefs and fix multi-stage cache probing (#25)
1 parent 3465042 commit 8cd5f9f

File tree

5 files changed

+411
-35
lines changed

5 files changed

+411
-35
lines changed

pkg/executor/build.go

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
"github.com/GoogleContainerTools/kaniko/pkg/filesystem"
4545
image_util "github.com/GoogleContainerTools/kaniko/pkg/image"
4646
"github.com/GoogleContainerTools/kaniko/pkg/image/remote"
47+
"github.com/GoogleContainerTools/kaniko/pkg/imagefs"
4748
"github.com/GoogleContainerTools/kaniko/pkg/snapshot"
4849
"github.com/GoogleContainerTools/kaniko/pkg/timing"
4950
"github.com/GoogleContainerTools/kaniko/pkg/util"
@@ -731,9 +732,12 @@ func (s *stageBuilder) saveLayerToImage(layer v1.Layer, createdBy string) error
731732
return err
732733
}
733734

734-
func CalculateDependencies(stages []config.KanikoStage, opts *config.KanikoOptions, stageNameToIdx map[string]string) (map[int][]string, error) {
735+
func CalculateDependencies(stages []config.KanikoStage, opts *config.KanikoOptions, stageNameToIdx map[string]string) (map[int][]string, map[string][]string, error) {
735736
images := []v1.Image{}
736-
depGraph := map[int][]string{}
737+
stageDepGraph := map[int][]string{}
738+
// imageDepGraph tracks stage dependencies from non-stage
739+
// images for use by imagefs to avoid extraction.
740+
imageDepGraph := map[string][]string{}
737741
for _, s := range stages {
738742
ba := dockerfile.NewBuildArgs(opts.BuildArgs)
739743
ba.AddMetaArgs(s.MetaArgs)
@@ -746,12 +750,12 @@ func CalculateDependencies(stages []config.KanikoStage, opts *config.KanikoOptio
746750
} else {
747751
image, err = image_util.RetrieveSourceImage(s, opts)
748752
if err != nil {
749-
return nil, err
753+
return nil, nil, err
750754
}
751755
}
752756
cfg, err := initializeConfig(image, opts)
753757
if err != nil {
754-
return nil, err
758+
return nil, nil, err
755759
}
756760

757761
cmds, err := dockerfile.GetOnBuildInstructions(&cfg.Config, stageNameToIdx)
@@ -761,37 +765,38 @@ func CalculateDependencies(stages []config.KanikoStage, opts *config.KanikoOptio
761765
switch cmd := c.(type) {
762766
case *instructions.CopyCommand:
763767
if cmd.From != "" {
764-
i, err := strconv.Atoi(cmd.From)
765-
if err != nil {
766-
continue
767-
}
768768
resolved, err := util.ResolveEnvironmentReplacementList(cmd.SourcesAndDest.SourcePaths, ba.ReplacementEnvs(cfg.Config.Env), true)
769769
if err != nil {
770-
return nil, err
770+
return nil, nil, err
771+
}
772+
i, err := strconv.Atoi(cmd.From)
773+
if err == nil {
774+
stageDepGraph[i] = append(stageDepGraph[i], resolved...)
775+
} else {
776+
imageDepGraph[cmd.From] = append(imageDepGraph[cmd.From], resolved...)
771777
}
772-
depGraph[i] = append(depGraph[i], resolved...)
773778
}
774779
case *instructions.EnvCommand:
775780
if err := util.UpdateConfigEnv(cmd.Env, &cfg.Config, ba.ReplacementEnvs(cfg.Config.Env)); err != nil {
776-
return nil, err
781+
return nil, nil, err
777782
}
778783
image, err = mutate.Config(image, cfg.Config)
779784
if err != nil {
780-
return nil, err
785+
return nil, nil, err
781786
}
782787
case *instructions.ArgCommand:
783788
for _, arg := range cmd.Args {
784789
k, v, err := commands.ParseArg(arg.Key, arg.Value, cfg.Config.Env, ba)
785790
if err != nil {
786-
return nil, err
791+
return nil, nil, err
787792
}
788793
ba.AddArg(k, v)
789794
}
790795
}
791796
}
792797
images = append(images, image)
793798
}
794-
return depGraph, nil
799+
return stageDepGraph, imageDepGraph, nil
795800
}
796801

797802
// DoBuild executes building the Dockerfile
@@ -816,15 +821,17 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
816821
return nil, err
817822
}
818823

819-
// Some stages may refer to other random images, not previous stages
820-
if err := fetchExtraStages(kanikoStages, opts); err != nil {
821-
return nil, err
822-
}
823-
crossStageDependencies, err := CalculateDependencies(kanikoStages, opts, stageNameToIdx)
824+
crossStageDependencies, imageDependencies, err := CalculateDependencies(kanikoStages, opts, stageNameToIdx)
824825
if err != nil {
825826
return nil, err
826827
}
827828
logrus.Infof("Built cross stage deps: %v", crossStageDependencies)
829+
logrus.Infof("Built image deps: %v", imageDependencies)
830+
831+
// Some stages may refer to other random images, not previous stages
832+
if err := fetchExtraStages(kanikoStages, opts, false, imageDependencies); err != nil {
833+
return nil, errors.Wrap(err, "fetch extra stages failed")
834+
}
828835

829836
var args *dockerfile.BuildArgs
830837

@@ -940,6 +947,12 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
940947
// cache without modifying the filesystem.
941948
// Returns an error if any layers are missing from build cache.
942949
func DoCacheProbe(opts *config.KanikoOptions) (v1.Image, error) {
950+
// Restore the filesystem after we're done since we're using imagefs.
951+
origFS := filesystem.FS
952+
defer func() {
953+
filesystem.SetFS(origFS)
954+
}()
955+
943956
digestToCacheKey := make(map[string]string)
944957
stageIdxToDigest := make(map[string]string)
945958

@@ -959,15 +972,16 @@ func DoCacheProbe(opts *config.KanikoOptions) (v1.Image, error) {
959972
return nil, err
960973
}
961974

962-
// Some stages may refer to other random images, not previous stages
963-
if err := fetchExtraStages(kanikoStages, opts); err != nil {
964-
return nil, err
965-
}
966-
crossStageDependencies, err := CalculateDependencies(kanikoStages, opts, stageNameToIdx)
975+
crossStageDependencies, imageDependencies, err := CalculateDependencies(kanikoStages, opts, stageNameToIdx)
967976
if err != nil {
968977
return nil, err
969978
}
970979
logrus.Infof("Built cross stage deps: %v", crossStageDependencies)
980+
logrus.Infof("Built image deps: %v", imageDependencies)
981+
// Some stages may refer to other random images, not previous stages
982+
if err := fetchExtraStages(kanikoStages, opts, true, imageDependencies); err != nil {
983+
return nil, errors.Wrap(err, "fetch extra stages failed")
984+
}
971985

972986
var args *dockerfile.BuildArgs
973987

@@ -1021,6 +1035,19 @@ func DoCacheProbe(opts *config.KanikoOptions) (v1.Image, error) {
10211035
digestToCacheKey[d.String()] = sb.finalCacheKey
10221036
logrus.Infof("Mapping digest %v to cachekey %v", d.String(), sb.finalCacheKey)
10231037

1038+
if filesToCache, ok := crossStageDependencies[sb.stage.Index]; ok {
1039+
ifs, err := imagefs.New(
1040+
filesystem.FS,
1041+
filepath.Join(config.KanikoDir, strconv.Itoa(sb.stage.Index)),
1042+
sourceImage,
1043+
filesToCache,
1044+
)
1045+
if err != nil {
1046+
return nil, errors.Wrap(err, "could not create image filesystem")
1047+
}
1048+
filesystem.SetFS(ifs)
1049+
}
1050+
10241051
if stage.Final {
10251052
sourceImage, err = mutateCanonicalWithoutLayerEdit(sourceImage)
10261053
if err != nil {
@@ -1143,7 +1170,7 @@ func deduplicatePaths(paths []string) []string {
11431170
return deduped
11441171
}
11451172

1146-
func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) error {
1173+
func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions, cacheProbe bool, imageDependencies map[string][]string) error {
11471174
t := timing.Start("Fetching Extra Stages")
11481175
defer timing.DefaultRun.Stop(t)
11491176

@@ -1177,8 +1204,21 @@ func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) e
11771204
if err := saveStageAsTarball(c.From, sourceImage); err != nil {
11781205
return err
11791206
}
1180-
if err := extractImageToDependencyDir(c.From, sourceImage); err != nil {
1181-
return err
1207+
if !cacheProbe {
1208+
if err := extractImageToDependencyDir(c.From, sourceImage); err != nil {
1209+
return err
1210+
}
1211+
} else {
1212+
ifs, err := imagefs.New(
1213+
filesystem.FS,
1214+
filepath.Join(config.KanikoDir, c.From),
1215+
sourceImage,
1216+
imageDependencies[c.From],
1217+
)
1218+
if err != nil {
1219+
return errors.Wrap(err, "could not create image filesystem")
1220+
}
1221+
filesystem.SetFS(ifs)
11821222
}
11831223
}
11841224
// Store the name of the current stage in the list with names, if applicable.

pkg/executor/build_test.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,10 @@ func TestCalculateDependencies(t *testing.T) {
216216
mockInitConfig func(partial.WithConfigFile, *config.KanikoOptions) (*v1.ConfigFile, error)
217217
}
218218
tests := []struct {
219-
name string
220-
args args
221-
want map[int][]string
219+
name string
220+
args args
221+
want map[int][]string
222+
wantImage map[string][]string
222223
}{
223224
{
224225
name: "no deps",
@@ -359,9 +360,27 @@ COPY --from=second /bar /bat
359360
1: {"/bar"},
360361
},
361362
},
363+
{
364+
name: "dependency from image",
365+
args: args{
366+
dockerfile: `
367+
FROM scratch as target
368+
COPY --from=alpine /etc/alpine-release /etc/alpine-release
369+
`,
370+
},
371+
wantImage: map[string][]string{
372+
"alpine": {"/etc/alpine-release"},
373+
},
374+
},
362375
}
363376
for _, tt := range tests {
364377
t.Run(tt.name, func(t *testing.T) {
378+
if tt.want == nil {
379+
tt.want = map[int][]string{}
380+
}
381+
if tt.wantImage == nil {
382+
tt.wantImage = map[string][]string{}
383+
}
365384
if tt.args.mockInitConfig != nil {
366385
original := initializeConfig
367386
defer func() { initializeConfig = original }()
@@ -385,14 +404,18 @@ COPY --from=second /bar /bat
385404
}
386405
stageNameToIdx := ResolveCrossStageInstructions(kanikoStages)
387406

388-
got, err := CalculateDependencies(kanikoStages, opts, stageNameToIdx)
407+
got, gotImage, err := CalculateDependencies(kanikoStages, opts, stageNameToIdx)
389408
if err != nil {
390409
t.Errorf("got error: %s,", err)
391410
}
392411

393412
if !reflect.DeepEqual(got, tt.want) {
394413
diff := cmp.Diff(got, tt.want)
395-
t.Errorf("CalculateDependencies() = %v, want %v, diff %v", got, tt.want, diff)
414+
t.Errorf("CalculateDependencies() crossStageDependencies = %v, want %v, diff %v", got, tt.want, diff)
415+
}
416+
if !reflect.DeepEqual(gotImage, tt.wantImage) {
417+
diff := cmp.Diff(gotImage, tt.wantImage)
418+
t.Errorf("CalculateDependencies() imageDependencies = %v, wantImage %v, diff %v", gotImage, tt.wantImage, diff)
396419
}
397420
})
398421
}

pkg/executor/cache_probe_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,6 @@ COPY foo/baz.txt copied/
165165
})
166166

167167
t.Run("MultiStage", func(t *testing.T) {
168-
t.Skip("TODO: https://github.com/coder/envbuilder/issues/230")
169-
170168
// Share cache between both builds.
171169
regCache := setupCacheRegistry(t)
172170

@@ -175,10 +173,12 @@ COPY foo/baz.txt copied/
175173
dockerFile := `
176174
FROM scratch as first
177175
COPY foo/bam.txt copied/
176+
COPY foo/bam.link copied/
178177
ENV test test
179178
180179
From scratch as second
181-
COPY --from=first copied/bam.txt output/bam.txt`
180+
COPY --from=first copied/bam.txt output/bam.txt
181+
COPY --from=first copied/bam.link output/bam.link`
182182
err := filesystem.WriteFile(filepath.Join(testDir, "workspace", "Dockerfile"), []byte(dockerFile), 0o755)
183183
testutil.CheckNoError(t, err)
184184
opts := &config.KanikoOptions{

0 commit comments

Comments
 (0)