diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index 0ce4fba1..1f16de92 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -158,6 +158,10 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat } func (c *DockerCommand) setDockerComposeCommand(config *config.AppConfig) { + if config.UserConfig.CommandTemplates.DockerComposeSetByUser { + return + } + if config.UserConfig.CommandTemplates.DockerCompose != "docker compose" { return } diff --git a/pkg/commands/docker_test.go b/pkg/commands/docker_test.go index 0d459686..cb3c8df5 100644 --- a/pkg/commands/docker_test.go +++ b/pkg/commands/docker_test.go @@ -1,10 +1,14 @@ package commands import ( + "fmt" "os" + "os/exec" + "path/filepath" "testing" "github.com/docker/docker/client" + "github.com/jesseduffield/lazydocker/pkg/config" "github.com/stretchr/testify/assert" ) @@ -61,3 +65,78 @@ func TestNewDockerClientVersionNegotiation(t *testing.T) { "client version should not be locked to DOCKER_API_VERSION env var") }) } + +func TestSetDockerComposeCommandRespectsExplicitUserOverride(t *testing.T) { + configDir := t.TempDir() + configPath := filepath.Join(configDir, "config.yml") + content := []byte("commandTemplates:\n dockerCompose: docker compose\n") + + if err := os.WriteFile(configPath, content, 0o600); err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + originalConfigDir := os.Getenv("CONFIG_DIR") + defer func() { + if originalConfigDir == "" { + os.Unsetenv("CONFIG_DIR") + } else { + os.Setenv("CONFIG_DIR", originalConfigDir) + } + }() + os.Setenv("CONFIG_DIR", configDir) + + appConfig, err := config.NewAppConfig("lazydocker", "version", "commit", "date", "buildSource", false, nil, "projectDir", "") + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + osCommand := NewOSCommand(NewDummyLog(), appConfig) + osCommand.SetCommand(newHelperCommand(true)) + + dockerCommand := &DockerCommand{ + OSCommand: osCommand, + Config: appConfig, + } + + dockerCommand.setDockerComposeCommand(appConfig) + + assert.Equal(t, "docker compose", appConfig.UserConfig.CommandTemplates.DockerCompose) +} + +func TestSetDockerComposeCommandFallsBackWhenUnconfigured(t *testing.T) { + userConfig := config.GetDefaultConfig() + appConfig := &config.AppConfig{UserConfig: &userConfig} + osCommand := NewOSCommand(NewDummyLog(), appConfig) + osCommand.SetCommand(newHelperCommand(true)) + + dockerCommand := &DockerCommand{ + OSCommand: osCommand, + Config: appConfig, + } + + dockerCommand.setDockerComposeCommand(appConfig) + + assert.Equal(t, "docker-compose", appConfig.UserConfig.CommandTemplates.DockerCompose) +} + +func TestDockerCommandHelperProcess(t *testing.T) { + if len(os.Args) < 4 || os.Args[2] != "--" || os.Args[3] != "docker-helper" { + return + } + + if len(os.Args) > 4 && os.Args[4] == "fail" { + fmt.Fprint(os.Stderr, "simulated failure") + os.Exit(1) + } + + os.Exit(0) +} + +func newHelperCommand(shouldFail bool) func(string, ...string) *exec.Cmd { + return func(name string, arg ...string) *exec.Cmd { + args := []string{"-test.run=TestDockerCommandHelperProcess", "--", "docker-helper"} + if shouldFail { + args = append(args, "fail") + } + return exec.Command(os.Args[0], args...) + } +} diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 0ebc1796..765161a6 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -174,6 +174,9 @@ type CommandTemplatesConfig struct { // of whatever you've set in this value rather than you having to copy and // paste it to all the other commands DockerCompose string `yaml:"dockerCompose,omitempty"` + // DockerComposeSetByUser tracks whether dockerCompose was explicitly present + // in the user's config file so command auto-detection does not overwrite it. + DockerComposeSetByUser bool `yaml:"-"` // StopService is the command for stopping a service StopService string `yaml:"stopService,omitempty"` @@ -579,10 +582,25 @@ func loadUserConfig(configDir string, base *UserConfig) (*UserConfig, error) { return nil, err } + type commandTemplatePresence struct { + DockerCompose *string `yaml:"dockerCompose,omitempty"` + } + + type userConfigPresence struct { + CommandTemplates commandTemplatePresence `yaml:"commandTemplates,omitempty"` + } + + presence := userConfigPresence{} + if err := yaml.Unmarshal(content, &presence); err != nil { + return nil, err + } + if err := yaml.Unmarshal(content, base); err != nil { return nil, err } + base.CommandTemplates.DockerComposeSetByUser = presence.CommandTemplates.DockerCompose != nil + return base, nil } diff --git a/pkg/config/app_config_test.go b/pkg/config/app_config_test.go index cd8167e4..771ef96a 100644 --- a/pkg/config/app_config_test.go +++ b/pkg/config/app_config_test.go @@ -2,6 +2,7 @@ package config import ( "os" + "path/filepath" "testing" "github.com/jesseduffield/yaml" @@ -96,3 +97,26 @@ func TestWritingToConfigFile(t *testing.T) { // modifying an existing file that already has 'ConfirmOnQuit' testFn(t, conf, false) } + +func TestLoadUserConfigMarksExplicitDockerComposeOverride(t *testing.T) { + configDir := t.TempDir() + configPath := filepath.Join(configDir, "config.yml") + content := []byte("commandTemplates:\n dockerCompose: docker compose\n") + + if err := os.WriteFile(configPath, content, 0o600); err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + conf, err := loadUserConfigWithDefaults(configDir) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + if conf.CommandTemplates.DockerCompose != "docker compose" { + t.Fatalf("Expected docker compose but got %s", conf.CommandTemplates.DockerCompose) + } + + if !conf.CommandTemplates.DockerComposeSetByUser { + t.Fatal("Expected dockerCompose override to be marked as user-set") + } +}