mirror of
https://github.com/containers/podman.git
synced 2025-05-17 15:18:43 +08:00
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:
302
cmd/podman/compose.go
Normal file
302
cmd/podman/compose.go
Normal 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)
|
||||
}
|
1
docs/source/markdown/.gitignore
vendored
1
docs/source/markdown/.gitignore
vendored
@ -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
|
||||
|
21
docs/source/markdown/podman-compose.1.md.in
Normal file
21
docs/source/markdown/podman-compose.1.md.in
Normal 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)**
|
@ -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
4
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
|
||||
|
8
go.sum
8
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=
|
||||
|
@ -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
|
||||
|
||||
|
@ -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') {
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
67
test/system/850-compose.bats
Normal file
67
test/system/850-compose.bats
Normal 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"
|
||||
}
|
11
vendor/github.com/containers/common/pkg/config/config.go
generated
vendored
11
vendor/github.com/containers/common/pkg/config/config.go
generated
vendored
@ -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"`
|
||||
|
||||
|
4
vendor/github.com/containers/common/pkg/config/config_local.go
generated
vendored
4
vendor/github.com/containers/common/pkg/config/config_local.go
generated
vendored
@ -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)
|
||||
|
14
vendor/github.com/containers/common/pkg/config/containers.conf
generated
vendored
14
vendor/github.com/containers/common/pkg/config/containers.conf
generated
vendored
@ -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`.
|
||||
#
|
||||
|
2
vendor/github.com/containers/common/pkg/config/containers.conf-freebsd
generated
vendored
2
vendor/github.com/containers/common/pkg/config/containers.conf-freebsd
generated
vendored
@ -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.
|
||||
#
|
||||
|
13
vendor/github.com/containers/common/pkg/config/default.go
generated
vendored
13
vendor/github.com/containers/common/pkg/config/default.go
generated
vendored
@ -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"}
|
||||
|
11
vendor/github.com/containers/common/pkg/config/default_darwin.go
generated
vendored
11
vendor/github.com/containers/common/pkg/config/default_darwin.go
generated
vendored
@ -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",
|
||||
}
|
||||
}
|
||||
|
4
vendor/github.com/containers/common/pkg/config/default_freebsd.go
generated
vendored
4
vendor/github.com/containers/common/pkg/config/default_freebsd.go
generated
vendored
@ -26,3 +26,7 @@ func getLibpodTmpDir() string {
|
||||
func getDefaultMachineVolumes() []string {
|
||||
return []string{"$HOME:$HOME"}
|
||||
}
|
||||
|
||||
func getDefaultComposeProviders() []string {
|
||||
return defaultUnixComposeProviders
|
||||
}
|
||||
|
4
vendor/github.com/containers/common/pkg/config/default_linux.go
generated
vendored
4
vendor/github.com/containers/common/pkg/config/default_linux.go
generated
vendored
@ -74,3 +74,7 @@ func getLibpodTmpDir() string {
|
||||
func getDefaultMachineVolumes() []string {
|
||||
return []string{"$HOME:$HOME"}
|
||||
}
|
||||
|
||||
func getDefaultComposeProviders() []string {
|
||||
return defaultUnixComposeProviders
|
||||
}
|
||||
|
5
vendor/github.com/containers/common/pkg/config/default_windows.go
generated
vendored
5
vendor/github.com/containers/common/pkg/config/default_windows.go
generated
vendored
@ -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"}
|
||||
}
|
||||
|
18
vendor/github.com/containers/common/pkg/util/util.go
generated
vendored
18
vendor/github.com/containers/common/pkg/util/util.go
generated
vendored
@ -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)
|
||||
|
2
vendor/github.com/opencontainers/runtime-spec/specs-go/version.go
generated
vendored
2
vendor/github.com/opencontainers/runtime-spec/specs-go/version.go
generated
vendored
@ -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
4
vendor/modules.txt
vendored
@ -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
|
||||
|
Reference in New Issue
Block a user