Merge pull request #13908 from n1hility/win-mounts

Implement Windows volume/mount support
This commit is contained in:
OpenShift Merge Robot
2022-04-26 08:38:33 -04:00
committed by GitHub
22 changed files with 387 additions and 31 deletions

View File

@ -347,7 +347,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c
cliOpts.Volume = append(cliOpts.Volume, vol)
// Extract the destination so we don't add duplicate mounts in
// the volumes phase.
splitVol := strings.SplitN(vol, ":", 3)
splitVol := specgen.SplitVolumeString(vol)
switch len(splitVol) {
case 1:
volDestinations[vol] = true

2
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/containernetworking/cni v1.0.1
github.com/containernetworking/plugins v1.1.1
github.com/containers/buildah v1.25.2-0.20220406205807-5b8e79118057
github.com/containers/common v0.47.5-0.20220421111103-112a47964ddb
github.com/containers/common v0.47.5-0.20220425182415-4081e6be9356
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.21.1-0.20220421124950-8527e238867c
github.com/containers/ocicrypt v1.1.3

4
go.sum
View File

@ -357,8 +357,8 @@ github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19
github.com/containers/buildah v1.25.2-0.20220406205807-5b8e79118057 h1:lKSxhMBpcHyyQrj2QJYzcm56uiSeibRdSL2KoppF6rg=
github.com/containers/buildah v1.25.2-0.20220406205807-5b8e79118057/go.mod h1:iSoopbYRb6K4b5c3hXgXNkGTI/T085t2+XiGjceud94=
github.com/containers/common v0.47.5-0.20220331143923-5f14ec785c18/go.mod h1:Vr2Fn6EdzD6JNAbz8L8bTv3uWLv2p31Ih2O3EAK6Hyc=
github.com/containers/common v0.47.5-0.20220421111103-112a47964ddb h1:TBrx1KcmWcesByqTb4Cq7F6bg7bDOjqCf6+6rbi8x4k=
github.com/containers/common v0.47.5-0.20220421111103-112a47964ddb/go.mod h1:r80nWTmJrG9EoLkuI6WfbWQDUNQVqkVuB8Oaj1VVjOA=
github.com/containers/common v0.47.5-0.20220425182415-4081e6be9356 h1:eJ1ghvyswTLRywF4YYEWrzZyOFEzlD1FUPLzJSz+wKo=
github.com/containers/common v0.47.5-0.20220425182415-4081e6be9356/go.mod h1:r80nWTmJrG9EoLkuI6WfbWQDUNQVqkVuB8Oaj1VVjOA=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.19.2-0.20220224100137-1045fb70b094/go.mod h1:XoYK6kE0dpazFNcuS+a8lra+QfbC6s8tzv+cUuCrZpE=

View File

@ -23,6 +23,7 @@ import (
"github.com/containers/common/libnetwork/etchosts"
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/machine"
"github.com/containers/common/pkg/netns"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/libpod/events"
@ -62,7 +63,7 @@ const (
// This is need because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports.
// For machine the HostIP must only be used by gvproxy and never in the VM.
func (c *Container) convertPortMappings() []types.PortMapping {
if !c.runtime.config.Engine.MachineEnabled || len(c.config.PortMappings) == 0 {
if !machine.IsGvProxyBased() || len(c.config.PortMappings) == 0 {
return c.config.PortMappings
}
// if we run in a machine VM we have to ignore the host IP part

View File

@ -14,6 +14,7 @@ import (
"time"
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/machine"
"github.com/sirupsen/logrus"
)
@ -117,7 +118,7 @@ func annotateGvproxyResponseError(r io.Reader) error {
// exposeMachinePorts exposes the ports for podman machine via gvproxy
func (r *Runtime) exposeMachinePorts(ports []types.PortMapping) error {
if !r.config.Engine.MachineEnabled {
if !machine.IsGvProxyBased() {
return nil
}
return requestMachinePorts(true, ports)
@ -125,7 +126,7 @@ func (r *Runtime) exposeMachinePorts(ports []types.PortMapping) error {
// unexposeMachinePorts closes the ports for podman machine via gvproxy
func (r *Runtime) unexposeMachinePorts(ports []types.PortMapping) error {
if !r.config.Engine.MachineEnabled {
if !machine.IsGvProxyBased() {
return nil
}
return requestMachinePorts(false, ports)

View File

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

View File

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

View File

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

59
pkg/specgen/winpath.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import (
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/libnetwork/util"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/machine"
"github.com/containers/storage/pkg/unshare"
)
@ -15,8 +16,8 @@ func GetHostContainersInternalIP(conf *config.Config, netStatus map[string]types
switch conf.Containers.HostContainersInternalIP {
case "":
// if empty (default) we will automatically choose one below
// if machine we let the gvproxy dns server handle the dns name so do not add it
if conf.Engine.MachineEnabled {
// if machine using gvproxy we let the gvproxy dns server handle the dns name so do not add it
if machine.IsGvProxyBased() {
return ""
}
case "none":

View File

@ -27,7 +27,7 @@ type netavarkNetwork struct {
// networkRunDir is where temporary files are stored, i.e.the ipam db, aardvark config etc
networkRunDir string
// tells netavark whether this is rootless mode or rootfull, "true" or "false"
// tells netavark whether this is rootless mode or rootful, "true" or "false"
networkRootless bool
// netavarkBinary is the path to the netavark binary.

View File

@ -14,6 +14,7 @@ import (
"github.com/containers/common/libnetwork/netavark"
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/machine"
"github.com/containers/storage"
"github.com/containers/storage/pkg/homedir"
"github.com/containers/storage/pkg/ioutils"
@ -173,7 +174,7 @@ func getCniInterface(conf *config.Config) (types.ContainerNetwork, error) {
DefaultNetwork: conf.Network.DefaultNetwork,
DefaultSubnet: conf.Network.DefaultSubnet,
DefaultsubnetPools: conf.Network.DefaultSubnetPools,
IsMachine: conf.Engine.MachineEnabled,
IsMachine: machine.IsGvProxyBased(),
})
}

View File

@ -312,6 +312,8 @@ type EngineConfig struct {
LockType string `toml:"lock_type,omitempty"`
// MachineEnabled indicates if Podman is running in a podman-machine VM
//
// This method is soft deprecated, use machine.IsPodmanMachine instead
MachineEnabled bool `toml:"machine_enabled,omitempty"`
// MultiImageArchive - if true, the container engine allows for storing

View File

@ -0,0 +1,25 @@
package config
import (
"os"
)
// podman remote clients on freebsd cannot use unshare.isRootless() to determine the configuration file locations.
func customConfigFile() (string, error) {
if path, found := os.LookupEnv("CONTAINERS_CONF"); found {
return path, nil
}
return rootlessConfigPath()
}
func ifRootlessConfigPath() (string, error) {
return rootlessConfigPath()
}
var defaultHelperBinariesDir = []string{
"/usr/local/bin",
"/usr/local/libexec/podman",
"/usr/local/lib/podman",
"/usr/local/libexec/podman",
"/usr/local/lib/podman",
}

View File

@ -455,12 +455,6 @@ default_sysctls = [
#
#lock_type** = "shm"
# Indicates if Podman is running inside a VM via Podman Machine.
# Podman uses this value to do extra setup around networking from the
# container inside the VM to to host.
#
#machine_enabled = false
# MultiImageArchive - if true, the container engine allows for storing archives
# (e.g., of the docker-archive transport) with multiple images. By default,
# Podman creates single-image archives.
@ -572,9 +566,9 @@ default_sysctls = [
# URI to access the Podman service
# Examples:
# rootless "unix://run/user/$UID/podman/podman.sock" (Default)
# rootfull "unix://run/podman/podman.sock (Default)
# rootful "unix://run/podman/podman.sock (Default)
# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
# remote rootfull ssh://root@10.10.1.136:22/run/podman/podman.sock
# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
#
# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
# Path to file containing ssh identity key

View File

@ -0,0 +1,70 @@
package machine
import (
"os"
"strings"
"sync"
"github.com/containers/common/pkg/config"
"github.com/sirupsen/logrus"
)
type MachineMarker struct {
Enabled bool
Type string
}
const (
markerFile = "/etc/containers/podman-machine"
Wsl = "wsl"
Qemu = "qemu"
)
var (
markerSync sync.Once
machineMarker *MachineMarker
)
func loadMachineMarker(file string) {
var kind string
// Support deprecated config value for compatibility
enabled := isLegacyConfigSet()
if content, err := os.ReadFile(file); err == nil {
enabled = true
kind = strings.TrimSpace(string(content))
}
machineMarker = &MachineMarker{enabled, kind}
}
func isLegacyConfigSet() bool {
config, err := config.Default()
if err != nil {
logrus.Warnf("could not obtain container configuration")
return false
}
//nolint:staticcheck //lint:ignore SA1019 deprecated call
return config.Engine.MachineEnabled
}
func IsPodmanMachine() bool {
return GetMachineMarker().Enabled
}
func MachineHostType() string {
return GetMachineMarker().Type
}
func IsGvProxyBased() bool {
return IsPodmanMachine() && MachineHostType() != Wsl
}
func GetMachineMarker() *MachineMarker {
markerSync.Do(func() {
loadMachineMarker(markerFile)
})
return machineMarker
}

3
vendor/modules.txt vendored
View File

@ -109,7 +109,7 @@ github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util
github.com/containers/buildah/util
# github.com/containers/common v0.47.5-0.20220421111103-112a47964ddb
# github.com/containers/common v0.47.5-0.20220425182415-4081e6be9356
## explicit
github.com/containers/common/libimage
github.com/containers/common/libimage/manifests
@ -132,6 +132,7 @@ github.com/containers/common/pkg/config
github.com/containers/common/pkg/download
github.com/containers/common/pkg/filters
github.com/containers/common/pkg/flag
github.com/containers/common/pkg/machine
github.com/containers/common/pkg/manifests
github.com/containers/common/pkg/netns
github.com/containers/common/pkg/parse