From e596b17fbef0097f78f78f1f04122ee1b52449f3 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Mon, 17 Jul 2023 13:43:24 +0200 Subject: [PATCH] add a podman-compose command **podman compose** is a thin wrapper around an external compose provider such as docker-compose or podman-compose. This means that `podman compose` is executing another tool that implements the compose functionality but sets up the environment in a way to let the compose provider communicate transparently with the local Podman socket. The specified options as well the command and argument are passed directly to the compose provider. The default compose providers are `docker-compose` and `podman-compose`. If installed, `docker-compose` takes precedence since it is the original implementation of the Compose specification and is widely used on the supported platforms (i.e., Linux, Mac OS, Windows). If you want to change the default behavior or have a custom installation path for your provider of choice, please change the `compose_provider` field in `containers.conf(5)`. You may also set the `PODMAN_COMPOSE_PROVIDER` environment variable. Signed-off-by: Valentin Rothberg --- cmd/podman/compose.go | 302 ++++++++++++++++++ docs/source/markdown/.gitignore | 1 + docs/source/markdown/podman-compose.1.md.in | 21 ++ docs/source/markdown/podman.1.md | 1 + go.mod | 4 +- go.sum | 8 +- hack/man-page-checker | 5 + hack/xref-helpmsgs-manpages | 13 +- pkg/rootless/rootless_linux.c | 1 + test/compose/test-compose | 13 +- test/system/015-help.bats | 5 + test/system/600-completion.bats | 5 + test/system/610-format.bats | 6 + test/system/850-compose.bats | 67 ++++ .../containers/common/pkg/config/config.go | 11 + .../common/pkg/config/config_local.go | 4 +- .../common/pkg/config/containers.conf | 14 + .../common/pkg/config/containers.conf-freebsd | 2 +- .../containers/common/pkg/config/default.go | 13 + .../common/pkg/config/default_darwin.go | 11 + .../common/pkg/config/default_freebsd.go | 4 + .../common/pkg/config/default_linux.go | 4 + .../common/pkg/config/default_windows.go | 5 + .../containers/common/pkg/util/util.go | 18 +- .../runtime-spec/specs-go/version.go | 2 +- vendor/modules.txt | 4 +- 26 files changed, 521 insertions(+), 23 deletions(-) create mode 100644 cmd/podman/compose.go create mode 100644 docs/source/markdown/podman-compose.1.md.in create mode 100644 test/system/850-compose.bats diff --git a/cmd/podman/compose.go b/cmd/podman/compose.go new file mode 100644 index 0000000000..8f507e63c7 --- /dev/null +++ b/cmd/podman/compose.go @@ -0,0 +1,302 @@ +//go:build amd64 || arm64 +// +build amd64 arm64 + +package main + +import ( + "errors" + "fmt" + "io" + "net/url" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "text/template" + + "github.com/containers/common/pkg/config" + cmdMachine "github.com/containers/podman/v4/cmd/podman/machine" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/pkg/machine" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var composeCommand = &cobra.Command{ + Use: "compose [options]", + Short: "Run compose workloads via an external provider such as docker-compose or podman-compose", + Long: `This command is a thin wrapper around an external compose provider such as docker-compose or podman-compose. This means that podman compose is executing another tool that implements the compose functionality but sets up the environment in a way to let the compose provider communicate transparently with the local Podman socket. The specified options as well the command and argument are passed directly to the compose provider. + +The default compose providers are docker-compose and podman-compose. If installed, docker-compose takes precedence since it is the original implementation of the Compose specification and is widely used on the supported platforms (i.e., Linux, Mac OS, Windows). + +If you want to change the default behavior or have a custom installation path for your provider of choice, please change the compose_provider field in containers.conf(5). You may also set PODMAN_COMPOSE_PROVIDER environment variable.`, + RunE: composeMain, + ValidArgsFunction: composeCompletion, + Example: `podman compose -f nginx.yaml up --detach + podman --log-level=debug compose -f many-images.yaml pull`, + DisableFlagParsing: true, + Annotations: map[string]string{registry.ParentNSRequired: ""}, // don't join user NS for SSH to work correctly +} + +func init() { + // NOTE: we need to fully disable flag parsing and manually parse the + // flags in composeMain. cobra's FParseErrWhitelist will strip off + // unknown flags _before_ the first argument. So `--unknown argument` + // will show as `argument`. + + registry.Commands = append(registry.Commands, registry.CliCommand{Command: composeCommand}) +} + +func composeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var stdout strings.Builder + + args = append(args, toComplete) + args = append([]string{"__complete"}, args...) + if err := composeProviderExec(args, &stdout, io.Discard, false); err != nil { + // Ignore errors since some providers may not expose a __complete command. + return nil, cobra.ShellCompDirectiveError + } + + var num int + output := strings.Split(strings.TrimRight(stdout.String(), "\n"), "\n") + if len(output) >= 1 { + if lastLine := output[len(output)-1]; strings.HasPrefix(lastLine, ":") { + var err error + if num, err = strconv.Atoi(lastLine[1:]); err != nil { + return nil, cobra.ShellCompDirectiveError + } + output = output[:len(output)-1] + } + } + return output, cobra.ShellCompDirective(num) +} + +// composeProvider provides the name of or absolute path to the compose +// provider (i.e., the external binary such as docker-compose). +func composeProvider() (string, error) { + if value, ok := os.LookupEnv("PODMAN_COMPOSE_PROVIDER"); ok { + return value, nil + } + + candidates := registry.PodmanConfig().ContainersConfDefaultsRO.Engine.ComposeProviders + if len(candidates) == 0 { + return "", errors.New("no compose provider specified, please refer to `man podman-compose` for details") + } + + for _, candidate := range candidates { + path, err := exec.LookPath(os.ExpandEnv(candidate)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } + return "", err + } + // First specified provider "candidate" wins. + logrus.Debugf("Found compose provider %q", path) + return path, nil + } + + return "", errors.New("no configured compose provider found on system, please refer to the documentation for details") +} + +// composeDockerHost returns the value to be set in the DOCKER_HOST environment +// variable. +func composeDockerHost() (string, error) { + if value, ok := os.LookupEnv("DOCKER_HOST"); ok { + return value, nil + } + + // For local clients (Linux/FreeBSD), use the default API + // address. + if !registry.IsRemote() { + return registry.DefaultAPIAddress(), nil + } + + cfg, err := config.ReadCustomConfig() + if err != nil { + return "", err + } + + // NOTE: podman --connection=foo and --url=... are injected + // into the default connection below in `root.go`. + defaultConnection := cfg.Engine.ActiveService + if defaultConnection == "" { + switch runtime.GOOS { + // If no default connection is set on Linux or FreeBSD, + // we just use the local socket by default - just as + // the remote client does. + case "linux", "freebsd": + return registry.DefaultAPIAddress(), nil + // If there is no default connection on Windows or Mac + // OS, we can safely assume that something went wrong. + // A `podman machine init` will set the connection. + default: + return "", fmt.Errorf("cannot connect to a socket or via SSH: no default connection found: consider running `podman machine init`") + } + } + + connection, ok := cfg.Engine.ServiceDestinations[defaultConnection] + if !ok { + return "", fmt.Errorf("internal error: default connection %q not found in database", defaultConnection) + } + parsedConnection, err := url.Parse(connection.URI) + if err != nil { + return "", fmt.Errorf("preparing connection to remote machine: %w", err) + } + + // If the default connection does not point to a `podman + // machine`, we cannot use a local path and need to use SSH. + if !connection.IsMachine { + // Compose doesn't like paths, so we optimistically + // assume the presence of a Docker socket on the remote + // machine which is the case for podman machines. + return strings.TrimSuffix(connection.URI, parsedConnection.Path), nil + } + + machineProvider, err := cmdMachine.GetSystemProvider() + if err != nil { + return "", fmt.Errorf("getting machine provider: %w", err) + } + machineList, err := machineProvider.List(machine.ListOptions{}) + if err != nil { + return "", fmt.Errorf("listing machines: %w", err) + } + + // Now we know that the connection points to a machine and we + // can find the machine by looking for the one with the + // matching port. + connectionPort, err := strconv.Atoi(parsedConnection.Port()) + if err != nil { + return "", fmt.Errorf("parsing connection port: %w", err) + } + for _, item := range machineList { + if connectionPort != item.Port { + continue + } + + vm, err := machineProvider.LoadVMByName(item.Name) + if err != nil { + return "", fmt.Errorf("loading machine: %w", err) + } + info, err := vm.Inspect() + if err != nil { + return "", fmt.Errorf("inspecting machine: %w", err) + } + if info.ConnectionInfo.PodmanSocket == nil { + return "", errors.New("socket of machine is not set") + } + if info.State != machine.Running { + return "", fmt.Errorf("machine %s is not running but in state %s", item.Name, info.State) + } + return "unix://" + info.ConnectionInfo.PodmanSocket.Path, nil + } + + return "", fmt.Errorf("could not find a matching machine for connection %q", connection.URI) +} + +// composeEnv returns the compose-specific environment variables. +func composeEnv() ([]string, error) { + hostValue, err := composeDockerHost() + if err != nil { + return nil, err + } + + return []string{ + "DOCKER_HOST=" + hostValue, + // Podman doesn't support all buildkit features and since it's + // a continuous catch-up game, disable buildkit on the client + // side. + // + // See https://github.com/containers/podman/issues/18617#issuecomment-1600495841 + "DOCKER_BUILDKIT=0", + // FIXME: DOCKER_CONFIG is limited by containers/podman/issues/18617 + // and it remains unclear which default path should be set + // w.r.t. Docker compatibility and a smooth experience of podman-login + // working with podman-compose _by default_. + "DOCKER_CONFIG=" + os.Getenv("DOCKER_CONFIG"), + }, nil +} + +// underline uses ANSI codes to underline the specified string. +func underline(str string) string { + return "\033[4m" + str + "\033[0m" +} + +// composeProviderExec executes the compose provider with the specified arguments. +func composeProviderExec(args []string, stdout io.Writer, stderr io.Writer, warn bool) error { + provider, err := composeProvider() + if err != nil { + return err + } + + env, err := composeEnv() + if err != nil { + return err + } + + if stdout == nil { + stdout = os.Stdout + } + if stderr == nil { + stderr = os.Stderr + } + + cmd := exec.Command(provider, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = stdout + cmd.Stderr = stderr + cmd.Env = append(os.Environ(), env...) + logrus.Debugf("Executing compose provider (%s %s) with additional env %s", provider, strings.Join(args, " "), strings.Join(env, " ")) + + if warn { + fmt.Fprint(os.Stderr, underline(fmt.Sprintf(">>>> Executing external compose provider %q. Please refer to the documentation for details. <<<<\n\n", provider))) + } + + if err := cmd.Run(); err != nil { + // Make sure podman returns with the same exit code as the compose provider. + if exitErr, isExit := err.(*exec.ExitError); isExit { + registry.SetExitCode(exitErr.ExitCode()) + } + // Format the error to make it explicit that error did not come + // from podman but from the executed compose provider. + return fmt.Errorf("executing %s %s: %w", provider, strings.Join(args, " "), err) + } + + return nil +} + +// composeHelp is a custom help function to display the help message of the +// configured compose-provider. +func composeHelp(cmd *cobra.Command) error { + tmpl, err := template.New("help_template").Parse(helpTemplate) + if err != nil { + return err + } + if err := tmpl.Execute(os.Stdout, cmd); err != nil { + return err + } + + return composeProviderExec([]string{"--help"}, nil, nil, registry.PodmanConfig().ContainersConfDefaultsRO.Engine.ComposeWarningLogs) +} + +// composeMain is the main function of the compose command. +func composeMain(cmd *cobra.Command, args []string) error { + // We have to manually parse the flags here to make sure all arguments + // after `podman compose [ARGS]` are passed to the compose provider. + // For now, we only look for the --help flag. + fs := pflag.NewFlagSet("args", pflag.ContinueOnError) + fs.ParseErrorsWhitelist.UnknownFlags = true + fs.SetInterspersed(false) + fs.BoolP("help", "h", false, "") + if err := fs.Parse(args); err != nil { + return fmt.Errorf("parsing arguments: %w", err) + } + + if len(args) == 0 || fs.Lookup("help").Changed { + return composeHelp(cmd) + } + + return composeProviderExec(args, nil, nil, registry.PodmanConfig().ContainersConfDefaultsRO.Engine.ComposeWarningLogs) +} diff --git a/docs/source/markdown/.gitignore b/docs/source/markdown/.gitignore index 8807c337f6..3aae56e5c6 100644 --- a/docs/source/markdown/.gitignore +++ b/docs/source/markdown/.gitignore @@ -1,6 +1,7 @@ podman-attach.1.md podman-auto-update.1.md podman-build.1.md +podman-compose.1.md podman-container-clone.1.md podman-container-diff.1.md podman-container-inspect.1.md diff --git a/docs/source/markdown/podman-compose.1.md.in b/docs/source/markdown/podman-compose.1.md.in new file mode 100644 index 0000000000..05c99135a1 --- /dev/null +++ b/docs/source/markdown/podman-compose.1.md.in @@ -0,0 +1,21 @@ +% podman-compose 1 + +## NAME +podman\-compose - Run Compose workloads via an external compose provider + +## SYNOPSIS +**podman compose** [*options*] [*command* [*arg* ...]] + +## DESCRIPTION +**podman compose** is a thin wrapper around an external compose provider such as docker-compose or podman-compose. This means that `podman compose` is executing another tool that implements the compose functionality but sets up the environment in a way to let the compose provider communicate transparently with the local Podman socket. The specified options as well the command and argument are passed directly to the compose provider. + +The default compose providers are `docker-compose` and `podman-compose`. If installed, `docker-compose` takes precedence since it is the original implementation of the Compose specification and is widely used on the supported platforms (i.e., Linux, Mac OS, Windows). + +If you want to change the default behavior or have a custom installation path for your provider of choice, please change the `compose_provider` field in `containers.conf(5)`. You may also set the `PODMAN_COMPOSE_PROVIDER` environment variable. + +## OPTIONS + +To see supported options of the installed compose provider, please run `podman compose --help`. + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[containers.conf(5)](https://github.com/containers/common/blob/main/docs/containers.conf.5.md)** diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index 0facec194c..8126ea37cc 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -315,6 +315,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-build(1)](podman-build.1.md) | Build a container image using a Containerfile. | | [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. | | [podman-completion(1)](podman-completion.1.md) | Generate shell completion scripts | +| [podman-compose(1)](podman-compose.1.md) | Run Compose workloads via an external compose provider. | | [podman-container(1)](podman-container.1.md) | Manage containers. | | [podman-cp(1)](podman-cp.1.md) | Copy files/folders between a container and the local filesystem. | | [podman-create(1)](podman-create.1.md) | Create a new container. | diff --git a/go.mod b/go.mod index 91979ddd29..373f30c32f 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/containernetworking/cni v1.1.2 github.com/containernetworking/plugins v1.3.0 github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6 - github.com/containers/common v0.55.1-0.20230721175448-664d013a6ae2 + github.com/containers/common v0.55.1-0.20230724161016-2966c705a7a3 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.26.1-0.20230721194716-30c87d4a5b8d github.com/containers/libhvee v0.4.0 @@ -48,7 +48,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0-rc4 github.com/opencontainers/runc v1.1.8 - github.com/opencontainers/runtime-spec v1.1.0-rc.3 + github.com/opencontainers/runtime-spec v1.1.0 github.com/opencontainers/runtime-tools v0.9.1-0.20230317050512-e931285f4b69 github.com/opencontainers/selinux v1.11.0 github.com/openshift/imagebuilder v1.2.5 diff --git a/go.sum b/go.sum index f0f1e5bf70..81c1c9f4f9 100644 --- a/go.sum +++ b/go.sum @@ -247,8 +247,8 @@ github.com/containernetworking/plugins v1.3.0 h1:QVNXMT6XloyMUoO2wUOqWTC1hWFV62Q github.com/containernetworking/plugins v1.3.0/go.mod h1:Pc2wcedTQQCVuROOOaLBPPxrEXqqXBFt3cZ+/yVg6l0= github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6 h1:K/S8SFQsnnNTF0Ws58SrBD9L0EuClzAG8Zp08d7+6AA= github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6/go.mod h1:0sptTFBBtSznLqoTh80DfvMOCNbdRsNRgVOKhBhrupA= -github.com/containers/common v0.55.1-0.20230721175448-664d013a6ae2 h1:4B42HUIAghFGSqej5RADTNf0WlOBFiGGzmGjNa3Do78= -github.com/containers/common v0.55.1-0.20230721175448-664d013a6ae2/go.mod h1:O/JSRY1dLfwgBxVvn3yJfKvF63KEjbNJcJAtjpNvO90= +github.com/containers/common v0.55.1-0.20230724161016-2966c705a7a3 h1:0fHDAdLNfOs5AuBizE7TOECDA4gCLoCgE6geR3k/H78= +github.com/containers/common v0.55.1-0.20230724161016-2966c705a7a3/go.mod h1:SUX+gHoElocPp664K79AEt+GGHwngBJLrKdwNIRW0tQ= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.26.1-0.20230721194716-30c87d4a5b8d h1:g6DFcXXEMd1OwSVtbrUolGzmkMNyQDyc4OKHOFxbNeE= @@ -820,8 +820,8 @@ github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.m github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.1.0-rc.3 h1:l04uafi6kxByhbxev7OWiuUv0LZxEsYUfDWZ6bztAuU= -github.com/opencontainers/runtime-spec v1.1.0-rc.3/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= +github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/runtime-tools v0.9.1-0.20230317050512-e931285f4b69 h1:NL4xDvl68WWqQ+8WPMM3l5PsZTxaT7Z4K3VSKDRuAGs= github.com/opencontainers/runtime-tools v0.9.1-0.20230317050512-e931285f4b69/go.mod h1:bNpfuSHA3DZRtD0TPWO8LzgtLpFPTVA/3jDkzD/OPyk= diff --git a/hack/man-page-checker b/hack/man-page-checker index b2b5049c65..748da6b7a0 100755 --- a/hack/man-page-checker +++ b/hack/man-page-checker @@ -78,6 +78,11 @@ function compare_usage() { local cmd="$1" local from_man="$2" + # Special case: this is an external call to docker + if [[ $cmd = "podman compose" ]] || [[ $cmd = "podmansh" ]]; then + return + fi + # Sometimes in CI we run before podman gets built. test -x ../../../bin/podman || return diff --git a/hack/xref-helpmsgs-manpages b/hack/xref-helpmsgs-manpages index 2c75ce620c..2335f5dc0a 100755 --- a/hack/xref-helpmsgs-manpages +++ b/hack/xref-helpmsgs-manpages @@ -95,6 +95,13 @@ my %Format_Option_Is_Special = map { $_ => 1 } ( 'inspect', # ambiguous (container/image) ); +# Do not cross-reference these. +my %Skip_Subcommand = map { $_ => 1 } ( + "help", # has no man page + "completion", # internal (hidden) subcommand + "compose", # external tool, outside of our control +); + # END user-customizable section ############################################################################### @@ -279,8 +286,8 @@ sub xref_by_man { next if "@subcommand" eq 'system' && $k eq 'service'; - # Special case: podman completion is a hidden command - next if $k eq 'completion'; + # Special case for hidden or external commands + next if $Skip_Subcommand{$k}; warn "$ME: 'podman @subcommand': $k in $man, but not --help\n"; ++$Errs; @@ -346,7 +353,7 @@ sub podman_help { } $help{$subcommand} = podman_help(@_, $subcommand) - unless $subcommand eq 'help'; # 'help' not in man + unless $Skip_Subcommand{$subcommand}; } } elsif ($section eq 'options') { diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index bf400c0594..66963660a7 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -387,6 +387,7 @@ can_use_shortcut (char **argv) || strcmp (argv[argc], "version") == 0 || strcmp (argv[argc], "context") == 0 || strcmp (argv[argc], "search") == 0 + || strcmp (argv[argc], "compose") == 0 || (strcmp (argv[argc], "system") == 0 && argv[argc+1] && strcmp (argv[argc+1], "service") != 0)) { ret = false; diff --git a/test/compose/test-compose b/test/compose/test-compose index fe2da9532f..201cead240 100755 --- a/test/compose/test-compose +++ b/test/compose/test-compose @@ -276,11 +276,12 @@ done # When rootless use a socket path accessible by the rootless user if is_rootless; then DOCKER_SOCK="$WORKDIR/docker.sock" - DOCKER_HOST="unix://$DOCKER_SOCK" - # export DOCKER_HOST docker-compose will use it - export DOCKER_HOST fi +# export DOCKER_HOST docker-compose will use it +DOCKER_HOST="unix://$DOCKER_SOCK" +export DOCKER_HOST + # Identify the tests to run. If called with args, use those as globs. tests_to_run=() if [ -n "$*" ]; then @@ -331,12 +332,12 @@ for t in "${tests_to_run[@]}"; do trap '. teardown.sh' 0 fi - docker-compose up -d &> $logfile + podman compose up -d &> $logfile docker_compose_rc=$? if [[ $docker_compose_rc -ne 0 ]]; then _show_ok 0 "$testname - up" "[ok]" "status=$docker_compose_rc" sed -e 's/^/# /' <$logfile - docker-compose down >>$logfile 2>&1 # No status check here + podman compose down >>$logfile 2>&1 # No status check here exit 1 fi _show_ok 1 "$testname - up" @@ -354,7 +355,7 @@ for t in "${tests_to_run[@]}"; do fi # Done. Clean up. - docker-compose down &>> $logfile + podman compose down &>> $logfile rc=$? if [[ $rc -eq 0 ]]; then _show_ok 1 "$testname - down" diff --git a/test/system/015-help.bats b/test/system/015-help.bats index 927645f296..36bda467cf 100644 --- a/test/system/015-help.bats +++ b/test/system/015-help.bats @@ -17,6 +17,11 @@ function check_help() { local -A found for cmd in $(_podman_commands "$@"); do + # Skip the compose command which is calling `docker-compose --help` + # and hence won't match the assumptions made below. + if [[ "$cmd" == "compose" ]]; then + continue + fi # Human-readable podman command string, with multiple spaces collapsed command_string="podman $* $cmd" command_string=${command_string// / } # 'podman x' -> 'podman x' diff --git a/test/system/600-completion.bats b/test/system/600-completion.bats index 1060cde46f..6a577ffc12 100644 --- a/test/system/600-completion.bats +++ b/test/system/600-completion.bats @@ -37,6 +37,11 @@ function check_shell_completion() { " for cmd in $(_podman_commands "$@"); do + # Skip the compose command which is calling `docker-compose --help` + # and hence won't match the assumptions made below. + if [[ "$cmd" == "compose" ]]; then + continue + fi # Human-readable podman command string, with multiple spaces collapsed name="podman" if is_remote; then diff --git a/test/system/610-format.bats b/test/system/610-format.bats index 1862301552..448d1e6ed6 100644 --- a/test/system/610-format.bats +++ b/test/system/610-format.bats @@ -55,6 +55,12 @@ can_run_stats= # > run the command with --format '{{"\n"}}' and make sure it passes function check_subcommand() { for cmd in $(_podman_commands "$@"); do + # Skip the compose command which is calling `docker-compose --help` + # and hence won't match the assumptions made below. + if [[ "$cmd" == "compose" ]]; then + continue + fi + # Human-readable podman command string, with multiple spaces collapsed # Special case: 'podman machine' can only be run under ideal conditions if [[ "$cmd" = "machine" ]] && [[ -z "$can_run_podman_machine" ]]; then continue diff --git a/test/system/850-compose.bats b/test/system/850-compose.bats new file mode 100644 index 0000000000..91e6afc267 --- /dev/null +++ b/test/system/850-compose.bats @@ -0,0 +1,67 @@ +#!/usr/bin/env bats -*- bats -*- +# +# Smoke tests for the podman-compose command. test/compose takes care of functional tests. +# + +load helpers + +@test "podman compose - smoke tests" { + fake_compose_bin="$PODMAN_TMPDIR/fake_compose" + cat >$fake_compose_bin <$compose_conf <