better support for custom commands

This commit is contained in:
Jesse Duffield 2019-06-10 20:14:44 +10:00
parent b02c92da33
commit 3e49e299b9
9 changed files with 166 additions and 83 deletions

View file

@ -36,6 +36,7 @@ type Container struct {
StatHistory []RecordedStats StatHistory []RecordedStats
Details Details Details Details
MonitoringStats bool MonitoringStats bool
DockerCommand LimitedDockerCommand
} }
// RecordedStats contains both the container stats we've received from docker, and our own derived stats from those container stats. When configuring a graph, you're basically specifying the path of a value in this struct // RecordedStats contains both the container stats we've received from docker, and our own derived stats from those container stats. When configuring a graph, you're basically specifying the path of a value in this struct
@ -354,13 +355,6 @@ func (c *Container) Restart() error {
return c.Client.ContainerRestart(context.Background(), c.ID, nil) return c.Client.ContainerRestart(context.Background(), c.ID, nil)
} }
// RestartService restarts the container
func (c *Container) RestartService() error {
templateString := c.OSCommand.Config.UserConfig.CommandTemplates.RestartService
command := utils.ApplyTemplate(templateString, c)
return c.OSCommand.RunCommand(command)
}
// Attach attaches the container // Attach attaches the container
func (c *Container) Attach() (*exec.Cmd, error) { func (c *Container) Attach() (*exec.Cmd, error) {
// verify that we can in fact attach to this container // verify that we can in fact attach to this container
@ -394,7 +388,10 @@ func (c *Container) EraseOldHistory() {
// ViewLogs attaches to a subprocess viewing the container's logs // ViewLogs attaches to a subprocess viewing the container's logs
func (c *Container) ViewLogs() (*exec.Cmd, error) { func (c *Container) ViewLogs() (*exec.Cmd, error) {
templateString := c.OSCommand.Config.UserConfig.CommandTemplates.ViewContainerLogs templateString := c.OSCommand.Config.UserConfig.CommandTemplates.ViewContainerLogs
command := utils.ApplyTemplate(templateString, c) command := utils.ApplyTemplate(
templateString,
c.DockerCommand.NewCommandObject(CommandObject{Container: c}),
)
cmd := c.OSCommand.ExecutableFromString(command) cmd := c.OSCommand.ExecutableFromString(command)
c.OSCommand.PrepareForChildren(cmd) c.OSCommand.PrepareForChildren(cmd)
@ -407,3 +404,17 @@ func (c *DockerCommand) PruneContainers() error {
_, err := c.Client.ContainersPrune(context.Background(), filters.Args{}) _, err := c.Client.ContainersPrune(context.Background(), filters.Args{})
return err return err
} }
// TTYLogsCommand returns a command for obtaining the logs from a TTY container
func (c *Container) TTYLogsCommand() *exec.Cmd {
command := utils.ApplyTemplate(
c.Config.UserConfig.CommandTemplates.ContainerTTYLogs,
c.DockerCommand.NewCommandObject(CommandObject{Container: c}),
)
return c.OSCommand.RunCustomCommand(command)
}
// Inspect returns details about the container
func (c *Container) Inspect() (types.ContainerJSON, error) {
return c.Client.ContainerInspect(context.Background(), c.ID)
}

View file

@ -14,6 +14,7 @@ import (
"github.com/acarl005/stripansi" "github.com/acarl005/stripansi"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/imdario/mergo"
"github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/i18n" "github.com/jesseduffield/lazydocker/pkg/i18n"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
@ -39,6 +40,25 @@ type DockerCommand struct {
Volumes []*Volume Volumes []*Volume
} }
// LimitedDockerCommand is a stripped-down DockerCommand with just the methods the container/service/image might need
type LimitedDockerCommand interface {
NewCommandObject(CommandObject) CommandObject
}
// CommandObject is what we pass to our template resolvers when we are running a custom command. We do not guarantee that all fields will be populated: just the ones that make sense for the current context
type CommandObject struct {
DockerCompose string
Service *Service
Container *Container
}
// NewCommandObject takes a command object and returns a default command object with the passed command object merged in
func (c *DockerCommand) NewCommandObject(obj CommandObject) CommandObject {
defaultObj := CommandObject{DockerCompose: c.Config.UserConfig.CommandTemplates.DockerCompose}
mergo.Merge(&defaultObj, obj)
return defaultObj
}
// NewDockerCommand it runs git commands // NewDockerCommand it runs git commands
func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*DockerCommand, error) { func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*DockerCommand, error) {
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
@ -46,22 +66,35 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
return nil, err return nil, err
} }
inDockerComposeProject := true dockerCommand := &DockerCommand{
err = osCommand.RunCommand(config.UserConfig.CommandTemplates.CheckDockerComposeConfig)
if err != nil {
inDockerComposeProject = false
log.Warn(err.Error())
}
return &DockerCommand{
Log: log, Log: log,
OSCommand: osCommand, OSCommand: osCommand,
Tr: tr, Tr: tr,
Config: config, Config: config,
Client: cli, Client: cli,
InDockerComposeProject: inDockerComposeProject,
ErrorChan: errorChan, ErrorChan: errorChan,
}, nil InDockerComposeProject: true,
}
command := utils.ApplyTemplate(
config.UserConfig.CommandTemplates.CheckDockerComposeConfig,
dockerCommand.NewCommandObject(CommandObject{}),
)
log.Warn(command)
err = osCommand.RunCommand(
utils.ApplyTemplate(
config.UserConfig.CommandTemplates.CheckDockerComposeConfig,
dockerCommand.NewCommandObject(CommandObject{}),
),
)
if err != nil {
dockerCommand.InDockerComposeProject = false
log.Warn(err.Error())
}
return dockerCommand, nil
} }
// MonitorContainerStats is a function // MonitorContainerStats is a function
@ -268,6 +301,7 @@ func (c *DockerCommand) GetContainers() ([]*Container, error) {
OSCommand: c.OSCommand, OSCommand: c.OSCommand,
Log: c.Log, Log: c.Log,
Config: c.Config, Config: c.Config,
DockerCommand: c,
} }
} }
@ -314,6 +348,7 @@ func (c *DockerCommand) GetServices() ([]*Service, error) {
ID: arr[1], ID: arr[1],
OSCommand: c.OSCommand, OSCommand: c.OSCommand,
Log: c.Log, Log: c.Log,
DockerCommand: c,
} }
} }
@ -353,7 +388,13 @@ func (c *DockerCommand) UpdateContainerDetails() error {
// ViewAllLogs attaches to a subprocess viewing all the logs from docker-compose // ViewAllLogs attaches to a subprocess viewing all the logs from docker-compose
func (c *DockerCommand) ViewAllLogs() (*exec.Cmd, error) { func (c *DockerCommand) ViewAllLogs() (*exec.Cmd, error) {
cmd := c.OSCommand.ExecutableFromString(c.OSCommand.Config.UserConfig.CommandTemplates.ViewAllLogs) cmd := c.OSCommand.ExecutableFromString(
utils.ApplyTemplate(
c.OSCommand.Config.UserConfig.CommandTemplates.ViewAllLogs,
c.NewCommandObject(CommandObject{}),
),
)
c.OSCommand.PrepareForChildren(cmd) c.OSCommand.PrepareForChildren(cmd)
return cmd, nil return cmd, nil
@ -361,7 +402,13 @@ func (c *DockerCommand) ViewAllLogs() (*exec.Cmd, error) {
// DockerComposeConfig returns the result of 'docker-compose config' // DockerComposeConfig returns the result of 'docker-compose config'
func (c *DockerCommand) DockerComposeConfig() string { func (c *DockerCommand) DockerComposeConfig() string {
output, err := c.OSCommand.RunCommandWithOutput(c.OSCommand.Config.UserConfig.CommandTemplates.DockerComposeConfig) output, err := c.OSCommand.RunCommandWithOutput(
utils.ApplyTemplate(
c.OSCommand.Config.UserConfig.CommandTemplates.DockerComposeConfig,
c.NewCommandObject(CommandObject{}),
),
)
if err != nil { if err != nil {
output = err.Error() output = err.Error()
} }

View file

@ -21,6 +21,7 @@ type Image struct {
Client *client.Client Client *client.Client
OSCommand *OSCommand OSCommand *OSCommand
Log *logrus.Entry Log *logrus.Entry
DockerCommand LimitedDockerCommand
} }
// GetDisplayStrings returns the display string of Image // GetDisplayStrings returns the display string of Image
@ -99,9 +100,6 @@ func (i *Image) RenderHistory() (string, error) {
return utils.RenderList(layers, utils.WithHeader([]string{"ID", "TAG", "SIZE", "COMMAND"})) return utils.RenderList(layers, utils.WithHeader([]string{"ID", "TAG", "SIZE", "COMMAND"}))
} }
// GetImages returns a slice of docker images // GetImages returns a slice of docker images
func (c *DockerCommand) GetImages() ([]*Image, error) { func (c *DockerCommand) GetImages() ([]*Image, error) {
images, err := c.Client.ImageList(context.Background(), types.ImageListOptions{}) images, err := c.Client.ImageList(context.Background(), types.ImageListOptions{})
@ -134,6 +132,7 @@ func (c *DockerCommand) GetImages() ([]*Image, error) {
Client: c.Client, Client: c.Client,
OSCommand: c.OSCommand, OSCommand: c.OSCommand,
Log: c.Log, Log: c.Log,
DockerCommand: c,
} }
} }

View file

@ -16,6 +16,7 @@ type Service struct {
OSCommand *OSCommand OSCommand *OSCommand
Log *logrus.Entry Log *logrus.Entry
Container *Container Container *Container
DockerCommand LimitedDockerCommand
} }
// GetDisplayStrings returns the dispaly string of Container // GetDisplayStrings returns the dispaly string of Container
@ -37,14 +38,20 @@ func (s *Service) Remove(options types.ContainerRemoveOptions) error {
// Stop stops the service's containers // Stop stops the service's containers
func (s *Service) Stop() error { func (s *Service) Stop() error {
templateString := s.OSCommand.Config.UserConfig.CommandTemplates.StopService templateString := s.OSCommand.Config.UserConfig.CommandTemplates.StopService
command := utils.ApplyTemplate(templateString, s) command := utils.ApplyTemplate(
templateString,
s.DockerCommand.NewCommandObject(CommandObject{Service: s}),
)
return s.OSCommand.RunCommand(command) return s.OSCommand.RunCommand(command)
} }
// Restart restarts the service // Restart restarts the service
func (s *Service) Restart() error { func (s *Service) Restart() error {
templateString := s.OSCommand.Config.UserConfig.CommandTemplates.RestartService templateString := s.OSCommand.Config.UserConfig.CommandTemplates.RestartService
command := utils.ApplyTemplate(templateString, s) command := utils.ApplyTemplate(
templateString,
s.DockerCommand.NewCommandObject(CommandObject{Service: s}),
)
return s.OSCommand.RunCommand(command) return s.OSCommand.RunCommand(command)
} }
@ -61,7 +68,10 @@ func (s *Service) Top() (types.ContainerProcessList, error) {
// ViewLogs attaches to a subprocess viewing the service's logs // ViewLogs attaches to a subprocess viewing the service's logs
func (s *Service) ViewLogs() (*exec.Cmd, error) { func (s *Service) ViewLogs() (*exec.Cmd, error) {
templateString := s.OSCommand.Config.UserConfig.CommandTemplates.ViewServiceLogs templateString := s.OSCommand.Config.UserConfig.CommandTemplates.ViewServiceLogs
command := utils.ApplyTemplate(templateString, s) command := utils.ApplyTemplate(
templateString,
s.DockerCommand.NewCommandObject(CommandObject{Service: s}),
)
cmd := s.OSCommand.ExecutableFromString(command) cmd := s.OSCommand.ExecutableFromString(command)
s.OSCommand.PrepareForChildren(cmd) s.OSCommand.PrepareForChildren(cmd)

View file

@ -17,6 +17,7 @@ type Volume struct {
Client *client.Client Client *client.Client
OSCommand *OSCommand OSCommand *OSCommand
Log *logrus.Entry Log *logrus.Entry
DockerCommand LimitedDockerCommand
} }
// GetDisplayStrings returns the dispaly string of Container // GetDisplayStrings returns the dispaly string of Container
@ -46,6 +47,7 @@ func (c *DockerCommand) RefreshVolumes() error {
Client: c.Client, Client: c.Client,
OSCommand: c.OSCommand, OSCommand: c.OSCommand,
Log: c.Log, Log: c.Log,
DockerCommand: c,
} }
} }

View file

@ -164,18 +164,18 @@ func GetDefaultConfig() UserConfig {
Reporting: "undetermined", Reporting: "undetermined",
ConfirmOnQuit: false, ConfirmOnQuit: false,
CommandTemplates: CommandTemplatesConfig{ CommandTemplates: CommandTemplatesConfig{
RestartService: "docker-compose restart {{ .Name }}",
RebuildService: "docker-compose up -d --build {{ .Name }}",
DockerCompose: "docker-compose", DockerCompose: "docker-compose",
StopService: "docker-compose stop {{ .Name }}", RestartService: "{{ .DockerCompose }} restart {{ .Service.Name }}",
ServiceLogs: "docker-compose logs {{ .Name }}", RebuildService: "{{ .DockerCompose }} up -d --build {{ .Service.Name }}",
AllLogs: "docker-compose logs --tail=100", StopService: "{{ .DockerCompose }} stop {{ .Service.Name }}",
ViewAllLogs: "docker-compose logs", ServiceLogs: "{{ .DockerCompose }} logs {{ .Service.Name }}",
DockerComposeConfig: "docker-compose config", AllLogs: "{{ .DockerCompose }} logs --tail=100",
CheckDockerComposeConfig: "docker-compose config --quiet", ViewAllLogs: "{{ .DockerCompose }} logs",
ContainerLogs: "docker logs --timestamps --follow --tail=100 {{ .ID }}", DockerComposeConfig: "{{ .DockerCompose }} config",
ViewContainerLogs: "docker logs --timestamps --follow --tail=100 {{ .ID }}", CheckDockerComposeConfig: "{{ .DockerCompose }} config --quiet",
ContainerTTYLogs: "docker logs --follow --tail=100 {{ .ID }}", ContainerLogs: "docker logs --timestamps --follow --tail=100 {{ .Container.ID }}",
ViewContainerLogs: "docker logs --timestamps --follow --tail=100 {{ .Container.ID }}",
ContainerTTYLogs: "docker logs --follow --tail=100 {{ .Container.ID }}",
}, },
OS: GetPlatformDefaultConfig(), OS: GetPlatformDefaultConfig(),
Update: UpdateConfig{ Update: UpdateConfig{

View file

@ -2,7 +2,6 @@ package gui
import ( import (
"bufio" "bufio"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os/exec" "os/exec"
@ -190,8 +189,7 @@ func (gui *Gui) renderLogs(container *commands.Container, template string, setup
gui.T.NewTickerTask(time.Millisecond*200, nil, func(stop, notifyStopped chan struct{}) { gui.T.NewTickerTask(time.Millisecond*200, nil, func(stop, notifyStopped chan struct{}) {
gui.clearMainView() gui.clearMainView()
command := utils.ApplyTemplate(gui.Config.UserConfig.CommandTemplates.ContainerTTYLogs, container) cmd := container.TTYLogsCommand()
cmd := gui.OSCommand.RunCustomCommand(command)
setup(cmd) setup(cmd)
@ -213,7 +211,7 @@ func (gui *Gui) renderLogs(container *commands.Container, template string, setup
case <-stop: case <-stop:
return return
default: default:
result, err := gui.DockerCommand.Client.ContainerInspect(context.Background(), container.ID) result, err := container.Inspect()
if err != nil { if err != nil {
// if we get an error, then the container has probably been removed so we'll get out of here // if we get an error, then the container has probably been removed so we'll get out of here
gui.Log.Error(err) gui.Log.Error(err)

View file

@ -284,12 +284,18 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
rebuildCommand := utils.ApplyTemplate(gui.Config.UserConfig.CommandTemplates.RebuildService, service) rebuildCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.RebuildService,
service,
)
options := []*commandOption{ options := []*commandOption{
{ {
description: gui.Tr.Restart, description: gui.Tr.Restart,
command: utils.ApplyTemplate(gui.Config.UserConfig.CommandTemplates.RestartService, service), command: utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.RestartService,
gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}),
),
f: func() error { f: func() error {
return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error { return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error {
if err := service.Restart(); err != nil { if err := service.Restart(); err != nil {
@ -302,7 +308,10 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
}, },
{ {
description: gui.Tr.Rebuild, description: gui.Tr.Rebuild,
command: utils.ApplyTemplate(gui.Config.UserConfig.CommandTemplates.RebuildService, service), command: utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.RebuildService,
gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}),
),
f: func() error { f: func() error {
gui.SubProcess = gui.OSCommand.RunCustomCommand(rebuildCommand) gui.SubProcess = gui.OSCommand.RunCustomCommand(rebuildCommand)
return gui.Errors.ErrSubProcess return gui.Errors.ErrSubProcess

View file

@ -6,6 +6,8 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/utils"
) )
func (gui *Gui) getStatusContexts() []string { func (gui *Gui) getStatusContexts() []string {
@ -105,7 +107,12 @@ func (gui *Gui) renderAllLogs() error {
gui.clearMainView() gui.clearMainView()
cmd := gui.OSCommand.RunCustomCommand(gui.Config.UserConfig.CommandTemplates.AllLogs) cmd := gui.OSCommand.RunCustomCommand(
utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.AllLogs,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
),
)
cmd.Stdout = mainView cmd.Stdout = mainView
cmd.Stderr = mainView cmd.Stderr = mainView