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 <vrothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2023-07-17 13:43:24 +02:00
parent b9383f41ac
commit e596b17fbe
26 changed files with 521 additions and 23 deletions

302
cmd/podman/compose.go Normal file
View File

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

View File

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

View File

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

View File

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

4
go.mod
View File

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

8
go.sum
View File

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

View File

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

View File

@ -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') {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <<EOF
#!/bin/bash
if [[ "\$@" == "fail" ]]; then
exit 42
fi
if [[ "\$@" == "env" ]]; then
printenv DOCKER_HOST DOCKER_BUILDKIT DOCKER_CONFIG
exit 0
fi
echo "arguments: \$@"
EOF
compose_conf="$PODMAN_TMPDIR/compose.conf"
cat >$compose_conf <<EOF
[engine]
compose_providers = ["$fake_compose_bin"]
compose_warning_logs = false
EOF
random_data="--foo=bar --random=$(random_string 15) -f /path/to/file ignore me"
# Make sure that the fake compose binary is used and that error reporting works
PODMAN_COMPOSE_PROVIDER=$fake_compose_bin run_podman 125 compose --help
is "$output" ".*executing $fake_compose_bin --help: .*: permission denied"
# Make the fake one executable and check the --help output
chmod +x $fake_compose_bin
PODMAN_COMPOSE_PROVIDER=$fake_compose_bin run_podman compose --help
is "$output" "Run compose workloads via an external provider .*arguments: --help"
# No argument yields the help message as well
PODMAN_COMPOSE_PROVIDER=$fake_compose_bin run_podman compose
is "$output" "Run compose workloads via an external provider .*arguments: "
# Make sure that the provider can be specified via containers.conf and that
# the warning logs can be turned off
CONTAINERS_CONF_OVERRIDE=$compose_conf run_podman compose --help
is "$output" "Run compose workloads via an external provider .*arguments: --help"
assert "$output" !~ ".*Executing external compose provider.*"
# Run with bogus arguments and make sure they're being returned
CONTAINERS_CONF_OVERRIDE=$compose_conf run_podman compose $random_data
is "$output" "arguments: $random_data"
# Make sure Podman returns the exit code of the compose provider
CONTAINERS_CONF_OVERRIDE=$compose_conf run_podman 42 compose fail
# Make sure the three env variables are set (and parsed)
CONTAINERS_CONF_OVERRIDE=$compose_conf run_podman compose env
is "${lines[0]}" ".*/podman.sock"
is "${lines[1]}" "0"
is "${lines[2]}" ""
DOCKER_HOST="$random_data" DOCKER_CONFIG="$random_data" CONTAINERS_CONF_OVERRIDE=$compose_conf run_podman compose env
is "${lines[0]}" "$random_data"
is "${lines[1]}" "0"
is "${lines[2]}" "$random_data"
}

View File

