// GenerateMounts maps MountPoint paths to volumes, returning a list of Mounts. func GenerateMounts(ra *schema.RuntimeApp, volumes map[types.ACName]types.Volume) []schema.Mount { app := ra.App mnts := make(map[string]schema.Mount) for _, m := range ra.Mounts { mnts[m.Path] = m } for _, mp := range app.MountPoints { // there's already an injected mount for this target path, skip if _, ok := mnts[mp.Path]; ok { continue } vol, ok := volumes[mp.Name] // there is no volume for this mount point, creating an "empty" volume // implicitly if !ok { emptyVol := types.Volume{ Name: mp.Name, Kind: "empty", } fmt.Fprintf(os.Stderr, "rkt: warning: no volume specified for mount point %q, implicitly creating an \"empty\" volume. This volume will be removed when the pod is garbage-collected.\n", mp.Name) volumes[mp.Name] = emptyVol ra.Mounts = append(ra.Mounts, schema.Mount{Volume: mp.Name, Path: mp.Path}) } else { ra.Mounts = append(ra.Mounts, schema.Mount{Volume: vol.Name, Path: mp.Path}) } } return ra.Mounts }
func GenerateMounts(ra *schema.RuntimeApp, volumes map[types.ACName]types.Volume) ([]schema.Mount, error) { appName := ra.Name id := ra.Image.ID app := ra.App mnts := make(map[string]schema.Mount) for _, m := range ra.Mounts { mnts[m.Path] = m } for _, mp := range app.MountPoints { // there's already an injected mount for this target path, skip if _, ok := mnts[mp.Path]; ok { continue } vol, ok := volumes[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}) } return ra.Mounts, nil }
// generatePodManifest creates the pod manifest from the command line input. // It returns the pod manifest as []byte on success. // This is invoked if no pod manifest is specified at the command line. func generatePodManifest(cfg PrepareConfig, dir string) ([]byte, error) { pm := schema.PodManifest{ ACKind: "PodManifest", Apps: make(schema.AppList, 0), } v, err := types.NewSemVer(version.Version) if err != nil { return nil, fmt.Errorf("error creating version: %v", err) } pm.ACVersion = *v if err := cfg.Apps.Walk(func(app *apps.App) error { img := app.ImageID am, err := cfg.Store.GetImageManifest(img.String()) if err != nil { return fmt.Errorf("error getting the manifest: %v", err) } appName, err := imageNameToAppName(am.Name) if err != nil { return fmt.Errorf("error converting image name to app name: %v", err) } if err := prepareAppImage(cfg, *appName, img, dir, cfg.UseOverlay); err != nil { return fmt.Errorf("error setting up image %s: %v", img, err) } if pm.Apps.Get(*appName) != nil { return fmt.Errorf("error: multiple apps with name %s", am.Name) } if am.App == nil && app.Exec == "" { return fmt.Errorf("error: image %s has no app section and --exec argument is not provided", img) } ra := schema.RuntimeApp{ // TODO(vc): leverage RuntimeApp.Name for disambiguating the apps Name: *appName, App: am.App, Image: schema.RuntimeImage{ Name: &am.Name, ID: img, Labels: am.Labels, }, Annotations: am.Annotations, Mounts: MergeMounts(cfg.Apps.Mounts, app.Mounts), } if execOverride := app.Exec; execOverride != "" { // Create a minimal App section if not present if am.App == nil { ra.App = &types.App{ User: strconv.Itoa(os.Getuid()), Group: strconv.Itoa(os.Getgid()), } } ra.App.Exec = []string{execOverride} } if execAppends := app.Args; execAppends != nil { ra.App.Exec = append(ra.App.Exec, execAppends...) } if memoryOverride := app.MemoryLimit; memoryOverride != nil { isolator := memoryOverride.AsIsolator() ra.App.Isolators = append(ra.App.Isolators, isolator) } if cpuOverride := app.CPULimit; cpuOverride != nil { isolator := cpuOverride.AsIsolator() ra.App.Isolators = append(ra.App.Isolators, isolator) } if cfg.InheritEnv || len(cfg.ExplicitEnv) > 0 { MergeEnvs(&ra.App.Environment, cfg.InheritEnv, cfg.ExplicitEnv) } pm.Apps = append(pm.Apps, ra) return nil }); err != nil { return nil, err } // TODO(jonboulle): check that app mountpoint expectations are // satisfied here, rather than waiting for stage1 pm.Volumes = cfg.Apps.Volumes pm.Ports = cfg.Ports pmb, err := json.Marshal(pm) if err != nil { return nil, fmt.Errorf("error marshalling pod manifest: %v", err) } return pmb, nil }
// generatePodManifest creates the pod manifest from the command line input. // It returns the pod manifest as []byte on success. // This is invoked if no pod manifest is specified at the command line. func generatePodManifest(cfg PrepareConfig, dir string) ([]byte, error) { pm := schema.PodManifest{ ACKind: "PodManifest", Apps: make(schema.AppList, 0), } v, err := types.NewSemVer(version.Version) if err != nil { return nil, fmt.Errorf("error creating version: %v", err) } pm.ACVersion = *v if err := cfg.Apps.Walk(func(app *apps.App) error { img := app.ImageID am, err := prepareAppImage(cfg, img, dir, cfg.UseOverlay) if err != nil { return fmt.Errorf("error setting up image %s: %v", img, err) } if pm.Apps.Get(am.Name) != nil { return fmt.Errorf("error: multiple apps with name %s", am.Name) } if am.App == nil { return fmt.Errorf("error: image %s has no app section", img) } ra := schema.RuntimeApp{ // TODO(vc): leverage RuntimeApp.Name for disambiguating the apps Name: am.Name, Image: schema.RuntimeImage{ Name: &am.Name, ID: img, }, Annotations: am.Annotations, } if execAppends := app.Args; execAppends != nil { ra.App = am.App ra.App.Exec = append(ra.App.Exec, execAppends...) } if cfg.InheritEnv || len(cfg.ExplicitEnv) > 0 { if ra.App == nil { ra.App = am.App } MergeEnvs(&ra.App.Environment, cfg.InheritEnv, cfg.ExplicitEnv) } pm.Apps = append(pm.Apps, ra) return nil }); err != nil { return nil, err } // TODO(jonboulle): check that app mountpoint expectations are // satisfied here, rather than waiting for stage1 pm.Volumes = cfg.Volumes pm.Ports = cfg.Ports pmb, err := json.Marshal(pm) if err != nil { return nil, fmt.Errorf("error marshalling pod manifest: %v", err) } return pmb, 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 }