Correlate Services and Containers by Service Name

The config hash ends up not being a unique identifier of a Service.  Additionally, Services may have multiple associated Containers.

``` yaml
# Sample docker-compose.yml
version: "2.2"

services:
  named-service:
    container_name: 'named-container'
    image: 'alpine'
    command: 'sh -c "while :; do sleep 1; done"'

  anonymous-service:
    image: 'alpine'
    command: 'sh -c "while :; do sleep 1; done"'

  scaled-service:
    image: 'alpine'
    scale: 3
    command: 'sh -c "while :; do sleep 1; done"'
```

``` bash
$ docker-compose config --hash="*"
named-service e6e7c4564dfc435ba96725098c62e6b6cf8da9f0b76b0a08be2baddedad6d007
anonymous-service 254f4815ef36bc9fcc2ce80f82278187c9e233309a4106951551f3692657ce9c
scaled-service 254f4815ef36bc9fcc2ce80f82278187c9e233309a4106951551f3692657ce9c
```

This commit changes how Services are discovered (`docker-compose config --services`), how they're associated (by Container ID, rather than config hash or name), and allows for Services to reference multiple Containers (though only the first is directly used for anything at this time).
This commit is contained in:
Pieter van de Bruggen 2019-07-23 13:42:18 -07:00
parent b5f64c0a2e
commit a4bd90ff8a
5 changed files with 74 additions and 65 deletions

View file

@ -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),
}
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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...)
}