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  }