Skip to content
Permalink
Browse files

more progress

  • Loading branch information...
jesseduffield committed Jun 1, 2019
1 parent 88758de commit f0ffdcd81f713e85585d5d81be00ed463e7df47e
Showing with 139 additions and 44 deletions.
  1. +2 −1 .gitignore
  2. +37 −9 pkg/commands/container.go
  3. +49 −12 pkg/commands/docker.go
  4. +2 −0 pkg/config/app_config.go
  5. +26 −22 pkg/gui/containers_panel.go
  6. +7 −0 pkg/gui/keybindings.go
  7. +8 −0 pkg/i18n/english.go
  8. +8 −0 pkg/utils/utils.go
@@ -1,2 +1,3 @@

TODO.md
TODO.md
Lazydocker.code-workspace
@@ -15,19 +15,35 @@ import (

// Container : A docker Container
type Container struct {
Name string
ServiceName string
ID string
Container types.Container
DisplayString string
Client *client.Client
OSCommand *OSCommand
Log *logrus.Entry
Name string
ServiceName string
ContainerNumber string // might make this an int in the future if need be
ProjectName string
ID string
Container types.Container
DisplayString string
Client *client.Client
OSCommand *OSCommand
Log *logrus.Entry
Stats ContainerCliStat
}

// ContainerCliStat is a stat object returned by the CLI docker stat command
type ContainerCliStat struct {
BlockIO string `json:"BlockIO"`
CPUPerc string `json:"CPUPerc"`
Container string `json:"Container"`
ID string `json:"ID"`
MemPerc string `json:"MemPerc"`
MemUsage string `json:"MemUsage"`
Name string `json:"Name"`
NetIO string `json:"NetIO"`
PIDs string `json:"PIDs"`
}

// GetDisplayStrings returns the dispaly string of Container
func (c *Container) GetDisplayStrings(isFocused bool) []string {
return []string{utils.ColoredString(c.Container.State, c.GetColor()), utils.ColoredString(c.Name, color.FgWhite)}
return []string{utils.ColoredString(c.Container.State, c.GetColor()), utils.ColoredString(c.Name, color.FgWhite), c.Stats.CPUPerc}
}

// GetColor Container color
@@ -79,8 +95,20 @@ func (c *Container) Restart() error {
return c.Client.ContainerRestart(context.Background(), c.ID, nil)
}

// RestartService restarts the container
func (c *Container) RestartService() error {
templateString := c.OSCommand.Config.GetUserConfig().GetString("commandTemplates.restartService")
command := utils.ApplyTemplate(templateString, c)
return c.OSCommand.RunCommand(command)
}

// Attach attaches the container
func (c *Container) Attach() *exec.Cmd {
cmd := c.OSCommand.PrepareSubProcess("docker", "attach", "--sig-proxy=false", c.ID)
return cmd
}

// Top returns process information
func (c *Container) Top() (types.ContainerProcessList, error) {
return c.Client.ContainerTop(context.Background(), c.ID, []string{})
}
@@ -2,6 +2,7 @@ package commands

import (
"context"
"encoding/json"
"strings"

"github.com/docker/docker/api/types"
@@ -37,6 +38,40 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localize
}, nil
}

// UpdateContainerStats takes a slice of containers and returns the same slice but with new stats added
// TODO: consider using this for everything stats-related
func (c *DockerCommand) UpdateContainerStats(containers []*Container) ([]*Container, error) {
// TODO: consider using a stream rather than polling
command := `docker stats --all --no-trunc --no-stream --format '{{json .}}'`
output, err := c.OSCommand.RunCommandWithOutput(command)
if err != nil {
return nil, err
}

jsonStats := "[" + strings.Join(
strings.Split(
strings.Trim(output, "\n"), "\n",
), ",",
) + "]"

c.Log.Warn(jsonStats)

var stats []ContainerCliStat
if err := json.Unmarshal([]byte(jsonStats), &stats); err != nil {
return nil, err
}

for _, stat := range stats {
for _, container := range containers {
if container.ID == stat.ID {
container.Stats = stat
}
}
}

return containers, nil
}

