@@ -444,6 +444,92 @@ func (s *stageBuilder) build() error {
444
444
return nil
445
445
}
446
446
447
+ // fakeBuild is like build(), but does not actually execute the commands or
448
+ // extract files.
449
+ func (s * stageBuilder ) fakeBuild () error {
450
+ // Set the initial cache key to be the base image digest, the build args and the SrcContext.
451
+ var compositeKey * CompositeCache
452
+ if cacheKey , ok := s .digestToCacheKey [s .baseImageDigest ]; ok {
453
+ compositeKey = NewCompositeCache (cacheKey )
454
+ } else {
455
+ compositeKey = NewCompositeCache (s .baseImageDigest )
456
+ }
457
+
458
+ // Apply optimizations to the instructions.
459
+ if err := s .optimize (* compositeKey , s .cf .Config ); err != nil {
460
+ return errors .Wrap (err , "failed to optimize instructions" )
461
+ }
462
+
463
+ for index , command := range s .cmds {
464
+ if command == nil {
465
+ continue
466
+ }
467
+
468
+ // If the command uses files from the context, add them.
469
+ files , err := command .FilesUsedFromContext (& s .cf .Config , s .args )
470
+ if err != nil {
471
+ return errors .Wrap (err , "failed to get files used from context" )
472
+ }
473
+
474
+ if s .opts .Cache {
475
+ * compositeKey , err = s .populateCompositeKey (command , files , * compositeKey , s .args , s .cf .Config .Env )
476
+ if err != nil && s .opts .Cache {
477
+ return err
478
+ }
479
+ }
480
+
481
+ logrus .Info (command .String ())
482
+
483
+ isCacheCommand := func () bool {
484
+ switch command .(type ) {
485
+ case commands.Cached :
486
+ return true
487
+ default :
488
+ return false
489
+ }
490
+ }()
491
+
492
+ if c , ok := command .(commands.FakeExecuteCommand ); ok {
493
+ if err := c .FakeExecuteCommand (& s .cf .Config , s .args ); err != nil {
494
+ return errors .Wrap (err , "failed to execute fake command" )
495
+ }
496
+ } else {
497
+ switch command .(type ) {
498
+ case * commands.UserCommand :
499
+ default :
500
+ return errors .Errorf ("uncached command %T is not supported in fake build" , command )
501
+ }
502
+ if err := command .ExecuteCommand (& s .cf .Config , s .args ); err != nil {
503
+ return errors .Wrap (err , "failed to execute command" )
504
+ }
505
+ }
506
+ files = command .FilesToSnapshot ()
507
+
508
+ if ! s .shouldTakeSnapshot (index , command .MetadataOnly ()) && ! s .opts .ForceBuildMetadata {
509
+ logrus .Debugf ("fakeBuild: skipping snapshot for [%v]" , command .String ())
510
+ continue
511
+ }
512
+ if isCacheCommand {
513
+ v := command .(commands.Cached )
514
+ layer := v .Layer ()
515
+ if err := s .saveLayerToImage (layer , command .String ()); err != nil {
516
+ return errors .Wrap (err , "failed to save layer" )
517
+ }
518
+ } else {
519
+ tarPath , err := s .takeSnapshot (files , command .ShouldDetectDeletedFiles ())
520
+ if err != nil {
521
+ return errors .Wrap (err , "failed to take snapshot" )
522
+ }
523
+
524
+ if err := s .saveSnapshotToImage (command .String (), tarPath ); err != nil {
525
+ return errors .Wrap (err , "failed to save snapshot to image" )
526
+ }
527
+ }
528
+ }
529
+
530
+ return nil
531
+ }
532
+
447
533
func (s * stageBuilder ) takeSnapshot (files []string , shdDelete bool ) (string , error ) {
448
534
var snapshot string
449
535
var err error
@@ -696,6 +782,14 @@ func CalculateDependencies(stages []config.KanikoStage, opts *config.KanikoOptio
696
782
697
783
// DoBuild executes building the Dockerfile
698
784
func DoBuild (opts * config.KanikoOptions ) (v1.Image , error ) {
785
+ // Create Tar archives as canonical to avoid unpack and recompress.
786
+ if opts .Reproducible {
787
+ util .NewTar = util .CanonicalNewTar (time.Time {})
788
+ defer func () {
789
+ util .NewTar = util .DefaultNewTar
790
+ }()
791
+ }
792
+
699
793
t := timing .Start ("Total Build Time" )
700
794
digestToCacheKey := make (map [string ]string )
701
795
stageIdxToDigest := make (map [string ]string )
@@ -787,7 +881,7 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
787
881
return nil , err
788
882
}
789
883
if opts .Reproducible {
790
- sourceImage , err = mutate . Canonical (sourceImage )
884
+ sourceImage , err = mutateCanonical (sourceImage )
791
885
if err != nil {
792
886
return nil , err
793
887
}
@@ -797,6 +891,10 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
797
891
return nil , err
798
892
}
799
893
}
894
+
895
+ digest , _ := sourceImage .Digest ()
896
+ logrus .Infof ("Stage %d built successfully with digest %s: %T" , index , digest .String (), sourceImage )
897
+
800
898
timing .DefaultRun .Stop (t )
801
899
return sourceImage , nil
802
900
}
@@ -833,6 +931,150 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
833
931
return nil , err
834
932
}
835
933
934
+ // DoFakeBuild executes building the Dockerfile
935
+ func DoFakeBuild (opts * config.KanikoOptions ) (v1.Image , error ) {
936
+ digestToCacheKey := make (map [string ]string )
937
+ stageIdxToDigest := make (map [string ]string )
938
+
939
+ stages , metaArgs , err := dockerfile .ParseStages (opts )
940
+ if err != nil {
941
+ return nil , err
942
+ }
943
+
944
+ kanikoStages , err := dockerfile .MakeKanikoStages (opts , stages , metaArgs )
945
+ if err != nil {
946
+ return nil , err
947
+ }
948
+ for _ , stage := range kanikoStages {
949
+ logrus .Infof ("Stage: %#v" , stage )
950
+ }
951
+ stageNameToIdx := ResolveCrossStageInstructions (kanikoStages )
952
+ logrus .Infof ("stageNameToIdx: %v" , stageNameToIdx )
953
+
954
+ fileContext , err := util .NewFileContextFromDockerfile (opts .DockerfilePath , opts .SrcContext )
955
+ if err != nil {
956
+ return nil , err
957
+ }
958
+ logrus .Infof ("File context: %#v" , fileContext )
959
+
960
+ // Some stages may refer to other random images, not previous stages
961
+ if err := fetchExtraStages (kanikoStages , opts ); err != nil {
962
+ return nil , err
963
+ }
964
+ for _ , stage := range kanikoStages {
965
+ logrus .Infof ("Stage (post extra): %#v" , stage )
966
+ }
967
+ crossStageDependencies , err := CalculateDependencies (kanikoStages , opts , stageNameToIdx )
968
+ if err != nil {
969
+ return nil , err
970
+ }
971
+ logrus .Infof ("Built cross stage deps: %v" , crossStageDependencies )
972
+
973
+ var args * dockerfile.BuildArgs
974
+
975
+ for index , stage := range kanikoStages {
976
+ sb , err := newStageBuilder (
977
+ args , opts , stage ,
978
+ crossStageDependencies ,
979
+ digestToCacheKey ,
980
+ stageIdxToDigest ,
981
+ stageNameToIdx ,
982
+ fileContext )
983
+ if err != nil {
984
+ return nil , err
985
+ }
986
+
987
+ args = sb .args
988
+ if err := sb .fakeBuild (); err != nil {
989
+ return nil , errors .Wrap (err , "error fake building stage" )
990
+ }
991
+
992
+ reviewConfig (stage , & sb .cf .Config )
993
+
994
+ sourceImage , err := mutate .Config (sb .image , sb .cf .Config )
995
+ if err != nil {
996
+ return nil , err
997
+ }
998
+
999
+ configFile , err := sourceImage .ConfigFile ()
1000
+ if err != nil {
1001
+ return nil , err
1002
+ }
1003
+ if opts .CustomPlatform == "" {
1004
+ configFile .OS = runtime .GOOS
1005
+ configFile .Architecture = runtime .GOARCH
1006
+ } else {
1007
+ configFile .OS = strings .Split (opts .CustomPlatform , "/" )[0 ]
1008
+ configFile .Architecture = strings .Split (opts .CustomPlatform , "/" )[1 ]
1009
+ }
1010
+ sourceImage , err = mutate .ConfigFile (sourceImage , configFile )
1011
+ if err != nil {
1012
+ return nil , err
1013
+ }
1014
+
1015
+ d , err := sourceImage .Digest ()
1016
+ if err != nil {
1017
+ return nil , err
1018
+ }
1019
+ stageIdxToDigest [fmt .Sprintf ("%d" , sb .stage .Index )] = d .String ()
1020
+ logrus .Infof ("Mapping stage idx %v to digest %v" , sb .stage .Index , d .String ())
1021
+
1022
+ digestToCacheKey [d .String ()] = sb .finalCacheKey
1023
+ logrus .Infof ("Mapping digest %v to cachekey %v" , d .String (), sb .finalCacheKey )
1024
+
1025
+ if stage .Final {
1026
+ sourceImage , err = mutateCanonical (sourceImage )
1027
+ if err != nil {
1028
+ return nil , err
1029
+ }
1030
+
1031
+ digest , _ := sourceImage .Digest ()
1032
+ logrus .Infof ("fakeStage %d built successfully with digest %s" , index , digest .String ())
1033
+
1034
+ return sourceImage , nil
1035
+ }
1036
+ }
1037
+
1038
+ return nil , err
1039
+ }
1040
+
1041
+ // From mutate.Canonical with layer de/compress stripped out.
1042
+ func mutateCanonical (image v1.Image ) (v1.Image , error ) {
1043
+ t := time.Time {}
1044
+
1045
+ ocf , err := image .ConfigFile ()
1046
+ if err != nil {
1047
+ return nil , fmt .Errorf ("setting config file: %w" , err )
1048
+ }
1049
+
1050
+ cfg := ocf .DeepCopy ()
1051
+
1052
+ // Copy basic config over
1053
+ cfg .Architecture = ocf .Architecture
1054
+ cfg .OS = ocf .OS
1055
+ cfg .OSVersion = ocf .OSVersion
1056
+ cfg .Config = ocf .Config
1057
+
1058
+ // Strip away timestamps from the config file
1059
+ cfg .Created = v1.Time {Time : t }
1060
+
1061
+ for i , h := range cfg .History {
1062
+ h .Created = v1.Time {Time : t }
1063
+ h .CreatedBy = ocf .History [i ].CreatedBy
1064
+ h .Comment = ocf .History [i ].Comment
1065
+ h .EmptyLayer = ocf .History [i ].EmptyLayer
1066
+ // Explicitly ignore Author field; which hinders reproducibility
1067
+ h .Author = ""
1068
+ cfg .History [i ] = h
1069
+ }
1070
+
1071
+ cfg .Container = ""
1072
+ cfg .Config .Hostname = ""
1073
+ cfg .DockerVersion = ""
1074
+
1075
+ return mutate .ConfigFile (image , cfg )
1076
+ }
1077
+
836
1078
// filesToSave returns all the files matching the given pattern in deps.
837
1079
// If a file is a symlink, it also returns the target file.
838
1080
func filesToSave (deps []string ) ([]string , error ) {
0 commit comments