diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index 86a70f90..0ddede2a 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -216,20 +216,26 @@ func (c *DockerCommand) RefreshContainersAndServices() error { } } - c.assignContainersToServices(containers, services) - var displayContainers = containers - if !c.Config.UserConfig.Gui.ShowAllContainers { - displayContainers = c.obtainStandaloneContainers(containers, services) + + if c.InDockerComposeProject { + var standaloneContainers, err = c.assignContainersToServices(containers, services) + if err != nil { + return err + } + + if !c.Config.UserConfig.Gui.ShowAllContainers { + displayContainers = standaloneContainers + } } // sort services first by whether they have a linked container, and second by alphabetical order sort.Slice(services, func(i, j int) bool { - if services[i].Container != nil && services[j].Container == nil { + if len(services[i].Containers) > 0 && len(services[j].Containers) == 0 { return true } - if services[i].Container == nil && services[j].Container != nil { + if len(services[i].Containers) == 0 && len(services[j].Containers) > 0 { return false } @@ -243,17 +249,40 @@ func (c *DockerCommand) RefreshContainersAndServices() error { return nil } -func (c *DockerCommand) assignContainersToServices(containers []*Container, services []*Service) { -L: - for _, service := range services { - for _, container := range containers { - if !container.OneOff && container.ServiceName == service.Name { - service.Container = container - continue L - } - } - service.Container = nil +func (c *DockerCommand) assignContainersToServices(containers []*Container, services []*Service) ([]*Container, error) { + // Get a list of the Container IDs for this Docker Compose project. + composeCommand := c.Config.UserConfig.CommandTemplates.DockerCompose + output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s ps -q", composeCommand)) + if err != nil { + return nil, err } + + // Create an index for Container IDs to make lookups less expensive. + lines := utils.SplitLines(output) + serviceContainerIds := make(map[string]bool) + for _, line := range lines { + serviceContainerIds[line] = true + } + + // Index Services by name. + serviceMap := make(map[string]*Service) + for _, service := range services { + serviceMap[service.Name] = service + } + + // Walk the list of Containers, categorizing them as either associated with a + // Service or as standalone. + standaloneContainers := make([]*Container, 0, len(containers) - len(serviceContainerIds)) + for _, container := range containers { + if !container.OneOff && serviceContainerIds[container.ID] { + service := serviceMap[container.ServiceName] + service.Containers = append(service.Containers, container) + } else { + standaloneContainers = append(standaloneContainers, container) + } + } + + return standaloneContainers, nil } // filterOutExited filters out the exited containers if c.ShowExited is false @@ -270,22 +299,6 @@ func (c *DockerCommand) filterOutExited(containers []*Container) []*Container { return toReturn } -// obtainStandaloneContainers returns standalone containers. Standalone containers are containers which are either one-off containers, or whose service is not part of this docker-compose context -func (c *DockerCommand) obtainStandaloneContainers(containers []*Container, services []*Service) []*Container { - standaloneContainers := []*Container{} -L: - for _, container := range containers { - for _, service := range services { - if !container.OneOff && container.ServiceName != "" && container.ServiceName == service.Name { - continue L - } - } - standaloneContainers = append(standaloneContainers, container) - } - - return standaloneContainers -} - // GetContainers gets the docker containers func (c *DockerCommand) GetContainers() ([]*Container, error) { c.ContainerMutex.Lock() @@ -349,25 +362,20 @@ func (c *DockerCommand) GetServices() ([]*Service, error) { } composeCommand := c.Config.UserConfig.CommandTemplates.DockerCompose - output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s config --hash=*", composeCommand)) + output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s config --services", composeCommand)) if err != nil { return nil, err } - // output looks like: - // service1 998d6d286b0499e0ff23d66302e720991a2asdkf9c30d0542034f610daf8a971 - // service2 asdld98asdklasd9bccd02438de0994f8e19cbe691feb3755336ec5ca2c55971 - - lines := utils.SplitLines(output) - services := make([]*Service, len(lines)) - for i, str := range lines { - arr := strings.Split(str, " ") + serviceNames := utils.SplitLines(output) + services := make([]*Service, len(serviceNames)) + for i, name := range serviceNames { services[i] = &Service{ - Name: arr[0], - ID: arr[1], + Name: name, OSCommand: c.OSCommand, Log: c.Log, DockerCommand: c, + Containers: make([]*Container, 0, 1), } } diff --git a/pkg/commands/service.go b/pkg/commands/service.go index 59580ef4..dbc1fdf8 100644 --- a/pkg/commands/service.go +++ b/pkg/commands/service.go @@ -13,27 +13,26 @@ import ( // Service : A docker Service type Service struct { Name string - ID string OSCommand *OSCommand Log *logrus.Entry - Container *Container + Containers []*Container DockerCommand LimitedDockerCommand } // GetDisplayStrings returns the dispaly string of Container func (s *Service) GetDisplayStrings(isFocused bool) []string { - if s.Container == nil { + if len(s.Containers) == 0 { return []string{utils.ColoredString("none", color.FgBlue), s.Name, ""} } - cont := s.Container + cont := s.Containers[0] return []string{cont.GetDisplayStatus(), s.Name, cont.GetDisplayCPUPerc()} } // Remove removes the service's containers func (s *Service) Remove(options types.ContainerRemoveOptions) error { - return s.Container.Remove(options) + return s.Containers[0].Remove(options) } // Stop stops the service's containers @@ -58,12 +57,12 @@ func (s *Service) Restart() error { // Attach attaches to the service func (s *Service) Attach() (*exec.Cmd, error) { - return s.Container.Attach() + return s.Containers[0].Attach() } // Top returns process information func (s *Service) Top() (container.ContainerTopOKBody, error) { - return s.Container.Top() + return s.Containers[0].Top() } // ViewLogs attaches to a subprocess viewing the service's logs diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index d1c61f5e..38886999 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -247,7 +247,7 @@ func (gui *Gui) refreshContainersAndServices() error { // see if our selected service has moved if selectedService != nil { for i, service := range gui.DockerCommand.Services { - if service.ID == selectedService.ID { + if service.Name == selectedService.Name { if i == sl { break } diff --git a/pkg/gui/project_panel.go b/pkg/gui/project_panel.go index 76435f6b..dfa4b93a 100644 --- a/pkg/gui/project_panel.go +++ b/pkg/gui/project_panel.go @@ -34,8 +34,8 @@ func (gui *Gui) refreshProject() error { projectName := path.Base(gui.Config.ProjectDir) if gui.DockerCommand.InDockerComposeProject { for _, service := range gui.DockerCommand.Services { - if service.Container != nil { - projectName = service.Container.Details.Config.Labels["com.docker.compose.project"] + if len(service.Containers) > 0 { + projectName = service.Containers[0].Details.Config.Labels["com.docker.compose.project"] break } } diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index 0c1e7a43..4718901a 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -46,15 +46,17 @@ func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error { } containerID := "" - if service.Container != nil { - containerID = service.Container.ID + containerNum := "" + if len(service.Containers) != 0 { + containerID = service.Containers[0].ID + containerNum = service.Containers[0].ContainerNumber } if err := gui.focusPoint(0, gui.State.Panels.Services.SelectedLine, len(gui.DockerCommand.Services), v); err != nil { return err } - key := "services-" + service.ID + "-" + containerID + "-" + gui.getServiceContexts()[gui.State.Panels.Services.ContextIndex] + key := "services-" + service.Name + "-" + containerID + "-" + containerNum + "-" + gui.getServiceContexts()[gui.State.Panels.Services.ContextIndex] if !gui.shouldRefresh(key) { return nil } @@ -74,10 +76,10 @@ func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error { return err } case "container-config": - if service.Container == nil { + if len(service.Containers) == 0 { return gui.renderString(gui.g, "main", gui.Tr.NoContainer) } - if err := gui.renderContainerConfig(service.Container); err != nil { + if err := gui.renderContainerConfig(service.Containers[0]); err != nil { return err } case "top": @@ -92,11 +94,11 @@ func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) renderServiceStats(service *commands.Service) error { - if service.Container == nil { + if len(service.Containers) == 0 { return nil } - return gui.renderContainerStats(service.Container) + return gui.renderContainerStats(service.Containers[0]) } func (gui *Gui) renderServiceTop(service *commands.Service) error { @@ -120,13 +122,13 @@ func (gui *Gui) renderServiceLogs(service *commands.Service) error { return nil } - if service.Container == nil { + if len(service.Containers) == 0 { return gui.T.NewTask(func(stop chan struct{}) { gui.clearMainView() }) } - return gui.renderContainerLogs(service.Container) + return gui.renderContainerLogs(service.Containers[0]) } func (gui *Gui) handleServicesNextLine(g *gocui.Gui, v *gocui.View) error { @@ -252,7 +254,7 @@ func (gui *Gui) handleServiceAttach(g *gocui.Gui, v *gocui.View) error { return nil } - if service.Container == nil { + if len(service.Containers) == 0 { return gui.createErrorPanel(gui.g, gui.Tr.NoContainers) } @@ -374,7 +376,7 @@ func (gui *Gui) handleServicesCustomCommand(g *gocui.Gui, v *gocui.View) error { commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{ Service: service, - Container: service.Container, + Container: service.Containers[0], }) var customCommands []config.CustomCommand @@ -396,7 +398,7 @@ L: } } - if service.Container != nil { + if len(service.Containers) > 0 { customCommands = append(customCommands, gui.Config.UserConfig.CustomCommands.Containers...) }