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  }