mirror of https://github.com/containers/podman.git
				
				
				
			
		
			
				
	
	
		
			188 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			188 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
| package specgen
 | |
| 
 | |
| import (
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/containers/common/pkg/parse"
 | |
| 	spec "github.com/opencontainers/runtime-spec/specs-go"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| // NamedVolume holds information about a named volume that will be mounted into
 | |
| // the container.
 | |
| type NamedVolume struct {
 | |
| 	// Name is the name of the named volume to be mounted. May be empty.
 | |
| 	// If empty, a new named volume with a pseudorandomly generated name
 | |
| 	// will be mounted at the given destination.
 | |
| 	Name string
 | |
| 	// Destination to mount the named volume within the container. Must be
 | |
| 	// an absolute path. Path will be created if it does not exist.
 | |
| 	Dest string
 | |
| 	// Options are options that the named volume will be mounted with.
 | |
| 	Options []string
 | |
| }
 | |
| 
 | |
| // OverlayVolume holds information about a overlay volume that will be mounted into
 | |
| // the container.
 | |
| type OverlayVolume struct {
 | |
| 	// Destination is the absolute path where the mount will be placed in the container.
 | |
| 	Destination string `json:"destination"`
 | |
| 	// Source specifies the source path of the mount.
 | |
| 	Source string `json:"source,omitempty"`
 | |
| 	// Options holds overlay volume options.
 | |
| 	Options []string `json:"options,omitempty"`
 | |
| }
 | |
| 
 | |
| // ImageVolume is a volume based on a container image.  The container image is
 | |
| // first mounted on the host and is then bind-mounted into the container.  An
 | |
| // ImageVolume is always mounted read only.
 | |
| type ImageVolume struct {
 | |
| 	// Source is the source of the image volume.  The image can be referred
 | |
| 	// to by name and by ID.
 | |
| 	Source string
 | |
| 	// Destination is the absolute path of the mount in the container.
 | |
| 	Destination string
 | |
| 	// ReadWrite sets the volume writable.
 | |
| 	ReadWrite bool
 | |
| }
 | |
| 
 | |
| // GenVolumeMounts parses user input into mounts, volumes and overlay volumes
 | |
| func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*NamedVolume, map[string]*OverlayVolume, error) {
 | |
| 	errDuplicateDest := errors.Errorf("duplicate mount destination")
 | |
| 
 | |
| 	mounts := make(map[string]spec.Mount)
 | |
| 	volumes := make(map[string]*NamedVolume)
 | |
| 	overlayVolumes := make(map[string]*OverlayVolume)
 | |
| 
 | |
| 	volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]")
 | |
| 
 | |
| 	for _, vol := range volumeFlag {
 | |
| 		var (
 | |
| 			options []string
 | |
| 			src     string
 | |
| 			dest    string
 | |
| 			err     error
 | |
| 		)
 | |
| 
 | |
| 		splitVol := SplitVolumeString(vol)
 | |
| 		if len(splitVol) > 3 {
 | |
| 			return nil, nil, nil, errors.Wrapf(volumeFormatErr, vol)
 | |
| 		}
 | |
| 
 | |
| 		src = splitVol[0]
 | |
| 		if len(splitVol) == 1 {
 | |
| 			// This is an anonymous named volume. Only thing given
 | |
| 			// is destination.
 | |
| 			// Name/source will be blank, and populated by libpod.
 | |
| 			src = ""
 | |
| 			dest = splitVol[0]
 | |
| 		} else if len(splitVol) > 1 {
 | |
| 			dest = splitVol[1]
 | |
| 		}
 | |
| 		if len(splitVol) > 2 {
 | |
| 			if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil {
 | |
| 				return nil, nil, nil, err
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Do not check source dir for anonymous volumes
 | |
| 		if len(splitVol) > 1 {
 | |
| 			if len(src) == 0 {
 | |
| 				return nil, nil, nil, errors.New("host directory cannot be empty")
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") || isHostWinPath(src) {
 | |
| 			// This is not a named volume
 | |
| 			overlayFlag := false
 | |
| 			chownFlag := false
 | |
| 			upperDirFlag := false
 | |
| 			workDirFlag := false
 | |
| 			for _, o := range options {
 | |
| 				if o == "O" {
 | |
| 					overlayFlag = true
 | |
| 
 | |
| 					joinedOpts := strings.Join(options, "")
 | |
| 					if strings.Contains(joinedOpts, "U") {
 | |
| 						chownFlag = true
 | |
| 					}
 | |
| 					if strings.Contains(joinedOpts, "upperdir") {
 | |
| 						upperDirFlag = true
 | |
| 					}
 | |
| 					if strings.Contains(joinedOpts, "workdir") {
 | |
| 						workDirFlag = true
 | |
| 					}
 | |
| 					if (workDirFlag && !upperDirFlag) || (!workDirFlag && upperDirFlag) {
 | |
| 						return nil, nil, nil, errors.New("must set both `upperdir` and `workdir`")
 | |
| 					}
 | |
| 					if len(options) > 2 && !(len(options) == 3 && upperDirFlag && workDirFlag) || (len(options) == 2 && !chownFlag) {
 | |
| 						return nil, nil, nil, errors.New("can't use 'O' with other options")
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			if overlayFlag {
 | |
| 				// This is a overlay volume
 | |
| 				newOverlayVol := new(OverlayVolume)
 | |
| 				newOverlayVol.Destination = dest
 | |
| 				newOverlayVol.Source = src
 | |
| 				newOverlayVol.Options = options
 | |
| 
 | |
| 				if _, ok := overlayVolumes[newOverlayVol.Destination]; ok {
 | |
| 					return nil, nil, nil, errors.Wrapf(errDuplicateDest, newOverlayVol.Destination)
 | |
| 				}
 | |
| 				overlayVolumes[newOverlayVol.Destination] = newOverlayVol
 | |
| 			} else {
 | |
| 				newMount := spec.Mount{
 | |
| 					Destination: dest,
 | |
| 					Type:        "bind",
 | |
| 					Source:      src,
 | |
| 					Options:     options,
 | |
| 				}
 | |
| 				if _, ok := mounts[newMount.Destination]; ok {
 | |
| 					return nil, nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
 | |
| 				}
 | |
| 				mounts[newMount.Destination] = newMount
 | |
| 			}
 | |
| 		} else {
 | |
| 			// This is a named volume
 | |
| 			newNamedVol := new(NamedVolume)
 | |
| 			newNamedVol.Name = src
 | |
| 			newNamedVol.Dest = dest
 | |
| 			newNamedVol.Options = options
 | |
| 
 | |
| 			if _, ok := volumes[newNamedVol.Dest]; ok {
 | |
| 				return nil, nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest)
 | |
| 			}
 | |
| 			volumes[newNamedVol.Dest] = newNamedVol
 | |
| 		}
 | |
| 
 | |
| 		logrus.Debugf("User mount %s:%s options %v", src, dest, options)
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| }
 |