Merge pull request #19375 from rhatdan/mount

Add support for mounts listed in containers.conf
This commit is contained in:
Daniel J Walsh
2023-07-27 06:29:24 -04:00
committed by GitHub
12 changed files with 188 additions and 98 deletions

4
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/containernetworking/cni v1.1.2
github.com/containernetworking/plugins v1.3.0
github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6
github.com/containers/common v0.55.1-0.20230724161016-2966c705a7a3
github.com/containers/common v0.55.1-0.20230726131109-ff8298511408
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.26.1-0.20230721194716-30c87d4a5b8d
github.com/containers/libhvee v0.4.0
@ -44,7 +44,7 @@ require (
github.com/moby/term v0.5.0
github.com/nxadm/tail v1.4.8
github.com/onsi/ginkgo/v2 v2.11.0
github.com/onsi/gomega v1.27.9
github.com/onsi/gomega v1.27.10
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc4
github.com/opencontainers/runc v1.1.8

8
go.sum
View File

@ -247,8 +247,8 @@ github.com/containernetworking/plugins v1.3.0 h1:QVNXMT6XloyMUoO2wUOqWTC1hWFV62Q
github.com/containernetworking/plugins v1.3.0/go.mod h1:Pc2wcedTQQCVuROOOaLBPPxrEXqqXBFt3cZ+/yVg6l0=
github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6 h1:K/S8SFQsnnNTF0Ws58SrBD9L0EuClzAG8Zp08d7+6AA=
github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6/go.mod h1:0sptTFBBtSznLqoTh80DfvMOCNbdRsNRgVOKhBhrupA=
github.com/containers/common v0.55.1-0.20230724161016-2966c705a7a3 h1:0fHDAdLNfOs5AuBizE7TOECDA4gCLoCgE6geR3k/H78=
github.com/containers/common v0.55.1-0.20230724161016-2966c705a7a3/go.mod h1:SUX+gHoElocPp664K79AEt+GGHwngBJLrKdwNIRW0tQ=
github.com/containers/common v0.55.1-0.20230726131109-ff8298511408 h1:l1CWncvHE9fXr1oTCbecNCM29pxozduFw3wyALlPpkQ=
github.com/containers/common v0.55.1-0.20230726131109-ff8298511408/go.mod h1:1JS5V5cf9DhEvSGMRfpD4YmTGeyhJ//YUee8qS+8fOw=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.26.1-0.20230721194716-30c87d4a5b8d h1:g6DFcXXEMd1OwSVtbrUolGzmkMNyQDyc4OKHOFxbNeE=
@ -799,8 +799,8 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.27.9 h1:qIyVWbOsvQEye2QCqLsNSeH/5L1RS9vS382erEWfT3o=
github.com/onsi/gomega v1.27.9/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=

View File

@ -714,7 +714,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
// Only add read-only tmpfs mounts in case that we are read-only and the
// read-only tmpfs flag has been set.
mounts, volumes, overlayVolumes, imageVolumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS)
mounts, volumes, overlayVolumes, imageVolumes, err := parseVolumes(rtc, c.Volume, c.Mount, c.TmpFS)
if err != nil {
return err
}

View File

