Skip to content
Merged
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
50 changes: 48 additions & 2 deletions pkg/compose/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
const (
ProjectLabel = "com.docker.compose.project"
ServiceLabel = "com.docker.compose.service"
podmanCmd = "podman"
composeArg = "compose"
)

func LoadDockerComposeProject(
Expand Down Expand Up @@ -56,7 +58,8 @@ type ComposeHelper struct {
}

// NewComposeHelper creates a new ComposeHelper instance after detecting whether Docker
// Compose V1 or V2 is installed. It returns an error if neither is found.
// Compose V2, Podman Compose, or Docker Compose V1 is installed. It returns an error
// if none are found.
func NewComposeHelper(dockerHelper *docker.DockerHelper) (*ComposeHelper, error) {
dockerCmd := dockerHelper.DockerCommand
if dockerCmd == "" {
Expand All @@ -68,12 +71,17 @@ func NewComposeHelper(dockerHelper *docker.DockerHelper) (*ComposeHelper, error)
return helper, nil
}

if helper, err := tryPodmanCompose(); err == nil {
helper.Docker = dockerHelper
return helper, nil
}

if helper, err := tryDockerComposeV1(); err == nil {
helper.Docker = dockerHelper
return helper, nil
}

return nil, fmt.Errorf("docker compose not installed")
return nil, fmt.Errorf("docker compose or podman compose not installed")
}

// tryDockerComposeV2 checks if Docker Compose V2 is available and returns a ComposeHelper if so.
Expand Down Expand Up @@ -120,6 +128,44 @@ func tryDockerComposeV2(dockerCmd string) (*ComposeHelper, error) {
return helper, nil
}

func tryPodmanCompose() (*ComposeHelper, error) {
if _, err := exec.LookPath(podmanCmd); err != nil {
return nil, fmt.Errorf("podman not found in PATH")
}

if exec.Command(podmanCmd, composeArg).Run() != nil {
return nil, fmt.Errorf("podman compose not available")
}

cmd := exec.Command(podmanCmd, composeArg, "version", "--short")
out, stderr, err := runCmdCapture(cmd)
if len(stderr) > 0 {
log.Warnf("%s: %s", strings.TrimSpace(string(stderr)), strings.TrimSpace(string(out)))
}
if err != nil {
return nil, fmt.Errorf(
"failed to get podman compose version %s: %w",
strings.TrimSpace(string(stderr)),
err,
)
}

parsed, parseErr := parseVersion(strings.TrimSpace(string(out)))
if parseErr != nil {
return nil, fmt.Errorf(
"failed to parse podman compose version %q: %w",
strings.TrimSpace(string(out)),
parseErr,
)
}

return &ComposeHelper{
Command: podmanCmd,
Version: parsed.String(),
Args: []string{composeArg},
}, nil
}

func tryDockerComposeV1() (*ComposeHelper, error) {
if _, err := exec.LookPath("docker-compose"); err != nil {
return nil, fmt.Errorf("docker-compose not found in PATH")
Expand Down
84 changes: 84 additions & 0 deletions pkg/compose/helper_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package compose

import (
"context"
"strings"
"testing"

"github.com/stretchr/testify/suite"
)

const (
testPodmanCmd = "podman"
testComposeArg = "compose"
testPodmanVersion = "2.32.4"
)

type HelperTestSuite struct {
suite.Suite
}
Expand Down Expand Up @@ -80,3 +88,79 @@ func (s *HelperTestSuite) TestParseVersionWithPodmanWarning() {
s.NoError(err)
s.Equal("5.1.0", v.String())
}

func (s *HelperTestSuite) TestParseVersionPodmanCompose() {
tests := []struct {
name string
version string
want string
wantErr bool
}{
{
name: "podman compose standard version",
version: testPodmanVersion,
want: testPodmanVersion,
},
{
name: "podman compose with v prefix",
version: "v" + testPodmanVersion,
want: testPodmanVersion,
},
{
name: "podman-compose python variant",
version: "1.0.6",
want: "1.0.6",
},
{
name: "podman compose with trailing newline",
version: testPodmanVersion + "\n",
want: testPodmanVersion,
},
{
name: "podman compose with external provider warning",
version: ">>>> Executing external compose provider." +
" Please see podman-compose(1) <<<<\n\n" + testPodmanVersion + "\n",
want: testPodmanVersion,
},
}

for _, tt := range tests {
s.Run(tt.name, func() {
got, err := parseVersion(tt.version)
if tt.wantErr {
s.Error(err)
} else {
s.NoError(err)
s.Equal(tt.want, got.String())
}
})
}
}

func (s *HelperTestSuite) TestComposeHelperPodmanFields() {
helper := &ComposeHelper{
Command: testPodmanCmd,
Version: testPodmanVersion,
Args: []string{testComposeArg},
}

s.Equal(testPodmanCmd, helper.Command)
s.Equal(testPodmanVersion, helper.Version)
s.Equal([]string{testComposeArg}, helper.Args)
}

func (s *HelperTestSuite) TestComposeHelperBuildCmdPodman() {
helper := &ComposeHelper{
Command: testPodmanCmd,
Version: testPodmanVersion,
Args: []string{testComposeArg},
}

cmd := helper.buildCmd(context.TODO(), "--project-name", "test", "up", "-d")
s.True(strings.HasSuffix(cmd.Path, testPodmanCmd))
s.Contains(cmd.Args, testComposeArg)
s.Contains(cmd.Args, "--project-name")
s.Contains(cmd.Args, "test")
s.Contains(cmd.Args, "up")
s.Contains(cmd.Args, "-d")
}
Loading