mirror of
https://github.com/containers/podman.git
synced 2025-05-18 15:47:51 +08:00

**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>
204 lines
5.4 KiB
Go
204 lines
5.4 KiB
Go
package util
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/common/libnetwork/types"
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
UnknownPackage = "Unknown"
|
|
)
|
|
|
|
// Note: This function is copied from containers/podman libpod/util.go
|
|
// Please see https://github.com/containers/common/pull/1460
|
|
func queryPackageVersion(cmdArg ...string) string {
|
|
output := UnknownPackage
|
|
if 1 < len(cmdArg) {
|
|
cmd := exec.Command(cmdArg[0], cmdArg[1:]...)
|
|
if outp, err := cmd.Output(); err == nil {
|
|
output = string(outp)
|
|
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])
|
|
if outp, err := cmd.Output(); err == nil {
|
|
output = string(outp)
|
|
}
|
|
}
|
|
}
|
|
if cmdArg[0] == "/sbin/apk" {
|
|
prefix := cmdArg[len(cmdArg)-1] + " is owned by "
|
|
output = strings.Replace(output, prefix, "", 1)
|
|
}
|
|
}
|
|
return strings.Trim(output, "\n")
|
|
}
|
|
|
|
// 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/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)
|
|
{"/sbin/apk", "info", "-W"}, // Alpine
|
|
{"/usr/local/sbin/pkg", "which", "-q"}, // FreeBSD
|
|
}
|
|
|
|
for _, cmd := range packagers {
|
|
cmd = append(cmd, program)
|
|
if out := queryPackageVersion(cmd...); out != UnknownPackage {
|
|
return out
|
|
}
|
|
}
|
|
return UnknownPackage
|
|
}
|
|
|
|
// Note: This function is copied from containers/podman libpod/util.go
|
|
// Please see https://github.com/containers/common/pull/1460
|
|
func ProgramVersion(program string) (string, error) {
|
|
return programVersion(program, false)
|
|
}
|
|
|
|
func ProgramVersionDnsname(program string) (string, error) {
|
|
return programVersion(program, true)
|
|
}
|
|
|
|
func programVersion(program string, dnsname bool) (string, error) {
|
|
cmd := exec.Command(program, "--version")
|
|
var stdout bytes.Buffer
|
|
var stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return "", fmt.Errorf("`%v --version` failed: %v %v (%v)", program, stderr.String(), stdout.String(), err)
|
|
}
|
|
|
|
output := strings.TrimSuffix(stdout.String(), "\n")
|
|
// dnsname --version returns the information to stderr
|
|
if dnsname {
|
|
output = strings.TrimSuffix(stderr.String(), "\n")
|
|
}
|
|
|
|
return output, nil
|
|
}
|
|
|
|
// StringInSlice determines if a string is in a string slice, returns bool
|
|
func StringInSlice(s string, sl []string) bool {
|
|
for _, i := range sl {
|
|
if i == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// StringMatchRegexSlice determines if a given string matches one of the given regexes, returns bool
|
|
func StringMatchRegexSlice(s string, re []string) bool {
|
|
for _, r := range re {
|
|
m, err := regexp.MatchString(r, s)
|
|
if err == nil && m {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// FilterID is a function used to compare an id against a set of ids, if the
|
|
// input is hex we check if the prefix matches. Otherwise we assume it is a
|
|
// regex and try to match that.
|
|
// see https://github.com/containers/podman/issues/18471 for why we do this
|
|
func FilterID(id string, filters []string) bool {
|
|
for _, want := range filters {
|
|
isRegex := types.NotHexRegex.MatchString(want)
|
|
if isRegex {
|
|
match, err := regexp.MatchString(want, id)
|
|
if err == nil && match {
|
|
return true
|
|
}
|
|
} else if strings.HasPrefix(id, strings.ToLower(want)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// WaitForFile waits until a file has been created or the given timeout has occurred
|
|
func WaitForFile(path string, chWait chan error, timeout time.Duration) (bool, error) {
|
|
var inotifyEvents chan fsnotify.Event
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err == nil {
|
|
if err := watcher.Add(filepath.Dir(path)); err == nil {
|
|
inotifyEvents = watcher.Events
|
|
}
|
|
defer func() {
|
|
if err := watcher.Close(); err != nil {
|
|
logrus.Errorf("Failed to close fsnotify watcher: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
var timeoutChan <-chan time.Time
|
|
|
|
if timeout != 0 {
|
|
timeoutChan = time.After(timeout)
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case e := <-chWait:
|
|
return true, e
|
|
case <-inotifyEvents:
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return false, nil
|
|
}
|
|
if !os.IsNotExist(err) {
|
|
return false, err
|
|
}
|
|
case <-time.After(25 * time.Millisecond):
|
|
// Check periodically for the file existence. It is needed
|
|
// if the inotify watcher could not have been created. It is
|
|
// also useful when using inotify as if for any reasons we missed
|
|
// a notification, we won't hang the process.
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return false, nil
|
|
}
|
|
if !os.IsNotExist(err) {
|
|
return false, err
|
|
}
|
|
case <-timeoutChan:
|
|
return false, fmt.Errorf("timed out waiting for file %s", path)
|
|
}
|
|
}
|
|
}
|