summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile12
-rw-r--r--RELEASE_NOTES.md3
-rw-r--r--cmd/podman-mac-helper/install.go244
-rw-r--r--cmd/podman-mac-helper/main.go149
-rw-r--r--cmd/podman-mac-helper/service.go85
-rw-r--r--cmd/podman-mac-helper/uninstall.go60
-rw-r--r--cmd/podman/common/create.go2
-rw-r--r--cmd/podman/machine/init.go5
-rw-r--r--cmd/podman/machine/set.go56
-rw-r--r--docs/source/markdown/podman-machine-init.1.md9
-rw-r--r--docs/source/markdown/podman-machine-set.1.md59
-rw-r--r--docs/source/markdown/podman-machine.1.md1
-rw-r--r--docs/source/markdown/podman-network-create.1.md2
-rw-r--r--docs/source/markdown/podman-network-inspect.1.md2
-rw-r--r--docs/source/markdown/podman-network-ls.1.md32
-rw-r--r--docs/source/markdown/podman-network-rm.1.md2
-rw-r--r--docs/source/markdown/podman-network.1.md38
-rw-r--r--docs/source/markdown/podman-pod-ps.1.md48
-rw-r--r--docs/source/markdown/podman.1.md6
-rw-r--r--libpod/runtime_ctr.go5
-rw-r--r--pkg/api/handlers/libpod/manifests.go2
-rw-r--r--pkg/api/server/register_networks.go2
-rw-r--r--pkg/bindings/images/build.go2
-rw-r--r--pkg/bindings/manifests/manifests.go115
-rw-r--r--pkg/bindings/test/common_test.go8
-rw-r--r--pkg/bindings/test/manifests_test.go4
-rw-r--r--pkg/checkpoint/checkpoint_restore.go7
-rw-r--r--pkg/machine/config.go9
-rw-r--r--pkg/machine/connection.go25
-rw-r--r--pkg/machine/ignition.go85
-rw-r--r--pkg/machine/qemu/claim_darwin.go63
-rw-r--r--pkg/machine/qemu/claim_unsupported.go20
-rw-r--r--pkg/machine/qemu/config.go2
-rw-r--r--pkg/machine/qemu/machine.go283
-rw-r--r--pkg/machine/wsl/machine.go80
-rw-r--r--test/apiv2/01-basic.at2
-rw-r--r--test/e2e/checkpoint_test.go4
-rw-r--r--test/e2e/common_test.go14
-rw-r--r--test/e2e/images_test.go14
-rw-r--r--test/e2e/network_create_test.go22
-rw-r--r--test/e2e/network_test.go57
-rw-r--r--test/e2e/run_networking_test.go91
-rw-r--r--test/system/070-build.bats28
-rw-r--r--version/version.go4
44 files changed, 1537 insertions, 226 deletions
diff --git a/Makefile b/Makefile
index 9e9b3676e..0a5389ce9 100644
--- a/Makefile
+++ b/Makefile
@@ -376,13 +376,23 @@ podman-winpath: .gopathok $(SOURCES) go.mod go.sum
./cmd/winpath
.PHONY: podman-remote-darwin
-podman-remote-darwin: ## Build podman-remote for macOS
+podman-remote-darwin: podman-mac-helper ## Build podman-remote for macOS
$(MAKE) \
CGO_ENABLED=$(DARWIN_GCO) \
GOOS=darwin \
GOARCH=$(GOARCH) \
bin/darwin/podman
+.PHONY: podman-mac-helper
+podman-mac-helper: ## Build podman-mac-helper for macOS
+ CGO_ENABLED=0 \
+ GOOS=darwin \
+ GOARCH=$(GOARCH) \
+ $(GO) build \
+ $(BUILDFLAGS) \
+ -o bin/darwin/podman-mac-helper \
+ ./cmd/podman-mac-helper
+
bin/rootlessport: .gopathok $(SOURCES) go.mod go.sum
CGO_ENABLED=$(CGO_ENABLED) \
$(GO) build \
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 1cd2f9381..4c07b033a 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -29,6 +29,7 @@
- The `podman machine init` command now supports a new VM type, `wsl`, available only on Windows; this uses WSL as a backend for `podman machine`, instead of creating a separate VM and managing it via QEMU ([#12503](https://github.com/containers/podman/pull/12503)).
- The `podman machine init` command now supports a new option, `--now`, to start the VM immediately after creating it.
- The `podman machine init` command now supports a new option, `--volume`, to mount contents from the host into the created virtual machine.
+- Virtual machines created by `podman machine` now automatically mount the Podman API socket to the host, so consumers of the Podman or Docker APIs can use them directly from the host machine ([#11462](https://github.com/containers/podman/issues/11462)).
- Virtual machines created by `podman machine` now automatically mount certificates from the host's keychain into the virtual machine ([#11507](https://github.com/containers/podman/issues/11507)).
- Virtual machines created by `podman machine` now automatically propagate standard proxy environment variables from the host into the virtual machine, including copying any required certificates from `SSL_FILE_CERT` into the VM.
- The `podman machine ssh` command now supports a new option, `--username`, to specify the username to connect to the VM with.
@@ -136,7 +137,7 @@
- Fixed a bug where the `podman build` command did not properly propagate non-0 exit codes from Buildah when builds failed.
- Fixed a bug where the remote Podman client's `podman build` command could fail to build images when the remote client was run on Windows and the Containerfile contained `COPY` instructions ([#13119](https://github.com/containers/podman/issues/13119)).
- Fixed a bug where the remote Podman client's `--secret` option to the `podman build` command was nonfunctional.
-- Fixed a bug where the remote Podman client's `podman build` command would error if given a relative path to a Containerfile ([#12841](https://github.com/containers/podman/issues/12841)).
+- Fixed a bug where the remote Podman client's `podman build` command would error if given a relative path to a Containerfile ([#12841](https://github.com/containers/podman/issues/12841) and [#12763](https://github.com/containers/podman/issues/12763)).
- Fixed a bug where the `podman generate kube` command would sometimes omit environment variables set in containers from generated YAML.
- Fixed a bug where setting `userns=auto` in `containers.conf` was not respected ([#12615](https://github.com/containers/podman/issues/12615)).
- Fixed a bug where the `podman run` command would fail if the host machine did not have a `/etc/hosts` file ([#12667](https://github.com/containers/podman/issues/12667)).
diff --git a/cmd/podman-mac-helper/install.go b/cmd/podman-mac-helper/install.go
new file mode 100644
index 000000000..7f623ecb6
--- /dev/null
+++ b/cmd/podman-mac-helper/install.go
@@ -0,0 +1,244 @@
+//go:build darwin
+// +build darwin
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "text/template"
+
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+const (
+ rwx_rx_rx = 0755
+ rw_r_r = 0644
+)
+
+const launchConfig = `<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>com.github.containers.podman.helper-{{.User}}</string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>{{.Program}}</string>
+ <string>service</string>
+ <string>{{.Target}}</string>
+ </array>
+ <key>inetdCompatibility</key>
+ <dict>
+ <key>Wait</key>
+ <false/>
+ </dict>
+ <key>UserName</key>
+ <string>root</string>
+ <key>Sockets</key>
+ <dict>
+ <key>Listeners</key>
+ <dict>
+ <key>SockFamily</key>
+ <string>Unix</string>
+ <key>SockPathName</key>
+ <string>/private/var/run/podman-helper-{{.User}}.socket</string>
+ <key>SockPathOwner</key>
+ <integer>{{.UID}}</integer>
+ <key>SockPathMode</key>
+ <!-- SockPathMode takes base 10 (384 = 0600) -->
+ <integer>384</integer>
+ <key>SockType</key>
+ <string>stream</string>
+ </dict>
+ </dict>
+</dict>
+</plist>
+`
+
+type launchParams struct {
+ Program string
+ User string
+ UID string
+ Target string
+}
+
+var installCmd = &cobra.Command{
+ Use: "install",
+ Short: "installs the podman helper agent",
+ Long: "installs the podman helper agent, which manages the /var/run/docker.sock link",
+ PreRun: silentUsage,
+ RunE: install,
+}
+
+func init() {
+ addPrefixFlag(installCmd)
+ rootCmd.AddCommand(installCmd)
+}
+
+func install(cmd *cobra.Command, args []string) error {
+ userName, uid, homeDir, err := getUser()
+ if err != nil {
+ return err
+ }
+
+ labelName := fmt.Sprintf("com.github.containers.podman.helper-%s.plist", userName)
+ fileName := filepath.Join("/Library", "LaunchDaemons", labelName)
+
+ if _, err := os.Stat(fileName); err == nil || !os.IsNotExist(err) {
+ return errors.New("helper is already installed, uninstall first")
+ }
+
+ prog, err := installExecutable(userName)
+ if err != nil {
+ return err
+ }
+
+ target := filepath.Join(homeDir, ".local", "share", "containers", "podman", "machine", "podman.sock")
+ var buf bytes.Buffer
+ t := template.Must(template.New("launchdConfig").Parse(launchConfig))
+ err = t.Execute(&buf, launchParams{prog, userName, uid, target})
+ if err != nil {
+ return err
+ }
+
+ file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, rw_r_r)
+ if err != nil {
+ return errors.Wrap(err, "error creating helper plist file")
+ }
+ defer file.Close()
+ _, err = buf.WriteTo(file)
+ if err != nil {
+ return err
+ }
+
+ if err = runDetectErr("launchctl", "load", fileName); err != nil {
+ return errors.Wrap(err, "launchctl failed loading service")
+ }
+
+ return nil
+}
+
+func restrictRecursive(targetDir string, until string) error {
+ for targetDir != until && len(targetDir) > 1 {
+ info, err := os.Lstat(targetDir)
+ if err != nil {
+ return err
+ }
+ if info.Mode()&fs.ModeSymlink != 0 {
+ return errors.Errorf("symlinks not allowed in helper paths (remove them and rerun): %s", targetDir)
+ }
+ if err = os.Chown(targetDir, 0, 0); err != nil {
+ return errors.Wrap(err, "could not update ownership of helper path")
+ }
+ if err = os.Chmod(targetDir, rwx_rx_rx|fs.ModeSticky); err != nil {
+ return errors.Wrap(err, "could not update permissions of helper path")
+ }
+ targetDir = filepath.Dir(targetDir)
+ }
+
+ return nil
+}
+
+func verifyRootDeep(path string) error {
+ path = filepath.Clean(path)
+ current := "/"
+ segs := strings.Split(path, "/")
+ depth := 0
+ for i := 1; i < len(segs); i++ {
+ seg := segs[i]
+ current = filepath.Join(current, seg)
+ info, err := os.Lstat(current)
+ if err != nil {
+ return err
+ }
+
+ stat := info.Sys().(*syscall.Stat_t)
+ if stat.Uid != 0 {
+ return errors.Errorf("installation target path must be solely owned by root: %s is not", current)
+ }
+
+ if info.Mode()&fs.ModeSymlink != 0 {
+ target, err := os.Readlink(current)
+ if err != nil {
+ return err
+ }
+
+ targetParts := strings.Split(target, "/")
+ segs = append(targetParts, segs[i+1:]...)
+
+ if depth++; depth > 1000 {
+ return errors.New("reached max recursion depth, link structure is cyclical or too complex")
+ }
+
+ if !filepath.IsAbs(target) {
+ current = filepath.Dir(current)
+ i = -1 // Start at 0
+ } else {
+ current = "/"
+ i = 0 // Skip empty first segment
+ }
+ }
+ }
+
+ return nil
+}
+
+func installExecutable(user string) (string, error) {
+ // Since the installed executable runs as root, as a precaution verify root ownership of
+ // the entire installation path, and utilize sticky + read only perms for the helper path
+ // suffix. The goal is to help users harden against privilege escalation from loose
+ // filesystem permissions.
+ //
+ // Since userpsace package management tools, such as brew, delegate management of system
+ // paths to standard unix users, the daemon executable is copied into a separate more
+ // restricted area of the filesystem.
+ if err := verifyRootDeep(installPrefix); err != nil {
+ return "", err
+ }
+
+ targetDir := filepath.Join(installPrefix, "podman", "helper", user)
+ if err := os.MkdirAll(targetDir, rwx_rx_rx); err != nil {
+ return "", errors.Wrap(err, "could not create helper directory structure")
+ }
+
+ // Correct any incorrect perms on previously existing directories and verify no symlinks
+ if err := restrictRecursive(targetDir, installPrefix); err != nil {
+ return "", err
+ }
+
+ exec, err := os.Executable()
+ if err != nil {
+ return "", err
+ }
+ install := filepath.Join(targetDir, filepath.Base(exec))
+
+ return install, copyFile(install, exec, rwx_rx_rx)
+}
+
+func copyFile(dest string, source string, perms fs.FileMode) error {
+ in, err := os.Open(source)
+ if err != nil {
+ return err
+ }
+
+ defer in.Close()
+ out, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perms)
+ if err != nil {
+ return err
+ }
+
+ defer out.Close()
+ if _, err := io.Copy(out, in); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/cmd/podman-mac-helper/main.go b/cmd/podman-mac-helper/main.go
new file mode 100644
index 000000000..8d995519f
--- /dev/null
+++ b/cmd/podman-mac-helper/main.go
@@ -0,0 +1,149 @@
+//go:build darwin
+// +build darwin
+
+package main
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+const (
+ defaultPrefix = "/usr/local"
+ dockerSock = "/var/run/docker.sock"
+)
+
+var installPrefix string
+
+var rootCmd = &cobra.Command{
+ Use: "podman-mac-helper",
+ Short: "A system helper to manage docker.sock",
+ Long: `podman-mac-helper is a system helper service and tool for managing docker.sock `,
+ CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
+ SilenceErrors: true,
+}
+
+// Note, this code is security sensitive since it runs under privilege.
+// Limit actions to what is strictly necessary, and take appropriate
+// safeguards
+//
+// After installation the service call is ran under launchd in a nowait
+// inetd style fashion, so stdin, stdout, and stderr are all pointing to
+// an accepted connection
+//
+// This service is installed once per user and will redirect
+// /var/run/docker to the fixed user-assigned unix socket location.
+//
+// Control communication is restricted to each user specific service via
+// unix file permissions
+
+func main() {
+ if os.Geteuid() != 0 {
+ fmt.Printf("This command must be ran as root via sudo or osascript\n")
+ os.Exit(1)
+ }
+
+ if err := rootCmd.Execute(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
+ }
+}
+
+func getUserInfo(name string) (string, string, string, error) {
+ // We exec id instead of using user.Lookup to remain compat
+ // with CGO disabled.
+ cmd := exec.Command("/usr/bin/id", "-P", name)
+ output, err := cmd.StdoutPipe()
+ if err != nil {
+ return "", "", "", err
+ }
+
+ if err := cmd.Start(); err != nil {
+ return "", "", "", err
+ }
+
+ entry := readCapped(output)
+ elements := strings.Split(entry, ":")
+ if len(elements) < 9 || elements[0] != name {
+ return "", "", "", errors.New("Could not lookup user")
+ }
+
+ return elements[0], elements[2], elements[8], nil
+}
+
+func getUser() (string, string, string, error) {
+ name, found := os.LookupEnv("SUDO_USER")
+ if !found {
+ name, found = os.LookupEnv("USER")
+ if !found {
+ return "", "", "", errors.New("could not determine user")
+ }
+ }
+
+ _, uid, home, err := getUserInfo(name)
+ if err != nil {
+ return "", "", "", fmt.Errorf("could not lookup user: %s", name)
+ }
+ id, err := strconv.Atoi(uid)
+ if err != nil {
+ return "", "", "", fmt.Errorf("invalid uid for user: %s", name)
+ }
+ if id == 0 {
+ return "", "", "", fmt.Errorf("unexpected root user")
+ }
+
+ return name, uid, home, nil
+}
+
+// Used for commands that don't return a proper exit code
+func runDetectErr(name string, args ...string) error {
+ cmd := exec.Command(name, args...)
+ errReader, err := cmd.StderrPipe()
+ if err != nil {
+ return err
+ }
+
+ err = cmd.Start()
+ if err == nil {
+ errString := readCapped(errReader)
+ if len(errString) > 0 {
+ re := regexp.MustCompile(`\r?\n`)
+ err = errors.New(re.ReplaceAllString(errString, ": "))
+ }
+ }
+
+ if werr := cmd.Wait(); werr != nil {
+ err = werr
+ }
+
+ return err
+}
+
+func readCapped(reader io.Reader) string {
+ // Cap output
+ buffer := make([]byte, 2048)
+ n, _ := io.ReadFull(reader, buffer)
+ _, _ = io.Copy(ioutil.Discard, reader)
+ if n > 0 {
+ return string(buffer[:n])
+ }
+
+ return ""
+}
+
+func addPrefixFlag(cmd *cobra.Command) {
+ cmd.Flags().StringVar(&installPrefix, "prefix", defaultPrefix, "Sets the install location prefix")
+}
+
+func silentUsage(cmd *cobra.Command, args []string) {
+ cmd.SilenceUsage = true
+ cmd.SilenceErrors = true
+}
diff --git a/cmd/podman-mac-helper/service.go b/cmd/podman-mac-helper/service.go
new file mode 100644
index 000000000..65cd89f34
--- /dev/null
+++ b/cmd/podman-mac-helper/service.go
@@ -0,0 +1,85 @@
+//go:build darwin
+// +build darwin
+
+package main
+
+import (
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "time"
+
+ "github.com/spf13/cobra"
+)
+
+const (
+ trigger = "GO\n"
+ fail = "NO"
+ success = "OK"
+)
+
+var serviceCmd = &cobra.Command{
+ Use: "service",
+ Short: "services requests",
+ Long: "services requests",
+ PreRun: silentUsage,
+ Run: serviceRun,
+ Hidden: true,
+}
+
+func init() {
+ rootCmd.AddCommand(serviceCmd)
+}
+
+func serviceRun(cmd *cobra.Command, args []string) {
+ info, err := os.Stdin.Stat()
+ if err != nil || info.Mode()&fs.ModeSocket == 0 {
+ fmt.Fprintln(os.Stderr, "This is an internal command that is not intended for standard terminal usage")
+ os.Exit(1)
+ }
+
+ os.Exit(service())
+}
+
+func service() int {
+ defer os.Stdout.Close()
+ defer os.Stdin.Close()
+ defer os.Stderr.Close()
+ if len(os.Args) < 3 {
+ fmt.Print(fail)
+ return 1
+ }
+ target := os.Args[2]
+
+ request := make(chan bool)
+ go func() {
+ buf := make([]byte, 3)
+ _, err := io.ReadFull(os.Stdin, buf)
+ request <- err == nil && string(buf) == trigger
+ }()
+
+ valid := false
+ select {
+ case valid = <-request:
+ case <-time.After(5 * time.Second):
+ }
+
+ if !valid {
+ fmt.Println(fail)
+ return 2
+ }
+
+ err := os.Remove(dockerSock)
+ if err == nil || os.IsNotExist(err) {
+ err = os.Symlink(target, dockerSock)
+ }
+
+ if err != nil {
+ fmt.Print(fail)
+ return 3
+ }
+
+ fmt.Print(success)
+ return 0
+}
diff --git a/cmd/podman-mac-helper/uninstall.go b/cmd/podman-mac-helper/uninstall.go
new file mode 100644
index 000000000..f72d0efd1
--- /dev/null
+++ b/cmd/podman-mac-helper/uninstall.go
@@ -0,0 +1,60 @@
+//go:build darwin
+// +build darwin
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var uninstallCmd = &cobra.Command{
+ Use: "uninstall",
+ Short: "uninstalls the podman helper agent",
+ Long: "uninstalls the podman helper agent, which manages the /var/run/docker.sock link",
+ PreRun: silentUsage,
+ RunE: uninstall,
+}
+
+func init() {
+ addPrefixFlag(uninstallCmd)
+ rootCmd.AddCommand(uninstallCmd)
+}
+
+func uninstall(cmd *cobra.Command, args []string) error {
+ userName, _, _, err := getUser()
+ if err != nil {
+ return err
+ }
+
+ labelName := fmt.Sprintf("com.github.containers.podman.helper-%s", userName)
+ fileName := filepath.Join("/Library", "LaunchDaemons", labelName+".plist")
+
+ if err = runDetectErr("launchctl", "unload", fileName); err != nil {
+ // Try removing the service by label in case the service is half uninstalled
+ if rerr := runDetectErr("launchctl", "remove", labelName); rerr != nil {
+ // Exit code 3 = no service to remove
+ if exitErr, ok := rerr.(*exec.ExitError); !ok || exitErr.ExitCode() != 3 {
+ fmt.Fprintf(os.Stderr, "Warning: service unloading failed: %s\n", err.Error())
+ fmt.Fprintf(os.Stderr, "Warning: remove also failed: %s\n", rerr.Error())
+ }
+ }
+ }
+
+ if err := os.Remove(fileName); err != nil {
+ if !os.IsNotExist(err) {
+ return errors.Errorf("could not remove plist file: %s", fileName)
+ }
+ }
+
+ helperPath := filepath.Join(installPrefix, "podman", "helper", userName)
+ if err := os.RemoveAll(helperPath); err != nil {
+ return errors.Errorf("could not remove helper binary path: %s", helperPath)
+ }
+ return nil
+}
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index 5fefbacdf..1121806d5 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -36,7 +36,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
createFlags.StringSliceVar(
&cf.Annotation,
annotationFlagName, []string{},
- "Add annotations to container (key:value)",
+ "Add annotations to container (key=value)",
)
_ = cmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone)
diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go
index 0834aa381..ab13d8651 100644
--- a/cmd/podman/machine/init.go
+++ b/cmd/podman/machine/init.go
@@ -26,7 +26,7 @@ var (
var (
initOpts = machine.InitOptions{}
- defaultMachineName = "podman-machine-default"
+ defaultMachineName = machine.DefaultMachineName
now bool
)
@@ -99,6 +99,9 @@ func init() {
IgnitionPathFlagName := "ignition-path"
flags.StringVar(&initOpts.IgnitionPath, IgnitionPathFlagName, "", "Path to ignition file")
_ = initCmd.RegisterFlagCompletionFunc(IgnitionPathFlagName, completion.AutocompleteDefault)
+
+ rootfulFlagName := "rootful"
+ flags.BoolVar(&initOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container exectution")
}
// TODO should we allow for a users to append to the qemu cmdline?
diff --git a/cmd/podman/machine/set.go b/cmd/podman/machine/set.go
new file mode 100644
index 000000000..c978206f0
--- /dev/null
+++ b/cmd/podman/machine/set.go
@@ -0,0 +1,56 @@
+// +build amd64 arm64
+
+package machine
+
+import (
+ "github.com/containers/common/pkg/completion"
+ "github.com/containers/podman/v4/cmd/podman/registry"
+ "github.com/containers/podman/v4/pkg/machine"
+ "github.com/spf13/cobra"
+)
+
+var (
+ setCmd = &cobra.Command{
+ Use: "set [options] [NAME]",
+ Short: "Sets a virtual machine setting",
+ Long: "Sets an updatable virtual machine setting",
+ RunE: setMachine,
+ Args: cobra.MaximumNArgs(1),
+ Example: `podman machine set --root=false`,
+ ValidArgsFunction: completion.AutocompleteNone,
+ }
+)
+
+var (
+ setOpts = machine.SetOptions{}
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Command: setCmd,
+ Parent: machineCmd,
+ })
+ flags := setCmd.Flags()
+
+ rootfulFlagName := "rootful"
+ flags.BoolVar(&setOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container execution")
+}
+
+func setMachine(cmd *cobra.Command, args []string) error {
+ var (
+ vm machine.VM
+ err error
+ )
+
+ vmName := defaultMachineName
+ if len(args) > 0 && len(args[0]) > 0 {
+ vmName = args[0]
+ }
+ provider := getSystemDefaultProvider()
+ vm, err = provider.LoadVMByName(vmName)
+ if err != nil {
+ return err
+ }
+
+ return vm.Set(vmName, setOpts)
+}
diff --git a/docs/source/markdown/podman-machine-init.1.md b/docs/source/markdown/podman-machine-init.1.md
index b515e8763..36db5b1cd 100644
--- a/docs/source/markdown/podman-machine-init.1.md
+++ b/docs/source/markdown/podman-machine-init.1.md
@@ -55,6 +55,14 @@ Memory (in MB).
Start the virtual machine immediately after it has been initialized.
+#### **--rootful**=*true|false*
+
+Whether this machine should prefer rootful (`true`) or rootless (`false`)
+container execution. This option will also determine the remote connection default
+if there is no existing remote connection configurations.
+
+API forwarding, if available, will follow this setting.
+
#### **--timezone**
Set the timezone for the machine and containers. Valid values are `local` or
@@ -84,6 +92,7 @@ Print usage statement.
```
$ podman machine init
$ podman machine init myvm
+$ podman machine init --rootful
$ podman machine init --disk-size 50
$ podman machine init --memory=1024 myvm
$ podman machine init -v /Users:/mnt/Users
diff --git a/docs/source/markdown/podman-machine-set.1.md b/docs/source/markdown/podman-machine-set.1.md
new file mode 100644
index 000000000..e69779564
--- /dev/null
+++ b/docs/source/markdown/podman-machine-set.1.md
@@ -0,0 +1,59 @@
+% podman-machine-set(1)
+
+## NAME
+podman\-machine\-set - Sets a virtual machine setting
+
+## SYNOPSIS
+**podman machine set** [*options*] [*name*]
+
+## DESCRIPTION
+
+Sets an updatable virtual machine setting.
+
+Options mirror values passed to `podman machine init`. Only a limited
+subset can be changed after machine initialization.
+
+## OPTIONS
+
+#### **--rootful**=*true|false*
+
+Whether this machine should prefer rootful (`true`) or rootless (`false`)
+container execution. This option will also update the current podman
+remote connection default if it is currently pointing at the specified
+machine name (or `podman-machine-default` if no name is specified).
+
+API forwarding, if available, will follow this setting.
+
+#### **--help**
+
+Print usage statement.
+
+## EXAMPLES
+
+To switch the default VM `podman-machine-default` from rootless to rootful:
+
+```
+$ podman machine set --rootful
+```
+
+or more explicitly:
+
+```
+$ podman machine set --rootful=true
+```
+
+To switch the default VM `podman-machine-default` from rootful to rootless:
+```
+$ podman machine set --rootful=false
+```
+
+To switch the VM `myvm` from rootless to rootful:
+```
+$ podman machine set --rootful myvm
+```
+
+## SEE ALSO
+**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)**
+
+## HISTORY
+February 2022, Originally compiled by Jason Greene <jason.greene@redhat.com>
diff --git a/docs/source/markdown/podman-machine.1.md b/docs/source/markdown/podman-machine.1.md
index 8d9e77ea5..3bdfd0be9 100644
--- a/docs/source/markdown/podman-machine.1.md
+++ b/docs/source/markdown/podman-machine.1.md
@@ -16,6 +16,7 @@ podman\-machine - Manage Podman's virtual machine
| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine |
| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines |
| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine |
+| set | [podman-machine-set(1)](podman-machine-set.1.md) | Sets a virtual machine setting |
| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine |
| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine |
| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine |
diff --git a/docs/source/markdown/podman-network-create.1.md b/docs/source/markdown/podman-network-create.1.md
index 5be0c2595..357a06cea 100644
--- a/docs/source/markdown/podman-network-create.1.md
+++ b/docs/source/markdown/podman-network-create.1.md
@@ -1,7 +1,7 @@
% podman-network-create(1)
## NAME
-podman\-network-create - Create a Podman CNI network
+podman\-network-create - Create a Podman network
## SYNOPSIS
**podman network create** [*options*] name
diff --git a/docs/source/markdown/podman-network-inspect.1.md b/docs/source/markdown/podman-network-inspect.1.md
index 726f167e5..ba9cc94d5 100644
--- a/docs/source/markdown/podman-network-inspect.1.md
+++ b/docs/source/markdown/podman-network-inspect.1.md
@@ -1,7 +1,7 @@
% podman-network-inspect(1)
## NAME
-podman\-network\-inspect - Displays the raw CNI network configuration for one or more networks
+podman\-network\-inspect - Displays the raw network configuration for one or more networks
## SYNOPSIS
**podman network inspect** [*options*] *network* [*network* ...]
diff --git a/docs/source/markdown/podman-network-ls.1.md b/docs/source/markdown/podman-network-ls.1.md
index 99b734157..d5bdb6a39 100644
--- a/docs/source/markdown/podman-network-ls.1.md
+++ b/docs/source/markdown/podman-network-ls.1.md
@@ -1,7 +1,7 @@
% podman-network-ls(1)
## NAME
-podman\-network\-ls - Display a summary of CNI networks
+podman\-network\-ls - Display a summary of networks
## SYNOPSIS
**podman network ls** [*options*]
@@ -12,20 +12,26 @@ Displays a list of existing podman networks.
## OPTIONS
#### **--filter**, **-f**=*filter=value*
-Filter output based on conditions given.
-Multiple filters can be given with multiple uses of the --filter option.
-Filters with the same key work inclusive with the only exception being
-`label` which is exclusive. Filters with different keys always work exclusive.
+Provide filter values.
-Valid filters are listed below:
+The *filters* argument format is of `key=value`. If there is more than one *filter*, then pass multiple OPTIONS: **--filter** *foo=bar* **--filter** *bif=baz*.
-| **Filter** | **Description** |
-| ---------- | ----------------------------------------------------------------- |
-| name | [Name] Network name (accepts regex) |
-| id | [ID] Full or partial network ID |
-| label | [Key] or [Key=Value] Label assigned to a network |
-| driver | [Driver] `bridge` or ,`macvlan` is supported |
-| until | [Until] Show all networks that were created before the given time |
+Supported filters:
+
+| **Filter** | **Description** |
+| ---------- | ------------------------------------------------------------------------------------------------ |
+| driver | Filter by driver type. |
+| id | Filter by full or partial network ID. |
+| label | Filter by network with (or without, in the case of label!=[...] is used) the specified labels. |
+| name | Filter by network name (accepts `regex`). |
+| until | Filter by networks created before given timestamp. |
+
+
+The `driver` filter accepts values: `bridge`, `macvlan`, `ipvlan`.
+
+The `label` *filter* accepts two formats. One is the `label`=*key* or `label`=*key*=*value*, which shows images with the specified labels. The other format is the `label!`=*key* or `label!`=*key*=*value*, which shows images without the specified labels.
+
+The `until` *filter* can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time.
#### **--format**=*format*
diff --git a/docs/source/markdown/podman-network-rm.1.md b/docs/source/markdown/podman-network-rm.1.md
index 12102ba5a..c6e33c571 100644
--- a/docs/source/markdown/podman-network-rm.1.md
+++ b/docs/source/markdown/podman-network-rm.1.md
@@ -1,7 +1,7 @@
% podman-network-rm(1)
## NAME
-podman\-network\-rm - Remove one or more CNI networks
+podman\-network\-rm - Remove one or more networks
## SYNOPSIS
**podman network rm** [*options*] [*network...*]
diff --git a/docs/source/markdown/podman-network.1.md b/docs/source/markdown/podman-network.1.md
index 29ee70139..bc75cce3b 100644
--- a/docs/source/markdown/podman-network.1.md
+++ b/docs/source/markdown/podman-network.1.md
@@ -1,27 +1,37 @@
% podman-network(1)
## NAME
-podman\-network - Manage Podman CNI networks
+podman\-network - Manage Podman networks
## SYNOPSIS
**podman network** *subcommand*
## DESCRIPTION
-The network command manages CNI networks for Podman.
+The network command manages networks for Podman.
+
+Podman supports two network backends [Netavark](https://github.com/containers/netavark)
+and [CNI](https://www.cni.dev/). Support for netavark was added in Podman v4.0. To configure
+the network backend use the `network_backend`key under the `[Network]` in
+**[containers.conf(5)](https://github.com/containers/common/blob/master/docs/containers.conf.5.md)**.
+New systems should use netavark by default, to check what backed is used run
+`podman info --format {{.Host.NetworkBackend}}`.
+
+All network commands work for both backends but CNI and Netavark use different config files
+so networks have to be created again after a backend change.
## COMMANDS
-| Command | Man Page | Description |
-| ---------- | -------------------------------------------------------------- | ------------------------------------------------------------------- |
-| connect | [podman-network-connect(1)](podman-network-connect.1.md) | Connect a container to a network |
-| create | [podman-network-create(1)](podman-network-create.1.md) | Create a Podman CNI network |
-| disconnect | [podman-network-disconnect(1)](podman-network-disconnect.1.md) | Disconnect a container from a network |
-| exists | [podman-network-exists(1)](podman-network-exists.1.md) | Check if the given network exists |
-| inspect | [podman-network-inspect(1)](podman-network-inspect.1.md) | Displays the raw CNI network configuration for one or more networks |
-| ls | [podman-network-ls(1)](podman-network-ls.1.md) | Display a summary of CNI networks |
-| prune | [podman-network-prune(1)](podman-network-prune.1.md) | Remove all unused networks |
-| reload | [podman-network-reload(1)](podman-network-reload.1.md) | Reload network configuration for containers |
-| rm | [podman-network-rm(1)](podman-network-rm.1.md) | Remove one or more CNI networks |
+| Command | Man Page | Description |
+| ---------- | -------------------------------------------------------------- | --------------------------------------------------------------- |
+| connect | [podman-network-connect(1)](podman-network-connect.1.md) | Connect a container to a network |
+| create | [podman-network-create(1)](podman-network-create.1.md) | Create a Podman network |
+| disconnect | [podman-network-disconnect(1)](podman-network-disconnect.1.md) | Disconnect a container from a network |
+| exists | [podman-network-exists(1)](podman-network-exists.1.md) | Check if the given network exists |
+| inspect | [podman-network-inspect(1)](podman-network-inspect.1.md) | Displays the raw network configuration for one or more networks |
+| ls | [podman-network-ls(1)](podman-network-ls.1.md) | Display a summary of networks |
+| prune | [podman-network-prune(1)](podman-network-prune.1.md) | Remove all unused networks |
+| reload | [podman-network-reload(1)](podman-network-reload.1.md) | Reload network configuration for containers |
+| rm | [podman-network-rm(1)](podman-network-rm.1.md) | Remove one or more networks |
## SEE ALSO
-**[podman(1)](podman.1.md)**
+**[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-pod-ps.1.md b/docs/source/markdown/podman-pod-ps.1.md
index a0581df50..8a9c3f7cc 100644
--- a/docs/source/markdown/podman-pod-ps.1.md
+++ b/docs/source/markdown/podman-pod-ps.1.md
@@ -86,25 +86,35 @@ Default: created
#### **--filter**, **-f**=*filter*
-Filter output based on conditions given.
-Multiple filters can be given with multiple uses of the --filter flag.
-Filters with the same key work inclusive with the only exception being
-`label` which is exclusive. Filters with different keys always work exclusive.
-
-Valid filters are listed below:
-
-| **Filter** | **Description** |
-| ---------- | ------------------------------------------------------------------------------------- |
-| id | [ID] Pod's ID (accepts regex) |
-| name | [Name] Pod's name (accepts regex) |
-| label | [Key] or [Key=Value] Label assigned to a container |
-| until | Only list pods created before given timestamp |
-| status | Pod's status: `stopped`, `running`, `paused`, `exited`, `dead`, `created`, `degraded` |
-| network | [Network] name or full ID of network |
-| ctr-names | Container name within the pod (accepts regex) |
-| ctr-ids | Container ID within the pod (accepts regex) |
-| ctr-status | Container status within the pod |
-| ctr-number | Number of containers in the pod |
+Provide filter values.
+
+The *filters* argument format is of `key=value`. If there is more than one *filter*, then pass multiple OPTIONS: **--filter** *foo=bar* **--filter** *bif=baz*.
+
+Supported filters:
+
+| Filter | Description |
+| ---------- | -------------------------------------------------------------------------------------------------- |
+| *ctr-ids* | Filter by container ID within the pod. |
+| *ctr-names* | Filter by container name within the pod. |
+| *ctr-number*| Filter by number of containers in the pod. |
+| *ctr-status*| Filter by container status within the pod. |
+| *id* | Filter by pod ID. |
+| *label* | Filter by container with (or without, in the case of label!=[...] is used) the specified labels. |
+| *name* | Filter by pod name. |
+| *network* | Filter by network name or full ID of network. |
+| *status* | Filter by pod status. |
+| *until* | Filter by pods created before given timestamp. |
+
+The `ctr-ids`, `ctr-names`, `id`, `name` filters accept `regex` format.
+
+The `ctr-status` filter accepts values: `created`, `running`, `paused`, `stopped`, `exited`, `unknown`.
+
+The `label` *filter* accepts two formats. One is the `label`=*key* or `label`=*key*=*value*, which removes containers with the specified labels. The other format is the `label!`=*key* or `label!`=*key*=*value*, which removes containers without the specified labels.
+
+The `until` *filter* can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time.
+
+The `status` filter accepts values: `stopped`, `running`, `paused`, `exited`, `dead`, `created`, `degraded`.
+
#### **--help**, **-h**
diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md
index a77e1ecbd..b318001e4 100644
--- a/docs/source/markdown/podman.1.md
+++ b/docs/source/markdown/podman.1.md
@@ -300,7 +300,7 @@ the exit codes follow the `chroot` standard, see below:
| [podman-events(1)](podman-events.1.md) | Monitor Podman events |
| [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. |
| [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. |
-| [podman-generate(1)](podman-generate.1.md) | Generate structured data based on containers, pods or volumes. |
+| [podman-generate(1)](podman-generate.1.md) | Generate structured data based on containers, pods or volumes. |
| [podman-healthcheck(1)](podman-healthcheck.1.md) | Manage healthchecks for containers |
| [podman-history(1)](podman-history.1.md) | Show the history of an image. |
| [podman-image(1)](podman-image.1.md) | Manage images. |
@@ -317,9 +317,9 @@ the exit codes follow the `chroot` standard, see below:
| [podman-machine(1)](podman-machine.1.md) | Manage Podman's virtual machine |
| [podman-manifest(1)](podman-manifest.1.md) | Create and manipulate manifest lists and image indexes. |
| [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. |
-| [podman-network(1)](podman-network.1.md) | Manage Podman CNI networks. |
+| [podman-network(1)](podman-network.1.md) | Manage Podman networks. |
| [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. |
-| [podman-play(1)](podman-play.1.md) | Play containers, pods or volumes based on a structured input file. |
+| [podman-play(1)](podman-play.1.md) | Play containers, pods or volumes based on a structured input file. |
| [podman-pod(1)](podman-pod.1.md) | Management tool for groups of containers, called pods. |
| [podman-port(1)](podman-port.1.md) | List port mappings for a container. |
| [podman-ps(1)](podman-ps.1.md) | Prints out information about containers. |
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index 3799b463f..44364100e 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -192,6 +192,11 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf
}
// Reset the log path to point to the default
ctr.config.LogPath = ""
+ // Later in validate() the check is for nil. JSONDeepCopy sets it to an empty
+ // object. Resetting it to nil if it was nil before.
+ if config.StaticMAC == nil {
+ ctr.config.StaticMAC = nil
+ }
}
ctr.config.Spec = rSpec
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
index 250736579..ad662f32c 100644
--- a/pkg/api/handlers/libpod/manifests.go
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -401,7 +401,7 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) {
case len(report.Errors) > 0 && len(report.Images) > 0:
statusCode = http.StatusConflict
case len(report.Errors) > 0:
- statusCode = http.StatusInternalServerError
+ statusCode = http.StatusBadRequest
}
utils.WriteResponse(w, statusCode, report)
}
diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go
index baa1fe6fb..4466c938f 100644
--- a/pkg/api/server/register_networks.go
+++ b/pkg/api/server/register_networks.go
@@ -320,6 +320,8 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// $ref: "#/responses/NetworkCreateReport"
// 400:
// $ref: "#/responses/BadParamError"
+ // 409:
+ // $ref: "#/responses/ConflictError"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/networks/create"), s.APIHandler(libpod.CreateNetwork)).Methods(http.MethodPost)
diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go
index a363f2c6e..c508cb767 100644
--- a/pkg/bindings/images/build.go
+++ b/pkg/bindings/images/build.go
@@ -352,11 +352,13 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
}
c = tmpFile.Name()
}
+ c = filepath.Clean(c)
cfDir := filepath.Dir(c)
if absDir, err := filepath.EvalSymlinks(cfDir); err == nil {
name := filepath.ToSlash(strings.TrimPrefix(c, cfDir+string(filepath.Separator)))
c = filepath.Join(absDir, name)
}
+
containerfile, err := filepath.Abs(c)
if err != nil {
logrus.Errorf("Cannot find absolute path of %v: %v", c, err)
diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go
index 458cb913a..18798e615 100644
--- a/pkg/bindings/manifests/manifests.go
+++ b/pkg/bindings/manifests/manifests.go
@@ -2,10 +2,8 @@ package manifests
import (
"context"
- "errors"
- "fmt"
+ "io/ioutil"
"net/http"
- "net/url"
"strconv"
"strings"
@@ -14,8 +12,10 @@ import (
"github.com/containers/podman/v4/pkg/api/handlers"
"github.com/containers/podman/v4/pkg/bindings"
"github.com/containers/podman/v4/pkg/bindings/images"
- "github.com/containers/podman/v4/version"
+ "github.com/containers/podman/v4/pkg/domain/entities"
+ "github.com/containers/podman/v4/pkg/errorhandling"
jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
)
// Create creates a manifest for the given name. Optional images to be associated with
@@ -91,74 +91,26 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error)
options = new(AddOptions)
}
- if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) {
- optionsv4 := ModifyOptions{
- All: options.All,
- Annotations: options.Annotation,
- Arch: options.Arch,
- Features: options.Features,
- Images: options.Images,
- OS: options.OS,
- OSFeatures: nil,
- OSVersion: options.OSVersion,
- Variant: options.Variant,
- }
- optionsv4.WithOperation("update")
- return Modify(ctx, name, options.Images, &optionsv4)
- }
-
- // API Version < 4.0.0
- conn, err := bindings.GetClient(ctx)
- if err != nil {
- return "", err
- }
- opts, err := jsoniter.MarshalToString(options)
- if err != nil {
- return "", err
- }
- reader := strings.NewReader(opts)
-
- headers := make(http.Header)
- v := version.APIVersion[version.Libpod][version.MinimalAPI]
- headers.Add("API-Version",
- fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch))
- response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/manifests/%s/add", nil, headers, name)
- if err != nil {
- return "", err
+ optionsv4 := ModifyOptions{
+ All: options.All,
+ Annotations: options.Annotation,
+ Arch: options.Arch,
+ Features: options.Features,
+ Images: options.Images,
+ OS: options.OS,
+ OSFeatures: nil,
+ OSVersion: options.OSVersion,
+ Variant: options.Variant,
}
- defer response.Body.Close()
-
- var idr handlers.IDResponse
- return idr.ID, response.Process(&idr)
+ optionsv4.WithOperation("update")
+ return Modify(ctx, name, options.Images, &optionsv4)
}
// Remove deletes a manifest entry from a manifest list. Both name and the digest to be
// removed are mandatory inputs. The ID of the new manifest list is returned as a string.
func Remove(ctx context.Context, name, digest string, _ *RemoveOptions) (string, error) {
- if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) {
- optionsv4 := new(ModifyOptions).WithOperation("remove")
- return Modify(ctx, name, []string{digest}, optionsv4)
- }
-
- // API Version < 4.0.0
- conn, err := bindings.GetClient(ctx)
- if err != nil {
- return "", err
- }
-
- headers := http.Header{}
- headers.Add("API-Version", "3.4.0")
-
- params := url.Values{}
- params.Set("digest", digest)
- response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/manifests/%s", params, headers, name)
- if err != nil {
- return "", err
- }
- defer response.Body.Close()
-
- var idr handlers.IDResponse
- return idr.ID, response.Process(&idr)
+ optionsv4 := new(ModifyOptions).WithOperation("remove")
+ return Modify(ctx, name, []string{digest}, optionsv4)
}
// Push takes a manifest list and pushes to a destination. If the destination is not specified,
@@ -229,8 +181,35 @@ func Modify(ctx context.Context, name string, images []string, options *ModifyOp
}
defer response.Body.Close()
- var idr handlers.IDResponse
- return idr.ID, response.Process(&idr)
+ data, err := ioutil.ReadAll(response.Body)
+ if err != nil {
+ return "", errors.Wrap(err, "unable to process API response")
+ }
+
+ if response.IsSuccess() || response.IsRedirection() {
+ var report entities.ManifestModifyReport
+ if err = jsoniter.Unmarshal(data, &report); err != nil {
+ return "", errors.Wrap(err, "unable to decode API response")
+ }
+
+ err = errorhandling.JoinErrors(report.Errors)
+ if err != nil {
+ errModel := errorhandling.ErrorModel{
+ Because: (errors.Cause(err)).Error(),
+ Message: err.Error(),
+ ResponseCode: response.StatusCode,
+ }
+ return report.ID, &errModel
+ }
+ return report.ID, nil
+ }
+ errModel := errorhandling.ErrorModel{
+ ResponseCode: response.StatusCode,
+ }
+ if err = jsoniter.Unmarshal(data, &errModel); err != nil {
+ return "", errors.Wrap(err, "unable to decode API response")
+ }
+ return "", &errModel
}
// Annotate modifies the given manifest list using options and the optional list of images
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
index f51e5f404..b75588251 100644
--- a/pkg/bindings/test/common_test.go
+++ b/pkg/bindings/test/common_test.go
@@ -8,6 +8,7 @@ import (
"os/exec"
"path/filepath"
"strings"
+ "testing"
"time"
"github.com/containers/podman/v4/libpod/define"
@@ -151,7 +152,12 @@ func createTempDirInTempDir() (string, error) {
}
func (b *bindingTest) startAPIService() *gexec.Session {
- cmd := []string{"--log-level=debug", "system", "service", "--timeout=0", b.sock}
+ logLevel := "debug"
+ if testing.Verbose() {
+ logLevel = "trace"
+ }
+
+ cmd := []string{"--log-level=" + logLevel, "system", "service", "--timeout=0", b.sock}
session := b.runPodman(cmd)
sock := strings.TrimPrefix(b.sock, "unix://")
diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go
index 64becda43..895e1a29d 100644
--- a/pkg/bindings/test/manifests_test.go
+++ b/pkg/bindings/test/manifests_test.go
@@ -87,7 +87,6 @@ var _ = Describe("podman manifest", func() {
list, err := manifests.Inspect(bt.conn, id, nil)
Expect(err).ToNot(HaveOccurred())
-
Expect(len(list.Manifests)).To(BeNumerically("==", 1))
// add bogus name to existing list should fail
@@ -96,7 +95,7 @@ var _ = Describe("podman manifest", func() {
Expect(err).To(HaveOccurred())
code, _ = bindings.CheckResponseCode(err)
- Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ Expect(code).To(BeNumerically("==", http.StatusBadRequest))
})
It("remove digest", func() {
@@ -129,7 +128,6 @@ var _ = Describe("podman manifest", func() {
// removal on good manifest with good digest should work
data, err = manifests.Inspect(bt.conn, id, nil)
Expect(err).ToNot(HaveOccurred())
-
Expect(data.Manifests).Should(BeEmpty())
})
diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go
index 1ebd6a455..270b5b6c4 100644
--- a/pkg/checkpoint/checkpoint_restore.go
+++ b/pkg/checkpoint/checkpoint_restore.go
@@ -140,6 +140,13 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
return nil, errors.Errorf("pod %s does not share the network namespace", ctrConfig.Pod)
}
ctrConfig.NetNsCtr = infraContainer.ID()
+ for net, opts := range ctrConfig.Networks {
+ opts.StaticIPs = nil
+ opts.StaticMAC = nil
+ ctrConfig.Networks[net] = opts
+ }
+ ctrConfig.StaticIP = nil
+ ctrConfig.StaticMAC = nil
}
if ctrConfig.PIDNsCtr != "" {
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index 97237f5e5..efb1eda15 100644
--- a/pkg/machine/config.go
+++ b/pkg/machine/config.go
@@ -27,6 +27,7 @@ type InitOptions struct {
URI url.URL
Username string
ReExec bool
+ Rootful bool
}
type QemuMachineStatus = string
@@ -35,7 +36,8 @@ const (
// Running indicates the qemu vm is running
Running QemuMachineStatus = "running"
// Stopped indicates the vm has stopped
- Stopped QemuMachineStatus = "stopped"
+ Stopped QemuMachineStatus = "stopped"
+ DefaultMachineName string = "podman-machine-default"
)
type Provider interface {
@@ -89,6 +91,10 @@ type ListResponse struct {
IdentityPath string
}
+type SetOptions struct {
+ Rootful bool
+}
+
type SSHOptions struct {
Username string
Args []string
@@ -107,6 +113,7 @@ type RemoveOptions struct {
type VM interface {
Init(opts InitOptions) (bool, error)
Remove(name string, opts RemoveOptions) (string, func() error, error)
+ Set(name string, opts SetOptions) error
SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error
Stop(name string, opts StopOptions) error
diff --git a/pkg/machine/connection.go b/pkg/machine/connection.go
index d28ffcef1..841b2afa6 100644
--- a/pkg/machine/connection.go
+++ b/pkg/machine/connection.go
@@ -39,6 +39,31 @@ func AddConnection(uri fmt.Stringer, name, identity string, isDefault bool) erro
return cfg.Write()
}
+func AnyConnectionDefault(name ...string) (bool, error) {
+ cfg, err := config.ReadCustomConfig()
+ if err != nil {
+ return false, err
+ }
+ for _, n := range name {
+ if n == cfg.Engine.ActiveService {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+func ChangeDefault(name string) error {
+ cfg, err := config.ReadCustomConfig()
+ if err != nil {
+ return err
+ }
+
+ cfg.Engine.ActiveService = name
+
+ return cfg.Write()
+}
+
func RemoveConnection(name string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index 206c9144f..47b1836f0 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -145,7 +145,42 @@ ExecStartPost=/bin/touch /var/lib/%N.stamp
[Install]
WantedBy=default.target
- `
+`
+ // This service gets environment variables that are provided
+ // through qemu fw_cfg and then sets them into systemd/system.conf.d,
+ // profile.d and environment.d files
+ //
+ // Currently, it is used for propagating
+ // proxy settings e.g. HTTP_PROXY and others, on a start avoiding
+ // a need of re-creating/re-initiating a VM
+ envset := `[Unit]
+Description=Environment setter from QEMU FW_CFG
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+Environment=FWCFGRAW=/sys/firmware/qemu_fw_cfg/by_name/opt/com.coreos/environment/raw
+Environment=SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf
+Environment=ENVD_CONF=/etc/environment.d/default-env.conf
+Environment=PROFILE_CONF=/etc/profile.d/default-env.sh
+ExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} &&\
+ echo "[Manager]\n#Got from QEMU FW_CFG\nDefaultEnvironment=$(/usr/bin/base64 -d ${FWCFGRAW} | sed -e "s+|+ +g")\n" > ${SYSTEMD_CONF} ||\
+ echo "[Manager]\n#Got nothing from QEMU FW_CFG\n#DefaultEnvironment=\n" > ${SYSTEMD_CONF}'
+ExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} && (\
+ echo "#Got from QEMU FW_CFG"> ${ENVD_CONF};\
+ IFS="|";\
+ for iprxy in $(/usr/bin/base64 -d ${FWCFGRAW}); do\
+ echo "$iprxy" >> ${ENVD_CONF}; done ) || \
+ echo "#Got nothing from QEMU FW_CFG"> ${ENVD_CONF}'
+ExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} && (\
+ echo "#Got from QEMU FW_CFG"> ${PROFILE_CONF};\
+ IFS="|";\
+ for iprxy in $(/usr/bin/base64 -d ${FWCFGRAW}); do\
+ echo "export $iprxy" >> ${PROFILE_CONF}; done ) || \
+ echo "#Got nothing from QEMU FW_CFG"> ${PROFILE_CONF}'
+ExecStartPost=/usr/bin/systemctl daemon-reload
+[Install]
+WantedBy=sysinit.target
+`
_ = ready
ignSystemd := Systemd{
Units: []Unit{
@@ -173,6 +208,11 @@ WantedBy=default.target
Name: "remove-moby.service",
Contents: &deMoby,
},
+ {
+ Enabled: boolToPtr(true),
+ Name: "envset-fwcfg.service",
+ Contents: &envset,
+ },
}}
ignConfig := Config{
Ignition: ignVersion,
@@ -226,6 +266,25 @@ func getDirs(usrName string) []Directory {
DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(0755)},
})
+ // The directory is used by envset-fwcfg.service
+ // for propagating environment variables that got
+ // from a host
+ dirs = append(dirs, Directory{
+ Node: Node{
+ Group: getNodeGrp("root"),
+ Path: "/etc/systemd/system.conf.d",
+ User: getNodeUsr("root"),
+ },
+ DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(0755)},
+ }, Directory{
+ Node: Node{
+ Group: getNodeGrp("root"),
+ Path: "/etc/environment.d",
+ User: getNodeUsr("root"),
+ },
+ DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(0755)},
+ })
+
return dirs
}
@@ -363,24 +422,6 @@ Delegate=memory pids cpu io
},
})
- setProxyOpts := getProxyVariables()
- if setProxyOpts != "" {
- files = append(files, File{
- Node: Node{
- Group: getNodeGrp("root"),
- Path: "/etc/profile.d/proxy-opts.sh",
- User: getNodeUsr("root"),
- },
- FileEmbedded1: FileEmbedded1{
- Append: nil,
- Contents: Resource{
- Source: encodeDataURLPtr(setProxyOpts),
- },
- Mode: intToPtr(0644),
- },
- })
- }
-
setDockerHost := `export DOCKER_HOST="unix://$(podman info -f "{{.Host.RemoteSocket.Path}}")"
`
@@ -506,11 +547,11 @@ func prepareCertFile(path string, name string) (File, error) {
return file, nil
}
-func getProxyVariables() string {
- proxyOpts := ""
+func GetProxyVariables() map[string]string {
+ proxyOpts := make(map[string]string)
for _, variable := range config.ProxyEnv {
if value, ok := os.LookupEnv(variable); ok {
- proxyOpts += fmt.Sprintf("\n export %s=%s", variable, value)
+ proxyOpts[variable] = value
}
}
return proxyOpts
diff --git a/pkg/machine/qemu/claim_darwin.go b/pkg/machine/qemu/claim_darwin.go
new file mode 100644
index 000000000..66aed9ad8
--- /dev/null
+++ b/pkg/machine/qemu/claim_darwin.go
@@ -0,0 +1,63 @@
+package qemu
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/user"
+ "path/filepath"
+ "time"
+)
+
+func dockerClaimSupported() bool {
+ return true
+}
+
+func dockerClaimHelperInstalled() bool {
+ u, err := user.Current()
+ if err != nil {
+ return false
+ }
+
+ labelName := fmt.Sprintf("com.github.containers.podman.helper-%s", u.Username)
+ fileName := filepath.Join("/Library", "LaunchDaemons", labelName+".plist")
+ info, err := os.Stat(fileName)
+ return err == nil && info.Mode().IsRegular()
+}
+
+func claimDockerSock() bool {
+ u, err := user.Current()
+ if err != nil {
+ return false
+ }
+
+ helperSock := fmt.Sprintf("/var/run/podman-helper-%s.socket", u.Username)
+ con, err := net.DialTimeout("unix", helperSock, time.Second*5)
+ if err != nil {
+ return false
+ }
+ _ = con.SetWriteDeadline(time.Now().Add(time.Second * 5))
+ _, err = fmt.Fprintln(con, "GO")
+ if err != nil {
+ return false
+ }
+ _ = con.SetReadDeadline(time.Now().Add(time.Second * 5))
+ read, err := ioutil.ReadAll(con)
+
+ return err == nil && string(read) == "OK"
+}
+
+func findClaimHelper() string {
+ exe, err := os.Executable()
+ if err != nil {
+ return ""
+ }
+
+ exe, err = filepath.EvalSymlinks(exe)
+ if err != nil {
+ return ""
+ }
+
+ return filepath.Join(filepath.Dir(exe), "podman-mac-helper")
+}
diff --git a/pkg/machine/qemu/claim_unsupported.go b/pkg/machine/qemu/claim_unsupported.go
new file mode 100644
index 000000000..e0b3dd3d3
--- /dev/null
+++ b/pkg/machine/qemu/claim_unsupported.go
@@ -0,0 +1,20 @@
+//go:build !darwin && !windows
+// +build !darwin,!windows
+
+package qemu
+
+func dockerClaimHelperInstalled() bool {
+ return false
+}
+
+func claimDockerSock() bool {
+ return false
+}
+
+func dockerClaimSupported() bool {
+ return false
+}
+
+func findClaimHelper() string {
+ return ""
+}
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index e76509bb1..c619b7dd4 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -33,6 +33,8 @@ type MachineVM struct {
QMPMonitor Monitor
// RemoteUsername of the vm user
RemoteUsername string
+ // Whether this machine should run in a rootful or rootless manner
+ Rootful bool
}
type Mount struct {
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index eb7b35ece..9beec2173 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -1,13 +1,19 @@
+//go:build (amd64 && !windows) || (arm64 && !windows)
// +build amd64,!windows arm64,!windows
package qemu
import (
"bufio"
+ "context"
+ "encoding/base64"
"encoding/json"
"fmt"
+ "io/fs"
"io/ioutil"
"net"
+ "net/http"
+ "net/url"
"os"
"os/exec"
"path/filepath"
@@ -37,8 +43,21 @@ func GetQemuProvider() machine.Provider {
}
const (
- VolumeTypeVirtfs = "virtfs"
- MountType9p = "9p"
+ VolumeTypeVirtfs = "virtfs"
+ MountType9p = "9p"
+ dockerSock = "/var/run/docker.sock"
+ dockerConnectTimeout = 5 * time.Second
+ apiUpTimeout = 20 * time.Second
+)
+
+type apiForwardingState int
+
+const (
+ noForwarding apiForwardingState = iota
+ claimUnsupported
+ notInstalled
+ machineLocal
+ dockerGlobal
)
// NewMachine initializes an instance of a virtual machine based on the qemu
@@ -123,6 +142,20 @@ func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
return nil, err
}
err = json.Unmarshal(b, vm)
+
+ // It is here for providing the ability to propagate
+ // proxy settings (e.g. HTTP_PROXY and others) on a start
+ // and avoid a need of re-creating/re-initiating a VM
+ if proxyOpts := machine.GetProxyVariables(); len(proxyOpts) > 0 {
+ proxyStr := "name=opt/com.coreos/environment,string="
+ var proxies string
+ for k, v := range proxyOpts {
+ proxies = fmt.Sprintf("%s%s=\"%s\"|", proxies, k, v)
+ }
+ proxyStr = fmt.Sprintf("%s%s", proxyStr, base64.StdEncoding.EncodeToString([]byte(proxies)))
+ vm.CmdLine = append(vm.CmdLine, "-fw_cfg", proxyStr)
+ }
+
logrus.Debug(vm.CmdLine)
return vm, err
}
@@ -134,14 +167,8 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
key string
)
sshDir := filepath.Join(homedir.Get(), ".ssh")
- // GetConfDir creates the directory so no need to check for
- // its existence
- vmConfigDir, err := machine.GetConfDir(vmtype)
- if err != nil {
- return false, err
- }
- jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
v.IdentityPath = filepath.Join(sshDir, v.Name)
+ v.Rootful = opts.Rootful
switch opts.ImagePath {
case "testing", "next", "stable", "":
@@ -224,29 +251,33 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
// This kind of stinks but no other way around this r/n
if len(opts.IgnitionPath) < 1 {
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername)
- if err := machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
- return false, err
+ uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
+ identity := filepath.Join(sshDir, v.Name)
+
+ uris := []url.URL{uri, uriRoot}
+ names := []string{v.Name, v.Name + "-root"}
+
+ // The first connection defined when connections is empty will become the default
+ // regardless of IsDefault, so order according to rootful
+ if opts.Rootful {
+ uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0]
}
- uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
- if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
- return false, err
+ for i := 0; i < 2; i++ {
+ if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil {
+ return false, err
+ }
}
} else {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
}
// Write the JSON file
- b, err := json.MarshalIndent(v, "", " ")
- if err != nil {
- return false, err
- }
- if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
- return false, err
- }
+ v.writeConfig()
// User has provided ignition file so keygen
// will be skipped.
if len(opts.IgnitionPath) < 1 {
+ var err error
key, err = machine.CreateSSHKeys(v.IdentityPath)
if err != nil {
return false, err
@@ -293,6 +324,30 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
return err == nil, err
}
+func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
+ if v.Rootful == opts.Rootful {
+ return nil
+ }
+
+ changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
+ if err != nil {
+ return err
+ }
+
+ if changeCon {
+ newDefault := v.Name
+ if opts.Rootful {
+ newDefault += "-root"
+ }
+ if err := machine.ChangeDefault(newDefault); err != nil {
+ return err
+ }
+ }
+
+ v.Rootful = opts.Rootful
+ return v.writeConfig()
+}
+
// Start executes the qemu command line and forks it
func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
var (
@@ -302,7 +357,8 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
wait time.Duration = time.Millisecond * 500
)
- if err := v.startHostNetworking(); err != nil {
+ forwardSock, forwardState, err := v.startHostNetworking()
+ if err != nil {
return errors.Errorf("unable to start host networking: %q", err)
}
@@ -423,6 +479,9 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return fmt.Errorf("unknown mount type: %s", mount.Type)
}
}
+
+ waitAPIAndPrintInfo(forwardState, forwardSock, v.Rootful, v.Name)
+
return nil
}
@@ -853,19 +912,19 @@ func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
// startHostNetworking runs a binary on the host system that allows users
// to setup port forwarding to the podman virtual machine
-func (v *MachineVM) startHostNetworking() error {
+func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
cfg, err := config.Default()
if err != nil {
- return err
+ return "", noForwarding, err
}
binary, err := cfg.FindHelperBinary(machine.ForwarderBinaryName, false)
if err != nil {
- return err
+ return "", noForwarding, err
}
qemuSocket, pidFile, err := v.getSocketandPid()
if err != nil {
- return err
+ return "", noForwarding, err
}
attr := new(os.ProcAttr)
// Pass on stdin, stdout, stderr
@@ -875,12 +934,82 @@ func (v *MachineVM) startHostNetworking() error {
cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", qemuSocket), "-pid-file", pidFile}...)
// Add the ssh port
cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...)
+
+ cmd, forwardSock, state := v.setupAPIForwarding(cmd)
+
if logrus.GetLevel() == logrus.DebugLevel {
cmd = append(cmd, "--debug")
fmt.Println(cmd)
}
_, err = os.StartProcess(cmd[0], cmd, attr)
- return err
+ return forwardSock, state, err
+}
+
+func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) {
+ socket, err := v.getForwardSocketPath()
+
+ if err != nil {
+ return cmd, "", noForwarding
+ }
+
+ destSock := "/run/user/1000/podman/podman.sock"
+ forwardUser := "core"
+
+ if v.Rootful {
+ destSock = "/run/podman/podman.sock"
+ forwardUser = "root"
+ }
+
+ cmd = append(cmd, []string{"-forward-sock", socket}...)
+ cmd = append(cmd, []string{"-forward-dest", destSock}...)
+ cmd = append(cmd, []string{"-forward-user", forwardUser}...)
+ cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...)
+ link := filepath.Join(filepath.Dir(filepath.Dir(socket)), "podman.sock")
+
+ // The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket)
+ // This allows the helper to only have to maintain one constant target to the user, which can be
+ // repositioned without updating docker.sock.
+ if !dockerClaimSupported() {
+ return cmd, socket, claimUnsupported
+ }
+
+ if !dockerClaimHelperInstalled() {
+ return cmd, socket, notInstalled
+ }
+
+ if !alreadyLinked(socket, link) {
+ if checkSockInUse(link) {
+ return cmd, socket, machineLocal
+ }
+
+ _ = os.Remove(link)
+ if err = os.Symlink(socket, link); err != nil {
+ logrus.Warnf("could not create user global API forwarding link: %s", err.Error())
+ return cmd, socket, machineLocal
+ }
+ }
+
+ if !alreadyLinked(link, dockerSock) {
+ if checkSockInUse(dockerSock) {
+ return cmd, socket, machineLocal
+ }
+
+ if !claimDockerSock() {
+ logrus.Warn("podman helper is installed, but was not able to claim the global docker sock")
+ return cmd, socket, machineLocal
+ }
+ }
+
+ return cmd, dockerSock, dockerGlobal
+}
+
+func (v *MachineVM) getForwardSocketPath() (string, error) {
+ path, err := machine.GetDataDir(v.Name)
+ if err != nil {
+ logrus.Errorf("Error resolving data dir: %s", err.Error())
+ return "", nil
+ }
+ return filepath.Join(path, "podman.sock"), nil
}
func (v *MachineVM) getSocketandPid() (string, string, error) {
@@ -896,3 +1025,103 @@ func (v *MachineVM) getSocketandPid() (string, string, error) {
qemuSocket := filepath.Join(socketDir, fmt.Sprintf("qemu_%s.sock", v.Name))
return qemuSocket, pidFile, nil
}
+
+func checkSockInUse(sock string) bool {
+ if info, err := os.Stat(sock); err == nil && info.Mode()&fs.ModeSocket == fs.ModeSocket {
+ _, err = net.DialTimeout("unix", dockerSock, dockerConnectTimeout)
+ return err == nil
+ }
+
+ return false
+}
+
+func alreadyLinked(target string, link string) bool {
+ read, err := os.Readlink(link)
+ return err == nil && read == target
+}
+
+func waitAndPingAPI(sock string) {
+ client := http.Client{
+ Transport: &http.Transport{
+ DialContext: func(context.Context, string, string) (net.Conn, error) {
+ con, err := net.DialTimeout("unix", sock, apiUpTimeout)
+ if err == nil {
+ con.SetDeadline(time.Now().Add(apiUpTimeout))
+ }
+ return con, err
+ },
+ },
+ }
+
+ resp, err := client.Get("http://host/_ping")
+ if err == nil {
+ defer resp.Body.Close()
+ }
+ if err != nil || resp.StatusCode != 200 {
+ logrus.Warn("API socket failed ping test")
+ }
+}
+
+func waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string, rootFul bool, name string) {
+ if forwardState != noForwarding {
+ waitAndPingAPI(forwardSock)
+ if !rootFul {
+ fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
+ fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
+ fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
+
+ suffix := ""
+ if name != machine.DefaultMachineName {
+ suffix = " " + name
+ }
+ fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
+ }
+
+ fmt.Printf("API forwarding listening on: %s\n", forwardSock)
+ if forwardState == dockerGlobal {
+ fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n")
+ } else {
+ stillString := "still "
+ switch forwardState {
+ case notInstalled:
+ fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n")
+ fmt.Printf("address can't be used by podman. ")
+ if helper := findClaimHelper(); len(helper) > 0 {
+ fmt.Printf("If you would like to install it run the\nfollowing command:\n")
+ fmt.Printf("\n\tsudo %s install\n\n", helper)
+ }
+ case machineLocal:
+ fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n")
+ case claimUnsupported:
+ fallthrough
+ default:
+ stillString = ""
+ }
+
+ fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString)
+ fmt.Printf("following command in your terminal session:\n")
+ fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock)
+ }
+ }
+}
+
+func (v *MachineVM) writeConfig() error {
+ // GetConfDir creates the directory so no need to check for
+ // its existence
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return err
+ }
+
+ jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
+ // Write the JSON file
+ b, err := json.MarshalIndent(v, "", " ")
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
index 3edf3ddf6..5b0c757f0 100644
--- a/pkg/machine/wsl/machine.go
+++ b/pkg/machine/wsl/machine.go
@@ -1,3 +1,4 @@
+//go:build windows
// +build windows
package wsl
@@ -8,6 +9,7 @@ import (
"fmt"
"io"
"io/ioutil"
+ "net/url"
"os"
"os/exec"
"path/filepath"
@@ -35,9 +37,6 @@ const (
ErrorSuccessRebootRequired = 3010
)
-// Usermode networking avoids potential nftables compatibility issues between the distro
-// and the WSL Kernel. Additionally it avoids fw rule conflicts between distros, since
-// all instances run under the same Kernel at runtime
const containersConf = `[containers]
[engine]
@@ -162,6 +161,8 @@ type MachineVM struct {
Port int
// RemoteUsername of the vm user
RemoteUsername string
+ // Whether this machine should run in a rootful or rootless manner
+ Rootful bool
}
type ExitCodeError struct {
@@ -227,12 +228,13 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
homeDir := homedir.Get()
sshDir := filepath.Join(homeDir, ".ssh")
v.IdentityPath = filepath.Join(sshDir, v.Name)
+ v.Rootful = opts.Rootful
if err := downloadDistro(v, opts); err != nil {
return false, err
}
- if err := writeJSON(v); err != nil {
+ if err := v.writeConfig(); err != nil {
return false, err
}
@@ -282,7 +284,7 @@ func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
return machine.DownloadImage(dd)
}
-func writeJSON(v *MachineVM) error {
+func (v *MachineVM) writeConfig() error {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return err
@@ -302,14 +304,26 @@ func writeJSON(v *MachineVM) error {
}
func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) error {
+ uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername)
uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
- if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
- return err
+ identity := filepath.Join(sshDir, v.Name)
+
+ uris := []url.URL{uri, uriRoot}
+ names := []string{v.Name, v.Name + "-root"}
+
+ // The first connection defined when connections is empty will become the default
+ // regardless of IsDefault, so order according to rootful
+ if opts.Rootful {
+ uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0]
+ }
+
+ for i := 0; i < 2; i++ {
+ if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil {
+ return err
+ }
}
- user := opts.Username
- uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", withUser("/run/[USER]/1000/podman/podman.sock", user), strconv.Itoa(v.Port), v.RemoteUsername)
- return machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault)
+ return nil
}
func provisionWSLDist(v *MachineVM) (string, error) {
@@ -335,6 +349,16 @@ func provisionWSLDist(v *MachineVM) (string, error) {
return "", errors.Wrap(err, "package upgrade on guest OS failed")
}
+ fmt.Println("Enabling Copr")
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install", "-y", "'dnf-command(copr)'"); err != nil {
+ return "", errors.Wrap(err, "enabling copr failed")
+ }
+
+ fmt.Println("Enabling podman4 repo")
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "-y", "copr", "enable", "rhcontainerbot/podman4"); err != nil {
+ return "", errors.Wrap(err, "enabling copr failed")
+ }
+
if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install",
"podman", "podman-docker", "openssh-server", "procps-ng", "-y"); err != nil {
return "", errors.Wrap(err, "package installation on guest OS failed")
@@ -704,6 +728,30 @@ func pipeCmdPassThrough(name string, input string, arg ...string) error {
return cmd.Run()
}
+func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
+ if v.Rootful == opts.Rootful {
+ return nil
+ }
+
+ changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
+ if err != nil {
+ return err
+ }
+
+ if changeCon {
+ newDefault := v.Name
+ if opts.Rootful {
+ newDefault += "-root"
+ }
+ if err := machine.ChangeDefault(newDefault); err != nil {
+ return err
+ }
+ }
+
+ v.Rootful = opts.Rootful
+ return v.writeConfig()
+}
+
func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
if v.isRunning() {
return errors.Errorf("%q is already running", name)
@@ -716,6 +764,18 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return errors.Wrap(err, "WSL bootstrap script failed")
}
+ if !v.Rootful {
+ fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
+ fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
+ fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
+
+ suffix := ""
+ if name != machine.DefaultMachineName {
+ suffix = " " + name
+ }
+ fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
+ }
+
globalName, pipeName, err := launchWinProxy(v)
if err != nil {
fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.")
diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at
index 06db62785..e4348a9a7 100644
--- a/test/apiv2/01-basic.at
+++ b/test/apiv2/01-basic.at
@@ -19,7 +19,7 @@ for i in /version version; do
t GET $i 200 \
.Components[0].Name="Podman Engine" \
.Components[0].Details.APIVersion~4[0-9.-]\\+ \
- .Components[0].Details.MinAPIVersion=3.3.1 \
+ .Components[0].Details.MinAPIVersion=4.0.0 \
.Components[0].Details.Os=linux \
.ApiVersion=1.40 \
.MinAPIVersion=1.24 \
diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go
index 5f1e4b1d1..5abc672e9 100644
--- a/test/e2e/checkpoint_test.go
+++ b/test/e2e/checkpoint_test.go
@@ -1081,10 +1081,6 @@ var _ = Describe("Podman checkpoint", func() {
})
namespaceCombination := []string{
- "cgroup,ipc,net,uts,pid",
- "cgroup,ipc,net,uts",
- "cgroup,ipc,net",
- "cgroup,ipc",
"ipc,net,uts,pid",
"ipc,net,uts",
"ipc,net",
diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go
index f843a8984..b1cd76d27 100644
--- a/test/e2e/common_test.go
+++ b/test/e2e/common_test.go
@@ -771,15 +771,15 @@ func SkipIfNotActive(unit string, reason string) {
}
}
-func SkipIfNetavark(p *PodmanTestIntegration) {
- if p.NetworkBackend == Netavark {
- Skip("This test is not compatible with the netavark network backend")
+func SkipIfCNI(p *PodmanTestIntegration) {
+ if p.NetworkBackend == CNI {
+ Skip("this test is not compatible with the CNI network backend")
}
}
-func SkipUntilAardvark(p *PodmanTestIntegration) {
+func SkipIfNetavark(p *PodmanTestIntegration) {
if p.NetworkBackend == Netavark {
- Skip("Re-enable when aardvark is functional")
+ Skip("This test is not compatible with the netavark network backend")
}
}
@@ -1038,3 +1038,7 @@ func ncz(port int) bool {
}
return false
}
+
+func createNetworkName(name string) string {
+ return name + stringid.GenerateNonCryptoID()[:10]
+}
diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go
index 6a534c9c8..d34c911ad 100644
--- a/test/e2e/images_test.go
+++ b/test/e2e/images_test.go
@@ -186,25 +186,21 @@ WORKDIR /test
Expect(result.OutputToString()).To(Equal("/test"))
})
- It("podman images filter since image", func() {
+ It("podman images filter since/after image", func() {
dockerfile := `FROM scratch
`
podmanTest.BuildImage(dockerfile, "foobar.com/one:latest", "false")
podmanTest.BuildImage(dockerfile, "foobar.com/two:latest", "false")
podmanTest.BuildImage(dockerfile, "foobar.com/three:latest", "false")
+
+ // `since` filter
result := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "since=foobar.com/one:latest"})
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
Expect(result.OutputToStringArray()).To(HaveLen(2))
- })
- It("podman image list filter after image", func() {
- dockerfile := `FROM scratch
-`
- podmanTest.BuildImage(dockerfile, "foobar.com/one:latest", "false")
- podmanTest.BuildImage(dockerfile, "foobar.com/two:latest", "false")
- podmanTest.BuildImage(dockerfile, "foobar.com/three:latest", "false")
- result := podmanTest.Podman([]string{"image", "list", "-q", "-f", "after=foobar.com/one:latest"})
+ // `after` filter
+ result = podmanTest.Podman([]string{"image", "list", "-q", "-f", "after=foobar.com/one:latest"})
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
Expect(result.OutputToStringArray()).Should(HaveLen(2), "list filter output: %q", result.OutputToString())
diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go
index 7589adaab..395759ee6 100644
--- a/test/e2e/network_create_test.go
+++ b/test/e2e/network_create_test.go
@@ -330,8 +330,8 @@ var _ = Describe("Podman network create", func() {
Expect(nc).To(ExitWithError())
})
- It("podman network create with internal should not have dnsname", func() {
- SkipUntilAardvark(podmanTest)
+ It("podman CNI network create with internal should not have dnsname", func() {
+ SkipIfNetavark(podmanTest)
net := "internal-test" + stringid.GenerateNonCryptoID()
nc := podmanTest.Podman([]string{"network", "create", "--internal", net})
nc.WaitWithDefaultTimeout()
@@ -348,6 +348,24 @@ var _ = Describe("Podman network create", func() {
Expect(nc.OutputToString()).ToNot(ContainSubstring("dnsname"))
})
+ It("podman Netavark network create with internal should have dnsname", func() {
+ SkipIfCNI(podmanTest)
+ net := "internal-test" + stringid.GenerateNonCryptoID()
+ nc := podmanTest.Podman([]string{"network", "create", "--internal", net})
+ nc.WaitWithDefaultTimeout()
+ defer podmanTest.removeNetwork(net)
+ Expect(nc).Should(Exit(0))
+ // Not performing this check on remote tests because it is a logrus error which does
+ // not come back via stderr on the remote client.
+ if !IsRemote() {
+ Expect(nc.ErrorToString()).To(BeEmpty())
+ }
+ nc = podmanTest.Podman([]string{"network", "inspect", net})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc).Should(Exit(0))
+ Expect(nc.OutputToString()).To(ContainSubstring(`"dns_enabled": true`))
+ })
+
It("podman network create with invalid name", func() {
for _, name := range []string{"none", "host", "bridge", "private", "slirp4netns", "container", "ns"} {
nc := podmanTest.Podman([]string{"network", "create", name})
diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go
index bd30a1f5d..89a9005f5 100644
--- a/test/e2e/network_test.go
+++ b/test/e2e/network_test.go
@@ -466,10 +466,61 @@ var _ = Describe("Podman network", func() {
Expect(lines[1]).To(Equal(netName2))
})
- It("podman network with multiple aliases", func() {
- SkipUntilAardvark(podmanTest)
+ It("podman CNI network with multiple aliases", func() {
+ SkipIfNetavark(podmanTest)
+ var worked bool
+ netName := createNetworkName("aliasTest")
+ session := podmanTest.Podman([]string{"network", "create", netName})
+ session.WaitWithDefaultTimeout()
+ defer podmanTest.removeNetwork(netName)
+ Expect(session).Should(Exit(0))
+
+ interval := time.Duration(250 * time.Millisecond)
+ for i := 0; i < 6; i++ {
+ n := podmanTest.Podman([]string{"network", "exists", netName})
+ n.WaitWithDefaultTimeout()
+ worked = n.ExitCode() == 0
+ if worked {
+ break
+ }
+ time.Sleep(interval)
+ interval *= 2
+ }
+
+ top := podmanTest.Podman([]string{"run", "-dt", "--name=web", "--network=" + netName, "--network-alias=web1", "--network-alias=web2", nginx})
+ top.WaitWithDefaultTimeout()
+ Expect(top).Should(Exit(0))
+ interval = time.Duration(250 * time.Millisecond)
+ // Wait for the nginx service to be running
+ for i := 0; i < 6; i++ {
+ // Test curl against the container's name
+ c1 := podmanTest.Podman([]string{"run", "--dns-search", "dns.podman", "--network=" + netName, nginx, "curl", "web"})
+ c1.WaitWithDefaultTimeout()
+ worked = c1.ExitCode() == 0
+ if worked {
+ break
+ }
+ time.Sleep(interval)
+ interval *= 2
+ }
+ Expect(worked).To(BeTrue())
+
+ // Nginx is now running so no need to do a loop
+ // Test against the first alias
+ c2 := podmanTest.Podman([]string{"run", "--dns-search", "dns.podman", "--network=" + netName, nginx, "curl", "web1"})
+ c2.WaitWithDefaultTimeout()
+ Expect(c2).Should(Exit(0))
+
+ // Test against the second alias
+ c3 := podmanTest.Podman([]string{"run", "--dns-search", "dns.podman", "--network=" + netName, nginx, "curl", "web2"})
+ c3.WaitWithDefaultTimeout()
+ Expect(c3).Should(Exit(0))
+ })
+
+ It("podman Netavark network with multiple aliases", func() {
+ SkipIfCNI(podmanTest)
var worked bool
- netName := "aliasTest" + stringid.GenerateNonCryptoID()
+ netName := createNetworkName("aliasTest")
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
defer podmanTest.removeNetwork(netName)
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index 4c056df10..aa1887f84 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -715,8 +715,8 @@ EXPOSE 2004-2005/tcp`, ALPINE)
Expect(run.OutputToString()).To(ContainSubstring(ipAddr))
})
- It("podman cni network works across user ns", func() {
- SkipUntilAardvark(podmanTest)
+ It("podman CNI network works across user ns", func() {
+ SkipIfNetavark(podmanTest)
netName := stringid.GenerateNonCryptoID()
create := podmanTest.Podman([]string{"network", "create", netName})
create.WaitWithDefaultTimeout()
@@ -740,6 +740,31 @@ EXPOSE 2004-2005/tcp`, ALPINE)
Expect(log.OutputToString()).To(Equal("podman"))
})
+ It("podman Netavark network works across user ns", func() {
+ SkipIfCNI(podmanTest)
+ netName := createNetworkName("")
+ create := podmanTest.Podman([]string{"network", "create", netName})
+ create.WaitWithDefaultTimeout()
+ Expect(create).Should(Exit(0))
+ defer podmanTest.removeNetwork(netName)
+
+ name := "nc-server"
+ run := podmanTest.Podman([]string{"run", "--log-driver", "k8s-file", "-d", "--name", name, "--net", netName, ALPINE, "nc", "-l", "-p", "9480"})
+ run.WaitWithDefaultTimeout()
+ Expect(run).Should(Exit(0))
+
+ // NOTE: we force the k8s-file log driver to make sure the
+ // tests are passing inside a container.
+ run = podmanTest.Podman([]string{"run", "--log-driver", "k8s-file", "--rm", "--net", netName, "--uidmap", "0:1:4096", ALPINE, "sh", "-c", fmt.Sprintf("echo podman | nc -w 1 %s.dns.podman 9480", name)})
+ run.WaitWithDefaultTimeout()
+ Expect(run).Should(Exit(0))
+
+ log := podmanTest.Podman([]string{"logs", name})
+ log.WaitWithDefaultTimeout()
+ Expect(log).Should(Exit(0))
+ Expect(log.OutputToString()).To(Equal("podman"))
+ })
+
It("podman run with new:pod and static-ip", func() {
netName := stringid.GenerateNonCryptoID()
ipAddr := "10.25.40.128"
@@ -814,14 +839,50 @@ EXPOSE 2004-2005/tcp`, ALPINE)
pingTest("--net=private")
})
- It("podman run check dnsname plugin", func() {
- SkipUntilAardvark(podmanTest)
+ It("podman run check dnsname plugin with CNI", func() {
+ SkipIfNetavark(podmanTest)
+ pod := "testpod"
+ session := podmanTest.Podman([]string{"pod", "create", "--name", pod})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ net := createNetworkName("IntTest")
+ session = podmanTest.Podman([]string{"network", "create", net})
+ session.WaitWithDefaultTimeout()
+ defer podmanTest.removeNetwork(net)
+ Expect(session).Should(Exit(0))
+
+ pod2 := "testpod2"
+ session = podmanTest.Podman([]string{"pod", "create", "--network", net, "--name", pod2})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"run", "--name", "con1", "--network", net, ALPINE, "nslookup", "con1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"run", "--name", "con2", "--pod", pod, "--network", net, ALPINE, "nslookup", "con2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"run", "--name", "con3", "--pod", pod2, ALPINE, "nslookup", "con1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(1))
+ Expect(session.ErrorToString()).To(ContainSubstring("can't resolve 'con1'"))
+
+ session = podmanTest.Podman([]string{"run", "--name", "con4", "--network", net, ALPINE, "nslookup", pod2 + ".dns.podman"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ })
+
+ It("podman run check dnsname plugin with Netavark", func() {
+ SkipIfCNI(podmanTest)
pod := "testpod"
session := podmanTest.Podman([]string{"pod", "create", "--name", pod})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
- net := "IntTest" + stringid.GenerateNonCryptoID()
+ net := createNetworkName("IntTest")
session = podmanTest.Podman([]string{"network", "create", net})
session.WaitWithDefaultTimeout()
defer podmanTest.removeNetwork(net)
@@ -850,9 +911,23 @@ EXPOSE 2004-2005/tcp`, ALPINE)
Expect(session).Should(Exit(0))
})
- It("podman run check dnsname adds dns search domain", func() {
- SkipUntilAardvark(podmanTest)
- net := "dnsname" + stringid.GenerateNonCryptoID()
+ It("podman run check dnsname adds dns search domain with CNI", func() {
+ SkipIfNetavark(podmanTest)
+ net := createNetworkName("dnsname")
+ session := podmanTest.Podman([]string{"network", "create", net})
+ session.WaitWithDefaultTimeout()
+ defer podmanTest.removeNetwork(net)
+ Expect(session).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"run", "--network", net, ALPINE, "cat", "/etc/resolv.conf"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToString()).To(ContainSubstring("search dns.podman"))
+ })
+
+ It("podman run check dnsname adds dns search domain with Netavark", func() {
+ SkipIfCNI(podmanTest)
+ net := createNetworkName("dnsname")
session := podmanTest.Podman([]string{"network", "create", net})
session.WaitWithDefaultTimeout()
defer podmanTest.removeNetwork(net)
diff --git a/test/system/070-build.bats b/test/system/070-build.bats
index d5f7365e8..a95acd986 100644
--- a/test/system/070-build.bats
+++ b/test/system/070-build.bats
@@ -88,12 +88,10 @@ EOF
containerfile=$PODMAN_TMPDIR/Containerfile
cat >$containerfile <<EOF
FROM $IMAGE
-RUN apk add nginx
RUN echo $rand_content > /$rand_filename
EOF
- # The 'apk' command can take a long time to fetch files; bump timeout
- PODMAN_TIMEOUT=240 run_podman build -t build_test -f - --format=docker $tmpdir < $containerfile
+ run_podman build -t build_test -f - --format=docker $tmpdir < $containerfile
is "$output" ".*COMMIT" "COMMIT seen in log"
run_podman run --rm build_test cat /$rand_filename
@@ -188,6 +186,30 @@ EOF
run_podman rmi -f build_test $iid
}
+@test "podman build test -f ./relative" {
+ rand_filename=$(random_string 20)
+ rand_content=$(random_string 50)
+
+ tmpdir=$PODMAN_TMPDIR/build-test
+ mkdir -p $tmpdir
+ mkdir -p $PODMAN_TMPDIR/reldir
+
+ containerfile=$PODMAN_TMPDIR/reldir/Containerfile
+ cat >$containerfile <<EOF
+FROM $IMAGE
+RUN echo $rand_content > /$rand_filename
+EOF
+
+ cd $PODMAN_TMPDIR
+ run_podman build -t build_test -f ./reldir/Containerfile --format=docker $tmpdir
+ is "$output" ".*COMMIT" "COMMIT seen in log"
+
+ run_podman run --rm build_test cat /$rand_filename
+ is "$output" "$rand_content" "reading generated file in image"
+
+ run_podman rmi -f build_test
+}
+
@test "podman build - URLs" {
tmpdir=$PODMAN_TMPDIR/build-test
mkdir -p $tmpdir
diff --git a/version/version.go b/version/version.go
index da7402967..3ec5e6d84 100644
--- a/version/version.go
+++ b/version/version.go
@@ -27,7 +27,7 @@ const (
// NOTE: remember to bump the version at the top
// of the top-level README.md file when this is
// bumped.
-var Version = semver.MustParse("4.0.0-dev")
+var Version = semver.MustParse("4.0.1-dev")
// See https://docs.docker.com/engine/api/v1.40/
// libpod compat handlers are expected to honor docker API versions
@@ -38,7 +38,7 @@ var Version = semver.MustParse("4.0.0-dev")
var APIVersion = map[Tree]map[Level]semver.Version{
Libpod: {
CurrentAPI: Version,
- MinimalAPI: semver.MustParse("3.3.1"),
+ MinimalAPI: semver.MustParse("4.0.0"),
},
Compat: {
CurrentAPI: semver.MustParse("1.40.0"),