add a new run command along with a new flag
This commit is contained in:
parent
8fdb78a858
commit
43338d4b2f
|
|
@ -174,6 +174,7 @@ func addKanikoOptionsFlags() {
|
|||
RootCmd.PersistentFlags().BoolVarP(&opts.IgnoreVarRun, "whitelist-var-run", "", true, "Ignore /var/run directory when taking image snapshot. Set it to false to preserve /var/run/ in destination image. (Default true).")
|
||||
RootCmd.PersistentFlags().VarP(&opts.Labels, "label", "", "Set metadata for an image. Set it repeatedly for multiple labels.")
|
||||
RootCmd.PersistentFlags().BoolVarP(&opts.SkipUnusedStages, "skip-unused-stages", "", false, "Build only used stages if defined to true. Otherwise it builds by default all stages, even the unnecessaries ones until it reaches the target stage / end of Dockerfile")
|
||||
RootCmd.PersistentFlags().BoolVarP(&opts.RunV2, "use-new-run", "", false, "Experimental run command to detect file system changes. This new run command does no rely on snapshotting to detect changes.")
|
||||
}
|
||||
|
||||
// addHiddenFlags marks certain flags as hidden from the executor help text
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright 2018 Google, Inc. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
FROM debian:9.11
|
||||
RUN echo "hey" > /etc/foo
|
||||
RUN echo "baz" > /etc/baz
|
||||
RUN cp /etc/baz /etc/bar
|
||||
RUN rm /etc/baz
|
||||
|
||||
# Test with ARG
|
||||
ARG file
|
||||
RUN echo "run" > $file
|
||||
|
||||
RUN echo "test home" > $HOME/file
|
||||
COPY context/foo $HOME/foo
|
||||
|
|
@ -48,6 +48,7 @@ const (
|
|||
// Arguments to build Dockerfiles with, used for both docker and kaniko builds
|
||||
var argsMap = map[string][]string{
|
||||
"Dockerfile_test_run": {"file=/file"},
|
||||
"Dockerfile_test_run_new": {"file=/file"},
|
||||
"Dockerfile_test_workdir": {"workdir=/arg/workdir"},
|
||||
"Dockerfile_test_add": {"file=context/foo"},
|
||||
"Dockerfile_test_arg_secret": {"SSH_PRIVATE_KEY", "SSH_PUBLIC_KEY=Pµbl1cK€Y"},
|
||||
|
|
@ -74,6 +75,7 @@ var additionalDockerFlagsMap = map[string][]string{
|
|||
// Arguments to build Dockerfiles with when building with kaniko
|
||||
var additionalKanikoFlagsMap = map[string][]string{
|
||||
"Dockerfile_test_add": {"--single-snapshot"},
|
||||
"Dockerfile_test_run_new": {"--use-new-run=true"},
|
||||
"Dockerfile_test_scratch": {"--single-snapshot"},
|
||||
"Dockerfile_test_maintainer": {"--single-snapshot"},
|
||||
"Dockerfile_test_target": {"--target=second"},
|
||||
|
|
|
|||
|
|
@ -51,3 +51,7 @@ func (b *BaseCommand) RequiresUnpackedFS() bool {
|
|||
func (b *BaseCommand) ShouldCacheOutput() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *BaseCommand) ShouldDetectDeletedFiles() bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,11 +52,17 @@ type DockerCommand interface {
|
|||
RequiresUnpackedFS() bool
|
||||
|
||||
ShouldCacheOutput() bool
|
||||
|
||||
// ShouldDetectDeletedFiles turns true if the command could delete files.
|
||||
ShouldDetectDeletedFiles() bool
|
||||
}
|
||||
|
||||
func GetCommand(cmd instructions.Command, buildcontext string) (DockerCommand, error) {
|
||||
func GetCommand(cmd instructions.Command, buildcontext string, useNewRun bool) (DockerCommand, error) {
|
||||
switch c := cmd.(type) {
|
||||
case *instructions.RunCommand:
|
||||
if useNewRun {
|
||||
return &RunMarkerCommand{cmd: c}, nil
|
||||
}
|
||||
return &RunCommand{cmd: c}, nil
|
||||
case *instructions.CopyCommand:
|
||||
return &CopyCommand{cmd: c, buildcontext: buildcontext}, nil
|
||||
|
|
|
|||
|
|
@ -46,8 +46,12 @@ var (
|
|||
)
|
||||
|
||||
func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
return runCommandInExec(config, buildArgs, r.cmd)
|
||||
}
|
||||
|
||||
func runCommandInExec(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun *instructions.RunCommand) error {
|
||||
var newCommand []string
|
||||
if r.cmd.PrependShell {
|
||||
if cmdRun.PrependShell {
|
||||
// This is the default shell on Linux
|
||||
var shell []string
|
||||
if len(config.Shell) > 0 {
|
||||
|
|
@ -56,9 +60,9 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui
|
|||
shell = append(shell, "/bin/sh", "-c")
|
||||
}
|
||||
|
||||
newCommand = append(shell, strings.Join(r.cmd.CmdLine, " "))
|
||||
newCommand = append(shell, strings.Join(cmdRun.CmdLine, " "))
|
||||
} else {
|
||||
newCommand = r.cmd.CmdLine
|
||||
newCommand = cmdRun.CmdLine
|
||||
}
|
||||
|
||||
logrus.Infof("cmd: %s", newCommand[0])
|
||||
|
|
@ -111,7 +115,6 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui
|
|||
if err := syscall.Kill(-pgid, syscall.SIGKILL); err != nil && err.Error() != "no such process" {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
)
|
||||
|
||||
type RunMarkerCommand struct {
|
||||
BaseCommand
|
||||
cmd *instructions.RunCommand
|
||||
Files []string
|
||||
}
|
||||
|
||||
func (r *RunMarkerCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
// run command `touch filemarker`
|
||||
markerFile, err := ioutil.TempFile("", "marker")
|
||||
defer func() {
|
||||
os.Remove(markerFile.Name())
|
||||
}()
|
||||
|
||||
if err := runCommandInExec(config, buildArgs, r.cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// run command find to find all new files generated
|
||||
find := exec.Command("find", "/", "-newer", markerFile.Name())
|
||||
out, err := find.Output()
|
||||
if err != nil {
|
||||
r.Files = []string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
r.Files = []string{}
|
||||
s := strings.Split(string(out), "\n")
|
||||
for _, path := range s {
|
||||
path = filepath.Clean(path)
|
||||
if util.IsDestDir(path) || util.CheckIgnoreList(path) {
|
||||
continue
|
||||
}
|
||||
r.Files = append(r.Files, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns some information about the command for the image config
|
||||
func (r *RunMarkerCommand) String() string {
|
||||
return r.cmd.String()
|
||||
}
|
||||
|
||||
func (r *RunMarkerCommand) FilesToSnapshot() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RunMarkerCommand) ProvidesFilesToSnapshot() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// CacheCommand returns true since this command should be cached
|
||||
func (r *RunMarkerCommand) CacheCommand(img v1.Image) DockerCommand {
|
||||
|
||||
return &CachingRunCommand{
|
||||
img: img,
|
||||
cmd: r.cmd,
|
||||
extractFn: util.ExtractFile,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RunMarkerCommand) MetadataOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *RunMarkerCommand) RequiresUnpackedFS() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *RunMarkerCommand) ShouldCacheOutput() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *BaseCommand) ShouldDetectDelete() bool {
|
||||
return true
|
||||
}
|
||||
|
|
@ -57,6 +57,7 @@ type KanikoOptions struct {
|
|||
Cleanup bool
|
||||
IgnoreVarRun bool
|
||||
SkipUnusedStages bool
|
||||
RunV2 bool
|
||||
}
|
||||
|
||||
// WarmerOptions are options that are set by command line arguments to the cache warmer.
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ type cachePusher func(*config.KanikoOptions, string, string, string) error
|
|||
type snapShotter interface {
|
||||
Init() error
|
||||
TakeSnapshotFS() (string, error)
|
||||
TakeSnapshot([]string) (string, error)
|
||||
TakeSnapshot([]string, bool) (string, error)
|
||||
}
|
||||
|
||||
// stageBuilder contains all fields necessary to build one stage of a Dockerfile
|
||||
|
|
@ -127,7 +127,7 @@ func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, cross
|
|||
}
|
||||
|
||||
for _, cmd := range s.stage.Commands {
|
||||
command, err := commands.GetCommand(cmd, opts.SrcContext)
|
||||
command, err := commands.GetCommand(cmd, opts.SrcContext, opts.RunV2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -382,7 +382,7 @@ func (s *stageBuilder) build() error {
|
|||
return errors.Wrap(err, "failed to save layer")
|
||||
}
|
||||
} else {
|
||||
tarPath, err := s.takeSnapshot(files)
|
||||
tarPath, err := s.takeSnapshot(files, command.ShouldDetectDeletedFiles())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to take snapshot")
|
||||
}
|
||||
|
|
@ -416,7 +416,7 @@ func (s *stageBuilder) build() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *stageBuilder) takeSnapshot(files []string) (string, error) {
|
||||
func (s *stageBuilder) takeSnapshot(files []string, shdDelete bool) (string, error) {
|
||||
var snapshot string
|
||||
var err error
|
||||
|
||||
|
|
@ -426,7 +426,7 @@ func (s *stageBuilder) takeSnapshot(files []string) (string, error) {
|
|||
} else {
|
||||
// Volumes are very weird. They get snapshotted in the next command.
|
||||
files = append(files, util.Volumes()...)
|
||||
snapshot, err = s.snapshotter.TakeSnapshot(files)
|
||||
snapshot, err = s.snapshotter.TakeSnapshot(files, shdDelete)
|
||||
}
|
||||
timing.DefaultRun.Stop(t)
|
||||
return snapshot, err
|
||||
|
|
|
|||
|
|
@ -1246,6 +1246,7 @@ func getCommands(dir string, cmds []instructions.Command) []commands.DockerComma
|
|||
cmd, err := commands.GetCommand(
|
||||
c,
|
||||
dir,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ func (m MockDockerCommand) RequiresUnpackedFS() bool {
|
|||
func (m MockDockerCommand) ShouldCacheOutput() bool {
|
||||
return true
|
||||
}
|
||||
func (m MockDockerCommand) ShouldDetectDeletedFiles() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type MockCachedDockerCommand struct {
|
||||
contextFiles []string
|
||||
|
|
@ -93,6 +96,9 @@ func (m MockCachedDockerCommand) ProvidesFilesToSnapshot() bool {
|
|||
func (m MockCachedDockerCommand) CacheCommand(image v1.Image) commands.DockerCommand {
|
||||
return nil
|
||||
}
|
||||
func (m MockCachedDockerCommand) ShouldDetectDeletedFiles() bool {
|
||||
return false
|
||||
}
|
||||
func (m MockCachedDockerCommand) FilesUsedFromContext(c *v1.Config, args *dockerfile.BuildArgs) ([]string, error) {
|
||||
return m.contextFiles, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func (s *Snapshotter) Key() (string, error) {
|
|||
|
||||
// TakeSnapshot takes a snapshot of the specified files, avoiding directories in the ignorelist, and creates
|
||||
// a tarball of the changed files. Return contents of the tarball, and whether or not any files were changed
|
||||
func (s *Snapshotter) TakeSnapshot(files []string) (string, error) {
|
||||
func (s *Snapshotter) TakeSnapshot(files []string, shdCheckDelete bool) (string, error) {
|
||||
f, err := ioutil.TempFile(config.KanikoDir, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -92,9 +92,30 @@ func (s *Snapshotter) TakeSnapshot(files []string) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Get whiteout paths
|
||||
filesToWhiteout := []string{}
|
||||
if shdCheckDelete {
|
||||
existingPaths := s.l.getFlattenedPathsForWhiteOut()
|
||||
foundFiles := walkFS(s.directory)
|
||||
for _, file := range foundFiles {
|
||||
delete(existingPaths, file)
|
||||
}
|
||||
// The paths left here are the ones that have been deleted in this layer.
|
||||
filesToWhiteOut := []string{}
|
||||
for path := range existingPaths {
|
||||
// Only add the whiteout if the directory for the file still exists.
|
||||
dir := filepath.Dir(path)
|
||||
if _, ok := existingPaths[dir]; !ok {
|
||||
if s.l.MaybeAddWhiteout(path) {
|
||||
logrus.Debugf("Adding whiteout for %s", path)
|
||||
filesToWhiteOut = append(filesToWhiteOut, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
t := util.NewTar(f)
|
||||
defer t.Close()
|
||||
if err := writeToTar(t, filesToAdd, nil); err != nil {
|
||||
if err := writeToTar(t, filesToAdd, filesToWhiteout); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return f.Name(), nil
|
||||
|
|
@ -133,31 +154,8 @@ func (s *Snapshotter) scanFullFilesystem() ([]string, []string, error) {
|
|||
|
||||
s.l.Snapshot()
|
||||
|
||||
timer := timing.Start("Walking filesystem")
|
||||
|
||||
foundPaths := make([]string, 0)
|
||||
|
||||
godirwalk.Walk(s.directory, &godirwalk.Options{
|
||||
Callback: func(path string, ent *godirwalk.Dirent) error {
|
||||
if util.IsInIgnoreList(path) {
|
||||
if util.IsDestDir(path) {
|
||||
logrus.Tracef("Skipping paths under %s, as it is a ignored directory", path)
|
||||
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
foundPaths = append(foundPaths, path)
|
||||
|
||||
return nil
|
||||
},
|
||||
Unsorted: true,
|
||||
},
|
||||
)
|
||||
timing.DefaultRun.Stop(timer)
|
||||
timer = timing.Start("Resolving Paths")
|
||||
foundPaths := walkFS(s.directory)
|
||||
timer := timing.Start("Resolving Paths")
|
||||
// First handle whiteouts
|
||||
// Get a list of all the files that existed before this layer
|
||||
existingPaths := s.l.getFlattenedPathsForWhiteOut()
|
||||
|
|
@ -267,3 +265,29 @@ func filesWithLinks(path string) ([]string, error) {
|
|||
}
|
||||
return []string{path, link}, nil
|
||||
}
|
||||
|
||||
func walkFS(dir string) []string {
|
||||
foundPaths := make([]string, 0)
|
||||
timer := timing.Start("Walking filesystem")
|
||||
godirwalk.Walk(dir, &godirwalk.Options{
|
||||
Callback: func(path string, ent *godirwalk.Dirent) error {
|
||||
if util.IsInIgnoreList(path) {
|
||||
if util.IsDestDir(path) {
|
||||
logrus.Tracef("Skipping paths under %s, as it is a ignored directory", path)
|
||||
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
foundPaths = append(foundPaths, path)
|
||||
|
||||
return nil
|
||||
},
|
||||
Unsorted: true,
|
||||
},
|
||||
)
|
||||
timing.DefaultRun.Stop(timer)
|
||||
return foundPaths
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ func TestSnapshotFiles(t *testing.T) {
|
|||
filesToSnapshot := []string{
|
||||
filepath.Join(testDir, "foo"),
|
||||
}
|
||||
tarPath, err := snapshotter.TakeSnapshot(filesToSnapshot)
|
||||
tarPath, err := snapshotter.TakeSnapshot(filesToSnapshot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -359,7 +359,7 @@ func TestSnasphotPreservesFileOrder(t *testing.T) {
|
|||
}
|
||||
|
||||
// Take a snapshot
|
||||
tarPath, err := snapshotter.TakeSnapshot(filesToSnapshot)
|
||||
tarPath, err := snapshotter.TakeSnapshot(filesToSnapshot, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error taking snapshot of fs: %s", err)
|
||||
|
|
|
|||
Loading…
Reference in New Issue