diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 97511e092..e5e0f8647 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -17,6 +17,7 @@ limitations under the License. package cmd import ( + "fmt" "os" "path/filepath" "strings" @@ -59,21 +60,23 @@ var RootCmd = &cobra.Command{ } return resolveDockerfilePath() }, - RunE: func(cmd *cobra.Command, args []string) error { + Run: func(cmd *cobra.Command, args []string) { if !checkContained() { if !force { - return errors.New("kaniko should only be run inside of a container, run with the --force flag if you are sure you want to continue") + exit(errors.New("kaniko should only be run inside of a container, run with the --force flag if you are sure you want to continue")) } logrus.Warn("kaniko is being run outside of a container. This can have dangerous effects on your system") } if err := os.Chdir("/"); err != nil { - return errors.Wrap(err, "error changing to root dir") + exit(errors.Wrap(err, "error changing to root dir")) } image, err := executor.DoBuild(opts) if err != nil { - return errors.Wrap(err, "error building image") + exit(errors.Wrap(err, "error building image")) + } + if err := executor.DoPush(image, opts); err != nil { + exit(errors.Wrap(err, "error pushing image")) } - return executor.DoPush(image, opts) }, } @@ -158,3 +161,8 @@ func resolveSourceContext() error { logrus.Debugf("Build context located at %s", opts.SrcContext) return nil } + +func exit(err error) { + fmt.Println(err) + os.Exit(1) +} diff --git a/pkg/config/args.go b/pkg/config/args.go index ae45b266c..44ac074e3 100644 --- a/pkg/config/args.go +++ b/pkg/config/args.go @@ -34,7 +34,7 @@ func (b *multiArg) String() string { // The second method is Set(value string) error func (b *multiArg) Set(value string) error { - logrus.Infof("appending to multi args %s", value) + logrus.Debugf("appending to multi args %s", value) *b = append(*b, value) return nil } diff --git a/pkg/config/stage.go b/pkg/config/stage.go index dba07bf31..b3f16a32e 100644 --- a/pkg/config/stage.go +++ b/pkg/config/stage.go @@ -22,7 +22,7 @@ import "github.com/moby/buildkit/frontend/dockerfile/instructions" type KanikoStage struct { instructions.Stage BaseImageIndex int - FinalStage bool + Final bool BaseImageStoredLocally bool SaveStage bool } diff --git a/pkg/dockerfile/dockerfile.go b/pkg/dockerfile/dockerfile.go index 00fc71cb7..8b06985b6 100644 --- a/pkg/dockerfile/dockerfile.go +++ b/pkg/dockerfile/dockerfile.go @@ -57,7 +57,7 @@ func Stages(opts *config.KanikoOptions) ([]config.KanikoStage, error) { BaseImageIndex: baseImageIndex(index, stages), BaseImageStoredLocally: (baseImageIndex(index, stages) != -1), SaveStage: saveStage(index, stages), - FinalStage: index == targetStage, + Final: index == targetStage, }) if index == targetStage { break diff --git a/pkg/executor/build.go b/pkg/executor/build.go index 923741126..83a6be42b 100644 --- a/pkg/executor/build.go +++ b/pkg/executor/build.go @@ -30,6 +30,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/GoogleContainerTools/kaniko/pkg/commands" @@ -40,117 +41,165 @@ import ( "github.com/GoogleContainerTools/kaniko/pkg/util" ) +// stageBuilder contains all fields necessary to build one stage of a Dockerfile +type stageBuilder struct { + stage config.KanikoStage + image v1.Image + cf *v1.ConfigFile + snapshotter *snapshot.Snapshotter + baseImageDigest string +} + +// newStageBuilder returns a new type stageBuilder which contains all the information required to build the stage +func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage) (*stageBuilder, error) { + sourceImage, err := util.RetrieveSourceImage(stage, opts.BuildArgs) + if err != nil { + return nil, err + } + imageConfig, err := util.RetrieveConfigFile(sourceImage) + if err != nil { + return nil, err + } + if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil { + return nil, err + } + hasher, err := getHasher(opts.SnapshotMode) + if err != nil { + return nil, err + } + l := snapshot.NewLayeredMap(hasher, util.CacheHasher()) + snapshotter := snapshot.NewSnapshotter(l, constants.RootDir) + + digest, err := sourceImage.Digest() + if err != nil { + return nil, err + } + return &stageBuilder{ + stage: stage, + image: sourceImage, + cf: imageConfig, + snapshotter: snapshotter, + baseImageDigest: digest.String(), + }, nil +} + +// key will return a string representation of the build at the cmd +// TODO: priyawadhwa@ to fill this out when implementing caching +// func (s *stageBuilder) key(cmd string) (string, error) { +// return "", nil +// } + +// extractCachedLayer will extract the cached layer and append it to the config file +// TODO: priyawadhwa@ to fill this out when implementing caching +// func (s *stageBuilder) extractCachedLayer(layer v1.Image, createdBy string) error { +// return nil +// } + +func (s *stageBuilder) build(opts *config.KanikoOptions) error { + // Unpack file system to root + if err := util.GetFSFromImage(constants.RootDir, s.image); err != nil { + return err + } + // Take initial snapshot + if err := s.snapshotter.Init(); err != nil { + return err + } + args := dockerfile.NewBuildArgs(opts.BuildArgs) + for index, cmd := range s.stage.Commands { + finalCmd := index == len(s.stage.Commands)-1 + command, err := commands.GetCommand(cmd, opts.SrcContext) + if err != nil { + return err + } + if command == nil { + continue + } + logrus.Info(command.String()) + if err := command.ExecuteCommand(&s.cf.Config, args); err != nil { + return err + } + files := command.FilesToSnapshot() + var contents []byte + + // If this is an intermediate stage, we only snapshot for the last command and we + // want to snapshot the entire filesystem since we aren't tracking what was changed + // by previous commands. + if !s.stage.Final { + if finalCmd { + contents, err = s.snapshotter.TakeSnapshotFS() + } + } else { + // If we are in single snapshot mode, we only take a snapshot once, after all + // commands have completed. + if opts.SingleSnapshot { + if finalCmd { + contents, err = s.snapshotter.TakeSnapshotFS() + } + } else { + // Otherwise, in the final stage we take a snapshot at each command. If we know + // the files that were changed, we'll snapshot those explicitly, otherwise we'll + // check if anything in the filesystem changed. + if files != nil { + contents, err = s.snapshotter.TakeSnapshot(files) + } else { + contents, err = s.snapshotter.TakeSnapshotFS() + } + } + } + if err != nil { + return fmt.Errorf("Error taking snapshot of files for command %s: %s", command, err) + } + + util.MoveVolumeWhitelistToWhitelist() + if contents == nil { + logrus.Info("No files were changed, appending empty layer to config. No layer added to image.") + continue + } + // Append the layer to the image + opener := func() (io.ReadCloser, error) { + return ioutil.NopCloser(bytes.NewReader(contents)), nil + } + layer, err := tarball.LayerFromOpener(opener) + if err != nil { + return err + } + s.image, err = mutate.Append(s.image, + mutate.Addendum{ + Layer: layer, + History: v1.History{ + Author: constants.Author, + CreatedBy: command.String(), + }, + }, + ) + if err != nil { + return err + } + } + return nil +} + +// DoBuild executes building the Dockerfile func DoBuild(opts *config.KanikoOptions) (v1.Image, error) { // Parse dockerfile and unpack base image to root stages, err := dockerfile.Stages(opts) if err != nil { return nil, err } - - hasher, err := getHasher(opts.SnapshotMode) - if err != nil { - return nil, err - } for index, stage := range stages { - // Unpack file system to root - sourceImage, err := util.RetrieveSourceImage(stage, opts.BuildArgs) + sb, err := newStageBuilder(opts, stage) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("getting stage builder for stage %d", index)) + } + if err := sb.build(opts); err != nil { + return nil, errors.Wrap(err, "error building stage") + } + reviewConfig(stage, &sb.cf.Config) + sourceImage, err := mutate.Config(sb.image, sb.cf.Config) if err != nil { return nil, err } - if err := util.GetFSFromImage(constants.RootDir, sourceImage); err != nil { - return nil, err - } - l := snapshot.NewLayeredMap(hasher, util.CacheHasher()) - snapshotter := snapshot.NewSnapshotter(l, constants.RootDir) - // Take initial snapshot - if err := snapshotter.Init(); err != nil { - return nil, err - } - imageConfig, err := util.RetrieveConfigFile(sourceImage) - if err != nil { - return nil, err - } - if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil { - return nil, err - } - buildArgs := dockerfile.NewBuildArgs(opts.BuildArgs) - for index, cmd := range stage.Commands { - finalCmd := index == len(stage.Commands)-1 - dockerCommand, err := commands.GetCommand(cmd, opts.SrcContext) - if err != nil { - return nil, err - } - if dockerCommand == nil { - continue - } - logrus.Info(dockerCommand.String()) - if err := dockerCommand.ExecuteCommand(&imageConfig.Config, buildArgs); err != nil { - return nil, err - } - snapshotFiles := dockerCommand.FilesToSnapshot() - var contents []byte - - // If this is an intermediate stage, we only snapshot for the last command and we - // want to snapshot the entire filesystem since we aren't tracking what was changed - // by previous commands. - if !stage.FinalStage { - if finalCmd { - contents, err = snapshotter.TakeSnapshotFS() - } - } else { - // If we are in single snapshot mode, we only take a snapshot once, after all - // commands have completed. - if opts.SingleSnapshot { - if finalCmd { - contents, err = snapshotter.TakeSnapshotFS() - } - } else { - // Otherwise, in the final stage we take a snapshot at each command. If we know - // the files that were changed, we'll snapshot those explicitly, otherwise we'll - // check if anything in the filesystem changed. - if snapshotFiles != nil { - contents, err = snapshotter.TakeSnapshot(snapshotFiles) - } else { - contents, err = snapshotter.TakeSnapshotFS() - } - } - } - if err != nil { - return nil, fmt.Errorf("Error taking snapshot of files for command %s: %s", dockerCommand, err) - } - - util.MoveVolumeWhitelistToWhitelist() - if contents == nil { - logrus.Info("No files were changed, appending empty layer to config. No layer added to image.") - continue - } - // Append the layer to the image - opener := func() (io.ReadCloser, error) { - return ioutil.NopCloser(bytes.NewReader(contents)), nil - } - layer, err := tarball.LayerFromOpener(opener) - if err != nil { - return nil, err - } - sourceImage, err = mutate.Append(sourceImage, - mutate.Addendum{ - Layer: layer, - History: v1.History{ - Author: constants.Author, - CreatedBy: dockerCommand.String(), - }, - }, - ) - if err != nil { - return nil, err - } - } - reviewConfig(stage, &imageConfig.Config) - sourceImage, err = mutate.Config(sourceImage, imageConfig.Config) - if err != nil { - return nil, err - } - if stage.FinalStage { + if stage.Final { sourceImage, err = mutate.CreatedAt(sourceImage, v1.Time{Time: time.Now()}) if err != nil { return nil, err