@ -7,6 +7,7 @@ import (
"path"
"strings"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/parse"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/specgen"
@ -26,9 +27,9 @@ var (
// Does not handle image volumes, init, and --volumes-from flags.
// Can also add tmpfs mounts from read-only tmpfs.
// TODO: handle options parsing/processing via containers/storage/pkg/mount
func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, []*specgen.ImageVolume, error) {
func parseVolumes(rtc *config.Config, volumeFlag, mountFlag, tmpfsFlag []string) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, []*specgen.ImageVolume, error) {
// Get mounts from the --mounts flag.
unifiedMounts, unifiedVolumes, unifiedImageVolumes, err := Mounts(mountFlag)
unifiedMounts, unifiedVolumes, unifiedImageVolumes, err := Mounts(mountFlag, rtc.Mounts())
if err != nil {
return nil, nil, nil, nil, err
}
@ -166,71 +167,100 @@ func findMountType(input string) (mountType string, tokens []string, err error)
return
}
// Mounts takes user-provided input from the --mount flag and creates OCI
// spec mounts and Libpod named volumes.
// Mounts takes user-provided input from the --mount flag as well as Mounts
// specified in containers.conf and creates OCI spec mounts and Libpod named volumes.
// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
// podman run --mount type=tmpfs,target=/dev/shm ...
// podman run --mount type=volume,source=test-volume, ...
func Mounts(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.ImageVolume, error) {
func Mounts(mountFlag []string, configMounts []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.ImageVolume, error) {
finalMounts := make(map[string]spec.Mount)
finalNamedVolumes := make(map[string]*specgen.NamedVolume)
finalImageVolumes := make(map[string]*specgen.ImageVolume)
parseMounts := func(mounts []string, ignoreDup bool) error {
for _, mount := range mounts {
// TODO: Docker defaults to "volume" if no mount type is specified.
mountType, tokens, err := findMountType(mount)
if err != nil {
return err
}
switch mountType {
case define.TypeBind:
mount, err := getBindMount(tokens)
if err != nil {
return err
}
if _, ok := finalMounts[mount.Destination]; ok {
if ignoreDup {
continue
}
return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest)
}
finalMounts[mount.Destination] = mount
case define.TypeTmpfs:
mount, err := getTmpfsMount(tokens)
if err != nil {
return err
}
if _, ok := finalMounts[mount.Destination]; ok {
if ignoreDup {
continue
}
return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest)
}
finalMounts[mount.Destination] = mount
case define.TypeDevpts:
mount, err := getDevptsMount(tokens)
if err != nil {
return err
}
if _, ok := finalMounts[mount.Destination]; ok {
if ignoreDup {
continue
}
return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest)
}
finalMounts[mount.Destination] = mount
case "image":
volume, err := getImageVolume(tokens)
if err != nil {
return err
}
if _, ok := finalImageVolumes[volume.Destination]; ok {
if ignoreDup {
continue
}
return fmt.Errorf("%v: %w", volume.Destination, specgen.ErrDuplicateDest)
}
finalImageVolumes[volume.Destination] = volume
case "volume":
volume, err := getNamedVolume(tokens)
if err != nil {
return err
}
if _, ok := finalNamedVolumes[volume.Dest]; ok {
if ignoreDup {
continue
}
return fmt.Errorf("%v: %w", volume.Dest, specgen.ErrDuplicateDest)
}
finalNamedVolumes[volume.Dest] = volume
default:
return fmt.Errorf("invalid filesystem type %q", mountType)
}
}
return nil
}
for _, mount := range mountFlag {
// TODO: Docker defaults to "volume" if no mount type is specified.
mountType, tokens, err := findMountType(mount)
if err != nil {
return nil, nil, nil, err
}
switch mountType {
case define.TypeBind:
mount, err := getBindMount(tokens)
if err != nil {
return nil, nil, nil, err
}
if _, ok := finalMounts[mount.Destination]; ok {
return nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest)
}
finalMounts[mount.Destination] = mount
case define.TypeTmpfs:
mount, err := getTmpfsMount(tokens)
if err != nil {
return nil, nil, nil, err
}
if _, ok := finalMounts[mount.Destination]; ok {
return nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest)
}
finalMounts[mount.Destination] = mount
case define.TypeDevpts:
mount, err := getDevptsMount(tokens)
if err != nil {
return nil, nil, nil, err
}
if _, ok := finalMounts[mount.Destination]; ok {
return nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest)
}
finalMounts[mount.Destination] = mount
case "image":
volume, err := getImageVolume(tokens)
if err != nil {
return nil, nil, nil, err
}
if _, ok := finalImageVolumes[volume.Destination]; ok {
return nil, nil, nil, fmt.Errorf("%v: %w", volume.Destination, specgen.ErrDuplicateDest)
}
finalImageVolumes[volume.Destination] = volume
case "volume":
volume, err := getNamedVolume(tokens)
if err != nil {
return nil, nil, nil, err
}
if _, ok := finalNamedVolumes[volume.Dest]; ok {
return nil, nil, nil, fmt.Errorf("%v: %w", volume.Dest, specgen.ErrDuplicateDest)
}
finalNamedVolumes[volume.Dest] = volume
default:
return nil, nil, nil, fmt.Errorf("invalid filesystem type %q", mountType)
}
// Parse mounts passed in from the user
if err := parseMounts(mountFlag, false); err != nil {
return nil, nil, nil, err
}
// If user specified a mount flag that conflicts with a containers.conf flag, then ignore
// the duplicate. This means that the parseing of containers.conf configMounts, should always
// happen second.
if err := parseMounts(configMounts, true); err != nil {
return nil, nil, nil, fmt.Errorf("parsing containers.conf mounts: %w", err)
}
return finalMounts, finalNamedVolumes, finalImageVolumes, nil

