summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/images_build.go6
-rw-r--r--pkg/autoupdate/autoupdate.go4
-rw-r--r--pkg/bindings/README.md8
-rw-r--r--pkg/bindings/images/build.go13
-rw-r--r--pkg/domain/infra/abi/images.go6
-rw-r--r--pkg/domain/infra/abi/play.go4
-rw-r--r--pkg/domain/infra/runtime_libpod.go4
-rw-r--r--pkg/machine/config.go4
-rw-r--r--pkg/machine/e2e/basic_test.go50
-rw-r--r--pkg/machine/e2e/config.go174
-rw-r--r--pkg/machine/e2e/config_basic.go19
-rw-r--r--pkg/machine/e2e/config_init.go101
-rw-r--r--pkg/machine/e2e/config_inspect.go24
-rw-r--r--pkg/machine/e2e/config_list.go45
-rw-r--r--pkg/machine/e2e/config_rm.go56
-rw-r--r--pkg/machine/e2e/config_ssh.go33
-rw-r--r--pkg/machine/e2e/config_start.go16
-rw-r--r--pkg/machine/e2e/config_stop.go16
-rw-r--r--pkg/machine/e2e/init_test.go77
-rw-r--r--pkg/machine/e2e/inspect_test.go67
-rw-r--r--pkg/machine/e2e/list_test.go79
-rw-r--r--pkg/machine/e2e/machine_test.go129
-rw-r--r--pkg/machine/e2e/rm_test.go67
-rw-r--r--pkg/machine/e2e/ssh_test.go59
-rw-r--r--pkg/machine/e2e/start_test.go36
-rw-r--r--pkg/machine/e2e/stop_test.go46
-rw-r--r--pkg/machine/fcos.go14
-rw-r--r--pkg/machine/ignition.go20
-rw-r--r--pkg/machine/qemu/config.go8
-rw-r--r--pkg/machine/qemu/machine.go62
-rw-r--r--pkg/machine/wsl/machine.go26
-rw-r--r--pkg/signal/signal_common_test.go120
-rw-r--r--pkg/specgen/generate/container_create.go8
-rw-r--r--pkg/specgen/generate/namespaces.go7
-rw-r--r--pkg/specgen/generate/oci.go12
-rw-r--r--pkg/specgen/generate/pod_create.go6
-rw-r--r--pkg/specgen/generate/ports.go3
-rw-r--r--pkg/specgen/volumes.go27
-rw-r--r--pkg/specgen/winpath.go59
-rw-r--r--pkg/specgen/winpath_linux.go24
-rw-r--r--pkg/specgen/winpath_unsupported.go20
-rw-r--r--pkg/specgen/winpath_windows.go30
-rw-r--r--pkg/specgenutil/ports_test.go57
-rw-r--r--pkg/specgenutil/specgen.go4
-rw-r--r--pkg/specgenutil/specgenutil_test.go77
-rw-r--r--pkg/specgenutil/util.go1
-rw-r--r--pkg/specgenutil/util_test.go146
-rw-r--r--pkg/specgenutil/volumes.go21
-rw-r--r--pkg/specgenutil/volumes_test.go68
49 files changed, 1868 insertions, 95 deletions
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index 08646202a..1a24f1ae3 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -123,6 +123,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
Tags []string `schema:"t"`
Target string `schema:"target"`
Timestamp int64 `schema:"timestamp"`
+ TLSVerify bool `schema:"tlsVerify"`
Ulimits string `schema:"ulimits"`
UnsetEnvs []string `schema:"unsetenv"`
Secrets string `schema:"secrets"`
@@ -491,6 +492,11 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
}
utils.PossiblyEnforceDockerHub(r, systemContext)
+ if _, found := r.URL.Query()["tlsVerify"]; found {
+ systemContext.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
+ systemContext.OCIInsecureSkipTLSVerify = !query.TLSVerify
+ systemContext.DockerDaemonInsecureSkipTLSVerify = !query.TLSVerify
+ }
// Channels all mux'ed in select{} below to follow API build protocol
stdout := channel.NewWriter(make(chan []byte))
defer stdout.Close()
diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go
index 07962a965..ee530528e 100644
--- a/pkg/autoupdate/autoupdate.go
+++ b/pkg/autoupdate/autoupdate.go
@@ -209,7 +209,7 @@ func autoUpdateRegistry(ctx context.Context, image *libimage.Image, ctr *libpod.
}
authfile := getAuthfilePath(ctr, options)
- needsUpdate, err := newerRemoteImageAvailable(ctx, runtime, image, rawImageName, authfile)
+ needsUpdate, err := newerRemoteImageAvailable(ctx, image, rawImageName, authfile)
if err != nil {
return report, errors.Wrapf(err, "registry auto-updating container %q: image check for %q failed", cid, rawImageName)
}
@@ -399,7 +399,7 @@ func getAuthfilePath(ctr *libpod.Container, options *entities.AutoUpdateOptions)
// newerRemoteImageAvailable returns true if there corresponding image on the remote
// registry is newer.
-func newerRemoteImageAvailable(ctx context.Context, runtime *libpod.Runtime, img *libimage.Image, origName string, authfile string) (bool, error) {
+func newerRemoteImageAvailable(ctx context.Context, img *libimage.Image, origName string, authfile string) (bool, error) {
remoteRef, err := docker.ParseReference("//" + origName)
if err != nil {
return false, err
diff --git a/pkg/bindings/README.md b/pkg/bindings/README.md
index 713adb104..ebc8a13d1 100644
--- a/pkg/bindings/README.md
+++ b/pkg/bindings/README.md
@@ -9,7 +9,7 @@ The bindings require that the Podman system service is running for the specified
by calling the service directly.
### Starting the service with system
-The command to start the Podman service differs slightly depending on the user that is running the service. For a rootfull service,
+The command to start the Podman service differs slightly depending on the user that is running the service. For a rootful service,
start the service like this:
```
# systemctl start podman.socket
@@ -26,7 +26,7 @@ It can be handy to run the system service manually. Doing so allows you to enab
$ podman --log-level=debug system service -t0
```
If you do not provide a specific path for the socket, a default is provided. The location of that socket for
-rootfull connections is `/run/podman/podman.sock` and for rootless it is `/run/USERID#/podman/podman.sock`. For more
+rootful connections is `/run/podman/podman.sock` and for rootless it is `/run/USERID#/podman/podman.sock`. For more
information about the Podman system service, see `man podman-system-service`.
### Creating a connection
@@ -35,7 +35,7 @@ as they will be required to compile a Go program making use of the bindings.
The first step for using the bindings is to create a connection to the socket. As mentioned earlier, the destination
-of the socket depends on the user who owns it. In this case, a rootfull connection is made.
+of the socket depends on the user who owns it. In this case, a rootful connection is made.
```
import (
@@ -59,7 +59,7 @@ The `conn` variable returned from the `bindings.NewConnection` function can then
to interact with containers.
### Examples
-The following examples build upon the connection example from above. They are all rootfull connections as well.
+The following examples build upon the connection example from above. They are all rootful connections as well.
Note: Optional arguments to the bindings methods are set using With*() methods on *Option structures.
Composite types are not duplicated rather the address is used. As such, you should not change an underlying
diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go
index 15900a2ed..1729bd922 100644
--- a/pkg/bindings/images/build.go
+++ b/pkg/bindings/images/build.go
@@ -312,10 +312,15 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
var (
headers http.Header
)
- if options.SystemContext != nil && options.SystemContext.DockerAuthConfig != nil {
- headers, err = auth.MakeXRegistryAuthHeader(options.SystemContext, options.SystemContext.DockerAuthConfig.Username, options.SystemContext.DockerAuthConfig.Password)
- } else {
- headers, err = auth.MakeXRegistryConfigHeader(options.SystemContext, "", "")
+ if options.SystemContext != nil {
+ if options.SystemContext.DockerAuthConfig != nil {
+ headers, err = auth.MakeXRegistryAuthHeader(options.SystemContext, options.SystemContext.DockerAuthConfig.Username, options.SystemContext.DockerAuthConfig.Password)
+ } else {
+ headers, err = auth.MakeXRegistryConfigHeader(options.SystemContext, "", "")
+ }
+ if options.SystemContext.DockerInsecureSkipTLSVerify == types.OptionalBoolTrue {
+ params.Set("tlsVerify", "false")
+ }
}
if err != nil {
return nil, err
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 43440b594..74478b26d 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -367,7 +367,7 @@ func (ir *ImageEngine) Transfer(ctx context.Context, source entities.ImageScpOpt
if rootless.IsRootless() && (len(dest.User) == 0 || dest.User == "root") { // if we are rootless and do not have a destination user we can just use sudo
return transferRootless(source, dest, podman, parentFlags)
}
- return transferRootfull(source, dest, podman, parentFlags)
+ return transferRootful(source, dest, podman, parentFlags)
}
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error {
@@ -785,8 +785,8 @@ func transferRootless(source entities.ImageScpOptions, dest entities.ImageScpOpt
return cmdLoad.Run()
}
-// transferRootfull creates new podman processes using exec.Command and a new uid/gid alongside a cleared environment
-func transferRootfull(source entities.ImageScpOptions, dest entities.ImageScpOptions, podman string, parentFlags []string) error {
+// TransferRootful creates new podman processes using exec.Command and a new uid/gid alongside a cleared environment
+func transferRootful(source entities.ImageScpOptions, dest entities.ImageScpOptions, podman string, parentFlags []string) error {
basicCommand := []string{podman}
basicCommand = append(basicCommand, parentFlags...)
saveCommand := append(basicCommand, "save")
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index c3f6bb17d..1d347ed8c 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -114,7 +114,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options
return nil, errors.Wrap(err, "unable to read YAML as Kube PersistentVolumeClaim")
}
- r, err := ic.playKubePVC(ctx, &pvcYAML, options)
+ r, err := ic.playKubePVC(ctx, &pvcYAML)
if err != nil {
return nil, err
}
@@ -592,7 +592,7 @@ func (ic *ContainerEngine) getImageAndLabelInfo(ctx context.Context, cwd string,
}
// playKubePVC creates a podman volume from a kube persistent volume claim.
-func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.PersistentVolumeClaim, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
+func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.PersistentVolumeClaim) (*entities.PlayKubeReport, error) {
var report entities.PlayKubeReport
opts := make(map[string]string)
diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go
index 5fdc252e2..ac557e9de 100644
--- a/pkg/domain/infra/runtime_libpod.go
+++ b/pkg/domain/infra/runtime_libpod.go
@@ -209,6 +209,10 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo
options = append(options, libpod.WithEventsLogger(cfg.Engine.EventsLogger))
}
+ if fs.Changed("volumepath") {
+ options = append(options, libpod.WithVolumePath(cfg.Engine.VolumePath))
+ }
+
if fs.Changed("cgroup-manager") {
options = append(options, libpod.WithCgroupManager(cfg.Engine.CgroupManager))
} else {
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index 505311264..1103933cd 100644
--- a/pkg/machine/config.go
+++ b/pkg/machine/config.go
@@ -28,7 +28,7 @@ type InitOptions struct {
URI url.URL
Username string
ReExec bool
- Rootfull bool
+ Rootful bool
// The numerical userid of the user that called machine
UID string
}
@@ -95,7 +95,7 @@ type ListResponse struct {
}
type SetOptions struct {
- Rootfull bool
+ Rootful bool
}
type SSHOptions struct {
diff --git a/pkg/machine/e2e/basic_test.go b/pkg/machine/e2e/basic_test.go
new file mode 100644
index 000000000..f67fb4c67
--- /dev/null
+++ b/pkg/machine/e2e/basic_test.go
@@ -0,0 +1,50 @@
+package e2e
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("run basic podman commands", func() {
+ var (
+ mb *machineTestBuilder
+ testDir string
+ )
+
+ BeforeEach(func() {
+ testDir, mb = setup()
+ })
+ AfterEach(func() {
+ teardown(originalHomeDir, testDir, mb)
+ })
+
+ It("Basic ops", func() {
+ name := randomString(12)
+ i := new(initMachine)
+ session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withNow()).run()
+ Expect(err).To(BeNil())
+ Expect(session).To(Exit(0))
+
+ bm := basicMachine{}
+ imgs, err := mb.setCmd(bm.withPodmanCommand([]string{"images", "-q"})).run()
+ Expect(err).To(BeNil())
+ Expect(imgs).To(Exit(0))
+ Expect(len(imgs.outputToStringSlice())).To(Equal(0))
+
+ newImgs, err := mb.setCmd(bm.withPodmanCommand([]string{"pull", "quay.io/libpod/alpine_nginx"})).run()
+ Expect(err).To(BeNil())
+ Expect(newImgs).To(Exit(0))
+ Expect(len(newImgs.outputToStringSlice())).To(Equal(1))
+
+ runAlp, err := mb.setCmd(bm.withPodmanCommand([]string{"run", "quay.io/libpod/alpine_nginx", "cat", "/etc/os-release"})).run()
+ Expect(err).To(BeNil())
+ Expect(runAlp).To(Exit(0))
+ Expect(runAlp.outputToString()).To(ContainSubstring("Alpine Linux"))
+
+ rmCon, err := mb.setCmd(bm.withPodmanCommand([]string{"rm", "-a"})).run()
+ Expect(err).To(BeNil())
+ Expect(rmCon).To(Exit(0))
+ })
+
+})
diff --git a/pkg/machine/e2e/config.go b/pkg/machine/e2e/config.go
new file mode 100644
index 000000000..7d75ca6bc
--- /dev/null
+++ b/pkg/machine/e2e/config.go
@@ -0,0 +1,174 @@
+package e2e
+
+import (
+ "encoding/json"
+ "fmt"
+ "math/rand"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/containers/podman/v4/pkg/machine"
+ "github.com/containers/podman/v4/pkg/machine/qemu"
+ "github.com/containers/podman/v4/pkg/util"
+ . "github.com/onsi/ginkgo" //nolint:golint,stylecheck
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+ . "github.com/onsi/gomega/gexec" //nolint:golint,stylecheck
+)
+
+var originalHomeDir = os.Getenv("HOME")
+
+const (
+ defaultTimeout time.Duration = 90 * time.Second
+)
+
+type machineCommand interface {
+ buildCmd(m *machineTestBuilder) []string
+}
+
+type MachineTestBuilder interface {
+ setName(string) *MachineTestBuilder
+ setCmd(mc machineCommand) *MachineTestBuilder
+ setTimeout(duration time.Duration) *MachineTestBuilder
+ run() (*machineSession, error)
+}
+type machineSession struct {
+ *gexec.Session
+}
+
+type machineTestBuilder struct {
+ cmd []string
+ imagePath string
+ name string
+ names []string
+ podmanBinary string
+ timeout time.Duration
+}
+type qemuMachineInspectInfo struct {
+ State machine.Status
+ VM qemu.MachineVM
+}
+
+// waitWithTimeout waits for a command to complete for a given
+// number of seconds
+func (ms *machineSession) waitWithTimeout(timeout time.Duration) {
+ Eventually(ms, timeout).Should(Exit())
+ os.Stdout.Sync()
+ os.Stderr.Sync()
+}
+
+func (ms *machineSession) Bytes() []byte {
+ return []byte(ms.outputToString())
+}
+
+func (ms *machineSession) outputToStringSlice() []string {
+ var results []string
+ output := string(ms.Out.Contents())
+ for _, line := range strings.Split(output, "\n") {
+ if line != "" {
+ results = append(results, line)
+ }
+ }
+ return results
+}
+
+// outputToString returns the output from a session in string form
+func (ms *machineSession) outputToString() string {
+ if ms == nil || ms.Out == nil || ms.Out.Contents() == nil {
+ return ""
+ }
+
+ fields := strings.Fields(string(ms.Out.Contents()))
+ return strings.Join(fields, " ")
+}
+
+// newMB constructor for machine test builders
+func newMB() (*machineTestBuilder, error) {
+ mb := machineTestBuilder{
+ timeout: defaultTimeout,
+ }
+ cwd, err := os.Getwd()
+ if err != nil {
+ return nil, err
+ }
+ mb.podmanBinary = filepath.Join(cwd, "../../../bin/podman-remote")
+ if os.Getenv("PODMAN_BINARY") != "" {
+ mb.podmanBinary = os.Getenv("PODMAN_BINARY")
+ }
+ return &mb, nil
+}
+
+// setName sets the name of the virtuaql machine for the command
+func (m *machineTestBuilder) setName(name string) *machineTestBuilder {
+ m.name = name
+ return m
+}
+
+// setCmd takes a machineCommand struct and assembles a cmd line
+// representation of the podman machine command
+func (m *machineTestBuilder) setCmd(mc machineCommand) *machineTestBuilder {
+ // If no name for the machine exists, we set a random name.
+ if !util.StringInSlice(m.name, m.names) {
+ if len(m.name) < 1 {
+ m.name = randomString(12)
+ }
+ m.names = append(m.names, m.name)
+ }
+ m.cmd = mc.buildCmd(m)
+ return m
+}
+
+func (m *machineTestBuilder) setTimeout(timeout time.Duration) *machineTestBuilder {
+ m.timeout = timeout
+ return m
+}
+
+// toQemuInspectInfo is only for inspecting qemu machines. Other providers will need
+// to make their own.
+func (mb *machineTestBuilder) toQemuInspectInfo() ([]qemuMachineInspectInfo, int, error) {
+ args := []string{"machine", "inspect"}
+ args = append(args, mb.names...)
+ session, err := runWrapper(mb.podmanBinary, args, defaultTimeout)
+ if err != nil {
+ return nil, -1, err
+ }
+ mii := []qemuMachineInspectInfo{}
+ err = json.Unmarshal(session.Bytes(), &mii)
+ return mii, session.ExitCode(), err
+}
+
+func (m *machineTestBuilder) run() (*machineSession, error) {
+ return runWrapper(m.podmanBinary, m.cmd, m.timeout)
+}
+
+func runWrapper(podmanBinary string, cmdArgs []string, timeout time.Duration) (*machineSession, error) {
+ if len(os.Getenv("DEBUG")) > 0 {
+ cmdArgs = append([]string{"--log-level=debug"}, cmdArgs...)
+ }
+ fmt.Println(podmanBinary + " " + strings.Join(cmdArgs, " "))
+ c := exec.Command(podmanBinary, cmdArgs...)
+ session, err := Start(c, GinkgoWriter, GinkgoWriter)
+ if err != nil {
+ Fail(fmt.Sprintf("Unable to start session: %q", err))
+ return nil, err
+ }
+ ms := machineSession{session}
+ ms.waitWithTimeout(timeout)
+ fmt.Println("output:", ms.outputToString())
+ return &ms, nil
+}
+
+func (m *machineTestBuilder) init() {}
+
+// randomString returns a string of given length composed of random characters
+func randomString(n int) string {
+ var randomLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ b := make([]rune, n)
+ for i := range b {
+ b[i] = randomLetters[rand.Intn(len(randomLetters))]
+ }
+ return string(b)
+}
diff --git a/pkg/machine/e2e/config_basic.go b/pkg/machine/e2e/config_basic.go
new file mode 100644
index 000000000..be0896156
--- /dev/null
+++ b/pkg/machine/e2e/config_basic.go
@@ -0,0 +1,19 @@
+package e2e
+
+type basicMachine struct {
+ args []string
+ cmd []string
+}
+
+func (s basicMachine) buildCmd(m *machineTestBuilder) []string {
+ cmd := []string{"-r"}
+ if len(s.args) > 0 {
+ cmd = append(cmd, s.args...)
+ }
+ return cmd
+}
+
+func (s *basicMachine) withPodmanCommand(args []string) *basicMachine {
+ s.args = args
+ return s
+}
diff --git a/pkg/machine/e2e/config_init.go b/pkg/machine/e2e/config_init.go
new file mode 100644
index 000000000..55218221d
--- /dev/null
+++ b/pkg/machine/e2e/config_init.go
@@ -0,0 +1,101 @@
+package e2e
+
+import (
+ "strconv"
+)
+
+type initMachine struct {
+ /*
+ --cpus uint Number of CPUs (default 1)
+ --disk-size uint Disk size in GB (default 100)
+ --ignition-path string Path to ignition file
+ --image-path string Path to qcow image (default "testing")
+ -m, --memory uint Memory in MB (default 2048)
+ --now Start machine now
+ --rootful Whether this machine should prefer rootful container exectution
+ --timezone string Set timezone (default "local")
+ -v, --volume stringArray Volumes to mount, source:target
+ --volume-driver string Optional volume driver
+
+ */
+ cpus *uint
+ diskSize *uint
+ ignitionPath string
+ imagePath string
+ memory *uint
+ now bool
+ timezone string
+ volumes []string
+
+ cmd []string
+}
+
+func (i *initMachine) buildCmd(m *machineTestBuilder) []string {
+ cmd := []string{"machine", "init"}
+ if i.cpus != nil {
+ cmd = append(cmd, "--cpus", strconv.Itoa(int(*i.cpus)))
+ }
+ if i.diskSize != nil {
+ cmd = append(cmd, "--disk-size", strconv.Itoa(int(*i.diskSize)))
+ }
+ if l := len(i.ignitionPath); l > 0 {
+ cmd = append(cmd, "--ignition-path", i.ignitionPath)
+ }
+ if l := len(i.imagePath); l > 0 {
+ cmd = append(cmd, "--image-path", i.imagePath)
+ }
+ if i.memory != nil {
+ cmd = append(cmd, "--memory", strconv.Itoa(int(*i.memory)))
+ }
+ if l := len(i.timezone); l > 0 {
+ cmd = append(cmd, "--timezone", i.timezone)
+ }
+ for _, v := range i.volumes {
+ cmd = append(cmd, "--volume", v)
+ }
+ if i.now {
+ cmd = append(cmd, "--now")
+ }
+ cmd = append(cmd, m.name)
+ i.cmd = cmd
+ return cmd
+}
+
+func (i *initMachine) withCPUs(num uint) *initMachine {
+ i.cpus = &num
+ return i
+}
+func (i *initMachine) withDiskSize(size uint) *initMachine {
+ i.diskSize = &size
+ return i
+}
+
+func (i *initMachine) withIgnitionPath(path string) *initMachine {
+ i.ignitionPath = path
+ return i
+}
+
+func (i *initMachine) withImagePath(path string) *initMachine {
+ i.imagePath = path
+ return i
+}
+
+func (i *initMachine) withMemory(num uint) *initMachine {
+ i.memory = &num
+ return i
+}
+
+func (i *initMachine) withNow() *initMachine {
+ i.now = true
+ return i
+}
+
+func (i *initMachine) withTimezone(tz string) *initMachine {
+ i.timezone = tz
+ return i
+}
+
+func (i *initMachine) withVolume(v string) *initMachine {
+ i.volumes = append(i.volumes, v)
+ return i
+}
diff --git a/pkg/machine/e2e/config_inspect.go b/pkg/machine/e2e/config_inspect.go
new file mode 100644
index 000000000..74c9a5d9c
--- /dev/null
+++ b/pkg/machine/e2e/config_inspect.go
@@ -0,0 +1,24 @@
+package e2e
+
+type inspectMachine struct {
+ /*
+ --format string Format volume output using JSON or a Go template
+ */
+ cmd []string
+ format string
+}
+
+func (i *inspectMachine) buildCmd(m *machineTestBuilder) []string {
+ cmd := []string{"machine", "inspect"}
+ if len(i.format) > 0 {
+ cmd = append(cmd, "--format", i.format)
+ }
+ cmd = append(cmd, m.names...)
+ i.cmd = cmd
+ return cmd
+}
+
+func (i *inspectMachine) withFormat(format string) *inspectMachine {
+ i.format = format
+ return i
+}
diff --git a/pkg/machine/e2e/config_list.go b/pkg/machine/e2e/config_list.go
new file mode 100644
index 000000000..150f984bc
--- /dev/null
+++ b/pkg/machine/e2e/config_list.go
@@ -0,0 +1,45 @@
+package e2e
+
+type listMachine struct {
+ /*
+ --format string Format volume output using JSON or a Go template (default "{{.Name}}\t{{.VMType}}\t{{.Created}}\t{{.LastUp}}\t{{.CPUs}}\t{{.Memory}}\t{{.DiskSize}}\n")
+ --noheading Do not print headers
+ -q, --quiet Show only machine names
+ */
+
+ format string
+ noHeading bool
+ quiet bool
+
+ cmd []string
+}
+
+func (i *listMachine) buildCmd(m *machineTestBuilder) []string {
+ cmd := []string{"machine", "list"}
+ if len(i.format) > 0 {
+ cmd = append(cmd, "--format", i.format)
+ }
+ if i.noHeading {
+ cmd = append(cmd, "--noheading")
+ }
+ if i.quiet {
+ cmd = append(cmd, "--quiet")
+ }
+ i.cmd = cmd
+ return cmd
+}
+
+func (i *listMachine) withNoHeading() *listMachine {
+ i.noHeading = true
+ return i
+}
+
+func (i *listMachine) withQuiet() *listMachine {
+ i.quiet = true
+ return i
+}
+
+func (i *listMachine) withFormat(format string) *listMachine {
+ i.format = format
+ return i
+}
diff --git a/pkg/machine/e2e/config_rm.go b/pkg/machine/e2e/config_rm.go
new file mode 100644
index 000000000..6cf262a22
--- /dev/null
+++ b/pkg/machine/e2e/config_rm.go
@@ -0,0 +1,56 @@
+package e2e
+
+type rmMachine struct {
+ /*
+ -f, --force Stop and do not prompt before rming
+ --save-ignition Do not delete ignition file
+ --save-image Do not delete the image file
+ --save-keys Do not delete SSH keys
+
+ */
+ force bool
+ saveIgnition bool
+ saveImage bool
+ saveKeys bool
+
+ cmd []string
+}
+
+func (i *rmMachine) buildCmd(m *machineTestBuilder) []string {
+ cmd := []string{"machine", "rm"}
+ if i.force {
+ cmd = append(cmd, "--force")
+ }
+ if i.saveIgnition {
+ cmd = append(cmd, "--save-ignition")
+ }
+ if i.saveImage {
+ cmd = append(cmd, "--save-image")
+ }
+ if i.saveKeys {
+ cmd = append(cmd, "--save-keys")
+ }
+ cmd = append(cmd, m.name)
+ i.cmd = cmd
+ return cmd
+}
+
+func (i *rmMachine) withForce() *rmMachine {
+ i.force = true
+ return i
+}
+
+func (i *rmMachine) withSaveIgnition() *rmMachine {
+ i.saveIgnition = true
+ return i
+}
+
+func (i *rmMachine) withSaveImage() *rmMachine {
+ i.saveImage = true
+ return i
+}
+
+func (i *rmMachine) withSaveKeys() *rmMachine {
+ i.saveKeys = true
+ return i
+}
diff --git a/pkg/machine/e2e/config_ssh.go b/pkg/machine/e2e/config_ssh.go
new file mode 100644
index 000000000..b09eed47d
--- /dev/null
+++ b/pkg/machine/e2e/config_ssh.go
@@ -0,0 +1,33 @@
+package e2e
+
+type sshMachine struct {
+ /*
+ --username string Username to use when ssh-ing into the VM.
+ */
+
+ username string
+ sshCommand []string
+
+ cmd []string
+}
+
+func (s sshMachine) buildCmd(m *machineTestBuilder) []string {
+ cmd := []string{"machine", "ssh"}
+ if len(m.name) > 0 {
+ cmd = append(cmd, m.name)
+ }
+ if len(s.sshCommand) > 0 {
+ cmd = append(cmd, s.sshCommand...)
+ }
+ return cmd
+}
+
+func (s *sshMachine) withUsername(name string) *sshMachine {
+ s.username = name
+ return s
+}
+
+func (s *sshMachine) withSSHComand(sshCommand []string) *sshMachine {
+ s.sshCommand = sshCommand
+ return s
+}
diff --git a/pkg/machine/e2e/config_start.go b/pkg/machine/e2e/config_start.go
new file mode 100644
index 000000000..86b1721f8
--- /dev/null
+++ b/pkg/machine/e2e/config_start.go
@@ -0,0 +1,16 @@
+package e2e
+
+type startMachine struct {
+ /*
+ No command line args other than a machine vm name (also not required)
+ */
+ cmd []string
+}
+
+func (s startMachine) buildCmd(m *machineTestBuilder) []string {
+ cmd := []string{"machine", "start"}
+ if len(m.name) > 0 {
+ cmd = append(cmd, m.name)
+ }
+ return cmd
+}
diff --git a/pkg/machine/e2e/config_stop.go b/pkg/machine/e2e/config_stop.go
new file mode 100644
index 000000000..04dcfb524
--- /dev/null
+++ b/pkg/machine/e2e/config_stop.go
@@ -0,0 +1,16 @@
+package e2e
+
+type stopMachine struct {
+ /*
+ No command line args other than a machine vm name (also not required)
+ */
+ cmd []string
+}
+
+func (s stopMachine) buildCmd(m *machineTestBuilder) []string {
+ cmd := []string{"machine", "stop"}
+ if len(m.name) > 0 {
+ cmd = append(cmd, m.name)
+ }
+ return cmd
+}
diff --git a/pkg/machine/e2e/init_test.go b/pkg/machine/e2e/init_test.go
new file mode 100644
index 000000000..309d460a9
--- /dev/null
+++ b/pkg/machine/e2e/init_test.go
@@ -0,0 +1,77 @@
+package e2e
+
+import (
+ "time"
+
+ "github.com/containers/podman/v4/pkg/machine"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("podman machine init", func() {
+ var (
+ mb *machineTestBuilder
+ testDir string
+ )
+
+ BeforeEach(func() {
+ testDir, mb = setup()
+ })
+ AfterEach(func() {
+ teardown(originalHomeDir, testDir, mb)
+ })
+
+ It("bad init name", func() {
+ i := initMachine{}
+ reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ session, err := mb.setName(reallyLongName).setCmd(&i).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(125))
+ })
+ It("simple init", func() {
+ i := new(initMachine)
+ session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(0))
+
+ inspectBefore, ec, err := mb.toQemuInspectInfo()
+ Expect(err).To(BeNil())
+ Expect(ec).To(BeZero())
+
+ Expect(len(inspectBefore)).To(BeNumerically(">", 0))
+ testMachine := inspectBefore[0]
+ Expect(testMachine.VM.Name).To(Equal(mb.names[0]))
+ Expect(testMachine.VM.CPUs).To(Equal(uint64(1)))
+ Expect(testMachine.VM.Memory).To(Equal(uint64(2048)))
+
+ })
+
+ It("simple init with start", func() {
+ i := initMachine{}
+ session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(0))
+
+ inspectBefore, ec, err := mb.toQemuInspectInfo()
+ Expect(ec).To(BeZero())
+ Expect(len(inspectBefore)).To(BeNumerically(">", 0))
+ Expect(err).To(BeNil())
+ Expect(len(inspectBefore)).To(BeNumerically(">", 0))
+ Expect(inspectBefore[0].VM.Name).To(Equal(mb.names[0]))
+
+ s := startMachine{}
+ ssession, err := mb.setCmd(s).setTimeout(time.Minute * 10).run()
+ Expect(err).To(BeNil())
+ Expect(ssession).Should(Exit(0))
+
+ inspectAfter, ec, err := mb.toQemuInspectInfo()
+ Expect(err).To(BeNil())
+ Expect(ec).To(BeZero())
+ Expect(len(inspectBefore)).To(BeNumerically(">", 0))
+ Expect(len(inspectAfter)).To(BeNumerically(">", 0))
+ Expect(inspectAfter[0].State).To(Equal(machine.Running))
+ })
+
+})
diff --git a/pkg/machine/e2e/inspect_test.go b/pkg/machine/e2e/inspect_test.go
new file mode 100644
index 000000000..30d810b8f
--- /dev/null
+++ b/pkg/machine/e2e/inspect_test.go
@@ -0,0 +1,67 @@
+package e2e
+
+import (
+ "encoding/json"
+
+ "github.com/containers/podman/v4/pkg/machine/qemu"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("podman machine stop", func() {
+ var (
+ mb *machineTestBuilder
+ testDir string
+ )
+
+ BeforeEach(func() {
+ testDir, mb = setup()
+ })
+ AfterEach(func() {
+ teardown(originalHomeDir, testDir, mb)
+ })
+
+ It("inspect bad name", func() {
+ i := inspectMachine{}
+ reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ session, err := mb.setName(reallyLongName).setCmd(&i).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(125))
+ })
+
+ It("inspect two machines", func() {
+ i := new(initMachine)
+ foo1, err := mb.setName("foo1").setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(foo1.ExitCode()).To(Equal(0))
+
+ ii := new(initMachine)
+ foo2, err := mb.setName("foo2").setCmd(ii.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(foo2.ExitCode()).To(Equal(0))
+
+ inspect := new(inspectMachine)
+ inspectSession, err := mb.setName("foo1").setCmd(inspect).run()
+ Expect(err).To(BeNil())
+ Expect(inspectSession.ExitCode()).To(Equal(0))
+
+ type fakeInfos struct {
+ Status string
+ VM qemu.MachineVM
+ }
+ infos := make([]fakeInfos, 0, 2)
+ err = json.Unmarshal(inspectSession.Bytes(), &infos)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(len(infos)).To(Equal(2))
+
+ //rm := new(rmMachine)
+ //// Must manually clean up due to multiple names
+ //for _, name := range []string{"foo1", "foo2"} {
+ // mb.setName(name).setCmd(rm.withForce()).run()
+ // mb.names = []string{}
+ //}
+ //mb.names = []string{}
+
+ })
+})
diff --git a/pkg/machine/e2e/list_test.go b/pkg/machine/e2e/list_test.go
new file mode 100644
index 000000000..e7a439945
--- /dev/null
+++ b/pkg/machine/e2e/list_test.go
@@ -0,0 +1,79 @@
+package e2e
+
+import (
+ "strings"
+
+ "github.com/containers/buildah/util"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("podman machine list", func() {
+ var (
+ mb *machineTestBuilder
+ testDir string
+ )
+
+ BeforeEach(func() {
+ testDir, mb = setup()
+ })
+ AfterEach(func() {
+ teardown(originalHomeDir, testDir, mb)
+ })
+
+ It("list machine", func() {
+ list := new(listMachine)
+ firstList, err := mb.setCmd(list).run()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(firstList).Should(Exit(0))
+ Expect(len(firstList.outputToStringSlice())).To(Equal(1)) // just the header
+
+ i := new(initMachine)
+ session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session).To(Exit(0))
+
+ secondList, err := mb.setCmd(list).run()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(secondList).To(Exit(0))
+ Expect(len(secondList.outputToStringSlice())).To(Equal(2)) // one machine and the header
+ })
+
+ It("list machines with quiet", func() {
+ // Random names for machines to test list
+ name1 := randomString(12)
+ name2 := randomString(12)
+
+ list := new(listMachine)
+ firstList, err := mb.setCmd(list.withQuiet()).run()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(firstList).Should(Exit(0))
+ Expect(len(firstList.outputToStringSlice())).To(Equal(0)) // No header with quiet
+
+ i := new(initMachine)
+ session, err := mb.setName(name1).setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session).To(Exit(0))
+
+ session2, err := mb.setName(name2).setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session2).To(Exit(0))
+
+ secondList, err := mb.setCmd(list.withQuiet()).run()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(secondList).To(Exit(0))
+ Expect(len(secondList.outputToStringSlice())).To(Equal(2)) // two machines, no header
+
+ listNames := secondList.outputToStringSlice()
+ stripAsterisk(listNames)
+ Expect(util.StringInSlice(name1, listNames)).To(BeTrue())
+ Expect(util.StringInSlice(name2, listNames)).To(BeTrue())
+ })
+})
+
+func stripAsterisk(sl []string) {
+ for idx, val := range sl {
+ sl[idx] = strings.TrimRight(val, "*")
+ }
+}
diff --git a/pkg/machine/e2e/machine_test.go b/pkg/machine/e2e/machine_test.go
new file mode 100644
index 000000000..46fe18069
--- /dev/null
+++ b/pkg/machine/e2e/machine_test.go
@@ -0,0 +1,129 @@
+package e2e
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ url2 "net/url"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/containers/podman/v4/pkg/machine"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+func TestMain(m *testing.M) {
+ os.Exit(m.Run())
+}
+
+const (
+ defaultStream string = "podman-testing"
+ tmpDir string = "/var/tmp"
+)
+
+var (
+ fqImageName string
+ suiteImageName string
+)
+
+// TestLibpod ginkgo master function
+func TestMachine(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "Podman Machine tests")
+}
+
+var _ = BeforeSuite(func() {
+ fcd, err := machine.GetFCOSDownload(defaultStream)
+ if err != nil {
+ Fail("unable to get virtual machine image")
+ }
+ suiteImageName = strings.TrimSuffix(path.Base(fcd.Location), ".xz")
+ fqImageName = filepath.Join(tmpDir, suiteImageName)
+ if _, err := os.Stat(fqImageName); err != nil {
+ if os.IsNotExist(err) {
+ getMe, err := url2.Parse(fcd.Location)
+ if err != nil {
+ Fail(fmt.Sprintf("unable to create url for download: %q", err))
+ }
+ now := time.Now()
+ if err := machine.DownloadVMImage(getMe, fqImageName+".xz"); err != nil {
+ Fail(fmt.Sprintf("unable to download machine image: %q", err))
+ }
+ fmt.Println("Download took: ", time.Since(now).String())
+ if err := machine.Decompress(fqImageName+".xz", fqImageName); err != nil {
+ Fail(fmt.Sprintf("unable to decompress image file: %q", err))
+ }
+ } else {
+ Fail(fmt.Sprintf("unable to check for cache image: %q", err))
+ }
+ }
+})
+
+var _ = SynchronizedAfterSuite(func() {},
+ func() {
+ fmt.Println("After")
+ })
+
+func setup() (string, *machineTestBuilder) {
+ homeDir, err := ioutil.TempDir("/var/tmp", "podman_test")
+ if err != nil {
+ Fail(fmt.Sprintf("failed to create home directory: %q", err))
+ }
+ if err := os.MkdirAll(filepath.Join(homeDir, ".ssh"), 0700); err != nil {
+ Fail(fmt.Sprintf("failed to create ssh dir: %q", err))
+ }
+ sshConfig, err := os.Create(filepath.Join(homeDir, ".ssh", "config"))
+ if err != nil {
+ Fail(fmt.Sprintf("failed to create ssh config: %q", err))
+ }
+ if _, err := sshConfig.WriteString("IdentitiesOnly=yes"); err != nil {
+ Fail(fmt.Sprintf("failed to write ssh config: %q", err))
+ }
+ if err := sshConfig.Close(); err != nil {
+ Fail(fmt.Sprintf("unable to close ssh config file descriptor: %q", err))
+ }
+ if err := os.Setenv("HOME", homeDir); err != nil {
+ Fail("failed to set home dir")
+ }
+ if err := os.Unsetenv("SSH_AUTH_SOCK"); err != nil {
+ Fail("unable to unset SSH_AUTH_SOCK")
+ }
+ mb, err := newMB()
+ if err != nil {
+ Fail(fmt.Sprintf("failed to create machine test: %q", err))
+ }
+ f, err := os.Open(fqImageName)
+ if err != nil {
+ Fail(fmt.Sprintf("failed to open file %s: %q", fqImageName, err))
+ }
+ mb.imagePath = filepath.Join(homeDir, suiteImageName)
+ n, err := os.Create(mb.imagePath)
+ if err != nil {
+ Fail(fmt.Sprintf("failed to create file %s: %q", mb.imagePath, err))
+ }
+ if _, err := io.Copy(n, f); err != nil {
+ Fail(fmt.Sprintf("failed to copy %ss to %s: %q", fqImageName, mb.imagePath, err))
+ }
+ return homeDir, mb
+}
+
+func teardown(origHomeDir string, testDir string, mb *machineTestBuilder) {
+ s := new(stopMachine)
+ for _, name := range mb.names {
+ if _, err := mb.setName(name).setCmd(s).run(); err != nil {
+ fmt.Printf("error occured rm'ing machine: %q\n", err)
+ }
+ }
+ if err := os.RemoveAll(testDir); err != nil {
+ Fail(fmt.Sprintf("failed to remove test dir: %q", err))
+ }
+ // this needs to be last in teardown
+ if err := os.Setenv("HOME", origHomeDir); err != nil {
+ Fail("failed to set home dir")
+ }
+}
diff --git a/pkg/machine/e2e/rm_test.go b/pkg/machine/e2e/rm_test.go
new file mode 100644
index 000000000..011da5dde
--- /dev/null
+++ b/pkg/machine/e2e/rm_test.go
@@ -0,0 +1,67 @@
+package e2e
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("podman machine rm", func() {
+ var (
+ mb *machineTestBuilder
+ testDir string
+ )
+
+ BeforeEach(func() {
+ testDir, mb = setup()
+ })
+ AfterEach(func() {
+ teardown(originalHomeDir, testDir, mb)
+ })
+
+ It("bad init name", func() {
+ i := rmMachine{}
+ reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ session, err := mb.setName(reallyLongName).setCmd(&i).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(125))
+ })
+
+ It("Remove machine", func() {
+ i := new(initMachine)
+ session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(0))
+ rm := rmMachine{}
+ _, err = mb.setCmd(rm.withForce()).run()
+ Expect(err).To(BeNil())
+
+ // Inspecting a non-existent machine should fail
+ // which means it is gone
+ _, ec, err := mb.toQemuInspectInfo()
+ Expect(err).To(BeNil())
+ Expect(ec).To(Equal(125))
+ })
+
+ It("Remove running machine", func() {
+ i := new(initMachine)
+ session, err := mb.setCmd(i.withImagePath(mb.imagePath).withNow()).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(0))
+ rm := new(rmMachine)
+
+ // Removing a running machine should fail
+ stop, err := mb.setCmd(rm).run()
+ Expect(err).To(BeNil())
+ Expect(stop.ExitCode()).To(Equal(125))
+
+ // Removing again with force
+ stopAgain, err := mb.setCmd(rm.withForce()).run()
+ Expect(err).To(BeNil())
+ Expect(stopAgain.ExitCode()).To(BeZero())
+
+ // Inspect to be dead sure
+ _, ec, err := mb.toQemuInspectInfo()
+ Expect(err).To(BeNil())
+ Expect(ec).To(Equal(125))
+ })
+})
diff --git a/pkg/machine/e2e/ssh_test.go b/pkg/machine/e2e/ssh_test.go
new file mode 100644
index 000000000..90296fa10
--- /dev/null
+++ b/pkg/machine/e2e/ssh_test.go
@@ -0,0 +1,59 @@
+package e2e
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("podman machine ssh", func() {
+ var (
+ mb *machineTestBuilder
+ testDir string
+ )
+
+ BeforeEach(func() {
+ testDir, mb = setup()
+ })
+ AfterEach(func() {
+ teardown(originalHomeDir, testDir, mb)
+ })
+
+ It("bad machine name", func() {
+ name := randomString(12)
+ ssh := sshMachine{}
+ session, err := mb.setName(name).setCmd(ssh).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(125))
+ // TODO seems like stderr is not being returned; re-enabled when fixed
+ //Expect(session.outputToString()).To(ContainSubstring("not exist"))
+ })
+
+ It("ssh to non-running machine", func() {
+ name := randomString(12)
+ i := new(initMachine)
+ session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(0))
+
+ ssh := sshMachine{}
+ sshSession, err := mb.setName(name).setCmd(ssh).run()
+ Expect(err).To(BeNil())
+ // TODO seems like stderr is not being returned; re-enabled when fixed
+ //Expect(sshSession.outputToString()).To(ContainSubstring("is not running"))
+ Expect(sshSession.ExitCode()).To(Equal(125))
+ })
+
+ It("ssh to running machine and check os-type", func() {
+ name := randomString(12)
+ i := new(initMachine)
+ session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withNow()).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(0))
+
+ ssh := sshMachine{}
+ sshSession, err := mb.setName(name).setCmd(ssh.withSSHComand([]string{"cat", "/etc/os-release"})).run()
+ Expect(err).To(BeNil())
+ Expect(sshSession.ExitCode()).To(Equal(0))
+ Expect(sshSession.outputToString()).To(ContainSubstring("Fedora CoreOS"))
+ })
+})
diff --git a/pkg/machine/e2e/start_test.go b/pkg/machine/e2e/start_test.go
new file mode 100644
index 000000000..1cda0e8f1
--- /dev/null
+++ b/pkg/machine/e2e/start_test.go
@@ -0,0 +1,36 @@
+package e2e
+
+import (
+ "github.com/containers/podman/v4/pkg/machine"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("podman machine start", func() {
+ var (
+ mb *machineTestBuilder
+ testDir string
+ )
+ BeforeEach(func() {
+ testDir, mb = setup()
+ })
+ AfterEach(func() {
+ teardown(originalHomeDir, testDir, mb)
+ })
+
+ It("start simple machine", func() {
+ i := new(initMachine)
+ session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(0))
+ s := new(startMachine)
+ startSession, err := mb.setCmd(s).run()
+ Expect(err).To(BeNil())
+ Expect(startSession.ExitCode()).To(Equal(0))
+
+ info, ec, err := mb.toQemuInspectInfo()
+ Expect(err).To(BeNil())
+ Expect(ec).To(BeZero())
+ Expect(info[0].State).To(Equal(machine.Running))
+ })
+})
diff --git a/pkg/machine/e2e/stop_test.go b/pkg/machine/e2e/stop_test.go
new file mode 100644
index 000000000..5dee6a345
--- /dev/null
+++ b/pkg/machine/e2e/stop_test.go
@@ -0,0 +1,46 @@
+package e2e
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("podman machine stop", func() {
+ var (
+ mb *machineTestBuilder
+ testDir string
+ )
+
+ BeforeEach(func() {
+ testDir, mb = setup()
+ })
+ AfterEach(func() {
+ teardown(originalHomeDir, testDir, mb)
+ })
+
+ It("stop bad name", func() {
+ i := stopMachine{}
+ reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ session, err := mb.setName(reallyLongName).setCmd(&i).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(125))
+ })
+
+ It("Stop running machine", func() {
+ i := new(initMachine)
+ session, err := mb.setCmd(i.withImagePath(mb.imagePath).withNow()).run()
+ Expect(err).To(BeNil())
+ Expect(session.ExitCode()).To(Equal(0))
+
+ stop := new(stopMachine)
+ // Removing a running machine should fail
+ stopSession, err := mb.setCmd(stop).run()
+ Expect(err).To(BeNil())
+ Expect(stopSession.ExitCode()).To(Equal(0))
+
+ // Stopping it again should not result in an error
+ stopAgain, err := mb.setCmd(stop).run()
+ Expect(err).To(BeNil())
+ Expect(stopAgain.ExitCode()).To(BeZero())
+ })
+})
diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go
index d8516dd59..df58b8a1e 100644
--- a/pkg/machine/fcos.go
+++ b/pkg/machine/fcos.go
@@ -43,7 +43,7 @@ type FcosDownload struct {
}
func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload, error) {
- info, err := getFCOSDownload(imageStream)
+ info, err := GetFCOSDownload(imageStream)
if err != nil {
return nil, err
}
@@ -79,7 +79,7 @@ func (f FcosDownload) Get() *Download {
return &f.Download
}
-type fcosDownloadInfo struct {
+type FcosDownloadInfo struct {
CompressionType string
Location string
Release string
@@ -139,7 +139,7 @@ func getStreamURL(streamType string) url2.URL {
// This should get Exported and stay put as it will apply to all fcos downloads
// getFCOS parses fedoraCoreOS's stream and returns the image download URL and the release version
-func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { // nolint:staticcheck
+func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) { //nolint:staticcheck
var (
fcosstable stream.Stream
altMeta release.Release
@@ -150,8 +150,8 @@ func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { // nolint:
// fcos trees, we should remove it and re-release at least on
// macs.
// TODO: remove when podman4.0 is in coreos
- // nolint:staticcheck
- imageStream = "podman-testing"
+
+ imageStream = "podman-testing" //nolint:staticcheck
switch imageStream {
case "podman-testing":
@@ -194,7 +194,7 @@ func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { // nolint:
}
disk := qcow2.Disk
- return &fcosDownloadInfo{
+ return &FcosDownloadInfo{
Location: disk.Location,
Sha256Sum: disk.Sha256,
CompressionType: "xz",
@@ -228,7 +228,7 @@ func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { // nolint:
if disk == nil {
return nil, fmt.Errorf("unable to pull VM image: no disk in stream")
}
- return &fcosDownloadInfo{
+ return &FcosDownloadInfo{
Location: disk.Location,
Release: qemu.Release,
Sha256Sum: disk.Sha256,
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index fe47437e3..35a9a30cb 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -304,6 +304,8 @@ ExecStart=/usr/bin/sleep infinity
containers := `[containers]
netns="bridge"
`
+ // Set deprecated machine_enabled until podman package on fcos is
+ // current enough to no longer require it
rootContainers := `[engine]
machine_enabled=true
`
@@ -392,7 +394,7 @@ Delegate=memory pids cpu io
FileEmbedded1: FileEmbedded1{Mode: intToPtr(0644)},
})
- // Set machine_enabled to true to indicate we're in a VM
+ // Set deprecated machine_enabled to true to indicate we're in a VM
files = append(files, File{
Node: Node{
Group: getNodeGrp("root"),
@@ -408,6 +410,22 @@ Delegate=memory pids cpu io
},
})
+ // Set machine marker file to indicate podman is in a qemu based machine
+ files = append(files, File{
+ Node: Node{
+ Group: getNodeGrp("root"),
+ Path: "/etc/containers/podman-machine",
+ User: getNodeUsr("root"),
+ },
+ FileEmbedded1: FileEmbedded1{
+ Append: nil,
+ Contents: Resource{
+ Source: encodeDataURLPtr("qemu\n"),
+ },
+ Mode: intToPtr(0644),
+ },
+ })
+
// Issue #11489: make sure that we can inject a custom registries.conf
// file on the system level to force a single search registry.
// The remote client does not yet support prompting for short-name
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index 840bd5c59..9473eef6f 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -57,8 +57,8 @@ type MachineVMV1 struct {
QMPMonitor Monitorv1
// RemoteUsername of the vm user
RemoteUsername string
- // Whether this machine should run in a rootfull or rootless manner
- Rootfull bool
+ // Whether this machine should run in a rootful or rootless manner
+ Rootful bool
// UID is the numerical id of the user that called machine
UID int
}
@@ -105,8 +105,8 @@ type ImageConfig struct {
// HostUser describes the host user
type HostUser struct {
- // Whether this machine should run in a rootfull or rootless manner
- Rootfull bool
+ // Whether this machine should run in a rootful or rootless manner
+ Rootful bool
// UID is the numerical id of the user that called machine
UID int
}
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index c54d18a4b..969acb760 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -76,7 +76,6 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
return nil, err
}
vm.IgnitionFilePath = *ignitionFile
-
imagePath, err := NewMachineFile(opts.ImagePath, nil)
if err != nil {
return nil, err
@@ -206,7 +205,7 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error {
vm.QMPMonitor = qmpMonitor
vm.ReadySocket = readySocket
vm.RemoteUsername = old.RemoteUsername
- vm.Rootfull = old.Rootfull
+ vm.Rootful = old.Rootful
vm.UID = old.UID
// Backup the original config file
@@ -260,7 +259,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
)
sshDir := filepath.Join(homedir.Get(), ".ssh")
v.IdentityPath = filepath.Join(sshDir, v.Name)
- v.Rootfull = opts.Rootfull
+ v.Rootful = opts.Rootful
switch opts.ImagePath {
case Testing, Next, Stable, "":
@@ -358,8 +357,8 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
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 rootfull
- if opts.Rootfull {
+ // 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]
}
@@ -375,7 +374,6 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err := v.writeConfig(); err != nil {
return false, fmt.Errorf("writing JSON file: %w", err)
}
-
// User has provided ignition file so keygen
// will be skipped.
if len(opts.IgnitionPath) < 1 {
@@ -389,7 +387,6 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err := v.prepare(); err != nil {
return false, err
}
-
originalDiskSize, err := getDiskSize(v.getImageFile())
if err != nil {
return false, err
@@ -437,7 +434,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
}
func (v *MachineVM) Set(_ string, opts machine.SetOptions) error {
- if v.Rootfull == opts.Rootfull {
+ if v.Rootful == opts.Rootful {
return nil
}
@@ -461,7 +458,7 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) error {
if changeCon {
newDefault := v.Name
- if opts.Rootfull {
+ if opts.Rootful {
newDefault += "-root"
}
if err := machine.ChangeDefault(newDefault); err != nil {
@@ -469,7 +466,7 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) error {
}
}
- v.Rootfull = opts.Rootfull
+ v.Rootful = opts.Rootful
return v.writeConfig()
}
@@ -528,17 +525,28 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
time.Sleep(wait)
wait++
}
+ defer qemuSocketConn.Close()
if err != nil {
return err
}
-
fd, err := qemuSocketConn.(*net.UnixConn).File()
if err != nil {
return err
}
+ defer fd.Close()
+ dnr, err := os.OpenFile("/dev/null", os.O_RDONLY, 0755)
+ if err != nil {
+ return err
+ }
+ defer dnr.Close()
+ dnw, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755)
+ if err != nil {
+ return err
+ }
+ defer dnw.Close()
attr := new(os.ProcAttr)
- files := []*os.File{os.Stdin, os.Stdout, os.Stderr, fd}
+ files := []*os.File{dnr, dnw, dnw, fd}
attr.Files = files
logrus.Debug(v.CmdLine)
cmd := v.CmdLine
@@ -566,7 +574,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
}
_, err = os.StartProcess(cmd[0], cmd, attr)
if err != nil {
- return err
+ return errors.Wrapf(err, "unable to execute %q", cmd)
}
}
fmt.Println("Waiting for VM ...")
@@ -589,11 +597,11 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
if err != nil {
return err
}
+ defer conn.Close()
_, err = bufio.NewReader(conn).ReadString('\n')
if err != nil {
return err
}
-
if len(v.Mounts) > 0 {
state, err := v.State(true)
if err != nil {
@@ -944,7 +952,7 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error {
sshDestination := username + "@localhost"
port := strconv.Itoa(v.Port)
- args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile /dev/null", "-o", "StrictHostKeyChecking no"}
+ args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no"}
if len(opts.Args) > 0 {
args = append(args, opts.Args...)
} else {
@@ -1120,9 +1128,19 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
}
attr := new(os.ProcAttr)
- // Pass on stdin, stdout, stderr
- files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
- attr.Files = files
+ dnr, err := os.OpenFile("/dev/null", os.O_RDONLY, 0755)
+ if err != nil {
+ return "", noForwarding, err
+ }
+ dnw, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755)
+ if err != nil {
+ return "", noForwarding, err
+ }
+
+ defer dnr.Close()
+ defer dnw.Close()
+
+ attr.Files = []*os.File{dnr, dnw, dnw}
cmd := []string{binary}
cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", v.QMPMonitor.Address.GetPath()), "-pid-file", v.PidFilePath.GetPath()}...)
// Add the ssh port
@@ -1139,7 +1157,7 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
fmt.Println(cmd)
}
_, err = os.StartProcess(cmd[0], cmd, attr)
- return forwardSock, state, err
+ return forwardSock, state, errors.Wrapf(err, "unable to execute: %q", cmd)
}
func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) {
@@ -1152,7 +1170,7 @@ func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwa
destSock := fmt.Sprintf("/run/user/%d/podman/podman.sock", v.UID)
forwardUser := "core"
- if v.Rootfull {
+ if v.Rootful {
destSock = "/run/podman/podman.sock"
forwardUser = "root"
}
@@ -1358,11 +1376,11 @@ func (v *MachineVM) waitAPIAndPrintInfo(forwardState apiForwardingState, forward
}
waitAndPingAPI(forwardSock)
- if !v.Rootfull {
+ 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")
- fmt.Printf("\n\tpodman machine set --rootfull%s\n\n", suffix)
+ fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
}
fmt.Printf("API forwarding listening on: %s\n", forwardSock)
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
index 6e0453f8f..f57dbd299 100644
--- a/pkg/machine/wsl/machine.go
+++ b/pkg/machine/wsl/machine.go
@@ -165,8 +165,8 @@ type MachineVM struct {
Port int
// RemoteUsername of the vm user
RemoteUsername string
- // Whether this machine should run in a rootfull or rootless manner
- Rootfull bool
+ // Whether this machine should run in a rootful or rootless manner
+ Rootful bool
}
type ExitCodeError struct {
@@ -232,7 +232,7 @@ 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.Rootfull = opts.Rootfull
+ v.Rootful = opts.Rootful
if err := downloadDistro(v, opts); err != nil {
return false, err
@@ -316,8 +316,8 @@ func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) err
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 rootfull
- if opts.Rootfull {
+ // 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]
}
@@ -448,6 +448,10 @@ func configureSystem(v *MachineVM, dist string) error {
return errors.Wrap(err, "could not create containers.conf for guest OS")
}
+ if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", "echo wsl > /etc/containers/podman-machine"); err != nil {
+ return errors.Wrap(err, "could not create podman-machine file for guest OS")
+ }
+
return nil
}
@@ -733,7 +737,7 @@ func pipeCmdPassThrough(name string, input string, arg ...string) error {
}
func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
- if v.Rootfull == opts.Rootfull {
+ if v.Rootful == opts.Rootful {
return nil
}
@@ -744,7 +748,7 @@ func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
if changeCon {
newDefault := v.Name
- if opts.Rootfull {
+ if opts.Rootful {
newDefault += "-root"
}
if err := machine.ChangeDefault(newDefault); err != nil {
@@ -752,7 +756,7 @@ func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
}
}
- v.Rootfull = opts.Rootfull
+ v.Rootful = opts.Rootful
return v.writeConfig()
}
@@ -768,7 +772,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return errors.Wrap(err, "WSL bootstrap script failed")
}
- if !v.Rootfull {
+ 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")
@@ -777,7 +781,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
if name != machine.DefaultMachineName {
suffix = " " + name
}
- fmt.Printf("\n\tpodman machine set --rootfull%s\n\n", suffix)
+ fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
}
globalName, pipeName, err := launchWinProxy(v)
@@ -833,7 +837,7 @@ func launchWinProxy(v *MachineVM) (bool, string, error) {
destSock := "/run/user/1000/podman/podman.sock"
forwardUser := v.RemoteUsername
- if v.Rootfull {
+ if v.Rootful {
destSock = "/run/podman/podman.sock"
forwardUser = "root"
}
diff --git a/pkg/signal/signal_common_test.go b/pkg/signal/signal_common_test.go
new file mode 100644
index 000000000..c4ae6b389
--- /dev/null
+++ b/pkg/signal/signal_common_test.go
@@ -0,0 +1,120 @@
+package signal
+
+import (
+ "syscall"
+ "testing"
+)
+
+func TestParseSignal(t *testing.T) {
+ type args struct {
+ rawSignal string
+ }
+ tests := []struct {
+ name string
+ args args
+ want syscall.Signal
+ wantErr bool
+ }{
+ {
+ name: "KILL to SIGKILL",
+ args: args{
+ rawSignal: "KILL",
+ },
+ want: syscall.SIGKILL,
+ wantErr: false,
+ },
+ {
+ name: "Case doesnt matter",
+ args: args{
+ rawSignal: "kIlL",
+ },
+ want: syscall.SIGKILL,
+ wantErr: false,
+ },
+ {
+ name: "Garbage signal",
+ args: args{
+ rawSignal: "FOO",
+ },
+ want: -1,
+ wantErr: true,
+ },
+ {
+ name: "Signal with prepended SIG",
+ args: args{
+ rawSignal: "SIGKILL",
+ },
+ want: syscall.SIGKILL,
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := ParseSignal(tt.args.rawSignal)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("ParseSignal() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("ParseSignal() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestParseSignalNameOrNumber(t *testing.T) {
+ type args struct {
+ rawSignal string
+ }
+ tests := []struct {
+ name string
+ args args
+ want syscall.Signal
+ wantErr bool
+ }{
+ {
+ name: "Kill should work",
+ args: args{
+ rawSignal: "kill",
+ },
+ want: syscall.SIGKILL,
+ wantErr: false,
+ },
+ {
+ name: "9 for kill should work",
+ args: args{
+ rawSignal: "9",
+ },
+ want: syscall.SIGKILL,
+ wantErr: false,
+ },
+ {
+ name: "Non-defined signal number should work",
+ args: args{
+ rawSignal: "923",
+ },
+ want: 923,
+ wantErr: false,
+ },
+ {
+ name: "garbage should fail",
+ args: args{
+ rawSignal: "foo",
+ },
+ want: -1,
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := ParseSignalNameOrNumber(tt.args.rawSignal)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("ParseSignalNameOrNumber() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("ParseSignalNameOrNumber() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 021b88280..50454cbab 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -146,13 +146,13 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
options = append(options, libpod.WithHostUsers(s.HostUsers))
}
- command, err := makeCommand(ctx, s, imageData, rtc)
+ command, err := makeCommand(s, imageData, rtc)
if err != nil {
return nil, nil, nil, err
}
infraVol := (len(compatibleOptions.Mounts) > 0 || len(compatibleOptions.Volumes) > 0 || len(compatibleOptions.ImageVolumes) > 0 || len(compatibleOptions.OverlayVolumes) > 0)
- opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, imageData, command, infraVol, *compatibleOptions)
+ opts, err := createContainerOptions(rt, s, pod, finalVolumes, finalOverlays, imageData, command, infraVol, *compatibleOptions)
if err != nil {
return nil, nil, nil, err
}
@@ -251,7 +251,7 @@ func isCDIDevice(device string) bool {
return cdi.IsQualifiedName(device)
}
-func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, imageData *libimage.ImageData, command []string, infraVolumes bool, compatibleOptions libpod.InfraInherit) ([]libpod.CtrCreateOption, error) {
+func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, imageData *libimage.ImageData, command []string, infraVolumes bool, compatibleOptions libpod.InfraInherit) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
@@ -453,7 +453,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
options = append(options, libpod.WithPrivileged(s.Privileged))
// Get namespace related options
- namespaceOpts, err := namespaceOptions(ctx, s, rt, pod, imageData)
+ namespaceOpts, err := namespaceOptions(s, rt, pod, imageData)
if err != nil {
return nil, err
}
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index d8d1ae652..2362f61c4 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -1,7 +1,6 @@
package generate
import (
- "context"
"fmt"
"os"
"strings"
@@ -80,7 +79,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod)
// joining a pod.
// TODO: Consider grouping options that are not directly attached to a namespace
// elsewhere.
-func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, imageData *libimage.ImageData) ([]libpod.CtrCreateOption, error) {
+func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, imageData *libimage.ImageData) ([]libpod.CtrCreateOption, error) {
toReturn := []libpod.CtrCreateOption{}
// If pod is not nil, get infra container.
@@ -256,7 +255,7 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
}
toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr))
case specgen.Slirp:
- portMappings, expose, err := createPortMappings(ctx, s, imageData)
+ portMappings, expose, err := createPortMappings(s, imageData)
if err != nil {
return nil, err
}
@@ -268,7 +267,7 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
case specgen.Private:
fallthrough
case specgen.Bridge:
- portMappings, expose, err := createPortMappings(ctx, s, imageData)
+ portMappings, expose, err := createPortMappings(s, imageData)
if err != nil {
return nil, err
}
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 961cea933..95bcea8f0 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -32,7 +32,7 @@ func setProcOpts(s *specgen.SpecGenerator, g *generate.Generator) {
}
}
-func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error {
+func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) {
var (
isRootless = rootless.IsRootless()
nofileSet = false
@@ -41,7 +41,7 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error {
if s.Rlimits == nil {
g.Config.Process.Rlimits = nil
- return nil
+ return
}
for _, u := range s.Rlimits {
@@ -91,12 +91,10 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error {
}
g.AddProcessRlimits("RLIMIT_NPROC", max, current)
}
-
- return nil
}
// Produce the final command for the container.
-func makeCommand(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData, rtc *config.Config) ([]string, error) {
+func makeCommand(s *specgen.SpecGenerator, imageData *libimage.ImageData, rtc *config.Config) ([]string, error) {
finalCommand := []string{}
entrypoint := s.Entrypoint
@@ -388,9 +386,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
g.AddProcessEnv(name, val)
}
- if err := addRlimits(s, &g); err != nil {
- return nil, err
- }
+ addRlimits(s, &g)
// NAMESPACES
if err := specConfigureNamespaces(s, &g, rt, pod); err != nil {
diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go
index ba823f3a8..a3408b402 100644
--- a/pkg/specgen/generate/pod_create.go
+++ b/pkg/specgen/generate/pod_create.go
@@ -119,7 +119,7 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) {
}
}
- options, err := createPodOptions(&p.PodSpecGen, rt, p.PodSpecGen.InfraContainerSpec)
+ options, err := createPodOptions(&p.PodSpecGen)
if err != nil {
return nil, err
}
@@ -161,11 +161,11 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) {
return pod, nil
}
-func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime, infraSpec *specgen.SpecGenerator) ([]libpod.PodCreateOption, error) {
+func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, error) {
var (
options []libpod.PodCreateOption
)
- if !p.NoInfra { //&& infraSpec != nil {
+ if !p.NoInfra {
options = append(options, libpod.WithInfraContainer())
if p.ShareParent == nil || (p.ShareParent != nil && *p.ShareParent) {
options = append(options, libpod.WithPodParent())
diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go
index c30c4e49d..bec548d3b 100644
--- a/pkg/specgen/generate/ports.go
+++ b/pkg/specgen/generate/ports.go
@@ -1,7 +1,6 @@
package generate
import (
- "context"
"fmt"
"net"
"sort"
@@ -338,7 +337,7 @@ func appendProtocolsNoDuplicates(slice []string, protocols []string) []string {
}
// Make final port mappings for the container
-func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]types.PortMapping, map[uint16][]string, error) {
+func createPortMappings(s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]types.PortMapping, map[uint16][]string, error) {
expose := make(map[uint16]string)
var err error
if imageData != nil {
diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go
index eca8c0c35..b26666df3 100644
--- a/pkg/specgen/volumes.go
+++ b/pkg/specgen/volumes.go
@@ -65,7 +65,7 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
err error
)
- splitVol := strings.Split(vol, ":")
+ splitVol := SplitVolumeString(vol)
if len(splitVol) > 3 {
return nil, nil, nil, errors.Wrapf(volumeFormatErr, vol)
}
@@ -93,7 +93,7 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
}
}
- if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
+ if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") || isHostWinPath(src) {
// This is not a named volume
overlayFlag := false
chownFlag := false
@@ -152,3 +152,26 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
return mounts, volumes, overlayVolumes, nil
}
+
+// Splits a volume string, accounting for Win drive paths
+// when running as a WSL linux guest or Windows client
+func SplitVolumeString(vol string) []string {
+ parts := strings.Split(vol, ":")
+ if !shouldResolveWinPaths() {
+ return parts
+ }
+
+ // Skip extended marker prefix if present
+ n := 0
+ if strings.HasPrefix(vol, `\\?\`) {
+ n = 4
+ }
+
+ if hasWinDriveScheme(vol, n) {
+ first := parts[0] + ":" + parts[1]
+ parts = parts[1:]
+ parts[0] = first
+ }
+
+ return parts
+}
diff --git a/pkg/specgen/winpath.go b/pkg/specgen/winpath.go
new file mode 100644
index 000000000..f4249fab1
--- /dev/null
+++ b/pkg/specgen/winpath.go
@@ -0,0 +1,59 @@
+package specgen
+
+import (
+ "fmt"
+ "strings"
+ "unicode"
+
+ "github.com/pkg/errors"
+)
+
+func isHostWinPath(path string) bool {
+ return shouldResolveWinPaths() && strings.HasPrefix(path, `\\`) || hasWinDriveScheme(path, 0) || winPathExists(path)
+}
+
+func hasWinDriveScheme(path string, start int) bool {
+ if len(path) < start+2 || path[start+1] != ':' {
+ return false
+ }
+
+ drive := rune(path[start])
+ return drive < unicode.MaxASCII && unicode.IsLetter(drive)
+}
+
+// Converts a Windows path to a WSL guest path if local env is a WSL linux guest or this is a Windows client.
+func ConvertWinMountPath(path string) (string, error) {
+ if !shouldResolveWinPaths() {
+ return path, nil
+ }
+
+ if strings.HasPrefix(path, "/") {
+ // Handle /[driveletter]/windows/path form (e.g. c:\Users\bar == /c/Users/bar)
+ if len(path) > 2 && path[2] == '/' && shouldResolveUnixWinVariant(path) {
+ drive := unicode.ToLower(rune(path[1]))
+ if unicode.IsLetter(drive) && drive <= unicode.MaxASCII {
+ return fmt.Sprintf("/mnt/%c/%s", drive, path[3:]), nil
+ }
+ }
+
+ // unix path - pass through
+ return path, nil
+ }
+
+ // Convert remote win client relative paths to absolute
+ path = resolveRelativeOnWindows(path)
+
+ // Strip extended marker prefix if present
+ path = strings.TrimPrefix(path, `\\?\`)
+
+ // Drive installed via wsl --mount
+ if strings.HasPrefix(path, `\\.\`) {
+ path = "/mnt/wsl/" + path[4:]
+ } else if len(path) > 1 && path[1] == ':' {
+ path = "/mnt/" + strings.ToLower(path[0:1]) + path[2:]
+ } else {
+ return path, errors.New("unsupported UNC path")
+ }
+
+ return strings.ReplaceAll(path, `\`, "/"), nil
+}
diff --git a/pkg/specgen/winpath_linux.go b/pkg/specgen/winpath_linux.go
new file mode 100644
index 000000000..f42ac7639
--- /dev/null
+++ b/pkg/specgen/winpath_linux.go
@@ -0,0 +1,24 @@
+package specgen
+
+import (
+ "os"
+
+ "github.com/containers/common/pkg/machine"
+)
+
+func shouldResolveWinPaths() bool {
+ return machine.MachineHostType() == "wsl"
+}
+
+func shouldResolveUnixWinVariant(path string) bool {
+ _, err := os.Stat(path)
+ return err != nil
+}
+
+func resolveRelativeOnWindows(path string) string {
+ return path
+}
+
+func winPathExists(path string) bool {
+ return false
+}
diff --git a/pkg/specgen/winpath_unsupported.go b/pkg/specgen/winpath_unsupported.go
new file mode 100644
index 000000000..4cd008fdd
--- /dev/null
+++ b/pkg/specgen/winpath_unsupported.go
@@ -0,0 +1,20 @@
+//go:build !linux && !windows
+// +build !linux,!windows
+
+package specgen
+
+func shouldResolveWinPaths() bool {
+ return false
+}
+
+func shouldResolveUnixWinVariant(path string) bool {
+ return false
+}
+
+func resolveRelativeOnWindows(path string) string {
+ return path
+}
+
+func winPathExists(path string) bool {
+ return false
+}
diff --git a/pkg/specgen/winpath_windows.go b/pkg/specgen/winpath_windows.go
new file mode 100644
index 000000000..c6aad314a
--- /dev/null
+++ b/pkg/specgen/winpath_windows.go
@@ -0,0 +1,30 @@
+package specgen
+
+import (
+ "github.com/sirupsen/logrus"
+ "os"
+ "path/filepath"
+)
+
+func shouldResolveUnixWinVariant(path string) bool {
+ return true
+}
+
+func shouldResolveWinPaths() bool {
+ return true
+}
+
+func resolveRelativeOnWindows(path string) string {
+ ret, err := filepath.Abs(path)
+ if err != nil {
+ logrus.Debugf("problem resolving possible relative path %q: %s", path, err.Error())
+ return path
+ }
+
+ return ret
+}
+
+func winPathExists(path string) bool {
+ _, err := os.Stat(path)
+ return err == nil
+}
diff --git a/pkg/specgenutil/ports_test.go b/pkg/specgenutil/ports_test.go
new file mode 100644
index 000000000..3f62c619c
--- /dev/null
+++ b/pkg/specgenutil/ports_test.go
@@ -0,0 +1,57 @@
+package specgenutil
+
+import "testing"
+
+func Test_verifyExpose(t *testing.T) {
+ type args struct {
+ expose []string
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ }{
+ {
+ name: "single port with tcp",
+ args: args{
+ expose: []string{"53/tcp"},
+ },
+ wantErr: false,
+ },
+ {
+ name: "single port with udp",
+ args: args{
+ expose: []string{"53/udp"},
+ },
+ wantErr: false,
+ },
+ {
+ name: "good port range",
+ args: args{
+ expose: []string{"100-133"},
+ },
+ wantErr: false,
+ },
+ {
+ name: "high to low should fail",
+ args: args{
+ expose: []string{"100-99"},
+ },
+ wantErr: true,
+ },
+ {
+ name: "range with protocol",
+ args: args{
+ expose: []string{"53/tcp-55/tcp"},
+ },
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if err := verifyExpose(tt.args.expose); (err != nil) != tt.wantErr {
+ t.Errorf("verifyExpose() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go
index 00de99817..f0dfcac1a 100644
--- a/pkg/specgenutil/specgen.go
+++ b/pkg/specgenutil/specgen.go
@@ -136,7 +136,7 @@ func LimitToSwap(memory *specs.LinuxMemory, swap string, ml int64) {
}
}
-func getMemoryLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxMemory, error) {
+func getMemoryLimits(c *entities.ContainerCreateOptions) (*specs.LinuxMemory, error) {
var err error
memory := &specs.LinuxMemory{}
hasLimits := false
@@ -497,7 +497,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
}
if s.ResourceLimits.Memory == nil || (len(c.Memory) != 0 || len(c.MemoryReservation) != 0 || len(c.MemorySwap) != 0 || c.MemorySwappiness != 0) {
- s.ResourceLimits.Memory, err = getMemoryLimits(s, c)
+ s.ResourceLimits.Memory, err = getMemoryLimits(c)
if err != nil {
return err
}
diff --git a/pkg/specgenutil/specgenutil_test.go b/pkg/specgenutil/specgenutil_test.go
new file mode 100644
index 000000000..5867b0ae0
--- /dev/null
+++ b/pkg/specgenutil/specgenutil_test.go
@@ -0,0 +1,77 @@
+//go:build linux
+// +build linux
+
+package specgenutil
+
+import (
+ "testing"
+
+ "github.com/containers/common/pkg/machine"
+ "github.com/containers/podman/v4/pkg/domain/entities"
+ "github.com/containers/podman/v4/pkg/specgen"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestWinPath(t *testing.T) {
+ const (
+ fail = false
+ pass = true
+ )
+ tests := []struct {
+ vol string
+ source string
+ dest string
+ isN bool
+ outcome bool
+ mach string
+ }{
+ {`C:\Foo:/blah`, "/mnt/c/Foo", "/blah", false, pass, "wsl"},
+ {`C:\Foo:/blah`, "/mnt/c/Foo", "/blah", false, fail, ""},
+ {`\\?\C:\Foo:/blah`, "/mnt/c/Foo", "/blah", false, pass, "wsl"},
+ {`/c/bar:/blah`, "/mnt/c/bar", "/blah", false, pass, "wsl"},
+ {`/c/bar:/blah`, "/c/bar", "/blah", false, pass, ""},
+ {`/test/this:/blah`, "/test/this", "/blah", false, pass, "wsl"},
+ {`c:/bar/something:/other`, "/mnt/c/bar/something", "/other", false, pass, "wsl"},
+ {`c:/foo:ro`, "c", "/foo", true, pass, ""},
+ {`\\computer\loc:/dest`, "", "", false, fail, "wsl"},
+ {`\\.\drive\loc:/target`, "/mnt/wsl/drive/loc", "/target", false, pass, "wsl"},
+ }
+
+ f := func(vol string, mach string) (*specgen.SpecGenerator, error) {
+ machine := machine.GetMachineMarker()
+ oldEnable, oldType := machine.Enabled, machine.Type
+ machine.Enabled, machine.Type = len(mach) > 0, mach
+ sg := specgen.NewSpecGenerator("nothing", false)
+ err := FillOutSpecGen(sg, &entities.ContainerCreateOptions{
+ ImageVolume: "ignore",
+ Volume: []string{vol}}, []string{},
+ )
+ machine.Enabled, machine.Type = oldEnable, oldType
+ return sg, err
+ }
+
+ for _, test := range tests {
+ msg := "Checking: " + test.vol
+ sg, err := f(test.vol, test.mach)
+ if test.outcome == fail {
+ assert.NotNil(t, err, msg)
+ continue
+ }
+ if !assert.Nil(t, err, msg) {
+ continue
+ }
+ if test.isN {
+ if !assert.Equal(t, 1, len(sg.Volumes), msg) {
+ continue
+ }
+ assert.Equal(t, test.source, sg.Volumes[0].Name, msg)
+ assert.Equal(t, test.dest, sg.Volumes[0].Dest, msg)
+ } else {
+ if !assert.Equal(t, 1, len(sg.Mounts), msg) {
+ continue
+ }
+ assert.Equal(t, test.source, sg.Mounts[0].Source, msg)
+ assert.Equal(t, test.dest, sg.Mounts[0].Destination, msg)
+ }
+ }
+}
diff --git a/pkg/specgenutil/util.go b/pkg/specgenutil/util.go
index 80d31398b..fa2e90457 100644
--- a/pkg/specgenutil/util.go
+++ b/pkg/specgenutil/util.go
@@ -281,6 +281,7 @@ func CreateExitCommandArgs(storageConfig storageTypes.StoreOptions, config *conf
"--tmpdir", config.Engine.TmpDir,
"--network-config-dir", config.Network.NetworkConfigDir,
"--network-backend", config.Network.NetworkBackend,
+ "--volumepath", config.Engine.VolumePath,
}
if config.Engine.OCIRuntime != "" {
command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...)
diff --git a/pkg/specgenutil/util_test.go b/pkg/specgenutil/util_test.go
new file mode 100644
index 000000000..79d60d335
--- /dev/null
+++ b/pkg/specgenutil/util_test.go
@@ -0,0 +1,146 @@
+package specgenutil
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestCreateExpose(t *testing.T) {
+ single := make(map[uint16]string, 0)
+ single[99] = "tcp"
+
+ simpleRange := make(map[uint16]string, 0)
+ simpleRange[99] = "tcp"
+ simpleRange[100] = "tcp"
+
+ simpleRangeUDP := make(map[uint16]string, 0)
+ simpleRangeUDP[99] = "udp"
+ simpleRangeUDP[100] = "udp"
+ type args struct {
+ expose []string
+ }
+ tests := []struct {
+ name string
+ args args
+ want map[uint16]string
+ wantErr bool
+ }{
+ {
+ name: "single port",
+ args: args{
+ expose: []string{"99"},
+ },
+ want: single,
+ wantErr: false,
+ },
+ {
+ name: "simple range tcp",
+ args: args{
+ expose: []string{"99-100"},
+ },
+ want: simpleRange,
+ wantErr: false,
+ },
+ {
+ name: "simple range udp",
+ args: args{
+ expose: []string{"99-100/udp"},
+ },
+ want: simpleRangeUDP,
+ wantErr: false,
+ },
+ {
+ name: "range inverted should fail",
+ args: args{
+ expose: []string{"100-99"},
+ },
+ want: nil,
+ wantErr: true,
+ },
+ {
+ name: "specifying protocol twice should fail",
+ args: args{
+ expose: []string{"99/tcp-100/tcp"},
+ },
+ want: nil,
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := CreateExpose(tt.args.expose)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("CreateExpose() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("CreateExpose() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_parseAndValidatePort(t *testing.T) {
+ type args struct {
+ port string
+ }
+ tests := []struct {
+ name string
+ args args
+ want uint16
+ wantErr bool
+ }{
+ {
+ name: "0 should fail",
+ args: args{
+ port: "0",
+ },
+ want: 0,
+ wantErr: true,
+ },
+ {
+ name: "over 65535 should fail",
+ args: args{
+ port: "66666",
+ },
+ want: 0,
+ wantErr: true,
+ },
+ {
+ name: "",
+ args: args{
+ port: "99",
+ },
+ want: 99,
+ wantErr: false,
+ },
+ {
+ name: "negative values should fail",
+ args: args{
+ port: "-1",
+ },
+ want: 0,
+ wantErr: true,
+ },
+ {
+ name: "protocol should fail",
+ args: args{
+ port: "99/tcp",
+ },
+ want: 0,
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := parseAndValidatePort(tt.args.port)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("parseAndValidatePort() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("parseAndValidatePort() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/pkg/specgenutil/volumes.go b/pkg/specgenutil/volumes.go
index 95ce420f8..50d745380 100644
--- a/pkg/specgenutil/volumes.go
+++ b/pkg/specgenutil/volumes.go
@@ -3,7 +3,7 @@ package specgenutil
import (
"encoding/csv"
"fmt"
- "path/filepath"
+ "path"
"strings"
"github.com/containers/common/pkg/parse"
@@ -123,7 +123,7 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo
finalMounts := make([]spec.Mount, 0, len(unifiedMounts))
for _, mount := range unifiedMounts {
if mount.Type == define.TypeBind {
- absSrc, err := filepath.Abs(mount.Source)
+ absSrc, err := specgen.ConvertWinMountPath(mount.Source)
if err != nil {
return nil, nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
}
@@ -334,7 +334,7 @@ func getBindMount(args []string) (spec.Mount, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return newMount, err
}
- newMount.Destination = filepath.Clean(kv[1])
+ newMount.Destination = unixPathClean(kv[1])
setDest = true
case "relabel":
if setRelabel {
@@ -456,7 +456,7 @@ func getTmpfsMount(args []string) (spec.Mount, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return newMount, err
}
- newMount.Destination = filepath.Clean(kv[1])
+ newMount.Destination = unixPathClean(kv[1])
setDest = true
case "U", "chown":
if setOwnership {
@@ -507,7 +507,7 @@ func getDevptsMount(args []string) (spec.Mount, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return newMount, err
}
- newMount.Destination = filepath.Clean(kv[1])
+ newMount.Destination = unixPathClean(kv[1])
setDest = true
default:
return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
@@ -572,7 +572,7 @@ func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return nil, err
}
- newVolume.Dest = filepath.Clean(kv[1])
+ newVolume.Dest = unixPathClean(kv[1])
setDest = true
case "U", "chown":
if setOwnership {
@@ -624,7 +624,7 @@ func getImageVolume(args []string) (*specgen.ImageVolume, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return nil, err
}
- newVolume.Destination = filepath.Clean(kv[1])
+ newVolume.Destination = unixPathClean(kv[1])
case "rw", "readwrite":
switch kv[1] {
case "true":
@@ -670,7 +670,7 @@ func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) {
}
mount := spec.Mount{
- Destination: filepath.Clean(destPath),
+ Destination: unixPathClean(destPath),
Type: define.TypeTmpfs,
Options: options,
Source: define.TypeTmpfs,
@@ -700,3 +700,8 @@ func validChownFlag(flag string) (bool, error) {
return true, nil
}
+
+// Use path instead of filepath to preserve Unix style paths on Windows
+func unixPathClean(p string) string {
+ return path.Clean(p)
+}
diff --git a/pkg/specgenutil/volumes_test.go b/pkg/specgenutil/volumes_test.go
new file mode 100644
index 000000000..fc6caf83c
--- /dev/null
+++ b/pkg/specgenutil/volumes_test.go
@@ -0,0 +1,68 @@
+package specgenutil
+
+import "testing"
+
+func Test_validChownFlag(t *testing.T) {
+ type args struct {
+ flag string
+ }
+ tests := []struct {
+ name string
+ args args
+ want bool
+ wantErr bool
+ }{
+ {
+ name: "U true",
+ args: args{
+ flag: "U=true",
+ },
+ want: true,
+ wantErr: false,
+ },
+ {
+ name: "U true case doesnt matter",
+ args: args{
+ flag: "u=True",
+ },
+ want: true,
+ wantErr: false,
+ },
+ {
+ name: "U is false",
+ args: args{
+ flag: "U=false",
+ },
+ want: false,
+ wantErr: false,
+ },
+ {
+ name: "chown should also work",
+ args: args{
+ flag: "chown=true",
+ },
+ want: true,
+ wantErr: false,
+ },
+ {
+ name: "garbage value should fail",
+ args: args{
+ flag: "U=foobar",
+ },
+ want: false,
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := validChownFlag(tt.args.flag)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("validChownFlag() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("validChownFlag() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}