From 4e86732cf2471c7faa52695b0a77f1403c39b5ee Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 29 Jun 2019 21:29:35 +1000 Subject: [PATCH] support custom commands that filter down to service names --- pkg/commands/container.go | 8 ++- pkg/commands/docker.go | 1 + pkg/config/app_config.go | 13 ++++- pkg/gui/custom_commands.go | 9 ++- pkg/gui/services_panel.go | 21 ++++++- pkg/i18n/english.go | 110 +++++++++++++++++++------------------ 6 files changed, 102 insertions(+), 60 deletions(-) diff --git a/pkg/commands/container.go b/pkg/commands/container.go index 0d55921e..4d02cb83 100644 --- a/pkg/commands/container.go +++ b/pkg/commands/container.go @@ -13,6 +13,7 @@ import ( "github.com/fatih/color" "github.com/go-errors/errors" "github.com/jesseduffield/lazydocker/pkg/config" + "github.com/jesseduffield/lazydocker/pkg/i18n" "github.com/jesseduffield/lazydocker/pkg/utils" "github.com/sirupsen/logrus" "golang.org/x/xerrors" @@ -39,6 +40,7 @@ type Container struct { Details Details MonitoringStats bool DockerCommand LimitedDockerCommand + Tr *i18n.TranslationSet } // Details is a struct containing what we get back from `docker inspect` on a container @@ -342,7 +344,11 @@ func (c *Container) Restart() error { func (c *Container) Attach() (*exec.Cmd, error) { // verify that we can in fact attach to this container if !c.Details.Config.OpenStdin { - return nil, errors.New("Container does not support attaching. You must either run the service with the '-it' flag or use `stdin_open: true, tty: true` in the docker-compose.yml file") + return nil, errors.New(c.Tr.UnattachableContainerError) + } + + if c.Container.State == "exited" { + return nil, errors.New(c.Tr.CannotAttachStoppedContainerError) } cmd := c.OSCommand.PrepareSubProcess("docker", "attach", "--sig-proxy=false", c.ID) diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index 33e0bdee..6408bb53 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -303,6 +303,7 @@ func (c *DockerCommand) GetContainers() ([]*Container, error) { Log: c.Log, Config: c.Config, DockerCommand: c, + Tr: c.Tr, } } diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 69a6ae49..0aa48f9b 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -183,11 +183,17 @@ type CustomCommands struct { // CustomCommand is a template for a command we want to run against a service or container type CustomCommand struct { + // Name is the name of the command, purely for visual display + Name string `yaml:"name"` + // Attach tells us whether to switch to a subprocess to interact with the called program, or just read its output. If Attach is set to false, the command will run in the background. I'm open to the idea of having a third option where the output plays in the main panel. - Attach bool + Attach bool `yaml:"attach"` // Command is the command we want to run. We can use the go templates here as well. One example might be `{{ .DockerCompose }} exec {{ .Service.Name }} /bin/sh` - Command string + Command string `yaml:"command"` + + // ServiceNames is used to restrict this command to just one or more services. An example might be 'rails migrate' for your rails api service(s). This field has no effect on customcommands under the 'communications' part of the customCommand config. + ServiceNames []string `yaml:"serviceNames"` } // GetDefaultConfig returns the application default configuration @@ -226,8 +232,9 @@ func GetDefaultConfig() UserConfig { CustomCommands: CustomCommands{ Containers: []CustomCommand{ { - Attach: true, + Name: "bash", Command: "docker exec -it {{ .Container.ID }} /bin/sh", + Attach: true, }, }, Services: []CustomCommand{}, diff --git a/pkg/gui/custom_commands.go b/pkg/gui/custom_commands.go index 8a19d227..7d858d2e 100644 --- a/pkg/gui/custom_commands.go +++ b/pkg/gui/custom_commands.go @@ -1,6 +1,7 @@ package gui import ( + "github.com/fatih/color" "github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/utils" @@ -10,13 +11,14 @@ type customCommandOption struct { customCommand config.CustomCommand description string command string + name string runCommand bool attach bool } // GetDisplayStrings is a function. func (r *customCommandOption) GetDisplayStrings(isFocused bool) []string { - return []string{r.description} + return []string{r.name, utils.ColoredString(r.description, color.FgCyan)} } func (gui *Gui) createCustomCommandMenu(customCommands []config.CustomCommand, commandObject commands.CommandObject) error { @@ -30,11 +32,12 @@ func (gui *Gui) createCustomCommandMenu(customCommands []config.CustomCommand, c command: resolvedCommand, runCommand: true, attach: command.Attach, + name: command.Name, } } options[len(options)-1] = &customCommandOption{ - description: gui.Tr.Cancel, - runCommand: false, + name: gui.Tr.Cancel, + runCommand: false, } handleMenuPress := func(index int) error { diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index 44bd7d65..8bdee811 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -8,6 +8,7 @@ import ( "github.com/go-errors/errors" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazydocker/pkg/commands" + "github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/utils" ) @@ -376,7 +377,25 @@ func (gui *Gui) handleServicesCustomCommand(g *gocui.Gui, v *gocui.View) error { Container: service.Container, }) - customCommands := gui.Config.UserConfig.CustomCommands.Services + var customCommands []config.CustomCommand + + customServiceCommands := gui.Config.UserConfig.CustomCommands.Services + // we only include service commands if they have no serviceNames defined or if our service happens to be one of the serviceNames defined +L: + for _, cmd := range customServiceCommands { + if len(cmd.ServiceNames) == 0 { + customCommands = append(customCommands, cmd) + continue L + } + for _, serviceName := range cmd.ServiceNames { + if serviceName == service.Name { + // appending these to the top given they're more likely to be selected + customCommands = append([]config.CustomCommand{cmd}, customCommands...) + continue L + } + } + } + if service.Container != nil { customCommands = append(customCommands, gui.Config.UserConfig.CustomCommands.Containers...) } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 9c6ca61f..69dc6a56 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -23,54 +23,57 @@ type TranslationSet struct { ConfirmQuit string ErrorOccurred string ConnectionFailed string - Donate string - Cancel string - CustomCommandTitle string - Remove string - ForceRemove string - RemoveWithVolumes string - MustForceToRemoveContainer string - Confirm string - Return string - FocusMain string - StopContainer string - RestartingStatus string - StoppingStatus string - RemovingStatus string - RunningCustomCommandStatus string - RemoveService string - Stop string - Restart string - Rebuild string - Recreate string - PreviousContext string - NextContext string - Attach string - ViewLogs string - ServicesTitle string - ContainersTitle string - StandaloneContainersTitle string - TopTitle string - ImagesTitle string - VolumesTitle string - NoContainers string - NoContainer string - NoImages string - NoVolumes string - RemoveImage string - RemoveVolume string - RemoveWithoutPrune string - PruneImages string - PruneContainers string - PruneVolumes string - ConfirmPruneContainers string - ConfirmPruneImages string - ConfirmPruneVolumes string - PruningStatus string - StopService string - PressEnterToReturn string - ViewRestartOptions string - RunCustomCommand string + UnattachableContainerError string + CannotAttachStoppedContainerError string + + Donate string + Cancel string + CustomCommandTitle string + Remove string + ForceRemove string + RemoveWithVolumes string + MustForceToRemoveContainer string + Confirm string + Return string + FocusMain string + StopContainer string + RestartingStatus string + StoppingStatus string + RemovingStatus string + RunningCustomCommandStatus string + RemoveService string + Stop string + Restart string + Rebuild string + Recreate string + PreviousContext string + NextContext string + Attach string + ViewLogs string + ServicesTitle string + ContainersTitle string + StandaloneContainersTitle string + TopTitle string + ImagesTitle string + VolumesTitle string + NoContainers string + NoContainer string + NoImages string + NoVolumes string + RemoveImage string + RemoveVolume string + RemoveWithoutPrune string + PruneImages string + PruneContainers string + PruneVolumes string + ConfirmPruneContainers string + ConfirmPruneImages string + ConfirmPruneVolumes string + PruningStatus string + StopService string + PressEnterToReturn string + ViewRestartOptions string + RunCustomCommand string LogsTitle string ConfigTitle string @@ -91,10 +94,13 @@ func englishSet() TranslationSet { RunningSubprocess: "running subprocess", NoViewMachingNewLineFocusedSwitchStatement: "No view matching newLineFocused switch statement", - ErrorOccurred: "An error occurred! Please create an issue at https://github.com/jesseduffield/lazydocker/issues", - ConnectionFailed: "connection to docker client failed. You may need to restart the docker client", - Donate: "Donate", - Confirm: "Confirm", + ErrorOccurred: "An error occurred! Please create an issue at https://github.com/jesseduffield/lazydocker/issues", + ConnectionFailed: "connection to docker client failed. You may need to restart the docker client", + UnattachableContainerError: "Container does not support attaching. You must either run the service with the '-it' flag or use `stdin_open: true, tty: true` in the docker-compose.yml file", + 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)", + + Donate: "Donate", + Confirm: "Confirm", Return: "return", FocusMain: "focus main panel",