github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/domain/infra/abi/containers_runlabel.go (about) 1 package abi 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/containers/common/libimage" 11 "github.com/containers/common/pkg/config" 12 "github.com/hanks177/podman/v4/libpod/define" 13 "github.com/hanks177/podman/v4/pkg/domain/entities" 14 envLib "github.com/hanks177/podman/v4/pkg/env" 15 "github.com/hanks177/podman/v4/utils" 16 "github.com/google/shlex" 17 "github.com/pkg/errors" 18 "github.com/sirupsen/logrus" 19 ) 20 21 func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, imageRef string, args []string, options entities.ContainerRunlabelOptions) error { 22 pullOptions := &libimage.PullOptions{} 23 pullOptions.AuthFilePath = options.Authfile 24 pullOptions.CertDirPath = options.CertDir 25 pullOptions.Credentials = options.Credentials 26 pullOptions.SignaturePolicyPath = options.SignaturePolicy 27 pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify 28 29 pullPolicy := config.PullPolicyNever 30 if options.Pull { 31 pullPolicy = config.PullPolicyMissing 32 } 33 if !options.Quiet { 34 pullOptions.Writer = os.Stderr 35 } 36 37 pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, imageRef, pullPolicy, pullOptions) 38 if err != nil { 39 return err 40 } 41 42 if len(pulledImages) != 1 { 43 return errors.Errorf("internal error: expected an image to be pulled (or an error)") 44 } 45 46 // Extract the runlabel from the image. 47 labels, err := pulledImages[0].Labels(ctx) 48 if err != nil { 49 return err 50 } 51 52 var runlabel string 53 for k, v := range labels { 54 if strings.EqualFold(k, label) { 55 runlabel = v 56 break 57 } 58 } 59 if runlabel == "" { 60 return errors.Errorf("cannot find the value of label: %s in image: %s", label, imageRef) 61 } 62 63 cmd, env, err := generateRunlabelCommand(runlabel, pulledImages[0], imageRef, args, options) 64 if err != nil { 65 return err 66 } 67 68 if options.Display { 69 fmt.Printf("command: %s\n", strings.Join(append([]string{os.Args[0]}, cmd[1:]...), " ")) 70 return nil 71 } 72 73 stdErr := os.Stderr 74 stdOut := os.Stdout 75 stdIn := os.Stdin 76 if options.Quiet { 77 stdErr = nil 78 stdOut = nil 79 stdIn = nil 80 } 81 82 // If container already exists && --replace given -- Nuke it 83 if options.Replace { 84 for i, entry := range cmd { 85 if entry == "--name" { 86 name := cmd[i+1] 87 ctr, err := ic.Libpod.LookupContainer(name) 88 if err != nil { 89 if errors.Cause(err) != define.ErrNoSuchCtr { 90 logrus.Debugf("Error occurred searching for container %s: %v", name, err) 91 return err 92 } 93 } else { 94 logrus.Debugf("Runlabel --replace option given. Container %s will be deleted. The new container will be named %s", ctr.ID(), name) 95 var timeout *uint 96 if err := ic.Libpod.RemoveContainer(ctx, ctr, true, false, timeout); err != nil { 97 return err 98 } 99 } 100 break 101 } 102 } 103 } 104 105 return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) 106 } 107 108 // generateRunlabelCommand generates the to-be-executed command as a string 109 // slice along with a base environment. 110 func generateRunlabelCommand(runlabel string, img *libimage.Image, inputName string, args []string, options entities.ContainerRunlabelOptions) ([]string, []string, error) { 111 var ( 112 err error 113 name, imageName string 114 cmd []string 115 ) 116 117 // Extract the imageName (or ID). 118 imgNames := img.NamesHistory() 119 if len(imgNames) == 0 { 120 imageName = img.ID() 121 } else { 122 // The newest name is the first entry in the `NamesHistory` 123 // slice. 124 imageName = imgNames[0] 125 } 126 127 // Use the user-specified name or extract one from the image. 128 name = options.Name 129 if name == "" { 130 normalize := imageName 131 if !strings.HasPrefix(img.ID(), inputName) { 132 normalize = inputName 133 } 134 splitImageName := strings.Split(normalize, "/") 135 name = splitImageName[len(splitImageName)-1] 136 // make sure to remove the tag from the image name, otherwise the name cannot 137 // be used as container name because a colon is an illegal character 138 name = strings.SplitN(name, ":", 2)[0] 139 } 140 141 // Append the user-specified arguments to the runlabel (command). 142 if len(args) > 0 { 143 runlabel = fmt.Sprintf("%s %s", runlabel, strings.Join(args, " ")) 144 } 145 146 cmd, err = generateCommand(runlabel, imageName, name) 147 if err != nil { 148 return nil, nil, err 149 } 150 151 env := generateRunEnvironment(options) 152 env = append(env, "PODMAN_RUNLABEL_NESTED=1") 153 envmap, err := envLib.ParseSlice(env) 154 if err != nil { 155 return nil, nil, err 156 } 157 158 envmapper := func(k string) string { 159 switch k { 160 case "OPT1": 161 return envmap["OPT1"] 162 case "OPT2": 163 return envmap["OPT2"] 164 case "OPT3": 165 return envmap["OPT3"] 166 case "PWD": 167 // I would prefer to use os.getenv but it appears PWD is not in the os env list. 168 d, err := os.Getwd() 169 if err != nil { 170 logrus.Error("Unable to determine current working directory") 171 return "" 172 } 173 return d 174 } 175 return "" 176 } 177 newS := os.Expand(strings.Join(cmd, " "), envmapper) 178 cmd, err = shlex.Split(newS) 179 if err != nil { 180 return nil, nil, err 181 } 182 return cmd, env, nil 183 } 184 185 func replaceName(arg, name string) string { 186 if arg == "NAME" { 187 return name 188 } 189 190 newarg := strings.ReplaceAll(arg, "$NAME", name) 191 newarg = strings.ReplaceAll(newarg, "${NAME}", name) 192 if strings.HasSuffix(newarg, "=NAME") { 193 newarg = strings.ReplaceAll(newarg, "=NAME", fmt.Sprintf("=%s", name)) 194 } 195 return newarg 196 } 197 198 func replaceImage(arg, image string) string { 199 if arg == "IMAGE" { 200 return image 201 } 202 newarg := strings.ReplaceAll(arg, "$IMAGE", image) 203 newarg = strings.ReplaceAll(newarg, "${IMAGE}", image) 204 if strings.HasSuffix(newarg, "=IMAGE") { 205 newarg = strings.ReplaceAll(newarg, "=IMAGE", fmt.Sprintf("=%s", image)) 206 } 207 return newarg 208 } 209 210 // generateCommand takes a label (string) and converts it to an executable command 211 func generateCommand(command, imageName, name string) ([]string, error) { 212 if name == "" { 213 name = imageName 214 } 215 216 cmd, err := shlex.Split(command) 217 if err != nil { 218 return nil, err 219 } 220 221 prog, err := substituteCommand(cmd[0]) 222 if err != nil { 223 return nil, err 224 } 225 newCommand := []string{prog} 226 for _, arg := range cmd[1:] { 227 var newArg string 228 switch arg { 229 case "IMAGE=IMAGE": 230 newArg = fmt.Sprintf("IMAGE=%s", imageName) 231 case "NAME=NAME": 232 newArg = fmt.Sprintf("NAME=%s", name) 233 default: 234 newArg = replaceName(arg, name) 235 newArg = replaceImage(newArg, imageName) 236 } 237 newCommand = append(newCommand, newArg) 238 } 239 return newCommand, nil 240 } 241 242 // GenerateRunEnvironment merges the current environment variables with optional 243 // environment variables provided by the user 244 func generateRunEnvironment(options entities.ContainerRunlabelOptions) []string { 245 newEnv := os.Environ() 246 if options.Optional1 != "" { 247 newEnv = append(newEnv, fmt.Sprintf("OPT1=%s", options.Optional1)) 248 } 249 if options.Optional2 != "" { 250 newEnv = append(newEnv, fmt.Sprintf("OPT2=%s", options.Optional2)) 251 } 252 if options.Optional3 != "" { 253 newEnv = append(newEnv, fmt.Sprintf("OPT3=%s", options.Optional3)) 254 } 255 return newEnv 256 } 257 258 func substituteCommand(cmd string) (string, error) { 259 var ( 260 newCommand string 261 ) 262 263 // Replace cmd with "/proc/self/exe" if "podman" or "docker" is being 264 // used. If "/usr/bin/docker" is provided, we also sub in podman. 265 // Otherwise, leave the command unchanged. 266 if cmd == "podman" || filepath.Base(cmd) == "docker" { 267 newCommand = "/proc/self/exe" 268 } else { 269 newCommand = cmd 270 } 271 272 // If cmd is an absolute or relative path, check if the file exists. 273 // Throw an error if it doesn't exist. 274 if strings.Contains(newCommand, "/") || strings.HasPrefix(newCommand, ".") { 275 res, err := filepath.Abs(newCommand) 276 if err != nil { 277 return "", err 278 } 279 if _, err := os.Stat(res); !os.IsNotExist(err) { 280 return res, nil 281 } else if err != nil { 282 return "", err 283 } 284 } 285 286 return newCommand, nil 287 }