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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/toolbox/pkg/podman"
|
||||
"github.com/containers/toolbox/pkg/shell"
|
||||
"github.com/containers/toolbox/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -60,6 +64,265 @@ func init() {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -115,3 +378,79 @@ func getEntryPointAndPID(container string) (string, int, error) {
|
|||
|
||||
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