func (p *Pod) getVolumeMountValue(mountName types.ACName) (*types.Volume, error) { for _, volume := range p.manifest.Pod.Volumes { if volume.Name.Equals(mountName) { return &volume, nil } } return nil, errors.New("Volume mount point not set :" + mountName.String()) }
// AppNameToImageName takes the name of an app in the Pod and returns the name // of the app's image. The mapping between these two is populated when a Pod is // loaded (using LoadPod). func (p *Pod) AppNameToImageName(appName types.ACName) types.ACIdentifier { image, ok := p.Images[appName.String()] if !ok { // This should be impossible as we have updated the map in LoadPod(). panic(fmt.Sprintf("No images for app %q", appName.String())) } return image.Name }
// Get retrieves an app by the specified name from the AppList; if there is // no such app, nil is returned. The returned *RuntimeApp MUST be considered // read-only. func (al AppList) Get(name types.ACName) *RuntimeApp { for _, a := range al { if name.Equals(a.Name) { aa := a return &aa } } return nil }
// getAppImageManifest returns an ImageManifest for the corresponding AppName. func (p *pod) getAppImageManifest(appName types.ACName) (*schema.ImageManifest, error) { imb, err := ioutil.ReadFile(common.AppImageManifestPath(p.path(), appName)) if err != nil { return nil, err } aim := &schema.ImageManifest{} if err := aim.UnmarshalJSON(imb); err != nil { return nil, errwrap.Wrap(fmt.Errorf("invalid image manifest for app %q", appName.String()), err) } return aim, nil }
func getImageName(p *pkgPod.Pod, appName types.ACName) (string, error) { aim, err := p.AppImageManifest(appName.String()) if err != nil { return "", errwrap.Wrap(errors.New("problem retrieving ImageManifests from pod"), err) } imageName := aim.Name.String() if version, ok := aim.Labels.Get("version"); ok { imageName = fmt.Sprintf("%s:%s", imageName, version) } return imageName, nil }
// setupAppImage mounts the overlay filesystem for the app image that // corresponds to the given hash if useOverlay is true. // It also creates an mtab file in the application's rootfs if one is not // present. func setupAppImage(cfg RunConfig, appName types.ACName, img types.Hash, cdir string, useOverlay bool) error { ad := common.AppPath(cdir, appName) if useOverlay { err := os.MkdirAll(ad, common.DefaultRegularDirPerm) if err != nil { return errwrap.Wrap(errors.New("error creating image directory"), err) } treeStoreID, err := ioutil.ReadFile(common.AppTreeStoreIDPath(cdir, appName)) if err != nil { return err } if err := copyAppManifest(cdir, appName, ad); err != nil { return err } if err := overlayRender(cfg, string(treeStoreID), cdir, ad, appName.String()); err != nil { return errwrap.Wrap(errors.New("error rendering overlay filesystem"), err) } } return ensureMtabExists(filepath.Join(ad, "rootfs")) }
// Enter enters the pod/app by exec()ing the stage1's /enter similar to /init // /enter can expect to have its CWD set to the app root. // appName and command are supplied to /enter on argv followed by any arguments. // stage1Path is the path of the stage1 rootfs func Enter(cdir string, podPID int, appName types.ACName, stage1Path string, cmdline []string) error { if err := os.Chdir(cdir); err != nil { return errwrap.Wrap(errors.New("error changing to dir"), err) } ep, err := getStage1Entrypoint(cdir, enterEntrypoint) if err != nil { return errwrap.Wrap(errors.New("error determining 'enter' entrypoint"), err) } argv := []string{filepath.Join(stage1Path, ep)} argv = append(argv, fmt.Sprintf("--pid=%d", podPID)) argv = append(argv, fmt.Sprintf("--appname=%s", appName.String())) argv = append(argv, "--") argv = append(argv, cmdline...) if err := syscall.Exec(argv[0], argv, os.Environ()); err != nil { return errwrap.Wrap(errors.New("error execing enter"), err) } // never reached return nil }
// AppInfoPath returns the path to the app's appsinfo directory of a pod. func AppInfoPath(root string, appName types.ACName) string { return filepath.Join(AppsInfoPath(root), appName.String()) }
// RelAppPath returns the path of an app relative to the stage1 chroot. func RelAppPath(appName types.ACName) string { return filepath.Join(stage2Dir, appName.String()) }
func (c *Pod) stage2(app types.ACName, user, group string, cwd string, env []string, exec ...string) error { if strings.HasPrefix(user, "/") || strings.HasPrefix(group, "/") { return errors.New("Path-based user/group not supported yet, sorry") } hasPath := false hasTerm := false for _, envVar := range env { if strings.HasPrefix(envVar, "PATH=") { hasPath = true } if strings.HasPrefix(envVar, "TERM=") { hasTerm = true } } if !hasPath { env = append(env, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") } if !hasTerm { // TODO: TERM= only if we're attached to a terminal term := os.Getenv("TERM") if term == "" { term = "vt100" } env = append(env, "TERM="+term) } // Ensure jail is created jid := c.Jid() if jid == 0 { if err := errors.Trace(c.runJail("-c")); err != nil { return errors.Trace(err) } jid = c.Jid() if jid == 0 { panic("Could not start jail") } } mds, err := c.Host.MetadataURL(c.UUID) if err != nil { return errors.Trace(err) } pwf, err := passwd.ReadPasswd(c.Path("rootfs", "app", app.String(), "rootfs", "etc", "passwd")) if err != nil { return errors.Trace(err) } pwent := pwf.Find(user) if pwent == nil { return errors.Errorf("Cannot find user: %#v", user) } if group != "" { grf, err := passwd.ReadGroup(c.Path("rootfs", "app", app.String(), "rootfs", "etc", "group")) if err != nil { return errors.Trace(err) } pwent.Gid = grf.FindGid(group) if pwent.Gid < 0 { return errors.Errorf("Cannot find group: %#v", group) } } if cwd == "" { cwd = "/" } stage2 := filepath.Join(Config().MustGetString("path.libexec"), "stage2") args := []string{ fmt.Sprintf("%d:%d:%d:%s:%s", jid, pwent.Uid, pwent.Gid, app, cwd), "AC_METADATA_URL=" + mds, "USER="******"LOGNAME=" + pwent.Username, "HOME=" + pwent.Home, "SHELL=" + pwent.Shell, } args = append(args, env...) args = append(args, exec...) return run.Command(stage2, args...).Run() }
// TODO(iaguis): RmConfig? func RmApp(dir string, uuid *types.UUID, usesOverlay bool, appName *types.ACName, podPID int) error { p, err := stage1types.LoadPod(dir, uuid) if err != nil { return errwrap.Wrap(errors.New("error loading pod manifest"), err) } pm := p.Manifest var mutable bool ms, ok := pm.Annotations.Get("coreos.com/rkt/stage1/mutable") if ok { mutable, err = strconv.ParseBool(ms) if err != nil { return errwrap.Wrap(errors.New("error parsing mutable annotation"), err) } } if !mutable { return errors.New("immutable pod: cannot remove application") } app := pm.Apps.Get(*appName) if app == nil { return fmt.Errorf("error: nonexistent app %q", *appName) } treeStoreID, err := ioutil.ReadFile(common.AppTreeStoreIDPath(dir, *appName)) if err != nil { return err } eep, err := getStage1Entrypoint(dir, enterEntrypoint) if err != nil { return errwrap.Wrap(errors.New("error determining 'enter' entrypoint"), err) } args := []string{ uuid.String(), appName.String(), filepath.Join(common.Stage1RootfsPath(dir), eep), strconv.Itoa(podPID), } if err := callEntrypoint(dir, appStopEntrypoint, args); err != nil { return err } if err := callEntrypoint(dir, appRmEntrypoint, args); err != nil { return err } appInfoDir := common.AppInfoPath(dir, *appName) if err := os.RemoveAll(appInfoDir); err != nil { return errwrap.Wrap(errors.New("error removing app info directory"), err) } if usesOverlay { appRootfs := common.AppRootfsPath(dir, *appName) if err := syscall.Unmount(appRootfs, 0); err != nil { return err } ts := filepath.Join(dir, "overlay", string(treeStoreID)) if err := os.RemoveAll(ts); err != nil { return errwrap.Wrap(errors.New("error removing app info directory"), err) } } if err := os.RemoveAll(common.AppPath(dir, *appName)); err != nil { return err } appStatusPath := filepath.Join(common.Stage1RootfsPath(dir), "rkt", "status", appName.String()) if err := os.Remove(appStatusPath); err != nil && !os.IsNotExist(err) { return err } envPath := filepath.Join(common.Stage1RootfsPath(dir), "rkt", "env", appName.String()) if err := os.Remove(envPath); err != nil && !os.IsNotExist(err) { return err } removeAppFromPodManifest(pm, appName) if err := updatePodManifest(dir, pm); err != nil { return err } return nil }
func AddApp(cfg AddConfig) error { // there should be only one app in the config app := cfg.Apps.Last() if app == nil { return errors.New("no image specified") } am, err := cfg.Store.GetImageManifest(cfg.Image.String()) if err != nil { return err } var appName *types.ACName if app.Name != "" { appName, err = types.NewACName(app.Name) if err != nil { return err } } else { appName, err = imageNameToAppName(am.Name) if err != nil { return err } } pod, err := pkgPod.PodFromUUIDString(cfg.DataDir, cfg.UUID.String()) if err != nil { return errwrap.Wrap(errors.New("error loading pod"), err) } defer pod.Close() debug("locking pod manifest") if err := pod.ExclusiveLockManifest(); err != nil { return errwrap.Wrap(errors.New("failed to lock pod manifest"), err) } defer pod.UnlockManifest() pm, err := pod.SandboxManifest() if err != nil { return errwrap.Wrap(errors.New("cannot add application"), err) } if pm.Apps.Get(*appName) != nil { return fmt.Errorf("error: multiple apps with name %s", *appName) } if am.App == nil && app.Exec == "" { return fmt.Errorf("error: image %s has no app section and --exec argument is not provided", cfg.Image) } appInfoDir := common.AppInfoPath(cfg.PodPath, *appName) if err := os.MkdirAll(appInfoDir, common.DefaultRegularDirPerm); err != nil { return errwrap.Wrap(errors.New("error creating apps info directory"), err) } pcfg := PrepareConfig{ CommonConfig: cfg.CommonConfig, PrivateUsers: user.NewBlankUidRange(), } if cfg.UsesOverlay { privateUsers, err := preparedWithPrivateUsers(cfg.PodPath) if err != nil { log.FatalE("error reading user namespace information", err) } if err := pcfg.PrivateUsers.Deserialize([]byte(privateUsers)); err != nil { return err } } treeStoreID, err := prepareAppImage(pcfg, *appName, cfg.Image, cfg.PodPath, cfg.UsesOverlay) if err != nil { return errwrap.Wrap(fmt.Errorf("error preparing image %s", cfg.Image), err) } rcfg := RunConfig{ CommonConfig: cfg.CommonConfig, UseOverlay: cfg.UsesOverlay, RktGid: cfg.RktGid, } if err := setupAppImage(rcfg, *appName, cfg.Image, cfg.PodPath, cfg.UsesOverlay); err != nil { return fmt.Errorf("error setting up app image: %v", err) } if cfg.UsesOverlay { imgDir := filepath.Join(cfg.PodPath, "overlay", treeStoreID) if err := os.Chown(imgDir, -1, cfg.RktGid); err != nil { return err } } ra := schema.RuntimeApp{ Name: *appName, App: am.App, Image: schema.RuntimeImage{ Name: &am.Name, ID: cfg.Image, Labels: am.Labels, }, Mounts: MergeMounts(cfg.Apps.Mounts, app.Mounts), ReadOnlyRootFS: app.ReadOnlyRootFS, } if app.Exec != "" { // 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{app.Exec} } if app.Args != nil { ra.App.Exec = append(ra.App.Exec, app.Args...) } if app.WorkingDir != "" { ra.App.WorkingDirectory = app.WorkingDir } if err := prepareIsolators(app, ra.App); err != nil { return err } if app.User != "" { ra.App.User = app.User } if app.Group != "" { ra.App.Group = app.Group } if app.SupplementaryGIDs != nil { ra.App.SupplementaryGIDs = app.SupplementaryGIDs } if app.UserAnnotations != nil { ra.App.UserAnnotations = app.UserAnnotations } if app.UserLabels != nil { ra.App.UserLabels = app.UserLabels } if app.Environments != nil { envs := make([]string, 0, len(app.Environments)) for name, value := range app.Environments { envs = append(envs, fmt.Sprintf("%s=%s", name, value)) } // Let the app level environment override the environment variables. mergeEnvs(&ra.App.Environment, envs, true) } env := ra.App.Environment env.Set("AC_APP_NAME", appName.String()) envFilePath := filepath.Join(common.Stage1RootfsPath(cfg.PodPath), "rkt", "env", appName.String()) if err := common.WriteEnvFile(env, pcfg.PrivateUsers, envFilePath); err != nil { return err } debug("adding app to sandbox") pm.Apps = append(pm.Apps, ra) if err := pod.UpdateManifest(pm, cfg.PodPath); err != nil { return err } args := []string{ fmt.Sprintf("--debug=%t", cfg.Debug), fmt.Sprintf("--uuid=%s", cfg.UUID), fmt.Sprintf("--app=%s", appName), } if _, err := os.Create(common.AppCreatedPath(pod.Path(), appName.String())); err != nil { return err } ce := CrossingEntrypoint{ PodPath: cfg.PodPath, PodPID: cfg.PodPID, AppName: appName.String(), EntrypointName: appAddEntrypoint, EntrypointArgs: args, Interactive: false, } if err := ce.Run(); err != nil { return err } return nil }
// SocketUnitName returns a systemd socket unit name for the given app name. func SocketUnitName(appName types.ACName) string { return appName.String() + ".socket" }
// RelEnvFilePath returns the path to the environment file for the given app name // relative to the pod's root. func RelEnvFilePath(appName types.ACName) string { return filepath.Join(envDir, appName.String()) }
// AppToSystemdMountUnits prepare bind mount unit for empty or host kind mounting // between stage1 rootfs and chrooted filesystem for application func AppToSystemdMountUnits(root string, appName types.ACName, volumes []types.Volume, ra *schema.RuntimeApp, unitsDir string) error { app := ra.App vols := make(map[types.ACName]types.Volume) for _, v := range volumes { vols[v.Name] = v } mounts := GenerateMounts(ra, vols) for _, m := range mounts { vol := vols[m.Volume] // source relative to stage1 rootfs to relative pod root whatPath := filepath.Join(stage1MntDir, vol.Name.String()) whatFullPath := filepath.Join(root, whatPath) if vol.Kind == "empty" { log.Printf("creating an empty volume folder for sharing: %q", whatFullPath) err := os.MkdirAll(whatFullPath, 0700) if err != nil { return err } } // destination relative to stage1 rootfs and relative to pod root wherePath := filepath.Join(common.RelAppRootfsPath(appName), m.Path) whereFullPath := filepath.Join(root, wherePath) // assertion to make sure that "what" exists (created earlier by PodToSystemdHostMountUnits) log.Printf("checking required source path: %q", whatFullPath) if _, err := os.Stat(whatFullPath); os.IsNotExist(err) { return fmt.Errorf("bug: missing source for volume %v", vol.Name) } // optionally prepare app directory log.Printf("optionally preparing destination path: %q", whereFullPath) err := os.MkdirAll(whereFullPath, 0700) if err != nil { return errwrap.Wrap(fmt.Errorf("failed to prepare dir for mount %v", m.Volume), err) } // install new mount unit for bind mount /mnt/volumeName -> /opt/stage2/{app-id}/rootfs/{{mountPoint.Path}} mu, err := installNewMountUnit( root, // where put a mount unit whatPath, // what - stage1 rootfs /mnt/VolumeName wherePath, // where - inside chroot app filesystem "bind", // fstype "bind", // options serviceUnitName(appName), unitsDir, ) if err != nil { return errwrap.Wrap(fmt.Errorf("cannot install new mount unit for app %q", appName.String()), err) } // TODO(iaguis) when we update util-linux to 2.27, this code can go // away and we can bind-mount RO with one unit file. // http://ftp.kernel.org/pub/linux/utils/util-linux/v2.27/v2.27-ReleaseNotes if IsMountReadOnly(vol, app.MountPoints) { opts := []*unit.UnitOption{ unit.NewUnitOption("Unit", "Description", fmt.Sprintf("Remount read-only unit for %s", wherePath)), unit.NewUnitOption("Unit", "DefaultDependencies", "false"), unit.NewUnitOption("Unit", "After", mu), unit.NewUnitOption("Unit", "Wants", mu), unit.NewUnitOption("Service", "ExecStart", fmt.Sprintf("/usr/bin/mount -o remount,ro %s", wherePath)), unit.NewUnitOption("Install", "RequiredBy", mu), } remountUnitPath := filepath.Join(root, unitsDir, unit.UnitNamePathEscape(wherePath+"-remount.service")) if err := writeUnit(opts, remountUnitPath); err != nil { return err } } } return nil }
// serviceUnitName returns a systemd service unit name for the given app name. // note: it was shamefully copy-pasted from stage1/init/path.go // TODO: extract common functions from path.go func serviceUnitName(appName types.ACName) string { return appName.String() + ".service" }
func (c *Pod) stage2(app types.ACName, user, group string, cwd string, env []string, exec ...string) error { if strings.HasPrefix(user, "/") || strings.HasPrefix(group, "/") { return errors.New("Path-based user/group not supported yet, sorry") } // Ensure jail is created jid := c.Jid() if jid == 0 { if err := errors.Trace(c.runJail("-c")); err != nil { return errors.Trace(err) } jid = c.Jid() if jid == 0 { panic("Could not start jail") } } mds, err := c.Host.MetadataURL(c.UUID) if err != nil { return errors.Trace(err) } pwf, err := passwd.ReadPasswd(c.Path("rootfs", "app", app.String(), "rootfs", "etc", "passwd")) if err != nil { return errors.Trace(err) } pwent := pwf.Find(user) if pwent == nil { return errors.Errorf("Cannot find user: %#v", user) } if group != "" { grf, err := passwd.ReadGroup(c.Path("rootfs", "app", app.String(), "rootfs", "etc", "group")) if err != nil { return errors.Trace(err) } pwent.Gid = grf.FindGid(group) if pwent.Gid < 0 { return errors.Errorf("Cannot find group: %#v", group) } } if cwd == "" { cwd = "/" } stage2 := filepath.Join(Config().MustGetString("path.libexec"), "stage2") args := []string{ "-jid", strconv.Itoa(jid), "-app", string(app), "-mds", mds, "-uid", strconv.Itoa(pwent.Uid), "-gid", strconv.Itoa(pwent.Gid), "-cwd", cwd, "-setenv", "USER="******"-setenv", "LOGNAME=" + pwent.Username, "-setenv", "HOME=" + pwent.Home, "-setenv", "SHELL=" + pwent.Shell, } args = append(args, env...) args = append(args, exec...) return run.Command(stage2, args...).Run() }