func mountSharedVolumes(root string, p *stage1commontypes.Pod, ra *schema.RuntimeApp) error { appName := ra.Name sharedVolPath := common.SharedVolumesPath(root) if err := os.MkdirAll(sharedVolPath, stage1initcommon.SharedVolPerm); err != nil { return errwrap.Wrap(errors.New("could not create shared volumes directory"), err) } if err := os.Chmod(sharedVolPath, stage1initcommon.SharedVolPerm); err != nil { return errwrap.Wrap(fmt.Errorf("could not change permissions of %q", sharedVolPath), err) } imageManifest := p.Images[appName.String()] mounts, err := stage1initcommon.GenerateMounts(ra, p.Manifest.Volumes, stage1initcommon.ConvertedFromDocker(imageManifest)) if err != nil { return err } for _, m := range mounts { absRoot, err := filepath.Abs(p.Root) // Absolute path to the pod's rootfs. if err != nil { return errwrap.Wrap(errors.New("could not get pod's root absolute path"), err) } absAppRootfs := common.AppRootfsPath(absRoot, appName) if err != nil { return fmt.Errorf(`could not evaluate absolute path for application rootfs in app: %v`, appName) } mntPath, err := stage1initcommon.EvaluateSymlinksInsideApp(absAppRootfs, m.Mount.Path) if err != nil { return errwrap.Wrap(fmt.Errorf("could not evaluate path %v", m.Mount.Path), err) } absDestination := filepath.Join(absAppRootfs, mntPath) shPath := filepath.Join(sharedVolPath, m.Volume.Name.String()) if err := stage1initcommon.PrepareMountpoints(shPath, absDestination, &m.Volume, m.DockerImplicit); err != nil { return err } var source string switch m.Volume.Kind { case "host": source = m.Volume.Source case "empty": source = filepath.Join(common.SharedVolumesPath(root), m.Volume.Name.String()) default: return fmt.Errorf(`invalid volume kind %q. Must be one of "host" or "empty"`, m.Volume.Kind) } if cleanedSource, err := filepath.EvalSymlinks(source); err != nil { return errwrap.Wrap(fmt.Errorf("could not resolve symlink for source: %v", source), err) } else if err := ensureDestinationExists(cleanedSource, absDestination); err != nil { return errwrap.Wrap(fmt.Errorf("could not create destination mount point: %v", absDestination), err) } else if err := doBindMount(cleanedSource, absDestination, m.ReadOnly, m.Volume.Recursive); err != nil { return errwrap.Wrap(fmt.Errorf("could not bind mount path %v (s: %v, d: %v)", m.Mount.Path, source, absDestination), err) } } return nil }
// Source computes the real volume source for a volume. // Volumes of type 'empty' use a workdir relative to podRoot func (m *mountWrapper) Source(podRoot string) string { switch m.Volume.Kind { case "host": return m.Volume.Source case "empty": return filepath.Join(common.SharedVolumesPath(podRoot), m.Volume.Name.String()) } return "" // We validate in GenerateMounts that it's valid }
// appToNspawnArgs transforms the given app manifest, with the given associated // app name, into a subset of applicable systemd-nspawn argument func appToNspawnArgs(p *stage1commontypes.Pod, ra *schema.RuntimeApp) ([]string, error) { var args []string appName := ra.Name app := ra.App sharedVolPath := common.SharedVolumesPath(p.Root) if err := os.MkdirAll(sharedVolPath, sharedVolPerm); err != nil { return nil, fmt.Errorf("could not create shared volumes directory: %v", err) } if err := os.Chmod(sharedVolPath, sharedVolPerm); err != nil { return nil, fmt.Errorf("could not change permissions of %q: %v", sharedVolPath, err) } vols := make(map[types.ACName]types.Volume) for _, v := range p.Manifest.Volumes { vols[v.Name] = v } mounts := GenerateMounts(ra, vols) for _, m := range mounts { vol := vols[m.Volume] if vol.Kind == "empty" { p := filepath.Join(sharedVolPath, vol.Name.String()) if err := os.MkdirAll(p, sharedVolPerm); err != nil { return nil, fmt.Errorf("could not create shared volume %q: %v", vol.Name, err) } if err := os.Chown(p, *vol.UID, *vol.GID); err != nil { return nil, fmt.Errorf("could not change owner of %q: %v", p, err) } mod, err := strconv.ParseUint(*vol.Mode, 8, 32) if err != nil { return nil, fmt.Errorf("invalid mode %q for volume %q: %v", *vol.Mode, vol.Name, err) } if err := os.Chmod(p, os.FileMode(mod)); err != nil { return nil, fmt.Errorf("could not change permissions of %q: %v", p, err) } } opt := make([]string, 4) if IsMountReadOnly(vol, app.MountPoints) { opt[0] = "--bind-ro=" } else { opt[0] = "--bind=" } switch vol.Kind { case "host": opt[1] = vol.Source case "empty": absRoot, err := filepath.Abs(p.Root) if err != nil { return nil, fmt.Errorf("cannot get pod's root absolute path: %v\n", err) } opt[1] = filepath.Join(common.SharedVolumesPath(absRoot), vol.Name.String()) default: return nil, fmt.Errorf(`invalid volume kind %q. Must be one of "host" or "empty"`, vol.Kind) } opt[2] = ":" opt[3] = filepath.Join(common.RelAppRootfsPath(appName), m.Path) args = append(args, strings.Join(opt, "")) } for _, i := range app.Isolators { switch v := i.Value().(type) { case types.LinuxCapabilitiesSet: var caps []string // TODO: cleanup the API on LinuxCapabilitiesSet to give strings easily. for _, c := range v.Set() { caps = append(caps, string(c)) } if i.Name == types.LinuxCapabilitiesRetainSetName { capList := strings.Join(caps, ",") args = append(args, "--capability="+capList) } } } return args, nil }
// appToNspawnArgs transforms the given app manifest, with the given associated // app name, into a subset of applicable systemd-nspawn argument func (p *Pod) appToNspawnArgs(ra *schema.RuntimeApp) ([]string, error) { var args []string appName := ra.Name id := ra.Image.ID app := ra.App vols := make(map[types.ACName]types.Volume) // TODO(philips): this is implicitly creating a mapping from MountPoint // to volumes. This is a nice convenience for users but we will need to // introduce a --mount flag so they can control which mountPoint maps to // which volume. sharedVolPath := common.SharedVolumesPath(p.Root) if err := os.MkdirAll(sharedVolPath, sharedVolPerm); err != nil { return nil, fmt.Errorf("could not create shared volumes directory: %v", err) } if err := os.Chmod(sharedVolPath, sharedVolPerm); err != nil { return nil, fmt.Errorf("could not change permissions of %q: %v", sharedVolPath, err) } for _, v := range p.Manifest.Volumes { vols[v.Name] = v if v.Kind == "empty" { if err := os.MkdirAll(filepath.Join(sharedVolPath, v.Name.String()), sharedVolPerm); err != nil { return nil, fmt.Errorf("could not create shared volume %q: %v", v.Name, err) } } } for _, mp := range app.MountPoints { key := mp.Name vol, ok := vols[key] if !ok { catCmd := fmt.Sprintf("sudo rkt image cat-manifest --pretty-print %v", id) volumeCmd := "" for _, mp := range app.MountPoints { volumeCmd += fmt.Sprintf("--volume %s,kind=host,source=/some/path ", mp.Name) } return nil, fmt.Errorf("no volume for mountpoint %q in app %q.\n"+ "You can inspect the volumes with:\n\t%v\n"+ "App %q requires the following volumes:\n\t%v", key, appName, catCmd, appName, volumeCmd) } opt := make([]string, 4) // If the readonly flag in the pod manifest is not nil, // then use it to override the readonly flag in the image manifest. readOnly := mp.ReadOnly if vol.ReadOnly != nil { readOnly = *vol.ReadOnly } if readOnly { opt[0] = "--bind-ro=" } else { opt[0] = "--bind=" } switch vol.Kind { case "host": opt[1] = vol.Source case "empty": absRoot, err := filepath.Abs(p.Root) if err != nil { return nil, fmt.Errorf("cannot get pod's root absolute path: %v\n", err) } opt[1] = filepath.Join(common.SharedVolumesPath(absRoot), vol.Name.String()) default: return nil, fmt.Errorf(`invalid volume kind %q. Must be one of "host" or "empty".`, vol.Kind) } opt[2] = ":" opt[3] = filepath.Join(common.RelAppRootfsPath(appName), mp.Path) args = append(args, strings.Join(opt, "")) } for _, i := range app.Isolators { switch v := i.Value().(type) { case types.LinuxCapabilitiesSet: var caps []string // TODO: cleanup the API on LinuxCapabilitiesSet to give strings easily. for _, c := range v.Set() { caps = append(caps, string(c)) } if i.Name == types.LinuxCapabilitiesRetainSetName { capList := strings.Join(caps, ",") args = append(args, "--capability="+capList) } } } return args, nil }
// appToNspawnArgs transforms the given app manifest, with the given associated // app name, into a subset of applicable systemd-nspawn argument func appToNspawnArgs(p *stage1commontypes.Pod, ra *schema.RuntimeApp, insecureOptions Stage1InsecureOptions) ([]string, error) { var args []string appName := ra.Name app := ra.App sharedVolPath := common.SharedVolumesPath(p.Root) if err := os.MkdirAll(sharedVolPath, SharedVolPerm); err != nil { return nil, errwrap.Wrap(errors.New("could not create shared volumes directory"), err) } if err := os.Chmod(sharedVolPath, SharedVolPerm); err != nil { return nil, errwrap.Wrap(fmt.Errorf("could not change permissions of %q", sharedVolPath), err) } vols := make(map[types.ACName]types.Volume) for _, v := range p.Manifest.Volumes { vols[v.Name] = v } imageManifest := p.Images[appName.String()] mounts, err := GenerateMounts(ra, p.Manifest.Volumes, ConvertedFromDocker(imageManifest)) if err != nil { return nil, errwrap.Wrap(fmt.Errorf("could not generate app %q mounts", appName), err) } for _, m := range mounts { shPath := filepath.Join(sharedVolPath, m.Volume.Name.String()) absRoot, err := filepath.Abs(p.Root) // Absolute path to the pod's rootfs. if err != nil { return nil, errwrap.Wrap(errors.New("could not get pod's root absolute path"), err) } appRootfs := common.AppRootfsPath(absRoot, appName) // TODO(yifan): This is a temporary fix for systemd-nspawn not handling symlink mounts well. // Could be removed when https://github.com/systemd/systemd/issues/2860 is resolved, and systemd // version is bumped. mntPath, err := EvaluateSymlinksInsideApp(appRootfs, m.Mount.Path) if err != nil { return nil, errwrap.Wrap(fmt.Errorf("could not evaluate path %v", m.Mount.Path), err) } mntAbsPath := filepath.Join(appRootfs, mntPath) if err := PrepareMountpoints(shPath, mntAbsPath, &m.Volume, m.DockerImplicit); err != nil { return nil, err } opt := make([]string, 6) if m.ReadOnly { opt[0] = "--bind-ro=" } else { opt[0] = "--bind=" } switch m.Volume.Kind { case "host": opt[1] = m.Volume.Source case "empty": opt[1] = filepath.Join(common.SharedVolumesPath(absRoot), m.Volume.Name.String()) default: return nil, fmt.Errorf(`invalid volume kind %q. Must be one of "host" or "empty"`, m.Volume.Kind) } opt[2] = ":" opt[3] = filepath.Join(common.RelAppRootfsPath(appName), mntPath) opt[4] = ":" // If Recursive is not set, default to recursive. recursive := true if m.Volume.Recursive != nil { recursive = *m.Volume.Recursive } // rbind/norbind options exist since systemd-nspawn v226 if recursive { opt[5] = "rbind" } else { opt[5] = "norbind" } args = append(args, strings.Join(opt, "")) } if !insecureOptions.DisableCapabilities { capabilitiesStr, err := getAppCapabilities(app.Isolators) if err != nil { return nil, err } capList := strings.Join(capabilitiesStr, ",") args = append(args, "--capability="+capList) } return args, nil }
func mountSharedVolumes(root string, p *stage1commontypes.Pod, ra *schema.RuntimeApp) error { app := ra.App appName := ra.Name volumes := p.Manifest.Volumes vols := make(map[types.ACName]types.Volume) for _, v := range volumes { vols[v.Name] = v } sharedVolPath := common.SharedVolumesPath(root) if err := os.MkdirAll(sharedVolPath, stage1initcommon.SharedVolPerm); err != nil { return errwrap.Wrap(errors.New("could not create shared volumes directory"), err) } if err := os.Chmod(sharedVolPath, stage1initcommon.SharedVolPerm); err != nil { return errwrap.Wrap(fmt.Errorf("could not change permissions of %q", sharedVolPath), err) } imageManifest := p.Images[appName.String()] mounts := stage1initcommon.GenerateMounts(ra, vols, imageManifest) for _, m := range mounts { vol := vols[m.Volume] if vol.Kind == "empty" { p := filepath.Join(sharedVolPath, vol.Name.String()) if err := os.MkdirAll(p, stage1initcommon.SharedVolPerm); err != nil { return errwrap.Wrap(fmt.Errorf("could not create shared volume %q", vol.Name), err) } if err := os.Chown(p, *vol.UID, *vol.GID); err != nil { return errwrap.Wrap(fmt.Errorf("could not change owner of %q", p), err) } mod, err := strconv.ParseUint(*vol.Mode, 8, 32) if err != nil { return errwrap.Wrap(fmt.Errorf("invalid mode %q for volume %q", *vol.Mode, vol.Name), err) } if err := os.Chmod(p, os.FileMode(mod)); err != nil { return errwrap.Wrap(fmt.Errorf("could not change permissions of %q", p), err) } } readOnly := stage1initcommon.IsMountReadOnly(vol, app.MountPoints) var source string switch vol.Kind { case "host": source = vol.Source case "empty": source = filepath.Join(common.SharedVolumesPath(root), vol.Name.String()) default: return fmt.Errorf(`invalid volume kind %q. Must be one of "host" or "empty"`, vol.Kind) } absAppRootfs, err := filepath.Abs(common.AppRootfsPath(root, appName)) if err != nil { return fmt.Errorf(`could not evaluate absolute path for application rootfs in app: %v`, appName) } absDestination, err := filepath.Abs(filepath.Join(absAppRootfs, m.Path)) if err != nil { return fmt.Errorf(`could not evaluate absolute path for application volume path %q in: %v`, m.Path, appName) } if !strings.HasPrefix(absDestination, absAppRootfs) { return fmt.Errorf("path escapes app's root: %v", absDestination) } if cleanedSource, err := filepath.EvalSymlinks(source); err != nil { return errwrap.Wrap(fmt.Errorf("could not resolve symlink for source: %v", source), err) } else if err := ensureDestinationExists(cleanedSource, absDestination); err != nil { return errwrap.Wrap(fmt.Errorf("could not create destination mount point: %v", absDestination), err) } else if err := doBindMount(cleanedSource, absDestination, readOnly); err != nil { return errwrap.Wrap(fmt.Errorf("could not bind mount path %v (s: %v, d: %v)", m.Path, source, absDestination), err) } } return nil }
// appToNspawnArgs transforms the given app manifest, with the given associated // app name, into a subset of applicable systemd-nspawn argument func appToNspawnArgs(p *stage1commontypes.Pod, ra *schema.RuntimeApp) ([]string, error) { var args []string appName := ra.Name app := ra.App sharedVolPath := common.SharedVolumesPath(p.Root) if err := os.MkdirAll(sharedVolPath, SharedVolPerm); err != nil { return nil, errwrap.Wrap(errors.New("could not create shared volumes directory"), err) } if err := os.Chmod(sharedVolPath, SharedVolPerm); err != nil { return nil, errwrap.Wrap(fmt.Errorf("could not change permissions of %q", sharedVolPath), err) } vols := make(map[types.ACName]types.Volume) for _, v := range p.Manifest.Volumes { vols[v.Name] = v } imageManifest := p.Images[appName.String()] mounts := GenerateMounts(ra, vols, imageManifest) for _, m := range mounts { vol := vols[m.Volume] shPath := filepath.Join(sharedVolPath, vol.Name.String()) absRoot, err := filepath.Abs(p.Root) // Absolute path to the pod's rootfs. if err != nil { return nil, errwrap.Wrap(errors.New("could not get pod's root absolute path"), err) } appRootfs := common.AppRootfsPath(absRoot, appName) // TODO(yifan): This is a temporary fix for systemd-nspawn not handling symlink mounts well. // Could be removed when https://github.com/systemd/systemd/issues/2860 is resolved, and systemd // version is bumped. mntPath, err := EvaluateSymlinksInsideApp(appRootfs, m.Path) if err != nil { return nil, errwrap.Wrap(fmt.Errorf("could not evaluate path %v", m.Path), err) } mntAbsPath := filepath.Join(appRootfs, mntPath) if err := PrepareMountpoints(shPath, mntAbsPath, &vol, m.DockerImplicit); err != nil { return nil, err } opt := make([]string, 4) if IsMountReadOnly(vol, app.MountPoints) { opt[0] = "--bind-ro=" } else { opt[0] = "--bind=" } switch vol.Kind { case "host": opt[1] = vol.Source case "empty": opt[1] = filepath.Join(common.SharedVolumesPath(absRoot), vol.Name.String()) default: return nil, fmt.Errorf(`invalid volume kind %q. Must be one of "host" or "empty"`, vol.Kind) } opt[2] = ":" opt[3] = filepath.Join(common.RelAppRootfsPath(appName), mntPath) args = append(args, strings.Join(opt, "")) } capabilitiesStr, err := getAppCapabilities(app.Isolators) if err != nil { return nil, err } capList := strings.Join(capabilitiesStr, ",") args = append(args, "--capability="+capList) return args, nil }
// appToNspawnArgs transforms the given app manifest, with the given associated // app name, into a subset of applicable systemd-nspawn argument func (p *Pod) appToNspawnArgs(ra *schema.RuntimeApp) ([]string, error) { var args []string appName := ra.Name id := ra.Image.ID app := ra.App vols := make(map[types.ACName]types.Volume) mounts := make(map[string]schema.Mount) for _, m := range ra.Mounts { mounts[m.Path] = m } sharedVolPath := common.SharedVolumesPath(p.Root) if err := os.MkdirAll(sharedVolPath, sharedVolPerm); err != nil { return nil, fmt.Errorf("could not create shared volumes directory: %v", err) } if err := os.Chmod(sharedVolPath, sharedVolPerm); err != nil { return nil, fmt.Errorf("could not change permissions of %q: %v", sharedVolPath, err) } // Here we bind the volumes to the mountpoints via runtime mounts (--mount) for _, v := range p.Manifest.Volumes { vols[v.Name] = v if v.Kind == "empty" { if err := os.MkdirAll(filepath.Join(sharedVolPath, v.Name.String()), sharedVolPerm); err != nil { return nil, fmt.Errorf("could not create shared volume %q: %v", v.Name, err) } } } for _, mp := range app.MountPoints { // there's already an injected mount for this target path, skip if _, ok := mounts[mp.Path]; ok { continue } vol, ok := vols[mp.Name] if !ok { catCmd := fmt.Sprintf("sudo rkt image cat-manifest --pretty-print %v", id) volumeCmd := "" for _, mp := range app.MountPoints { volumeCmd += fmt.Sprintf("--volume %s,kind=host,source=/some/path ", mp.Name) } return nil, fmt.Errorf("no volume for mountpoint %q:%q in app %q.\n"+ "You can inspect the volumes with:\n\t%v\n"+ "App %q requires the following volumes:\n\t%v", mp.Name, mp.Path, appName, catCmd, appName, volumeCmd) } ra.Mounts = append(ra.Mounts, schema.Mount{Volume: vol.Name, Path: mp.Path}) } for _, m := range ra.Mounts { vol := vols[m.Volume] opt := make([]string, 4) // If the readonly flag in the pod manifest is not nil, // then use it to override the readonly flag in the image manifest. readOnly := isMPReadOnly(app.MountPoints, vol.Name) if vol.ReadOnly != nil { readOnly = *vol.ReadOnly } if readOnly { opt[0] = "--bind-ro=" } else { opt[0] = "--bind=" } switch vol.Kind { case "host": opt[1] = vol.Source case "empty": absRoot, err := filepath.Abs(p.Root) if err != nil { return nil, fmt.Errorf("cannot get pod's root absolute path: %v\n", err) } opt[1] = filepath.Join(common.SharedVolumesPath(absRoot), vol.Name.String()) default: return nil, fmt.Errorf(`invalid volume kind %q. Must be one of "host" or "empty".`, vol.Kind) } opt[2] = ":" opt[3] = filepath.Join(common.RelAppRootfsPath(appName), m.Path) args = append(args, strings.Join(opt, "")) } for _, i := range app.Isolators { switch v := i.Value().(type) { case types.LinuxCapabilitiesSet: var caps []string // TODO: cleanup the API on LinuxCapabilitiesSet to give strings easily. for _, c := range v.Set() { caps = append(caps, string(c)) } if i.Name == types.LinuxCapabilitiesRetainSetName { capList := strings.Join(caps, ",") args = append(args, "--capability="+capList) } } } return args, nil }