@ -266,6 +266,17 @@ type EngineConfig struct {
// in containers-registries.conf(5).
CompatAPIEnforceDockerHub bool `toml:"compat_api_enforce_docker_hub,omitempty"`
// ComposeProviders specifies one or more external providers for the
// compose command. The first found provider is used for execution.
// Can be an absolute and relative path or a (file) name. Make sure to
// expand the return items via `os.ExpandEnv`.
ComposeProviders []string `toml:"compose_providers,omitempty"`
// ComposeWarningLogs emits logs on each invocation of the compose
// command indicating that an external compose provider is being
// executed.
ComposeWarningLogs bool `toml:"compose_warning_logs,omitempty"`
// DBBackend is the database backend to be used by Podman.
DBBackend string `toml:"database_backend,omitempty"`

View File

@ -11,7 +11,7 @@ import (
"strings"
"syscall"
"github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
units "github.com/docker/go-units"
)
@ -58,7 +58,7 @@ func (c *EngineConfig) validatePaths() error {
func (c *ContainersConfig) validateDevices() error {
for _, d := range c.Devices {
if cdi.IsQualifiedName(d) {
if parser.IsQualifiedName(d) {
continue
}
_, _, _, err := Device(d)

View File

@ -376,6 +376,20 @@ default_sysctls = [
#
#active_service = "production"
# Enforces using docker.io for completing short names in Podman's compatibility
# REST API. Note that this will ignore unqualified-search-registries and
# short-name aliases defined in containers-registries.conf(5).
#compat_api_enforce_docker_hub = true
# Specify one or more external providers for the compose command. The first
# found provider is used for execution. Can be an absolute and relative path
# or a (file) name.
#compose_providers=[]
# Emit logs on each invocation of the compose command indicating that an
# external compose provider is being executed.
#compose_warning_logs = true
# The compression format to use when pushing an image.
# Valid options are: `gzip`, `zstd` and `zstd:chunked`.
#

View File

@ -500,7 +500,7 @@ default_sysctls = [
# List of the OCI runtimes that support --format=json. When json is supported
# engine will use it for reporting nicer errors.
#
#runtime_supports_json = ["crun", "runc", "kata", "runsc", "youki", "krun"]
#runtime_supports_json = ["crun", "runc", "kata", "runsc", "youki", "krun", "ocijail"]
# List of the OCI runtimes that supports running containers with KVM Separation.
#

View File

@ -87,6 +87,16 @@ var (
// should be set during link-time, if different packagers put their
// helper binary in a different location.
additionalHelperBinariesDir string
defaultUnixComposeProviders = []string{
"docker-compose",
"$HOME/.docker/cli-plugins/docker-compose",
"/usr/local/lib/docker/cli-plugins/docker-compose",
"/usr/local/libexec/docker/cli-plugins/docker-compose",
"/usr/lib/docker/cli-plugins/docker-compose",
"/usr/libexec/docker/cli-plugins/docker-compose",
"podman-compose",
}
)
// nolint:unparam
@ -260,6 +270,8 @@ func defaultConfigFromMemory() (*EngineConfig, error) {
c.EventsLogFileMaxSize = eventsLogMaxSize(DefaultEventsLogSizeMax)
c.CompatAPIEnforceDockerHub = true
c.ComposeProviders = getDefaultComposeProviders() // may vary across supported platforms
c.ComposeWarningLogs = true
if path, ok := os.LookupEnv("CONTAINERS_STORAGE_CONF"); ok {
if err := types.SetDefaultConfigFilePath(path); err != nil {
@ -406,6 +418,7 @@ func defaultConfigFromMemory() (*EngineConfig, error) {
"runsc",
"youki",
"krun",
"ocijail",
}
c.RuntimeSupportsNoCgroups = []string{"crun", "krun"}
c.RuntimeSupportsKVM = []string{"kata", "kata-runtime", "kata-qemu", "kata-fc", "krun"}

View File

@ -20,3 +20,14 @@ func getDefaultMachineVolumes() []string {
"/var/folders:/var/folders",
}
}
func getDefaultComposeProviders() []string {
return []string{
"docker-compose",
"$HOME/.docker/cli-plugins/docker-compose",
"/opt/homebrew/bin/docker-compose",
"/usr/local/bin/docker-compose",
"/Applications/Docker.app/Contents/Resources/cli-plugins/docker-compose",
"podman-compose",
}
}

View File

@ -26,3 +26,7 @@ func getLibpodTmpDir() string {
func getDefaultMachineVolumes() []string {
return []string{"$HOME:$HOME"}
}
func getDefaultComposeProviders() []string {
return defaultUnixComposeProviders
}

View File

@ -74,3 +74,7 @@ func getLibpodTmpDir() string {
func getDefaultMachineVolumes() []string {
return []string{"$HOME:$HOME"}
}
func getDefaultComposeProviders() []string {
return defaultUnixComposeProviders
}

View File

@ -49,3 +49,8 @@ func getLibpodTmpDir() string {
func getDefaultMachineVolumes() []string {
return []string{}
}
func getDefaultComposeProviders() []string {
// Rely on os.LookPath to do the trick on Windows.
return []string{"docker-compose", "podman-compose"}
}

View File

@ -27,7 +27,16 @@ func queryPackageVersion(cmdArg ...string) string {
cmd := exec.Command(cmdArg[0], cmdArg[1:]...)
if outp, err := cmd.Output(); err == nil {
output = string(outp)
if cmdArg[0] == "/usr/bin/dpkg" {
deb := false
if cmdArg[0] == "/usr/bin/dlocate" {
// can return multiple matches
l := strings.Split(output, "\n")
output = l[0]
deb = true
} else if cmdArg[0] == "/usr/bin/dpkg" {
deb = true
}
if deb {
r := strings.Split(output, ": ")
queryFormat := `${Package}_${Version}_${Architecture}`
cmd = exec.Command("/usr/bin/dpkg-query", "-f", queryFormat, "-W", r[0])
@ -47,9 +56,14 @@ func queryPackageVersion(cmdArg ...string) string {
// Note: This function is copied from containers/podman libpod/util.go
// Please see https://github.com/containers/common/pull/1460
func PackageVersion(program string) string { // program is full path
_, err := os.Stat(program)
if err != nil {
return UnknownPackage
}
packagers := [][]string{
{"/usr/bin/rpm", "-q", "-f"},
{"/usr/bin/dpkg", "-S"}, // Debian, Ubuntu
{"/usr/bin/dlocate", "-F"}, // Debian, Ubuntu (quick)
{"/usr/bin/dpkg", "-S"}, // Debian, Ubuntu (slow)
{"/usr/bin/pacman", "-Qo"}, // Arch
{"/usr/bin/qfile", "-qv"}, // Gentoo (quick)
{"/usr/bin/equery", "b"}, // Gentoo (slow)

View File

@ -11,7 +11,7 @@ const (
VersionPatch = 0
// VersionDev indicates development branch. Releases will be empty string.
VersionDev = "-rc.3"
VersionDev = ""
)
// Version is the specification version that the package types support.

4
vendor/modules.txt vendored
View File

@ -157,7 +157,7 @@ github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util
github.com/containers/buildah/util
# github.com/containers/common v0.55.1-0.20230721175448-664d013a6ae2
# github.com/containers/common v0.55.1-0.20230724161016-2966c705a7a3
## explicit; go 1.18
github.com/containers/common/libimage
github.com/containers/common/libimage/define
@ -815,7 +815,7 @@ github.com/opencontainers/runc/libcontainer/devices
github.com/opencontainers/runc/libcontainer/user
github.com/opencontainers/runc/libcontainer/userns
github.com/opencontainers/runc/libcontainer/utils
# github.com/opencontainers/runtime-spec v1.1.0-rc.3
# github.com/opencontainers/runtime-spec v1.1.0
## explicit
github.com/opencontainers/runtime-spec/specs-go
# github.com/opencontainers/runtime-tools v0.9.1-0.20230317050512-e931285f4b69