cmd/run: Implement the run command in Go
https://github.com/containers/toolbox/pull/318
This commit is contained in:
		
							parent
							
								
									cf5c58ab00
								
							
						
					
					
						commit
						238f2451e7
					
				
							
								
								
									
										339
									
								
								src/cmd/run.go
								
								
								
								
							
							
						
						
									
										339
									
								
								src/cmd/run.go
								
								
								
								
							|  | @ -17,10 +17,14 @@ | ||||||
| package cmd | package cmd | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/containers/toolbox/pkg/podman" | 	"github.com/containers/toolbox/pkg/podman" | ||||||
|  | 	"github.com/containers/toolbox/pkg/shell" | ||||||
| 	"github.com/containers/toolbox/pkg/utils" | 	"github.com/containers/toolbox/pkg/utils" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
|  | @ -60,6 +64,265 @@ func init() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func run(cmd *cobra.Command, args []string) error { | func run(cmd *cobra.Command, args []string) error { | ||||||
|  | 	if utils.IsInsideContainer() { | ||||||
|  | 		if !utils.IsInsideToolboxContainer() { | ||||||
|  | 			return errors.New("this is not a toolbox container") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if _, err := utils.ForwardToHost(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var nonDefaultContainer bool | ||||||
|  | 
 | ||||||
|  | 	if runFlags.container != "" { | ||||||
|  | 		nonDefaultContainer = true | ||||||
|  | 
 | ||||||
|  | 		if _, err := utils.IsContainerNameValid(runFlags.container); err != nil { | ||||||
|  | 			var builder strings.Builder | ||||||
|  | 			fmt.Fprintf(&builder, "invalid argument for '--container'\n") | ||||||
|  | 			fmt.Fprintf(&builder, "Container names must match '%s'\n", utils.ContainerNameRegexp) | ||||||
|  | 			fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase) | ||||||
|  | 
 | ||||||
|  | 			errMsg := builder.String() | ||||||
|  | 			return errors.New(errMsg) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var release string | ||||||
|  | 	if runFlags.release != "" { | ||||||
|  | 		nonDefaultContainer = true | ||||||
|  | 
 | ||||||
|  | 		var err error | ||||||
|  | 		release, err = utils.ParseRelease(runFlags.release) | ||||||
|  | 		if err != nil { | ||||||
|  | 			err := utils.CreateErrorInvalidRelease(executableBase) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(args) == 0 { | ||||||
|  | 		var builder strings.Builder | ||||||
|  | 		fmt.Fprintf(&builder, "missing argument for \"run\"\n") | ||||||
|  | 		fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase) | ||||||
|  | 
 | ||||||
|  | 		errMsg := builder.String() | ||||||
|  | 		return errors.New(errMsg) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	command := args | ||||||
|  | 
 | ||||||
|  | 	container, image, release, err := utils.ResolveContainerAndImageNames(runFlags.container, "", release) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := runCommand(container, | ||||||
|  | 		!nonDefaultContainer, | ||||||
|  | 		image, | ||||||
|  | 		release, | ||||||
|  | 		command, | ||||||
|  | 		false, | ||||||
|  | 		false, | ||||||
|  | 		true); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func runCommand(container string, | ||||||
|  | 	defaultContainer bool, | ||||||
|  | 	image, release string, | ||||||
|  | 	command []string, | ||||||
|  | 	emitEscapeSequence, fallbackToBash, pedantic bool) error { | ||||||
|  | 	if !pedantic { | ||||||
|  | 		if image == "" { | ||||||
|  | 			panic("image not specified") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if release == "" { | ||||||
|  | 			panic("release not specified") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Debugf("Checking if container %s exists", container) | ||||||
|  | 
 | ||||||
|  | 	if _, err := podman.ContainerExists(container); err != nil { | ||||||
|  | 		logrus.Debugf("Container %s not found", container) | ||||||
|  | 
 | ||||||
|  | 		if pedantic { | ||||||
|  | 			err := utils.CreateErrorContainerNotFound(container, executableBase) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		containers, err := listContainers() | ||||||
|  | 		if err != nil { | ||||||
|  | 			err := utils.CreateErrorContainerNotFound(container, executableBase) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		containersCount := len(containers) | ||||||
|  | 		logrus.Debugf("Found %d containers", containersCount) | ||||||
|  | 
 | ||||||
|  | 		if containersCount == 0 { | ||||||
|  | 			var shouldCreateContainer bool | ||||||
|  | 			promptForCreate := true | ||||||
|  | 
 | ||||||
|  | 			if rootFlags.assumeYes { | ||||||
|  | 				shouldCreateContainer = true | ||||||
|  | 				promptForCreate = false | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if promptForCreate { | ||||||
|  | 				prompt := "No toolbox containers found. Create now? [y/N]" | ||||||
|  | 				shouldCreateContainer = utils.AskForConfirmation(prompt) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if !shouldCreateContainer { | ||||||
|  | 				fmt.Printf("A container can be created later with the 'create' command.\n") | ||||||
|  | 				fmt.Printf("Run '%s --help' for usage.\n", executableBase) | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if err := createContainer(container, image, release, false); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} else if containersCount == 1 && defaultContainer { | ||||||
|  | 			fmt.Fprintf(os.Stderr, "Error: container %s not found\n", container) | ||||||
|  | 
 | ||||||
|  | 			container = containers[0]["Names"].(string) | ||||||
|  | 			fmt.Fprintf(os.Stderr, "Entering container %s instead.\n", container) | ||||||
|  | 			fmt.Fprintf(os.Stderr, "Use the 'create' command to create a different toolbox.\n") | ||||||
|  | 			fmt.Fprintf(os.Stderr, "Run '%s --help' for usage.\n", executableBase) | ||||||
|  | 		} else { | ||||||
|  | 			var builder strings.Builder | ||||||
|  | 			fmt.Fprintf(&builder, "container %s not found\n", container) | ||||||
|  | 			fmt.Fprintf(&builder, "Use the '--container' option to select a toolbox.\n") | ||||||
|  | 			fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase) | ||||||
|  | 
 | ||||||
|  | 			errMsg := builder.String() | ||||||
|  | 			return errors.New(errMsg) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if _, err := utils.CallFlatpakSessionHelper(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Debugf("Starting container %s", container) | ||||||
|  | 	if err := startContainer(container); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	entryPoint, entryPointPID, err := getEntryPointAndPID(container) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if entryPoint != "toolbox" { | ||||||
|  | 		var builder strings.Builder | ||||||
|  | 		fmt.Fprintf(&builder, "container %s is too old and no longer supported \n", container) | ||||||
|  | 		fmt.Fprintf(&builder, "Recreate it with Toolbox version 0.0.17 or newer.\n") | ||||||
|  | 
 | ||||||
|  | 		errMsg := builder.String() | ||||||
|  | 		return errors.New(errMsg) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if entryPointPID <= 0 { | ||||||
|  | 		return fmt.Errorf("invalid entry point PID of container %s", container) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Debugf("Waiting for container %s to finish initializing", container) | ||||||
|  | 
 | ||||||
|  | 	runtimeDirectory := os.Getenv("XDG_RUNTIME_DIR") | ||||||
|  | 	toolboxRuntimeDirectory := runtimeDirectory + "/toolbox" | ||||||
|  | 	initializedStamp := fmt.Sprintf("%s/container-initialized-%d", toolboxRuntimeDirectory, entryPointPID) | ||||||
|  | 
 | ||||||
|  | 	logrus.Debugf("Checking if initialization stamp %s exists", initializedStamp) | ||||||
|  | 
 | ||||||
|  | 	initializedTimeout := 25 // seconds
 | ||||||
|  | 	for i := 0; !utils.PathExists(initializedStamp); i++ { | ||||||
|  | 		if i == initializedTimeout { | ||||||
|  | 			return fmt.Errorf("failed to initialize container %s", container) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		time.Sleep(time.Second) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Debugf("Container %s is initialized", container) | ||||||
|  | 
 | ||||||
|  | 	if _, err := isCommandPresent(container, command[0]); err != nil { | ||||||
|  | 		if fallbackToBash { | ||||||
|  | 			logrus.Debugf("command %s not found in container %s; using /bin/bash instead", | ||||||
|  | 				command[0], | ||||||
|  | 				container) | ||||||
|  | 
 | ||||||
|  | 			command = []string{"/bin/bash"} | ||||||
|  | 		} else { | ||||||
|  | 			return fmt.Errorf("command %s not found in container %s", command[0], container) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	envOptions := utils.GetEnvOptionsForPreservedVariables() | ||||||
|  | 	logLevelString := podman.LogLevel.String() | ||||||
|  | 
 | ||||||
|  | 	execArgs := []string{ | ||||||
|  | 		"--log-level", logLevelString, | ||||||
|  | 		"exec", | ||||||
|  | 		"--interactive", | ||||||
|  | 		"--tty", | ||||||
|  | 		"--user", currentUser.Username, | ||||||
|  | 		"--workdir", workingDirectory, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	execArgs = append(execArgs, envOptions...) | ||||||
|  | 
 | ||||||
|  | 	execArgs = append(execArgs, []string{ | ||||||
|  | 		container, | ||||||
|  | 		"capsh", "--caps=", "--", "-c", "exec \"$@\"", "/bin/sh", | ||||||
|  | 	}...) | ||||||
|  | 
 | ||||||
|  | 	execArgs = append(execArgs, command...) | ||||||
|  | 
 | ||||||
|  | 	if emitEscapeSequence { | ||||||
|  | 		fmt.Printf("\033]777;container;push;%s;toolbox\033\\", container) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Debugf("Running in container %s:", container) | ||||||
|  | 	logrus.Debug("podman") | ||||||
|  | 	for _, arg := range execArgs { | ||||||
|  | 		logrus.Debugf("%s", arg) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	exitCode, err := shell.RunWithExitCode("podman", os.Stdin, os.Stdout, nil, execArgs...) | ||||||
|  | 
 | ||||||
|  | 	if emitEscapeSequence { | ||||||
|  | 		fmt.Print("\033]777;container;pop;;\033\\") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch exitCode { | ||||||
|  | 	case 0: | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic("unexpected error: 'podman exec' finished successfully") | ||||||
|  | 		} | ||||||
|  | 	case 125: | ||||||
|  | 		err = fmt.Errorf("failed to invoke 'podman exec' in container %s", container) | ||||||
|  | 	case 126: | ||||||
|  | 		err = fmt.Errorf("failed to invoke command %s in container %s", command[0], container) | ||||||
|  | 	case 127: | ||||||
|  | 		err = fmt.Errorf("command %s not found in container %s", command[0], container) | ||||||
|  | 	default: | ||||||
|  | 		err = nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -115,3 +378,79 @@ func getEntryPointAndPID(container string) (string, int, error) { | ||||||
| 
 | 
 | ||||||
| 	return entryPoint, entryPointPIDInt, nil | 	return entryPoint, entryPointPIDInt, nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func isCommandPresent(container, command string) (bool, error) { | ||||||
|  | 	logrus.Debugf("Looking for command %s in container %s", command, container) | ||||||
|  | 
 | ||||||
|  | 	logLevelString := podman.LogLevel.String() | ||||||
|  | 	args := []string{ | ||||||
|  | 		"--log-level", logLevelString, | ||||||
|  | 		"exec", | ||||||
|  | 		"--user", currentUser.Username, | ||||||
|  | 		container, | ||||||
|  | 		"sh", "-c", "command -v \"$1\"", "sh", command, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := shell.Run("podman", nil, nil, nil, args...); err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func startContainer(container string) error { | ||||||
|  | 	var stderr strings.Builder | ||||||
|  | 	if err := podman.Start(container, &stderr); err == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	errString := stderr.String() | ||||||
|  | 	if !strings.Contains(errString, "use system migrate to mitigate") { | ||||||
|  | 		return fmt.Errorf("failed to start container %s", container) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Debug("Checking if 'podman system migrate' supports '--new-runtime'") | ||||||
|  | 
 | ||||||
|  | 	if !podman.CheckVersion("1.6.2") { | ||||||
|  | 		var builder strings.Builder | ||||||
|  | 
 | ||||||
|  | 		fmt.Fprintf(&builder, | ||||||
|  | 			"container %s doesn't support cgroups v%d\n", | ||||||
|  | 			container, | ||||||
|  | 			cgroupsVersion) | ||||||
|  | 
 | ||||||
|  | 		fmt.Fprintf(&builder, "Update Podman to version 1.6.2 or newer.\n") | ||||||
|  | 
 | ||||||
|  | 		errMsg := builder.String() | ||||||
|  | 		return errors.New(errMsg) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Debug("'podman system migrate' supports '--new-runtime'") | ||||||
|  | 
 | ||||||
|  | 	ociRuntimeRequired := "runc" | ||||||
|  | 	if cgroupsVersion == 2 { | ||||||
|  | 		ociRuntimeRequired = "crun" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Debugf("Migrating containers to OCI runtime %s", ociRuntimeRequired) | ||||||
|  | 
 | ||||||
|  | 	if err := podman.SystemMigrate(ociRuntimeRequired); err != nil { | ||||||
|  | 		var builder strings.Builder | ||||||
|  | 		fmt.Fprintf(&builder, "failed to migrate containers to OCI runtime %s\n", ociRuntimeRequired) | ||||||
|  | 		fmt.Fprintf(&builder, "Factory reset with: podman system reset") | ||||||
|  | 
 | ||||||
|  | 		errMsg := builder.String() | ||||||
|  | 		return errors.New(errMsg) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := podman.Start(container, nil); err != nil { | ||||||
|  | 		var builder strings.Builder | ||||||
|  | 		fmt.Fprintf(&builder, "container %s doesn't support cgroups v%d\n", container, cgroupsVersion) | ||||||
|  | 		fmt.Fprintf(&builder, "Factory reset with: podman system reset") | ||||||
|  | 
 | ||||||
|  | 		errMsg := builder.String() | ||||||
|  | 		return errors.New(errMsg) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue