diff --git a/docs/keybindings/Keybindings_de.md b/docs/keybindings/Keybindings_de.md index f463e21bd..9dfa3140f 100644 --- a/docs/keybindings/Keybindings_de.md +++ b/docs/keybindings/Keybindings_de.md @@ -19,7 +19,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct d: entfernen e: hide/show stopped containers p: pause - s: anhalten + s: start/stop r: neustarten a: anbinden m: zeige Protokolle diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index a5a8ff3f0..6639c6b18 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -19,7 +19,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct d: remove e: hide/show stopped containers p: pause - s: stop + s: start/stop r: restart a: attach m: view logs diff --git a/docs/keybindings/Keybindings_es.md b/docs/keybindings/Keybindings_es.md index ff4eee148..314e58eaa 100644 --- a/docs/keybindings/Keybindings_es.md +++ b/docs/keybindings/Keybindings_es.md @@ -19,7 +19,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct d: borrar e: esconder/mostrar contenedores parados p: pausa - s: parar + s: start/stop r: reiniciar a: attach m: ver logs diff --git a/docs/keybindings/Keybindings_fr.md b/docs/keybindings/Keybindings_fr.md index 64fb4197d..369fbafd2 100644 --- a/docs/keybindings/Keybindings_fr.md +++ b/docs/keybindings/Keybindings_fr.md @@ -19,7 +19,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct d: supprimer e: cacher/montrer les conteneurs arrêtés p: pause - s: arrêter + s: start/stop r: redémarrer a: attacher m: voir les enregistrements diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md index 3de0c93e8..d784855ab 100644 --- a/docs/keybindings/Keybindings_nl.md +++ b/docs/keybindings/Keybindings_nl.md @@ -19,7 +19,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct d: verwijder e: verberg gestopte containers p: pause - s: stop + s: start/stop r: herstart a: verbinden m: bekijk logs diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md index 8d46b0efe..0b99a9e11 100644 --- a/docs/keybindings/Keybindings_pl.md +++ b/docs/keybindings/Keybindings_pl.md @@ -19,7 +19,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct d: usuń e: hide/show stopped containers p: pause - s: zatrzymaj + s: start/stop r: restartuj a: przyczep m: pokaż logi diff --git a/docs/keybindings/Keybindings_pt.md b/docs/keybindings/Keybindings_pt.md index 41604314c..c703a641f 100644 --- a/docs/keybindings/Keybindings_pt.md +++ b/docs/keybindings/Keybindings_pt.md @@ -19,7 +19,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct d: remover e: ocultar/mostrar contêineres parados p: pausar - s: parar + s: start/stop r: reiniciar a: anexar m: ver logs diff --git a/docs/keybindings/Keybindings_tr.md b/docs/keybindings/Keybindings_tr.md index 018f68c28..0ceea2412 100644 --- a/docs/keybindings/Keybindings_tr.md +++ b/docs/keybindings/Keybindings_tr.md @@ -19,7 +19,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct d: kaldır e: hide/show stopped containers p: pause - s: durdur + s: start/stop r: yeniden başlat a: bağlan/iliştir m: kayıt defterini görüntüle diff --git a/docs/keybindings/Keybindings_zh.md b/docs/keybindings/Keybindings_zh.md index 8b1607dde..38f49708b 100644 --- a/docs/keybindings/Keybindings_zh.md +++ b/docs/keybindings/Keybindings_zh.md @@ -19,7 +19,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct d: 移除 e: 隐藏/显示已停止的容器 p: 暂停 - s: 停止 + s: start/stop r: 重新启动 a: attach m: 查看日志 diff --git a/pkg/commands/container.go b/pkg/commands/container.go index 997175f23..99ca14e03 100644 --- a/pkg/commands/container.go +++ b/pkg/commands/container.go @@ -59,6 +59,12 @@ func (c *Container) Remove(options container.RemoveOptions) error { return nil } +// Start starts the container +func (c *Container) Start() error { + c.Log.Warn(fmt.Sprintf("starting container %s", c.Name)) + return c.Client.ContainerStart(context.Background(), c.ID, container.StartOptions{}) +} + // Stop stops the container func (c *Container) Stop() error { c.Log.Warn(fmt.Sprintf("stopping container %s", c.Name)) diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 9c0ad4745..f19f72654 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -220,6 +220,9 @@ type CommandTemplatesConfig struct { // ServiceTop is the command for viewing the processes under a given service ServiceTop string `yaml:"serviceTop,omitempty"` + + // List of shells that, in given order, will be tried when attaching to a container. + PreferredExecShells []string `yaml:"preferedExecShell,omitempty"` } // OSConfig contains config on the level of the os @@ -402,6 +405,7 @@ func GetDefaultConfig() UserConfig { DockerComposeConfig: "{{ .DockerCompose }} config", CheckDockerComposeConfig: "{{ .DockerCompose }} config --quiet", ServiceTop: "{{ .DockerCompose }} top {{ .Service.Name }}", + PreferredExecShells: []string{}, }, CustomCommands: CustomCommands{ Containers: []CustomCommand{}, diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 92aa95b37..4fe7ca2fe 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -342,6 +342,8 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error { }) } +// TODO: do same thing for start +// Fix UI not showing it being paused (it should say unpaused) func (gui *Gui) PauseContainer(container *commands.Container) error { return gui.WithWaitingStatus(gui.Tr.PausingStatus, func() (err error) { if container.Details.State.Paused { @@ -367,12 +369,22 @@ func (gui *Gui) handleContainerPause(g *gocui.Gui, v *gocui.View) error { return gui.PauseContainer(ctr) } -func (gui *Gui) handleContainerStop(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) handleContainerStartStop(g *gocui.Gui, v *gocui.View) error { ctr, err := gui.Panels.Containers.GetSelectedItem() if err != nil { return nil } + if !(ctr.Container.State == "exited" || ctr.Container.State == "running") { + return gui.createErrorPanel(gui.Tr.CannotStartStop) + } + + if ctr.Container.State == "exited" { + return gui.WithWaitingStatus(gui.Tr.StoppingStatus, func() error { + return ctr.Start() + }) + } + return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.StopContainer, func(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.StoppingStatus, func() error { if err := ctr.Stop(); err != nil { @@ -449,11 +461,32 @@ func (gui *Gui) containerExecShell(container *commands.Container) error { commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{ Container: container, }) + var command string + shell := "" + + preferredExecShells := gui.Config.UserConfig.CommandTemplates.PreferredExecShells + if len(preferredExecShells) > 0 { + for _, preferredExecShell := range preferredExecShells { + command := utils.ApplyTemplate(fmt.Sprintf("docker exec {{ .Container.ID }} which %s", preferredExecShell), commandObject) + + err := gui.runCommandSilently(gui.OSCommand.ExecutableFromString(command)) + if err == nil { + shell = preferredExecShell + break + } + } + } + + // TODO: Use SDK + // Use default implementation in case we cannot fulfill user's preference + if shell == "" { + command = utils.ApplyTemplate("docker exec -it {{ .Container.ID }} /bin/sh -c 'eval $(grep ^$(id -un): /etc/passwd | cut -d : -f 7-)'", commandObject) + } else { + command = utils.ApplyTemplate(fmt.Sprintf("docker exec -it {{ .Container.ID }} %s", shell), commandObject) + } - // TODO: use SDK - resolvedCommand := utils.ApplyTemplate("docker exec -it {{ .Container.ID }} /bin/sh -c 'eval $(grep ^$(id -un): /etc/passwd | cut -d : -f 7-)'", commandObject) // attach and return the subprocess error - cmd := gui.OSCommand.ExecutableFromString(resolvedCommand) + cmd := gui.OSCommand.ExecutableFromString(command) return gui.runSubprocess(cmd) } diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 424f47c56..5290132f9 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -210,8 +210,8 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "containers", Key: 's', Modifier: gocui.ModNone, - Handler: gui.handleContainerStop, - Description: gui.Tr.Stop, + Handler: gui.handleContainerStartStop, + Description: gui.Tr.StartStop, }, { ViewName: "containers", diff --git a/pkg/gui/subprocess.go b/pkg/gui/subprocess.go index b41ac4757..300bfca45 100644 --- a/pkg/gui/subprocess.go +++ b/pkg/gui/subprocess.go @@ -26,7 +26,7 @@ func (gui *Gui) runSubprocessWithMessage(cmd *exec.Cmd, msg string) error { gui.PauseBackgroundThreads = true - gui.runCommand(cmd, msg) + err := gui.runCommand(cmd, msg) if err := gui.g.Resume(); err != nil { return gui.createErrorPanel(err.Error()) @@ -34,10 +34,26 @@ func (gui *Gui) runSubprocessWithMessage(cmd *exec.Cmd, msg string) error { gui.PauseBackgroundThreads = false - return nil + return err } -func (gui *Gui) runCommand(cmd *exec.Cmd, msg string) { +func (gui *Gui) runCommandSilently(cmd *exec.Cmd) error { + stop := make(chan os.Signal, 1) + defer signal.Stop(stop) + + go func() { + signal.Notify(stop, os.Interrupt) + <-stop + + if err := gui.OSCommand.Kill(cmd); err != nil { + gui.Log.Error(err) + } + }() + + return cmd.Run() +} + +func (gui *Gui) runCommand(cmd *exec.Cmd, msg string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stdout cmd.Stdin = os.Stdin @@ -58,9 +74,8 @@ func (gui *Gui) runCommand(cmd *exec.Cmd, msg string) { if msg != "" { fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString(msg, color.FgGreen)) } - if err := cmd.Run(); err != nil { - // not handling the error explicitly because usually we're going to see it - // in the output anyway + err := cmd.Run() + if err != nil { gui.Log.Error(err) } @@ -69,4 +84,6 @@ func (gui *Gui) runCommand(cmd *exec.Cmd, msg string) { cmd.Stderr = io.Discard gui.promptToReturn() + + return err } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 0d4f82222..8085f5d9b 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -26,6 +26,7 @@ type TranslationSet struct { CannotAttachStoppedContainerError string CannotAccessDockerSocketError string CannotKillChildError string + CannotStartStop string Donate string Cancel string @@ -59,6 +60,7 @@ type TranslationSet struct { Down string DownWithVolumes string Start string + StartStop string Rebuild string Recreate string PreviousContext string @@ -153,6 +155,7 @@ func englishSet() TranslationSet { CannotAttachStoppedContainerError: "You cannot attach to a stopped container, you need to start it first (which you can actually do with the 'r' key) (yes I'm too lazy to do this automatically for you) (pretty cool that I get to communicate one-on-one with you in the form of an error message though)", CannotAccessDockerSocketError: "Can't access docker socket at: unix:///var/run/docker.sock\nRun lazydocker as root or read https://docs.docker.com/install/linux/linux-postinstall/", CannotKillChildError: "Waited three seconds for child process to stop. There may be an orphan process that continues to run on your system.", + CannotStartStop: "Cannot start stop this container", Donate: "Donate", Confirm: "Confirm", @@ -182,6 +185,7 @@ func englishSet() TranslationSet { Down: "down project", DownWithVolumes: "down project with volumes", Start: "start", + StartStop: "start/stop", Rebuild: "rebuild", Recreate: "recreate", PreviousContext: "previous tab",