Skip to content

Commit 238f245

Browse files
martymichaldebarshiray
authored andcommitted
cmd/run: Implement the run command in Go
#318
1 parent cf5c58a commit 238f245

File tree

1 file changed

+339
-0
lines changed

1 file changed

+339
-0
lines changed

src/cmd/run.go

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717
package cmd
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"os"
23+
"strings"
24+
"time"
2225

2326
"github.com/containers/toolbox/pkg/podman"
27+
"github.com/containers/toolbox/pkg/shell"
2428
"github.com/containers/toolbox/pkg/utils"
2529
"github.com/sirupsen/logrus"
2630
"github.com/spf13/cobra"
@@ -60,6 +64,265 @@ func init() {
6064
}
6165

6266
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+
63326
return nil
64327
}
65328

@@ -115,3 +378,79 @@ func getEntryPointAndPID(container string) (string, int, error) {
115378

116379
return entryPoint, entryPointPIDInt, nil
117380
}
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

Comments
 (0)