|
17 | 17 | package cmd |
18 | 18 |
|
19 | 19 | import ( |
| 20 | + "errors" |
20 | 21 | "fmt" |
21 | 22 | "os" |
| 23 | + "strings" |
| 24 | + "time" |
22 | 25 |
|
23 | 26 | "github.com/containers/toolbox/pkg/podman" |
| 27 | + "github.com/containers/toolbox/pkg/shell" |
24 | 28 | "github.com/containers/toolbox/pkg/utils" |
25 | 29 | "github.com/sirupsen/logrus" |
26 | 30 | "github.com/spf13/cobra" |
@@ -60,6 +64,265 @@ func init() { |
60 | 64 | } |
61 | 65 |
|
62 | 66 | func run(cmd *cobra.Command, args []string) error { |
| 67 | + if utils.IsInsideContainer() { |
| 68 | + if !utils.IsInsideToolboxContainer() { |
| 69 | + return errors.New("this is not a toolbox container") |
| 70 | + } |
| 71 | + |
| 72 | + if _, err := utils.ForwardToHost(); err != nil { |
| 73 | + return err |
| 74 | + } |
| 75 | + |
| 76 | + return nil |
| 77 | + } |
| 78 | + |
| 79 | + var nonDefaultContainer bool |
| 80 | + |
| 81 | + if runFlags.container != "" { |
| 82 | + nonDefaultContainer = true |
| 83 | + |
| 84 | + if _, err := utils.IsContainerNameValid(runFlags.container); err != nil { |
| 85 | + var builder strings.Builder |
| 86 | + fmt.Fprintf(&builder, "invalid argument for '--container'\n") |
| 87 | + fmt.Fprintf(&builder, "Container names must match '%s'\n", utils.ContainerNameRegexp) |
| 88 | + fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase) |
| 89 | + |
| 90 | + errMsg := builder.String() |
| 91 | + return errors.New(errMsg) |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + var release string |
| 96 | + if runFlags.release != "" { |
| 97 | + nonDefaultContainer = true |
| 98 | + |
| 99 | + var err error |
| 100 | + release, err = utils.ParseRelease(runFlags.release) |
| 101 | + if err != nil { |
| 102 | + err := utils.CreateErrorInvalidRelease(executableBase) |
| 103 | + return err |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + if len(args) == 0 { |
| 108 | + var builder strings.Builder |
| 109 | + fmt.Fprintf(&builder, "missing argument for \"run\"\n") |
| 110 | + fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase) |
| 111 | + |
| 112 | + errMsg := builder.String() |
| 113 | + return errors.New(errMsg) |
| 114 | + } |
| 115 | + |
| 116 | + command := args |
| 117 | + |
| 118 | + container, image, release, err := utils.ResolveContainerAndImageNames(runFlags.container, "", release) |
| 119 | + if err != nil { |
| 120 | + return err |
| 121 | + } |
| 122 | + |
| 123 | + if err := runCommand(container, |
| 124 | + !nonDefaultContainer, |
| 125 | + image, |
| 126 | + release, |
| 127 | + command, |
| 128 | + false, |
| 129 | + false, |
| 130 | + true); err != nil { |
| 131 | + return err |
| 132 | + } |
| 133 | + |
| 134 | + return nil |
| 135 | +} |
| 136 | + |
| 137 | +func runCommand(container string, |
| 138 | + defaultContainer bool, |
| 139 | + image, release string, |
| 140 | + command []string, |
| 141 | + emitEscapeSequence, fallbackToBash, pedantic bool) error { |
| 142 | + if !pedantic { |
| 143 | + if image == "" { |
| 144 | + panic("image not specified") |
| 145 | + } |
| 146 | + |
| 147 | + if release == "" { |
| 148 | + panic("release not specified") |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + logrus.Debugf("Checking if container %s exists", container) |
| 153 | + |
| 154 | + if _, err := podman.ContainerExists(container); err != nil { |
| 155 | + logrus.Debugf("Container %s not found", container) |
| 156 | + |
| 157 | + if pedantic { |
| 158 | + err := utils.CreateErrorContainerNotFound(container, executableBase) |
| 159 | + return err |
| 160 | + } |
| 161 | + |
| 162 | + containers, err := listContainers() |
| 163 | + if err != nil { |
| 164 | + err := utils.CreateErrorContainerNotFound(container, executableBase) |
| 165 | + return err |
| 166 | + } |
| 167 | + |
| 168 | + containersCount := len(containers) |
| 169 | + logrus.Debugf("Found %d containers", containersCount) |
| 170 | + |
| 171 | + if containersCount == 0 { |
| 172 | + var shouldCreateContainer bool |
| 173 | + promptForCreate := true |
| 174 | + |
| 175 | + if rootFlags.assumeYes { |
| 176 | + shouldCreateContainer = true |
| 177 | + promptForCreate = false |
| 178 | + } |
| 179 | + |
| 180 | + if promptForCreate { |
| 181 | + prompt := "No toolbox containers found. Create now? [y/N]" |
| 182 | + shouldCreateContainer = utils.AskForConfirmation(prompt) |
| 183 | + } |
| 184 | + |
| 185 | + if !shouldCreateContainer { |
| 186 | + fmt.Printf("A container can be created later with the 'create' command.\n") |
| 187 | + fmt.Printf("Run '%s --help' for usage.\n", executableBase) |
| 188 | + return nil |
| 189 | + } |
| 190 | + |
| 191 | + if err := createContainer(container, image, release, false); err != nil { |
| 192 | + return err |
| 193 | + } |
| 194 | + } else if containersCount == 1 && defaultContainer { |
| 195 | + fmt.Fprintf(os.Stderr, "Error: container %s not found\n", container) |
| 196 | + |
| 197 | + container = containers[0]["Names"].(string) |
| 198 | + fmt.Fprintf(os.Stderr, "Entering container %s instead.\n", container) |
| 199 | + fmt.Fprintf(os.Stderr, "Use the 'create' command to create a different toolbox.\n") |
| 200 | + fmt.Fprintf(os.Stderr, "Run '%s --help' for usage.\n", executableBase) |
| 201 | + } else { |
| 202 | + var builder strings.Builder |
| 203 | + fmt.Fprintf(&builder, "container %s not found\n", container) |
| 204 | + fmt.Fprintf(&builder, "Use the '--container' option to select a toolbox.\n") |
| 205 | + fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase) |
| 206 | + |
| 207 | + errMsg := builder.String() |
| 208 | + return errors.New(errMsg) |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + if _, err := utils.CallFlatpakSessionHelper(); err != nil { |
| 213 | + return err |
| 214 | + } |
| 215 | + |
| 216 | + logrus.Debugf("Starting container %s", container) |
| 217 | + if err := startContainer(container); err != nil { |
| 218 | + return err |
| 219 | + } |
| 220 | + |
| 221 | + entryPoint, entryPointPID, err := getEntryPointAndPID(container) |
| 222 | + if err != nil { |
| 223 | + return err |
| 224 | + } |
| 225 | + |
| 226 | + if entryPoint != "toolbox" { |
| 227 | + var builder strings.Builder |
| 228 | + fmt.Fprintf(&builder, "container %s is too old and no longer supported \n", container) |
| 229 | + fmt.Fprintf(&builder, "Recreate it with Toolbox version 0.0.17 or newer.\n") |
| 230 | + |
| 231 | + errMsg := builder.String() |
| 232 | + return errors.New(errMsg) |
| 233 | + } |
| 234 | + |
| 235 | + if entryPointPID <= 0 { |
| 236 | + return fmt.Errorf("invalid entry point PID of container %s", container) |
| 237 | + } |
| 238 | + |
| 239 | + logrus.Debugf("Waiting for container %s to finish initializing", container) |
| 240 | + |
| 241 | + runtimeDirectory := os.Getenv("XDG_RUNTIME_DIR") |
| 242 | + toolboxRuntimeDirectory := runtimeDirectory + "/toolbox" |
| 243 | + initializedStamp := fmt.Sprintf("%s/container-initialized-%d", toolboxRuntimeDirectory, entryPointPID) |
| 244 | + |
| 245 | + logrus.Debugf("Checking if initialization stamp %s exists", initializedStamp) |
| 246 | + |
| 247 | + initializedTimeout := 25 // seconds |
| 248 | + for i := 0; !utils.PathExists(initializedStamp); i++ { |
| 249 | + if i == initializedTimeout { |
| 250 | + return fmt.Errorf("failed to initialize container %s", container) |
| 251 | + } |
| 252 | + |
| 253 | + time.Sleep(time.Second) |
| 254 | + } |
| 255 | + |
| 256 | + logrus.Debugf("Container %s is initialized", container) |
| 257 | + |
| 258 | + if _, err := isCommandPresent(container, command[0]); err != nil { |
| 259 | + if fallbackToBash { |
| 260 | + logrus.Debugf("command %s not found in container %s; using /bin/bash instead", |
| 261 | + command[0], |
| 262 | + container) |
| 263 | + |
| 264 | + command = []string{"/bin/bash"} |
| 265 | + } else { |
| 266 | + return fmt.Errorf("command %s not found in container %s", command[0], container) |
| 267 | + } |
| 268 | + } |
| 269 | + |
| 270 | + envOptions := utils.GetEnvOptionsForPreservedVariables() |
| 271 | + logLevelString := podman.LogLevel.String() |
| 272 | + |
| 273 | + execArgs := []string{ |
| 274 | + "--log-level", logLevelString, |
| 275 | + "exec", |
| 276 | + "--interactive", |
| 277 | + "--tty", |
| 278 | + "--user", currentUser.Username, |
| 279 | + "--workdir", workingDirectory, |
| 280 | + } |
| 281 | + |
| 282 | + execArgs = append(execArgs, envOptions...) |
| 283 | + |
| 284 | + execArgs = append(execArgs, []string{ |
| 285 | + container, |
| 286 | + "capsh", "--caps=", "--", "-c", "exec \"$@\"", "/bin/sh", |
| 287 | + }...) |
| 288 | + |
| 289 | + execArgs = append(execArgs, command...) |
| 290 | + |
| 291 | + if emitEscapeSequence { |
| 292 | + fmt.Printf("\033]777;container;push;%s;toolbox\033\\", container) |
| 293 | + } |
| 294 | + |
| 295 | + logrus.Debugf("Running in container %s:", container) |
| 296 | + logrus.Debug("podman") |
| 297 | + for _, arg := range execArgs { |
| 298 | + logrus.Debugf("%s", arg) |
| 299 | + } |
| 300 | + |
| 301 | + exitCode, err := shell.RunWithExitCode("podman", os.Stdin, os.Stdout, nil, execArgs...) |
| 302 | + |
| 303 | + if emitEscapeSequence { |
| 304 | + fmt.Print("\033]777;container;pop;;\033\\") |
| 305 | + } |
| 306 | + |
| 307 | + switch exitCode { |
| 308 | + case 0: |
| 309 | + if err != nil { |
| 310 | + panic("unexpected error: 'podman exec' finished successfully") |
| 311 | + } |
| 312 | + case 125: |
| 313 | + err = fmt.Errorf("failed to invoke 'podman exec' in container %s", container) |
| 314 | + case 126: |
| 315 | + err = fmt.Errorf("failed to invoke command %s in container %s", command[0], container) |
| 316 | + case 127: |
| 317 | + err = fmt.Errorf("command %s not found in container %s", command[0], container) |
| 318 | + default: |
| 319 | + err = nil |
| 320 | + } |
| 321 | + |
| 322 | + if err != nil { |
| 323 | + return err |
| 324 | + } |
| 325 | + |
63 | 326 | return nil |
64 | 327 | } |
65 | 328 |
|
@@ -115,3 +378,79 @@ func getEntryPointAndPID(container string) (string, int, error) { |
115 | 378 |
|
116 | 379 | return entryPoint, entryPointPIDInt, nil |
117 | 380 | } |
| 381 | + |
| 382 | +func isCommandPresent(container, command string) (bool, error) { |
| 383 | + logrus.Debugf("Looking for command %s in container %s", command, container) |
| 384 | + |
| 385 | + logLevelString := podman.LogLevel.String() |
| 386 | + args := []string{ |
| 387 | + "--log-level", logLevelString, |
| 388 | + "exec", |
| 389 | + "--user", currentUser.Username, |
| 390 | + container, |
| 391 | + "sh", "-c", "command -v \"$1\"", "sh", command, |
| 392 | + } |
| 393 | + |
| 394 | + if err := shell.Run("podman", nil, nil, nil, args...); err != nil { |
| 395 | + return false, err |
| 396 | + } |
| 397 | + |
| 398 | + return true, nil |
| 399 | +} |
| 400 | + |
| 401 | +func startContainer(container string) error { |
| 402 | + var stderr strings.Builder |
| 403 | + if err := podman.Start(container, &stderr); err == nil { |
| 404 | + return nil |
| 405 | + } |
| 406 | + |
| 407 | + errString := stderr.String() |
| 408 | + if !strings.Contains(errString, "use system migrate to mitigate") { |
| 409 | + return fmt.Errorf("failed to start container %s", container) |
| 410 | + } |
| 411 | + |
| 412 | + logrus.Debug("Checking if 'podman system migrate' supports '--new-runtime'") |
| 413 | + |
| 414 | + if !podman.CheckVersion("1.6.2") { |
| 415 | + var builder strings.Builder |
| 416 | + |
| 417 | + fmt.Fprintf(&builder, |
| 418 | + "container %s doesn't support cgroups v%d\n", |
| 419 | + container, |
| 420 | + cgroupsVersion) |
| 421 | + |
| 422 | + fmt.Fprintf(&builder, "Update Podman to version 1.6.2 or newer.\n") |
| 423 | + |
| 424 | + errMsg := builder.String() |
| 425 | + return errors.New(errMsg) |
| 426 | + } |
| 427 | + |
| 428 | + logrus.Debug("'podman system migrate' supports '--new-runtime'") |
| 429 | + |
| 430 | + ociRuntimeRequired := "runc" |
| 431 | + if cgroupsVersion == 2 { |
| 432 | + ociRuntimeRequired = "crun" |
| 433 | + } |
| 434 | + |
| 435 | + logrus.Debugf("Migrating containers to OCI runtime %s", ociRuntimeRequired) |
| 436 | + |
| 437 | + if err := podman.SystemMigrate(ociRuntimeRequired); err != nil { |
| 438 | + var builder strings.Builder |
| 439 | + fmt.Fprintf(&builder, "failed to migrate containers to OCI runtime %s\n", ociRuntimeRequired) |
| 440 | + fmt.Fprintf(&builder, "Factory reset with: podman system reset") |
| 441 | + |
| 442 | + errMsg := builder.String() |
| 443 | + return errors.New(errMsg) |
| 444 | + } |
| 445 | + |
| 446 | + if err := podman.Start(container, nil); err != nil { |
| 447 | + var builder strings.Builder |
| 448 | + fmt.Fprintf(&builder, "container %s doesn't support cgroups v%d\n", container, cgroupsVersion) |
| 449 | + fmt.Fprintf(&builder, "Factory reset with: podman system reset") |
| 450 | + |
| 451 | + errMsg := builder.String() |
| 452 | + return errors.New(errMsg) |
| 453 | + } |
| 454 | + |
| 455 | + return nil |
| 456 | +} |
0 commit comments