// GetContainers returns a slice of docker containers
func (c *DockerCommand) GetContainers() ([]*Container, error) {
containers, err := c.Client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
@@ -47,22 +82,24 @@ func (c *DockerCommand) GetContainers() ([]*Container, error) {
ownContainers := make([]*Container, len(containers))

for i, container := range containers {
serviceName, ok := container.Labels["com.docker.compose.service"]
if !ok {
serviceName = ""
c.Log.Warn("Could not get service name from docker container")
}
ownContainers[i] = &Container{
ID: container.ID,
Name: strings.TrimLeft(container.Names[0], "/"),
ServiceName: serviceName,
Container: container,
Client: c.Client,
OSCommand: c.OSCommand,
Log: c.Log,
ID: container.ID,
Name: strings.TrimLeft(container.Names[0], "/"),
ServiceName: container.Labels["com.docker.compose.service"],
ProjectName: container.Labels["com.docker.compose.project"],
ContainerNumber: container.Labels["com.docker.compose.container"],
Container: container,
Client: c.Client,
OSCommand: c.OSCommand,
Log: c.Log,
}
}

// ownContainers, err = c.UpdateContainerStats(ownContainers)
// if err != nil {
// return nil, err
// }

return ownContainers, nil
}

@@ -248,6 +248,8 @@ update:
days: 14 # how often a update is checked for
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
confirmOnQuit: false
commandTemplates:
restartService: "docker-compose restart {{ .ServiceName }}"
`)
}

@@ -8,9 +8,7 @@ import (
"io"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"time"

"github.com/docker/docker/api/types"
@@ -374,11 +372,14 @@ func (gui *Gui) handleContainerStop(g *gocui.Gui, v *gocui.View) error {
}

return gui.createConfirmationPanel(gui.g, v, gui.Tr.SLocalize("Confirm"), gui.Tr.SLocalize("StopContainer"), func(g *gocui.Gui, v *gocui.View) error {
if err := container.Stop(); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
return gui.WithWaitingStatus(gui.Tr.SLocalize("StoppingStatus"), func() error {
if err := container.Stop(); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}

return gui.refreshContainers()
})

return gui.refreshContainers()
}, nil)
}

@@ -405,7 +406,9 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {

gui.State.Panels.Main.WriterID++

// Create arbitrary command.
// c := container.Attach()

// // Create arbitrary command.
c := exec.Command("bash")

// Start the command with a pty.
@@ -414,20 +417,6 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
return err
}

// Handle pty size.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {

for range ch {
x, y := gui.getMainView().Size()
if err := pty.Setsize(ptmx, &pty.Winsize{Cols: uint16(x), Rows: uint16(y), X: 0, Y: 0}); err != nil {
gui.Log.Error(err)
}
}
}()
ch <- syscall.SIGWINCH // Initial resize.

go func() {
// Set stdin in raw mode.
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
@@ -445,7 +434,7 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
gui.State.Panels.Main.ObjectKey = "attached-" + container.ID

scanner := bufio.NewScanner(ptmx)
scanner.Split(bufio.ScanRunes)
scanner.Split(bufio.ScanBytes)

content := ""
for scanner.Scan() {
@@ -465,3 +454,18 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {

return nil
}

func (gui *Gui) handleServiceRestart(g *gocui.Gui, v *gocui.View) error {
container, err := gui.getSelectedContainer(g)
if err != nil {
return nil
}

return gui.WithWaitingStatus(gui.Tr.SLocalize("RestartingStatus"), func() error {
if err := container.RestartService(); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}

return gui.refreshContainers()
})
}
@@ -179,6 +179,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleContainerAttach,
Description: gui.Tr.SLocalize("attachContainer"),
},
{
ViewName: "containers",
Key: 'R',
Modifier: gocui.ModNone,
Handler: gui.handleServiceRestart,
Description: gui.Tr.SLocalize("restartService"),
},
{
ViewName: "images",
Key: '[',
@@ -780,6 +780,10 @@ func addEnglish(i18nObject *i18n.Bundle) error {
ID: "RestartingStatus",
Other: "restarting",
},
&i18n.Message{
ID: "StoppingStatus",
Other: "stopping",
},
&i18n.Message{
ID: "removeContainer",
Other: "remove container",
@@ -792,6 +796,10 @@ func addEnglish(i18nObject *i18n.Bundle) error {
ID: "restartContainer",
Other: "restart container",
},
&i18n.Message{
ID: "restartService",
Other: "restart container's service",
},
&i18n.Message{
ID: "previousContext",
Other: "previous context",
@@ -1,8 +1,10 @@
package utils

import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"math"
"reflect"
"regexp"
@@ -288,3 +290,9 @@ func FormatDecimalBytes(b int) string {
}
return "a lot"
}

func ApplyTemplate(str string, object interface{}) string {
var buf bytes.Buffer
template.Must(template.New("").Parse(str)).Execute(&buf, object)
return buf.String()
}

0 comments on commit f0ffdcd

Please sign in to comment.
You can’t perform that action at this time.