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