github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/specgen/generate/storage.go (about)

     1  package generate
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/containers/common/pkg/config"
    12  	"github.com/containers/podman/v2/libpod"
    13  	"github.com/containers/podman/v2/libpod/image"
    14  	"github.com/containers/podman/v2/pkg/specgen"
    15  	"github.com/containers/podman/v2/pkg/util"
    16  	spec "github.com/opencontainers/runtime-spec/specs-go"
    17  	"github.com/pkg/errors"
    18  	"github.com/sirupsen/logrus"
    19  )
    20  
    21  // TODO unify this in one place - maybe libpod/define
    22  const (
    23  	// TypeBind is the type for mounting host dir
    24  	TypeBind = "bind"
    25  	// TypeVolume is the type for named volumes
    26  	TypeVolume = "volume"
    27  	// TypeTmpfs is the type for mounting tmpfs
    28  	TypeTmpfs = "tmpfs"
    29  )
    30  
    31  var (
    32  	errDuplicateDest = errors.Errorf("duplicate mount destination")
    33  )
    34  
    35  // Produce final mounts and named volumes for a container
    36  func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *image.Image) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) {
    37  	// Get image volumes
    38  	baseMounts, baseVolumes, err := getImageVolumes(ctx, img, s)
    39  	if err != nil {
    40  		return nil, nil, nil, err
    41  	}
    42  
    43  	// Get volumes-from mounts
    44  	volFromMounts, volFromVolumes, err := getVolumesFrom(s.VolumesFrom, rt)
    45  	if err != nil {
    46  		return nil, nil, nil, err
    47  	}
    48  
    49  	// Supersede from --volumes-from.
    50  	for dest, mount := range volFromMounts {
    51  		baseMounts[dest] = mount
    52  	}
    53  	for dest, volume := range volFromVolumes {
    54  		baseVolumes[dest] = volume
    55  	}
    56  
    57  	// Need to make map forms of specgen mounts/volumes.
    58  	unifiedMounts := map[string]spec.Mount{}
    59  	unifiedVolumes := map[string]*specgen.NamedVolume{}
    60  	unifiedOverlays := map[string]*specgen.OverlayVolume{}
    61  
    62  	// Need to make map forms of specgen mounts/volumes.
    63  	commonMounts, commonVolumes, commonOverlayVolumes, err := specgen.GenVolumeMounts(rtc.Volumes())
    64  	if err != nil {
    65  		return nil, nil, nil, err
    66  	}
    67  
    68  	for _, m := range s.Mounts {
    69  		if _, ok := unifiedMounts[m.Destination]; ok {
    70  			return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", m.Destination)
    71  		}
    72  		unifiedMounts[m.Destination] = m
    73  	}
    74  
    75  	for _, m := range commonMounts {
    76  		if _, ok := unifiedMounts[m.Destination]; !ok {
    77  			unifiedMounts[m.Destination] = m
    78  		}
    79  	}
    80  
    81  	for _, v := range s.Volumes {
    82  		if _, ok := unifiedVolumes[v.Dest]; ok {
    83  			return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", v.Dest)
    84  		}
    85  		unifiedVolumes[v.Dest] = v
    86  	}
    87  
    88  	for _, v := range commonVolumes {
    89  		if _, ok := unifiedVolumes[v.Dest]; !ok {
    90  			unifiedVolumes[v.Dest] = v
    91  		}
    92  	}
    93  
    94  	for _, v := range s.OverlayVolumes {
    95  		if _, ok := unifiedOverlays[v.Destination]; ok {
    96  			return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", v.Destination)
    97  		}
    98  		unifiedOverlays[v.Destination] = v
    99  	}
   100  
   101  	for _, v := range commonOverlayVolumes {
   102  		if _, ok := unifiedOverlays[v.Destination]; ok {
   103  			unifiedOverlays[v.Destination] = v
   104  		}
   105  	}
   106  
   107  	// If requested, add container init binary
   108  	if s.Init {
   109  		initPath := s.InitPath
   110  		if initPath == "" && rtc != nil {
   111  			initPath = rtc.Engine.InitPath
   112  		}
   113  		initMount, err := addContainerInitBinary(s, initPath)
   114  		if err != nil {
   115  			return nil, nil, nil, err
   116  		}
   117  		if _, ok := unifiedMounts[initMount.Destination]; ok {
   118  			return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination)
   119  		}
   120  		unifiedMounts[initMount.Destination] = initMount
   121  	}
   122  
   123  	// Before superseding, we need to find volume mounts which conflict with
   124  	// named volumes, and vice versa.
   125  	// We'll delete the conflicts here as we supersede.
   126  	for dest := range unifiedMounts {
   127  		if _, ok := baseVolumes[dest]; ok {
   128  			delete(baseVolumes, dest)
   129  		}
   130  	}
   131  	for dest := range unifiedVolumes {
   132  		if _, ok := baseMounts[dest]; ok {
   133  			delete(baseMounts, dest)
   134  		}
   135  	}
   136  
   137  	// Supersede volumes-from/image volumes with unified volumes from above.
   138  	// This is an unconditional replacement.
   139  	for dest, mount := range unifiedMounts {
   140  		baseMounts[dest] = mount
   141  	}
   142  	for dest, volume := range unifiedVolumes {
   143  		baseVolumes[dest] = volume
   144  	}
   145  
   146  	// TODO: Investigate moving readonlyTmpfs into here. Would be more
   147  	// correct.
   148  
   149  	// Check for conflicts between named volumes and mounts
   150  	for dest := range baseMounts {
   151  		if _, ok := baseVolumes[dest]; ok {
   152  			return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
   153  		}
   154  	}
   155  	for dest := range baseVolumes {
   156  		if _, ok := baseMounts[dest]; ok {
   157  			return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
   158  		}
   159  	}
   160  	// Final step: maps to arrays
   161  	finalMounts := make([]spec.Mount, 0, len(baseMounts))
   162  	for _, mount := range baseMounts {
   163  		if mount.Type == TypeBind {
   164  			absSrc, err := filepath.Abs(mount.Source)
   165  			if err != nil {
   166  				return nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
   167  			}
   168  			mount.Source = absSrc
   169  		}
   170  		finalMounts = append(finalMounts, mount)
   171  	}
   172  	finalVolumes := make([]*specgen.NamedVolume, 0, len(baseVolumes))
   173  	for _, volume := range baseVolumes {
   174  		finalVolumes = append(finalVolumes, volume)
   175  	}
   176  
   177  	finalOverlays := make([]*specgen.OverlayVolume, 0, len(unifiedOverlays))
   178  	for _, volume := range unifiedOverlays {
   179  		finalOverlays = append(finalOverlays, volume)
   180  	}
   181  
   182  	return finalMounts, finalVolumes, finalOverlays, nil
   183  }
   184  
   185  // Get image volumes from the given image
   186  func getImageVolumes(ctx context.Context, img *image.Image, s *specgen.SpecGenerator) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
   187  	mounts := make(map[string]spec.Mount)
   188  	volumes := make(map[string]*specgen.NamedVolume)
   189  
   190  	mode := strings.ToLower(s.ImageVolumeMode)
   191  
   192  	// Image may be nil (rootfs in use), or image volume mode may be ignore.
   193  	if img == nil || mode == "ignore" {
   194  		return mounts, volumes, nil
   195  	}
   196  
   197  	inspect, err := img.InspectNoSize(ctx)
   198  	if err != nil {
   199  		return nil, nil, errors.Wrapf(err, "error inspecting image to get image volumes")
   200  	}
   201  	for volume := range inspect.Config.Volumes {
   202  		logrus.Debugf("Image has volume at %q", volume)
   203  		cleanDest := filepath.Clean(volume)
   204  		switch mode {
   205  		case "", "anonymous":
   206  			// Anonymous volumes have no name.
   207  			newVol := new(specgen.NamedVolume)
   208  			newVol.Dest = cleanDest
   209  			newVol.Options = []string{"rprivate", "rw", "nodev", "exec"}
   210  			volumes[cleanDest] = newVol
   211  			logrus.Debugf("Adding anonymous image volume at %q", cleanDest)
   212  		case "tmpfs":
   213  			mount := spec.Mount{
   214  				Destination: cleanDest,
   215  				Source:      TypeTmpfs,
   216  				Type:        TypeTmpfs,
   217  				Options:     []string{"rprivate", "rw", "nodev", "exec"},
   218  			}
   219  			mounts[cleanDest] = mount
   220  			logrus.Debugf("Adding tmpfs image volume at %q", cleanDest)
   221  		}
   222  	}
   223  
   224  	return mounts, volumes, nil
   225  }
   226  
   227  func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
   228  	finalMounts := make(map[string]spec.Mount)
   229  	finalNamedVolumes := make(map[string]*specgen.NamedVolume)
   230  
   231  	for _, volume := range volumesFrom {
   232  		var options []string
   233  
   234  		splitVol := strings.SplitN(volume, ":", 2)
   235  		if len(splitVol) == 2 {
   236  			splitOpts := strings.Split(splitVol[1], ",")
   237  			setRORW := false
   238  			setZ := false
   239  			for _, opt := range splitOpts {
   240  				switch opt {
   241  				case "z":
   242  					if setZ {
   243  						return nil, nil, errors.Errorf("cannot set :z more than once in mount options")
   244  					}
   245  					setZ = true
   246  				case "ro", "rw":
   247  					if setRORW {
   248  						return nil, nil, errors.Errorf("cannot set ro or rw options more than once")
   249  					}
   250  					setRORW = true
   251  				default:
   252  					return nil, nil, errors.Errorf("invalid option %q specified - volumes from another container can only use z,ro,rw options", opt)
   253  				}
   254  			}
   255  			options = splitOpts
   256  		}
   257  
   258  		ctr, err := runtime.LookupContainer(splitVol[0])
   259  		if err != nil {
   260  			return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0])
   261  		}
   262  
   263  		logrus.Debugf("Adding volumes from container %s", ctr.ID())
   264  
   265  		// Look up the container's user volumes. This gets us the
   266  		// destinations of all mounts the user added to the container.
   267  		userVolumesArr := ctr.UserVolumes()
   268  
   269  		// We're going to need to access them a lot, so convert to a map
   270  		// to reduce looping.
   271  		// We'll also use the map to indicate if we missed any volumes along the way.
   272  		userVolumes := make(map[string]bool)
   273  		for _, dest := range userVolumesArr {
   274  			userVolumes[dest] = false
   275  		}
   276  
   277  		// Now we get the container's spec and loop through its volumes
   278  		// and append them in if we can find them.
   279  		spec := ctr.Spec()
   280  		if spec == nil {
   281  			return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID())
   282  		}
   283  		for _, mnt := range spec.Mounts {
   284  			if mnt.Type != TypeBind {
   285  				continue
   286  			}
   287  			if _, exists := userVolumes[mnt.Destination]; exists {
   288  				userVolumes[mnt.Destination] = true
   289  
   290  				if len(options) != 0 {
   291  					mnt.Options = options
   292  				}
   293  
   294  				if _, ok := finalMounts[mnt.Destination]; ok {
   295  					logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID())
   296  				}
   297  				finalMounts[mnt.Destination] = mnt
   298  			}
   299  		}
   300  
   301  		// We're done with the spec mounts. Add named volumes.
   302  		// Add these unconditionally - none of them are automatically
   303  		// part of the container, as some spec mounts are.
   304  		namedVolumes := ctr.NamedVolumes()
   305  		for _, namedVol := range namedVolumes {
   306  			if _, exists := userVolumes[namedVol.Dest]; exists {
   307  				userVolumes[namedVol.Dest] = true
   308  			}
   309  
   310  			if len(options) != 0 {
   311  				namedVol.Options = options
   312  			}
   313  
   314  			if _, ok := finalMounts[namedVol.Dest]; ok {
   315  				logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID())
   316  			}
   317  
   318  			newVol := new(specgen.NamedVolume)
   319  			newVol.Dest = namedVol.Dest
   320  			newVol.Options = namedVol.Options
   321  			newVol.Name = namedVol.Name
   322  
   323  			finalNamedVolumes[namedVol.Dest] = newVol
   324  		}
   325  
   326  		// Check if we missed any volumes
   327  		for volDest, found := range userVolumes {
   328  			if !found {
   329  				logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
   330  			}
   331  		}
   332  	}
   333  
   334  	return finalMounts, finalNamedVolumes, nil
   335  }
   336  
   337  // AddContainerInitBinary adds the init binary specified by path iff the
   338  // container will run in a private PID namespace that is not shared with the
   339  // host or another pre-existing container, where an init-like process is
   340  // already running.
   341  // This does *NOT* modify the container command - that must be done elsewhere.
   342  func addContainerInitBinary(s *specgen.SpecGenerator, path string) (spec.Mount, error) {
   343  	mount := spec.Mount{
   344  		Destination: "/dev/init",
   345  		Type:        TypeBind,
   346  		Source:      path,
   347  		Options:     []string{TypeBind, "ro"},
   348  	}
   349  
   350  	if path == "" {
   351  		return mount, fmt.Errorf("please specify a path to the container-init binary")
   352  	}
   353  	if !s.PidNS.IsPrivate() {
   354  		return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
   355  	}
   356  	if s.Systemd == "always" {
   357  		return mount, fmt.Errorf("cannot use container-init binary with systemd=always")
   358  	}
   359  	if _, err := os.Stat(path); os.IsNotExist(err) {
   360  		return mount, errors.Wrap(err, "container-init binary not found on the host")
   361  	}
   362  	return mount, nil
   363  }
   364  
   365  // Supersede existing mounts in the spec with new, user-specified mounts.
   366  // TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
   367  // one mount, and we already have /tmp/a and /tmp/b, should we remove
   368  // the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
   369  func SupercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
   370  	if len(mounts) > 0 {
   371  		// If we have overlappings mounts, remove them from the spec in favor of
   372  		// the user-added volume mounts
   373  		destinations := make(map[string]bool)
   374  		for _, mount := range mounts {
   375  			destinations[path.Clean(mount.Destination)] = true
   376  		}
   377  		// Copy all mounts from spec to defaultMounts, except for
   378  		//  - mounts overridden by a user supplied mount;
   379  		//  - all mounts under /dev if a user supplied /dev is present;
   380  		mountDev := destinations["/dev"]
   381  		for _, mount := range configMount {
   382  			if _, ok := destinations[path.Clean(mount.Destination)]; !ok {
   383  				if mountDev && strings.HasPrefix(mount.Destination, "/dev/") {
   384  					// filter out everything under /dev if /dev is user-mounted
   385  					continue
   386  				}
   387  
   388  				logrus.Debugf("Adding mount %s", mount.Destination)
   389  				mounts = append(mounts, mount)
   390  			}
   391  		}
   392  		return mounts
   393  	}
   394  	return configMount
   395  }
   396  
   397  func InitFSMounts(mounts []spec.Mount) error {
   398  	for i, m := range mounts {
   399  		switch {
   400  		case m.Type == TypeBind:
   401  			opts, err := util.ProcessOptions(m.Options, false, m.Source)
   402  			if err != nil {
   403  				return err
   404  			}
   405  			mounts[i].Options = opts
   406  		case m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev":
   407  			opts, err := util.ProcessOptions(m.Options, true, "")
   408  			if err != nil {
   409  				return err
   410  			}
   411  			mounts[i].Options = opts
   412  		}
   413  	}
   414  	return nil
   415  }