mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00
Quadlet container mount - support non key=val options
Some keys, e.g. ro do not have values. The current implementation crashed looking for the = sign Externalize findMountType in a new package Parse mount command using FindMountType Rebuild parameter string using csv Add test case and adjust the test framework Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
This commit is contained in:
@ -1,7 +1,6 @@
|
|||||||
package specgenutil
|
package specgenutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/csv"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
@ -13,14 +12,14 @@ import (
|
|||||||
"github.com/containers/podman/v4/libpod/define"
|
"github.com/containers/podman/v4/libpod/define"
|
||||||
"github.com/containers/podman/v4/pkg/rootless"
|
"github.com/containers/podman/v4/pkg/rootless"
|
||||||
"github.com/containers/podman/v4/pkg/specgen"
|
"github.com/containers/podman/v4/pkg/specgen"
|
||||||
|
"github.com/containers/podman/v4/pkg/specgenutilexternal"
|
||||||
"github.com/containers/podman/v4/pkg/util"
|
"github.com/containers/podman/v4/pkg/util"
|
||||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errOptionArg = errors.New("must provide an argument for option")
|
errOptionArg = errors.New("must provide an argument for option")
|
||||||
errNoDest = errors.New("must set volume destination")
|
errNoDest = errors.New("must set volume destination")
|
||||||
errInvalidSyntax = errors.New("incorrect mount format: should be --mount type=<bind|glob|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parse all volume-related options in the create config into a set of mounts
|
// Parse all volume-related options in the create config into a set of mounts
|
||||||
@ -140,35 +139,6 @@ func parseVolumes(rtc *config.Config, volumeFlag, mountFlag, tmpfsFlag []string)
|
|||||||
return finalMounts, finalVolumes, finalOverlayVolume, finalImageVolumes, nil
|
return finalMounts, finalVolumes, finalOverlayVolume, finalImageVolumes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// findMountType parses the input and extracts the type of the mount type and
|
|
||||||
// the remaining non-type tokens.
|
|
||||||
func findMountType(input string) (mountType string, tokens []string, err error) {
|
|
||||||
// Split by comma, iterate over the slice and look for
|
|
||||||
// "type=$mountType". Everything else is appended to tokens.
|
|
||||||
found := false
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(input))
|
|
||||||
records, err := csvReader.ReadAll()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
if len(records) != 1 {
|
|
||||||
return "", nil, errInvalidSyntax
|
|
||||||
}
|
|
||||||
for _, s := range records[0] {
|
|
||||||
kv := strings.Split(s, "=")
|
|
||||||
if found || !(len(kv) == 2 && kv[0] == "type") {
|
|
||||||
tokens = append(tokens, s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
mountType = kv[1]
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
err = errInvalidSyntax
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mounts takes user-provided input from the --mount flag as well as Mounts
|
// 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.
|
// 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=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
|
||||||
@ -181,7 +151,7 @@ func Mounts(mountFlag []string, configMounts []string) (map[string]spec.Mount, m
|
|||||||
parseMounts := func(mounts []string, ignoreDup bool) error {
|
parseMounts := func(mounts []string, ignoreDup bool) error {
|
||||||
for _, mount := range mounts {
|
for _, mount := range mounts {
|
||||||
// TODO: Docker defaults to "volume" if no mount type is specified.
|
// TODO: Docker defaults to "volume" if no mount type is specified.
|
||||||
mountType, tokens, err := findMountType(mount)
|
mountType, tokens, err := specgenutilexternal.FindMountType(mount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
40
pkg/specgenutilexternal/mount.go
Normal file
40
pkg/specgenutilexternal/mount.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package specgenutilexternal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidSyntax = errors.New("incorrect mount format: should be --mount type=<bind|glob|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
|
||||||
|
)
|
||||||
|
|
||||||
|
// FindMountType parses the input and extracts the type of the mount type and
|
||||||
|
// the remaining non-type tokens.
|
||||||
|
func FindMountType(input string) (mountType string, tokens []string, err error) {
|
||||||
|
// Split by comma, iterate over the slice and look for
|
||||||
|
// "type=$mountType". Everything else is appended to tokens.
|
||||||
|
found := false
|
||||||
|
csvReader := csv.NewReader(strings.NewReader(input))
|
||||||
|
records, err := csvReader.ReadAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
if len(records) != 1 {
|
||||||
|
return "", nil, errInvalidSyntax
|
||||||
|
}
|
||||||
|
for _, s := range records[0] {
|
||||||
|
kv := strings.Split(s, "=")
|
||||||
|
if found || !(len(kv) == 2 && kv[0] == "type") {
|
||||||
|
tokens = append(tokens, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mountType = kv[1]
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
err = errInvalidSyntax
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -1,12 +1,15 @@
|
|||||||
package quadlet
|
package quadlet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/csv"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v4/pkg/specgenutilexternal"
|
||||||
"github.com/containers/podman/v4/pkg/systemd/parser"
|
"github.com/containers/podman/v4/pkg/systemd/parser"
|
||||||
"github.com/containers/storage/pkg/regexp"
|
"github.com/containers/storage/pkg/regexp"
|
||||||
)
|
)
|
||||||
@ -729,33 +732,10 @@ func ConvertContainer(container *parser.UnitFile, names map[string]string, isUse
|
|||||||
|
|
||||||
mounts := container.LookupAllArgs(ContainerGroup, KeyMount)
|
mounts := container.LookupAllArgs(ContainerGroup, KeyMount)
|
||||||
for _, mount := range mounts {
|
for _, mount := range mounts {
|
||||||
params := strings.Split(mount, ",")
|
mountStr, err := resolveContainerMountParams(container, service, mount, names)
|
||||||
paramsMap := make(map[string]string, len(params))
|
if err != nil {
|
||||||
for _, param := range params {
|
return nil, err
|
||||||
kv := strings.Split(param, "=")
|
|
||||||
paramsMap[kv[0]] = kv[1]
|
|
||||||
}
|
}
|
||||||
if paramType, ok := paramsMap["type"]; ok {
|
|
||||||
if paramType == "volume" || paramType == "bind" || paramType == "glob" {
|
|
||||||
var err error
|
|
||||||
if paramSource, ok := paramsMap["source"]; ok {
|
|
||||||
paramsMap["source"], err = handleStorageSource(container, service, paramSource, names)
|
|
||||||
} else if paramSource, ok = paramsMap["src"]; ok {
|
|
||||||
paramsMap["src"], err = handleStorageSource(container, service, paramSource, names)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
paramsArray := make([]string, 0, len(params))
|
|
||||||
paramsArray = append(paramsArray, fmt.Sprintf("%s=%s", "type", paramsMap["type"]))
|
|
||||||
for k, v := range paramsMap {
|
|
||||||
if k != "type" {
|
|
||||||
paramsArray = append(paramsArray, fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mountStr := strings.Join(paramsArray, ",")
|
|
||||||
podman.add("--mount", mountStr)
|
podman.add("--mount", mountStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1538,3 +1518,56 @@ func handleImageSource(quadletImageName string, serviceUnitFile *parser.UnitFile
|
|||||||
|
|
||||||
return quadletImageName, nil
|
return quadletImageName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveContainerMountParams(containerUnitFile, serviceUnitFile *parser.UnitFile, mount string, names map[string]string) (string, error) {
|
||||||
|
mountType, tokens, err := specgenutilexternal.FindMountType(mount)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source resolution is required only for these types of mounts
|
||||||
|
if !(mountType == "volume" || mountType == "bind" || mountType == "glob") {
|
||||||
|
return mount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceIndex := -1
|
||||||
|
originalSource := ""
|
||||||
|
for i, token := range tokens {
|
||||||
|
kv := strings.SplitN(token, "=", 2)
|
||||||
|
if kv[0] == "source" || kv[0] == "src" {
|
||||||
|
if len(kv) < 2 {
|
||||||
|
return "", fmt.Errorf("source parameter does not include a value")
|
||||||
|
}
|
||||||
|
sourceIndex = i
|
||||||
|
originalSource = kv[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedSource, err := handleStorageSource(containerUnitFile, serviceUnitFile, originalSource, names)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
tokens[sourceIndex] = fmt.Sprintf("source=%s", resolvedSource)
|
||||||
|
|
||||||
|
tokens = append([]string{fmt.Sprintf("type=%s", mountType)}, tokens...)
|
||||||
|
|
||||||
|
return convertToCSV(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToCSV(s []string) (string, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := csv.NewWriter(&buf)
|
||||||
|
|
||||||
|
err := writer.Write(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
writer.Flush()
|
||||||
|
|
||||||
|
ret := buf.String()
|
||||||
|
if ret[len(ret)-1] == '\n' {
|
||||||
|
ret = ret[:len(ret)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
Image=localhost/imagename
|
Image=localhost/imagename
|
||||||
## assert-podman-args-key-val "--mount" "," "type=bind,source=/path/on/host,destination=/path/in/container"
|
## assert-podman-args-key-val "--mount" "," "type=bind,source=/path/on/host,destination=/path/in/container"
|
||||||
Mount=type=bind,source=/path/on/host,destination=/path/in/container
|
Mount=type=bind,source=/path/on/host,destination=/path/in/container
|
||||||
## assert-podman-args-key-val "--mount" "," "type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared"
|
## assert-podman-args-key-val "--mount" "," "type=bind,source=/path/on/host,dst=/path/in/container,relabel=shared"
|
||||||
Mount=type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared
|
Mount=type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared
|
||||||
## assert-podman-args-key-val "--mount" "," "type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true"
|
## assert-podman-args-key-val "--mount" "," "type=bind,source=/path/on/host,dst=/path/in/container,relabel=shared,U=true"
|
||||||
Mount=type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true
|
Mount=type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true
|
||||||
## assert-podman-args-key-val "--mount" "," "type=volume,source=vol1,destination=/path/in/container,ro=true"
|
## assert-podman-args-key-val "--mount" "," "type=volume,source=vol1,destination=/path/in/container,ro=true"
|
||||||
Mount=type=volume,source=vol1,destination=/path/in/container,ro=true
|
Mount=type=volume,source=vol1,destination=/path/in/container,ro=true
|
||||||
@ -20,3 +20,7 @@ Mount=type=image,source=fedora,destination=/fedora-image,rw=true
|
|||||||
Mount=type=devpts,destination=/dev/pts
|
Mount=type=devpts,destination=/dev/pts
|
||||||
## assert-podman-args-key-val-regex "--mount" "," "type=bind,source=.*/podman_test.*/quadlet/path/on/host,destination=/path/in/container"
|
## assert-podman-args-key-val-regex "--mount" "," "type=bind,source=.*/podman_test.*/quadlet/path/on/host,destination=/path/in/container"
|
||||||
Mount=type=bind,source=./path/on/host,destination=/path/in/container
|
Mount=type=bind,source=./path/on/host,destination=/path/in/container
|
||||||
|
## assert-podman-args-key-val "--mount" "," "type=volume,source=vol1,destination=/path/in/container,ro"
|
||||||
|
Mount=type=volume,source=vol1,destination=/path/in/container,ro
|
||||||
|
## assert-podman-args-key-val "--mount" "," "type=bind,source=/tmp,\"dst=/path,1\""
|
||||||
|
Mount=type=bind,src=/tmp,\"dst=/path,1\"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/csv"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -177,15 +178,24 @@ func (t *quadletTestcase) assertPodmanArgsRegex(args []string, unit *parser.Unit
|
|||||||
return findSublistRegex(podmanArgs, args) != -1
|
return findSublistRegex(podmanArgs, args) != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyValueStringToMap(keyValueString, separator string) map[string]string {
|
func keyValueStringToMap(keyValueString, separator string) (map[string]string, error) {
|
||||||
keyValMap := make(map[string]string)
|
keyValMap := make(map[string]string)
|
||||||
keyVarList := strings.Split(keyValueString, separator)
|
csvReader := csv.NewReader(strings.NewReader(keyValueString))
|
||||||
for _, param := range keyVarList {
|
csvReader.Comma = []rune(separator)[0]
|
||||||
kv := strings.Split(param, "=")
|
keyVarList, err := csvReader.ReadAll()
|
||||||
keyValMap[kv[0]] = kv[1]
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, param := range keyVarList[0] {
|
||||||
|
val := ""
|
||||||
|
kv := strings.SplitN(param, "=", 2)
|
||||||
|
if len(kv) == 2 {
|
||||||
|
val = kv[1]
|
||||||
|
}
|
||||||
|
keyValMap[kv[0]] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyValMap
|
return keyValMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyValMapEqualRegex(expectedKeyValMap, actualKeyValMap map[string]string) bool {
|
func keyValMapEqualRegex(expectedKeyValMap, actualKeyValMap map[string]string) bool {
|
||||||
@ -208,7 +218,10 @@ func keyValMapEqualRegex(expectedKeyValMap, actualKeyValMap map[string]string) b
|
|||||||
func (t *quadletTestcase) assertPodmanArgsKeyVal(args []string, unit *parser.UnitFile, key string, allowRegex bool) bool {
|
func (t *quadletTestcase) assertPodmanArgsKeyVal(args []string, unit *parser.UnitFile, key string, allowRegex bool) bool {
|
||||||
podmanArgs, _ := unit.LookupLastArgs("Service", key)
|
podmanArgs, _ := unit.LookupLastArgs("Service", key)
|
||||||
|
|
||||||
expectedKeyValMap := keyValueStringToMap(args[2], args[1])
|
expectedKeyValMap, err := keyValueStringToMap(args[2], args[1])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
argKeyLocation := 0
|
argKeyLocation := 0
|
||||||
for {
|
for {
|
||||||
subListLocation := findSublist(podmanArgs[argKeyLocation:], []string{args[0]})
|
subListLocation := findSublist(podmanArgs[argKeyLocation:], []string{args[0]})
|
||||||
@ -217,7 +230,10 @@ func (t *quadletTestcase) assertPodmanArgsKeyVal(args []string, unit *parser.Uni
|
|||||||
}
|
}
|
||||||
|
|
||||||
argKeyLocation += subListLocation
|
argKeyLocation += subListLocation
|
||||||
actualKeyValMap := keyValueStringToMap(podmanArgs[argKeyLocation+1], args[1])
|
actualKeyValMap, err := keyValueStringToMap(podmanArgs[argKeyLocation+1], args[1])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
if allowRegex {
|
if allowRegex {
|
||||||
if keyValMapEqualRegex(expectedKeyValMap, actualKeyValMap) {
|
if keyValMapEqualRegex(expectedKeyValMap, actualKeyValMap) {
|
||||||
return true
|
return true
|
||||||
|
Reference in New Issue
Block a user