View File

@ -173,6 +173,50 @@ load helpers
run_podman rm -t 0 -f $cid
}
@test "podman mount containers.conf" {
skip_if_remote "remote does not support CONTAINERS_CONF*"
dest=/$(random_string 30)
tmpfile1=$PODMAN_TMPDIR/volume-test1
random1=$(random_string 30)
echo $random1 > $tmpfile1
tmpfile2=$PODMAN_TMPDIR/volume-test2
random2=$(random_string 30)
echo $random2 > $tmpfile2
bogus=$(random_string 10)
mountStr1=type=bind,src=$tmpfile1,destination=$dest,ro,Z
mountStr2=type=bind,src=$tmpfile2,destination=$dest,ro,Z
containersconf=$PODMAN_TMPDIR/containers.conf
cat >$containersconf <<EOF
[containers]
mounts=[ "$mountStr1", ]
EOF
badcontainersconf=$PODMAN_TMPDIR/badcontainers.conf
cat >$badcontainersconf <<EOF
[containers]
mounts=[ "type=$bogus,src=$tmpfile2,destination=$dest,ro", ]
EOF
run_podman 1 run $IMAGE cat $dest
is "$output" "cat: can't open '$dest': No such file or directory" "$dest does not exist"
CONTAINERS_CONF_OVERRIDE="$containersconf" run_podman run $IMAGE cat $dest
is "$output" "$random1" "file should contain $random1"
CONTAINERS_CONF_OVERRIDE="$containersconf" run_podman run --mount $mountStr2 $IMAGE cat $dest
is "$output" "$random2" "overridden file should contain $random2"
CONTAINERS_CONF_OVERRIDE="$containersconf" run_podman 125 run --mount $mountStr1 --mount $mountStr2 $IMAGE cat $dest
is "$output" "Error: $dest: duplicate mount destination" "Should through duplicate destination error for $dest"
CONTAINERS_CONF_OVERRIDE="$badcontainersconf" run_podman 125 run $IMAGE cat $dest
is "$output" "Error: parsing containers.conf mounts: invalid filesystem type \"$bogus\"" "containers.conf should fail with bad mounts entry"
run_podman rm --all --force -t 0
}
@test "podman mount external container - basic test" {
# Only works with root (FIXME: does it work with rootless + vfs?)
skip_if_rootless "mount does not work rootless"

View File

@ -185,6 +185,9 @@ type ContainersConfig struct {
// Containers logs default to truncated container ID as a tag.
LogTag string `toml:"log_tag,omitempty"`
// Mount to add to all containers
Mounts []string `toml:"mounts,omitempty"`
// NetNS indicates how to create a network namespace for the container
NetNS string `toml:"netns,omitempty"`
@ -1021,17 +1024,7 @@ func (c *NetworkConfig) Validate() error {
}
}
if stringsEq(c.CNIPluginDirs, DefaultCNIPluginDirs) {
return nil
}
for _, pluginDir := range c.CNIPluginDirs {
if err := isDirectory(pluginDir); err == nil {
return nil
}
}
return fmt.Errorf("invalid cni_plugin_dirs: %s", strings.Join(c.CNIPluginDirs, ","))
return nil
}
// FindConmon iterates over (*Config).ConmonPath and returns the path

View File

