Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions pkg/commands/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
Client: cli,
ErrorChan: errorChan,
InDockerComposeProject: true,
LocalProjectName: config.ProjectName,
Closers: []io.Closer{tunnelCloser},
}

Expand All @@ -154,17 +155,40 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
log.Warn(err.Error())
}

// When the user passes -p outside of a compose directory, treat it as the
// local project so the project/services panels still appear and filtering
// is applied. Inside a compose dir, LocalProjectName is derived from
// container labels later in RefreshContainersAndServices.
if !dockerCommand.InDockerComposeProject && config.ProjectName != "" {
dockerCommand.LocalProjectName = config.ProjectName
// Resolve the local project from Compose before inspecting containers. This
// keeps identical service names in other projects from determining which
// project this instance belongs to.
if dockerCommand.InDockerComposeProject && dockerCommand.LocalProjectName == "" {
if projectName, err := dockerCommand.getComposeProjectName(); err == nil {
dockerCommand.LocalProjectName = projectName
} else {
log.Warn("Failed to determine local Compose project name: " + err.Error())
}
}

return dockerCommand, nil
}

func (c *DockerCommand) getComposeProjectName() (string, error) {
composeCommand := c.Config.UserConfig.CommandTemplates.DockerCompose
output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s config --format json", composeCommand))
if err != nil {
return "", err
}

var composeConfig struct {
Name string `json:"name"`
}
if err := json.Unmarshal([]byte(output), &composeConfig); err != nil {
return "", err
}
if composeConfig.Name == "" {
return "", fmt.Errorf("Compose config did not contain a project name")
}

return composeConfig.Name, nil
}

// IsProjectScoped reports whether lazydocker should be scoped to a single
// compose project — either because we're inside a compose directory or
// because the user passed -p. When false, the project/services panels are
Expand Down
48 changes: 48 additions & 0 deletions pkg/commands/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,51 @@ func TestIsProjectScoped(t *testing.T) {
})
}
}

func TestGetComposeProjectNameDoesNotDependOnContainers(t *testing.T) {
composeCommand := NewDummyOSCommand()
composeCommand.Config.UserConfig = &config.UserConfig{}
composeCommand.Config.UserConfig.CommandTemplates.DockerCompose = "printf '{\"name\":\"docker1\"}'"
dockerCommand := &DockerCommand{
Log: NewDummyLog(),
OSCommand: composeCommand,
Config: composeCommand.Config,
}

// A container from another project may expose the same service name. The
// local project must come from Compose config, not from container ordering.
containers := []*Container{{ProjectName: "docker2", ServiceName: "web"}}
assert.Len(t, dockerCommand.GetServicesFromContainers(containers), 1)

projectName, err := dockerCommand.getComposeProjectName()

assert.NoError(t, err)
assert.Equal(t, "docker1", projectName)
}

func TestGetComposeProjectNameRejectsInvalidConfig(t *testing.T) {
tests := []struct {
name string
output string
}{
{name: "invalid json", output: "not-json"},
{name: "missing name", output: `{}`},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
composeCommand := NewDummyOSCommand()
composeCommand.Config.UserConfig = &config.UserConfig{}
composeCommand.Config.UserConfig.CommandTemplates.DockerCompose = "printf '%s' '" + test.output + "'"
dockerCommand := &DockerCommand{
Log: NewDummyLog(),
OSCommand: composeCommand,
Config: composeCommand.Config,
}

_, err := dockerCommand.getComposeProjectName()

assert.Error(t, err)
})
}
}