github.com/GoogleContainerTools/kaniko@v1.23.0/pkg/commands/run.go (about)

     1  /*
     2  Copyright 2018 Google LLC
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package commands
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"os/exec"
    23  	"strings"
    24  	"syscall"
    25  
    26  	kConfig "github.com/GoogleContainerTools/kaniko/pkg/config"
    27  	"github.com/GoogleContainerTools/kaniko/pkg/constants"
    28  	"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
    29  	"github.com/GoogleContainerTools/kaniko/pkg/util"
    30  	v1 "github.com/google/go-containerregistry/pkg/v1"
    31  	"github.com/moby/buildkit/frontend/dockerfile/instructions"
    32  	"github.com/pkg/errors"
    33  	"github.com/sirupsen/logrus"
    34  )
    35  
    36  type RunCommand struct {
    37  	BaseCommand
    38  	cmd      *instructions.RunCommand
    39  	shdCache bool
    40  }
    41  
    42  // for testing
    43  var (
    44  	userLookup = util.LookupUser
    45  )
    46  
    47  func (r *RunCommand) IsArgsEnvsRequiredInCache() bool {
    48  	return true
    49  }
    50  
    51  func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
    52  	return runCommandInExec(config, buildArgs, r.cmd)
    53  }
    54  
    55  func runCommandInExec(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun *instructions.RunCommand) error {
    56  	var newCommand []string
    57  	if cmdRun.PrependShell {
    58  		// This is the default shell on Linux
    59  		var shell []string
    60  		if len(config.Shell) > 0 {
    61  			shell = config.Shell
    62  		} else {
    63  			shell = append(shell, "/bin/sh", "-c")
    64  		}
    65  
    66  		newCommand = append(shell, strings.Join(cmdRun.CmdLine, " "))
    67  	} else {
    68  		newCommand = cmdRun.CmdLine
    69  		// Find and set absolute path of executable by setting PATH temporary
    70  		replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
    71  		for _, v := range replacementEnvs {
    72  			entry := strings.SplitN(v, "=", 2)
    73  			if entry[0] != "PATH" {
    74  				continue
    75  			}
    76  			oldPath := os.Getenv("PATH")
    77  			defer os.Setenv("PATH", oldPath)
    78  			os.Setenv("PATH", entry[1])
    79  			path, err := exec.LookPath(newCommand[0])
    80  			if err == nil {
    81  				newCommand[0] = path
    82  			}
    83  		}
    84  	}
    85  
    86  	logrus.Infof("Cmd: %s", newCommand[0])
    87  	logrus.Infof("Args: %s", newCommand[1:])
    88  
    89  	cmd := exec.Command(newCommand[0], newCommand[1:]...)
    90  
    91  	cmd.Dir = setWorkDirIfExists(config.WorkingDir)
    92  	cmd.Stdout = os.Stdout
    93  	cmd.Stderr = os.Stderr
    94  	replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
    95  	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    96  
    97  	u := config.User
    98  	userAndGroup := strings.Split(u, ":")
    99  	userStr, err := util.ResolveEnvironmentReplacement(userAndGroup[0], replacementEnvs, false)
   100  	if err != nil {
   101  		return errors.Wrapf(err, "resolving user %s", userAndGroup[0])
   102  	}
   103  
   104  	// If specified, run the command as a specific user
   105  	if userStr != "" {
   106  		cmd.SysProcAttr.Credential, err = util.SyscallCredentials(userStr)
   107  		if err != nil {
   108  			return errors.Wrap(err, "credentials")
   109  		}
   110  	}
   111  
   112  	env, err := addDefaultHOME(userStr, replacementEnvs)
   113  	if err != nil {
   114  		return errors.Wrap(err, "adding default HOME variable")
   115  	}
   116  
   117  	cmd.Env = env
   118  
   119  	logrus.Infof("Running: %s", cmd.Args)
   120  	if err := cmd.Start(); err != nil {
   121  		return errors.Wrap(err, "starting command")
   122  	}
   123  
   124  	pgid, err := syscall.Getpgid(cmd.Process.Pid)
   125  	if err != nil {
   126  		return errors.Wrap(err, "getting group id for process")
   127  	}
   128  	if err := cmd.Wait(); err != nil {
   129  		return errors.Wrap(err, "waiting for process to exit")
   130  	}
   131  
   132  	//it's not an error if there are no grandchildren
   133  	if err := syscall.Kill(-pgid, syscall.SIGKILL); err != nil && err.Error() != "no such process" {
   134  		return err
   135  	}
   136  	return nil
   137  }
   138  
   139  // addDefaultHOME adds the default value for HOME if it isn't already set
   140  func addDefaultHOME(u string, envs []string) ([]string, error) {
   141  	for _, env := range envs {
   142  		split := strings.SplitN(env, "=", 2)
   143  		if split[0] == constants.HOME {
   144  			return envs, nil
   145  		}
   146  	}
   147  
   148  	// If user isn't set, set default value of HOME
   149  	if u == "" || u == constants.RootUser {
   150  		return append(envs, fmt.Sprintf("%s=%s", constants.HOME, constants.DefaultHOMEValue)), nil
   151  	}
   152  
   153  	// If user is set to username, set value of HOME to /home/${user}
   154  	// Otherwise the user is set to uid and HOME is /
   155  	userObj, err := userLookup(u)
   156  	if err != nil {
   157  		return nil, fmt.Errorf("lookup user %v: %w", u, err)
   158  	}
   159  
   160  	return append(envs, fmt.Sprintf("%s=%s", constants.HOME, userObj.HomeDir)), nil
   161  }
   162  
   163  // String returns some information about the command for the image config
   164  func (r *RunCommand) String() string {
   165  	return r.cmd.String()
   166  }
   167  
   168  func (r *RunCommand) FilesToSnapshot() []string {
   169  	return nil
   170  }
   171  
   172  func (r *RunCommand) ProvidesFilesToSnapshot() bool {
   173  	return false
   174  }
   175  
   176  // CacheCommand returns true since this command should be cached
   177  func (r *RunCommand) CacheCommand(img v1.Image) DockerCommand {
   178  
   179  	return &CachingRunCommand{
   180  		img:       img,
   181  		cmd:       r.cmd,
   182  		extractFn: util.ExtractFile,
   183  	}
   184  }
   185  
   186  func (r *RunCommand) MetadataOnly() bool {
   187  	return false
   188  }
   189  
   190  func (r *RunCommand) RequiresUnpackedFS() bool {
   191  	return true
   192  }
   193  
   194  func (r *RunCommand) ShouldCacheOutput() bool {
   195  	return r.shdCache
   196  }
   197  
   198  type CachingRunCommand struct {
   199  	BaseCommand
   200  	caching
   201  	img            v1.Image
   202  	extractedFiles []string
   203  	cmd            *instructions.RunCommand
   204  	extractFn      util.ExtractFunction
   205  }
   206  
   207  func (cr *CachingRunCommand) IsArgsEnvsRequiredInCache() bool {
   208  	return true
   209  }
   210  
   211  func (cr *CachingRunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
   212  	logrus.Infof("Found cached layer, extracting to filesystem")
   213  	var err error
   214  
   215  	if cr.img == nil {
   216  		return errors.New(fmt.Sprintf("command image is nil %v", cr.String()))
   217  	}
   218  
   219  	layers, err := cr.img.Layers()
   220  	if err != nil {
   221  		return errors.Wrap(err, "retrieving image layers")
   222  	}
   223  
   224  	if len(layers) != 1 {
   225  		return errors.New(fmt.Sprintf("expected %d layers but got %d", 1, len(layers)))
   226  	}
   227  
   228  	cr.layer = layers[0]
   229  
   230  	cr.extractedFiles, err = util.GetFSFromLayers(
   231  		kConfig.RootDir,
   232  		layers,
   233  		util.ExtractFunc(cr.extractFn),
   234  		util.IncludeWhiteout(),
   235  	)
   236  	if err != nil {
   237  		return errors.Wrap(err, "extracting fs from image")
   238  	}
   239  
   240  	return nil
   241  }
   242  
   243  func (cr *CachingRunCommand) FilesToSnapshot() []string {
   244  	f := cr.extractedFiles
   245  	logrus.Debugf("%d files extracted by caching run command", len(f))
   246  	logrus.Tracef("Extracted files: %s", f)
   247  
   248  	return f
   249  }
   250  
   251  func (cr *CachingRunCommand) String() string {
   252  	if cr.cmd == nil {
   253  		return "nil command"
   254  	}
   255  	return cr.cmd.String()
   256  }
   257  
   258  func (cr *CachingRunCommand) MetadataOnly() bool {
   259  	return false
   260  }
   261  
   262  // todo: this should create the workdir if it doesn't exist, atleast this is what docker does
   263  func setWorkDirIfExists(workdir string) string {
   264  	if _, err := os.Lstat(workdir); err == nil {
   265  		return workdir
   266  	}
   267  	return ""
   268  }