@ -196,6 +196,13 @@ default_sysctls = [
#
#log_tag = ""
# List of mounts. Specified as
# "type=TYPE,source=<directory-on-host>,destination=<directory-in-container>,<options>", for example:
# "type=bind,source=/var/lib/foobar,destination=/var/lib/foobar,ro".
# If it is empty or commented out, no mounts will be added
#
#mounts = []
# Default way to to create a Network namespace for the container
# Options are:
# `private` Create private Network Namespace for the container.
@ -276,7 +283,7 @@ default_sysctls = [
# If it is empty or commented out, no volumes will be added
#
#volumes = []
#
#[engine.platform_to_oci_runtime]
#"wasi/wasm" = ["crun-wasm"]
#"wasi/wasm32" = ["crun-wasm"]

View File

@ -186,19 +186,18 @@ func DefaultConfig() (*Config, error) {
return &Config{
Containers: ContainersConfig{
Devices: []string{},
Volumes: []string{},
Annotations: []string{},
ApparmorProfile: DefaultApparmorProfile,
BaseHostsFile: "",
CgroupNS: cgroupNS,
Cgroups: getDefaultCgroupsMode(),
DNSOptions: []string{},
DNSSearches: []string{},
DNSServers: []string{},
DefaultCapabilities: DefaultCapabilities,
DefaultSysctls: []string{},
DefaultUlimits: getDefaultProcessLimits(),
DNSServers: []string{},
DNSOptions: []string{},
DNSSearches: []string{},
Devices: []string{},
EnableKeyring: true,
EnableLabeling: selinuxEnabled(),
Env: []string{
@ -207,20 +206,22 @@ func DefaultConfig() (*Config, error) {
},
EnvHost: false,
HTTPProxy: true,
IPCNS: "shareable",
Init: false,
InitPath: "",
IPCNS: "shareable",
LogDriver: defaultLogDriver(),
LogSizeMax: DefaultLogSizeMax,
Mounts: []string{},
NetNS: "private",
NoHosts: false,
PidsLimit: DefaultPidsLimit,
PidNS: "private",
PidsLimit: DefaultPidsLimit,
ShmSize: DefaultShmSize,
TZ: "",
Umask: "0022",
UTSNS: "private",
Umask: "0022",
UserNSSize: DefaultUserNSSize, // Deprecated
Volumes: []string{},
},
Network: NetworkConfig{
DefaultNetwork: "podman",
@ -500,6 +501,11 @@ func (c *Config) Volumes() []string {
return c.Containers.Volumes
}
// Mounts returns the default set of mounts that should be mounted in containers.
func (c *Config) Mounts() []string {
return c.Containers.Mounts
}
// Devices returns the default additional devices for containers.
func (c *Config) Devices() []string {
return c.Containers.Devices

View File

@ -15,6 +15,8 @@ import (
"golang.org/x/term"
)
const sshdPort = 22
func Validate(user *url.Userinfo, path string, port int, identity string) (*config.Destination, *url.URL, error) {
// url.Parse NEEDS ssh://, if this ever fails or returns some nonsense, that is why.
uri, err := url.Parse(path)
@ -28,11 +30,10 @@ func Validate(user *url.Userinfo, path string, port int, identity string) (*conf
}
if uri.Port() == "" {
if port != 0 {
uri.Host = net.JoinHostPort(uri.Host, strconv.Itoa(port))
} else {
uri.Host = net.JoinHostPort(uri.Host, "22")
if port == 0 {
port = sshdPort
}
uri.Host = net.JoinHostPort(uri.Host, strconv.Itoa(port))
}
if user != nil {
@ -165,11 +166,15 @@ func ParseScpArgs(options ConnectionScpOptions) (string, string, string, bool, e
}
func DialNet(sshClient *ssh.Client, mode string, url *url.URL) (net.Conn, error) {
port, err := strconv.Atoi(url.Port())
if err != nil {
return nil, err
port := sshdPort
if url.Port() != "" {
p, err := strconv.Atoi(url.Port())
if err != nil {
return nil, err
}
port = p
}
if _, _, err = Validate(url.User, url.Hostname(), port, ""); err != nil {
if _, _, err := Validate(url.User, url.Hostname(), port, ""); err != nil {
return nil, err
}
return sshClient.Dial(mode, url.Path)

View File

@ -1,3 +1,8 @@
## 1.27.10
### Fixes
- fix: go 1.21 adding goroutine ID to creator+location (#685) [bdc7803]
## 1.27.9
### Fixes

View File

@ -22,7 +22,7 @@ import (
"github.com/onsi/gomega/types"
)
const GOMEGA_VERSION = "1.27.9"
const GOMEGA_VERSION = "1.27.10"
const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler.
If you're using Ginkgo then you probably forgot to put your assertion in an It().

4
vendor/modules.txt vendored
View File

@ -157,7 +157,7 @@ github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util
github.com/containers/buildah/util
# github.com/containers/common v0.55.1-0.20230724161016-2966c705a7a3
# github.com/containers/common v0.55.1-0.20230726131109-ff8298511408
## explicit; go 1.18
github.com/containers/common/libimage
github.com/containers/common/libimage/define
@ -780,7 +780,7 @@ github.com/onsi/ginkgo/v2/internal/parallel_support
github.com/onsi/ginkgo/v2/internal/testingtproxy
github.com/onsi/ginkgo/v2/reporters
github.com/onsi/ginkgo/v2/types
# github.com/onsi/gomega v1.27.9
# github.com/onsi/gomega v1.27.10
## explicit; go 1.18
github.com/onsi/gomega
github.com/onsi/gomega/format