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 }