// Run wraps the execution of a stage1 entrypoint which // requires crossing the stage0/stage1/stage2 boundary during its execution, // by setting up proper environment variables for enter. func (ce CrossingEntrypoint) Run() error { enterCmd, err := getStage1Entrypoint(ce.PodPath, enterEntrypoint) if err != nil { return errwrap.Wrap(errors.New("error determining 'enter' entrypoint"), err) } previousDir, err := os.Getwd() if err != nil { return err } if err := os.Chdir(ce.PodPath); err != nil { return errwrap.Wrap(errors.New("failed changing to dir"), err) } ep, err := getStage1Entrypoint(ce.PodPath, ce.EntrypointName) if err != nil { return fmt.Errorf("%q not implemented for pod's stage1: %v", ce.EntrypointName, err) } execArgs := []string{filepath.Join(common.Stage1RootfsPath(ce.PodPath), ep)} execArgs = append(execArgs, ce.EntrypointArgs...) pathEnv := os.Getenv("PATH") if pathEnv == "" { pathEnv = common.DefaultPath } execEnv := []string{ fmt.Sprintf("%s=%s", common.CrossingEnterCmd, filepath.Join(common.Stage1RootfsPath(ce.PodPath), enterCmd)), fmt.Sprintf("%s=%d", common.CrossingEnterPID, ce.PodPID), fmt.Sprintf("PATH=%s", pathEnv), } c := exec.Cmd{ Path: execArgs[0], Args: execArgs, Env: execEnv, } if ce.Interactive { c.Stdin = os.Stdin c.Stdout = os.Stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { return fmt.Errorf("error executing stage1 entrypoint: %v", err) } } else { out, err := c.CombinedOutput() if err != nil { return fmt.Errorf("error executing stage1 entrypoint: %s", string(out)) } } if err := os.Chdir(previousDir); err != nil { return errwrap.Wrap(errors.New("failed changing to dir"), err) } return nil }
/* Bind-mount the hosts /etc/hosts in to the stage1's /etc/rkt-hosts That file will then be bind-mounted in to the stage2 by perpare-app.c */ func UseHostHosts(mnt fs.MountUnmounter, podRoot string) error { return BindMount( mnt, "/etc/hosts", filepath.Join(_common.Stage1RootfsPath(podRoot), "etc/rkt-hosts"), true) }
/* Bind-mount the hosts /etc/resolv.conf in to the stage1's /etc/rkt-resolv.conf. That file will then be bind-mounted in to the stage2 by perpare-app.c */ func UseHostResolv(mnt fs.MountUnmounter, podRoot string) error { return BindMount( mnt, "/etc/resolv.conf", filepath.Join(_common.Stage1RootfsPath(podRoot), "etc/rkt-resolv.conf"), true) }
// Loads nets specified by user and default one from stage1 func (e *podEnv) loadNets() ([]activeNet, error) { nets, err := loadUserNets(e.localConfig, e.netsLoadList) if err != nil { return nil, err } if e.netsLoadList.None() { return nets, nil } if !netExists(nets, "default") && !netExists(nets, "default-restricted") { var defaultNet string if e.netsLoadList.Specific("default") || e.netsLoadList.All() { defaultNet = DefaultNetPath } else { defaultNet = DefaultRestrictedNetPath } defPath := path.Join(common.Stage1RootfsPath(e.podRoot), defaultNet) n, err := loadNet(defPath) if err != nil { return nil, err } nets = append(nets, *n) } missing := missingNets(e.netsLoadList, nets) if len(missing) > 0 { return nil, fmt.Errorf("networks not found: %v", strings.Join(missing, ", ")) } return nets, nil }
func copyResolv(p *stage1commontypes.Pod) error { ra := p.Manifest.Apps[0] stage1Rootfs := common.Stage1RootfsPath(p.Root) resolvPath := filepath.Join(stage1Rootfs, "etc", "rkt-resolv.conf") appRootfs := common.AppRootfsPath(p.Root, ra.Name) targetEtc := filepath.Join(appRootfs, "etc") targetResolvPath := filepath.Join(targetEtc, "resolv.conf") _, err := os.Stat(resolvPath) switch { case os.IsNotExist(err): return nil case err != nil: return err } _, err = os.Stat(targetResolvPath) if err != nil && !os.IsNotExist(err) { return err } return fileutil.CopyRegularFile(resolvPath, targetResolvPath) }
// mirrorLocalZoneInfo tries to reproduce the /etc/localtime target in stage1/ to satisfy systemd-nspawn func mirrorLocalZoneInfo(root string) { zif, err := os.Readlink(localtimePath) if err != nil { return } // On some systems /etc/localtime is a relative symlink, make it absolute if !filepath.IsAbs(zif) { zif = filepath.Join(filepath.Dir(localtimePath), zif) zif = filepath.Clean(zif) } src, err := os.Open(zif) if err != nil { return } defer src.Close() destp := filepath.Join(common.Stage1RootfsPath(root), zif) if err = os.MkdirAll(filepath.Dir(destp), 0755); err != nil { return } dest, err := os.OpenFile(destp, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return } defer dest.Close() _, _ = io.Copy(dest, src) }
func gcNetworking(podID *types.UUID) error { var flavor string // we first try to read the flavor from stage1 for backwards compatibility flavor, err := os.Readlink(filepath.Join(common.Stage1RootfsPath("."), "flavor")) if err != nil { // if we couldn't read the flavor from stage1 it could mean the overlay // filesystem is already unmounted (e.g. the system has been rebooted). // In that case we try to read it from the pod's root directory flavor, err = os.Readlink("flavor") if err != nil { return errwrap.Wrap(errors.New("failed to get stage1 flavor"), err) } } n, err := networking.Load(".", podID) switch { case err == nil: n.Teardown(flavor, debug) case os.IsNotExist(err): // probably ran with --net=host default: return errwrap.Wrap(errors.New("failed loading networking state"), err) } return nil }
// PodToSystemd creates the appropriate systemd service unit files for // all the constituent apps of the Pod func (p *Pod) PodToSystemd(interactive bool, flavor string, privateUsers string) error { if flavor == "kvm" { // prepare all applications names to become dependency for mount units // all host-shared folder has to become available before applications starts var appNames []types.ACName for _, runtimeApp := range p.Manifest.Apps { appNames = append(appNames, runtimeApp.Name) } // mount host volumes through some remote file system e.g. 9p to /mnt/volumeName location // order is important here: podToSystemHostMountUnits prepares folders that are checked by each appToSystemdMountUnits later err := kvm.PodToSystemdHostMountUnits(common.Stage1RootfsPath(p.Root), p.Manifest.Volumes, appNames, unitsDir) if err != nil { return fmt.Errorf("failed to transform pod volumes into mount units: %v", err) } } for i := range p.Manifest.Apps { ra := &p.Manifest.Apps[i] if err := p.appToSystemd(ra, interactive, flavor, privateUsers); err != nil { return fmt.Errorf("failed to transform app %q into systemd service: %v", ra.Name, err) } } return nil }
// GC enters the pod by fork/exec()ing the stage1's /gc similar to /init. // /gc can expect to have its CWD set to the pod root. func GC(pdir string, uuid *types.UUID) error { err := unregisterPod(pdir, uuid) if err != nil { // Probably not worth abandoning the rest log.PrintE("warning: could not unregister pod with metadata service", err) } stage1Path := common.Stage1RootfsPath(pdir) ep, err := getStage1Entrypoint(pdir, gcEntrypoint) if err != nil { return errwrap.Wrap(errors.New("error determining 'gc' entrypoint"), err) } args := []string{filepath.Join(stage1Path, ep)} if debugEnabled { args = append(args, "--debug") } args = append(args, uuid.String()) c := exec.Cmd{ Path: args[0], Args: args, Stderr: os.Stderr, Dir: pdir, } return c.Run() }
// WritePrepareAppTemplate writes service unit files for preparing the pod's applications func WritePrepareAppTemplate(p *stage1commontypes.Pod) error { opts := []*unit.UnitOption{ unit.NewUnitOption("Unit", "Description", "Prepare minimum environment for chrooted applications"), unit.NewUnitOption("Unit", "DefaultDependencies", "false"), unit.NewUnitOption("Unit", "OnFailureJobMode", "fail"), unit.NewUnitOption("Unit", "Requires", "systemd-journald.service"), unit.NewUnitOption("Unit", "After", "systemd-journald.service"), unit.NewUnitOption("Service", "Type", "oneshot"), unit.NewUnitOption("Service", "Restart", "no"), unit.NewUnitOption("Service", "ExecStart", "/prepare-app %I"), unit.NewUnitOption("Service", "User", "0"), unit.NewUnitOption("Service", "Group", "0"), unit.NewUnitOption("Service", "CapabilityBoundingSet", "CAP_SYS_ADMIN CAP_DAC_OVERRIDE"), } unitsPath := filepath.Join(common.Stage1RootfsPath(p.Root), UnitsDir) file, err := os.OpenFile(filepath.Join(unitsPath, "[email protected]"), os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return fmt.Errorf("failed to create service unit file: %v", err) } defer file.Close() if _, err = io.Copy(file, unit.Serialize(opts)); err != nil { return fmt.Errorf("failed to write service unit file: %v", err) } return nil }
func writeAppReaper(p *stage1commontypes.Pod, appName string) error { opts := []*unit.UnitOption{ unit.NewUnitOption("Unit", "Description", fmt.Sprintf("%s Reaper", appName)), unit.NewUnitOption("Unit", "DefaultDependencies", "false"), unit.NewUnitOption("Unit", "StopWhenUnneeded", "yes"), unit.NewUnitOption("Unit", "Wants", "shutdown.service"), unit.NewUnitOption("Unit", "After", "shutdown.service"), unit.NewUnitOption("Unit", "Conflicts", "exit.target"), unit.NewUnitOption("Unit", "Conflicts", "halt.target"), unit.NewUnitOption("Unit", "Conflicts", "poweroff.target"), unit.NewUnitOption("Service", "RemainAfterExit", "yes"), unit.NewUnitOption("Service", "ExecStop", fmt.Sprintf("/reaper.sh %s", appName)), } unitsPath := filepath.Join(common.Stage1RootfsPath(p.Root), UnitsDir) file, err := os.OpenFile(filepath.Join(unitsPath, fmt.Sprintf("reaper-%s.service", appName)), os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return fmt.Errorf("failed to create service unit file: %v", err) } defer file.Close() if _, err = io.Copy(file, unit.Serialize(opts)); err != nil { return fmt.Errorf("failed to write service unit file: %v", err) } return nil }
func NewBuilder(podRoot string, podUUID *types.UUID) (*Builder, error) { pod, err := stage1commontypes.LoadPod(podRoot, podUUID) if err != nil { logs.WithError(err).Fatal("Failed to load pod") } if len(pod.Manifest.Apps) != 1 { logs.Fatal("dgr builder support only 1 application") } fields := data.WithField("aci", manifestApp(pod).Name) aciPath, ok := manifestApp(pod).App.Environment.Get(common.EnvAciPath) if !ok || aciPath == "" { return nil, errs.WithF(fields, "Builder image require "+common.EnvAciPath+" environment variable") } aciTarget, ok := manifestApp(pod).App.Environment.Get(common.EnvAciTarget) if !ok || aciPath == "" { return nil, errs.WithF(fields, "Builder image require "+common.EnvAciTarget+" environment variable") } return &Builder{ fields: fields, aciHomePath: aciPath, aciTargetPath: aciTarget, pod: pod, stage1Rootfs: rktcommon.Stage1RootfsPath(pod.Root), stage2Rootfs: filepath.Join(rktcommon.AppPath(pod.Root, manifestApp(pod).Name), "rootfs"), }, nil }
func StopPod(dir string, force bool, uuid *types.UUID) error { s1rootfs := common.Stage1RootfsPath(dir) if err := os.Chdir(dir); err != nil { return fmt.Errorf("failed changing to dir: %v", err) } ep, err := getStage1Entrypoint(dir, stopEntrypoint) if err != nil { return fmt.Errorf("rkt stop not implemented for pod's stage1: %v", err) } args := []string{filepath.Join(s1rootfs, ep)} debug("Execing %s", ep) if force { args = append(args, "--force") } args = append(args, uuid.String()) c := exec.Cmd{ Path: args[0], Args: args, Stdout: os.Stdout, Stderr: os.Stderr, } return c.Run() }
func (e *podEnv) pluginPaths() []string { // try 3rd-party path first return []string{ UserNetPluginsPath, filepath.Join(common.Stage1RootfsPath(e.podRoot), BuiltinNetPluginsPath), } }
// WriteDefaultTarget writes the default.target unit file // which is responsible for bringing up the applications func WriteDefaultTarget(p *stage1commontypes.Pod) error { opts := []*unit.UnitOption{ unit.NewUnitOption("Unit", "Description", "rkt apps target"), unit.NewUnitOption("Unit", "DefaultDependencies", "false"), } for i := range p.Manifest.Apps { ra := &p.Manifest.Apps[i] serviceName := ServiceUnitName(ra.Name) opts = append(opts, unit.NewUnitOption("Unit", "After", serviceName)) opts = append(opts, unit.NewUnitOption("Unit", "Wants", serviceName)) } unitsPath := filepath.Join(common.Stage1RootfsPath(p.Root), UnitsDir) file, err := os.OpenFile(filepath.Join(unitsPath, "default.target"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err } defer file.Close() if _, err = io.Copy(file, unit.Serialize(opts)); err != nil { return err } return nil }
func (e *podEnv) setupNets(nets []activeNet, noDNS bool) error { err := os.MkdirAll(e.netDir(), 0755) if err != nil { return err } i := 0 defer func() { if err != nil { e.teardownNets(nets[:i]) } }() n := activeNet{} // did stage0 already make /etc/rkt-resolv.conf (i.e. --dns passed) resolvPath := filepath.Join(common.Stage1RootfsPath(e.podRoot), "etc/rkt-resolv.conf") _, err = os.Stat(resolvPath) if err != nil && !os.IsNotExist(err) { return errwrap.Wrap(fmt.Errorf("error statting /etc/rkt-resolv.conf"), err) } podHasResolvConf := err == nil for i, n = range nets { if debuglog { stderr.Printf("loading network %v with type %v", n.conf.Name, n.conf.Type) } n.runtime.IfName = fmt.Sprintf(IfNamePattern, i) if n.runtime.ConfPath, err = copyFileToDir(n.runtime.ConfPath, e.netDir()); err != nil { return errwrap.Wrap(fmt.Errorf("error copying %q to %q", n.runtime.ConfPath, e.netDir()), err) } // Actually shell out to the plugin err = e.netPluginAdd(&n, e.podNS.Path()) if err != nil { return errwrap.Wrap(fmt.Errorf("error adding network %q", n.conf.Name), err) } // Generate rkt-resolv.conf if it's not already there. // The first network plugin that supplies a non-empty // DNS response will win, unless noDNS is true (--dns passed to rkt run) if !common.IsDNSZero(&n.runtime.DNS) && !noDNS { if !podHasResolvConf { err := ioutil.WriteFile( resolvPath, []byte(common.MakeResolvConf(n.runtime.DNS, "Generated by rkt from network "+n.conf.Name)), 0644) if err != nil { return errwrap.Wrap(fmt.Errorf("error creating resolv.conf"), err) } podHasResolvConf = true } else { stderr.Printf("Warning: network %v plugin specified DNS configuration, but DNS already supplied", n.conf.Name) } } } return nil }
func ensureKeysExistInPod(workDir string) error { destRootfs := common.Stage1RootfsPath(workDir) keyDirPath := filepath.Join(destRootfs, u.HomeDir, ".ssh") if err := os.MkdirAll(keyDirPath, 0700); err != nil { return err } return ensureAuthorizedKeysExist(keyDirPath) }
// GetFlavor populates a flavor string based on the flavor itself and respectively the systemd version func GetFlavor(p *stage1commontypes.Pod) (flavor string, systemdVersion string, err error) { flavor, err = os.Readlink(filepath.Join(common.Stage1RootfsPath(p.Root), "flavor")) if err != nil { return "", "", fmt.Errorf("unable to determine stage1 flavor: %v", err) } if flavor == "host" { // This flavor does not contain systemd, so don't return systemdVersion return flavor, "", nil } systemdVersionBytes, err := ioutil.ReadFile(filepath.Join(common.Stage1RootfsPath(p.Root), "systemd-version")) if err != nil { return "", "", fmt.Errorf("unable to determine stage1's systemd version: %v", err) } systemdVersion = strings.Trim(string(systemdVersionBytes), " \n") return flavor, systemdVersion, nil }
// GetFlavor populates a flavor string based on the flavor itself and respectively the systemd version // If the systemd version couldn't be guessed, it will be set to 0. func GetFlavor(p *stage1commontypes.Pod) (flavor string, systemdVersion int, err error) { flavor, err = os.Readlink(filepath.Join(common.Stage1RootfsPath(p.Root), "flavor")) if err != nil { return "", -1, errwrap.Wrap(errors.New("unable to determine stage1 flavor"), err) } if flavor == "host" { // This flavor does not contain systemd, parse "systemctl --version" systemctlBin, err := common.LookupPath("systemctl", os.Getenv("PATH")) if err != nil { return "", -1, err } systemdVersion, err := common.SystemdVersion(systemctlBin) if err != nil { return "", -1, errwrap.Wrap(errors.New("error finding systemctl version"), err) } return flavor, systemdVersion, nil } systemdVersionBytes, err := ioutil.ReadFile(filepath.Join(common.Stage1RootfsPath(p.Root), "systemd-version")) if err != nil { return "", -1, errwrap.Wrap(errors.New("unable to determine stage1's systemd version"), err) } systemdVersionString := strings.Trim(string(systemdVersionBytes), " \n") // systemdVersionString is either a tag name or a branch name. If it's a // tag name it's of the form "v229", remove the first character to get the // number. systemdVersion, err = strconv.Atoi(systemdVersionString[1:]) if err != nil { // If we get a syntax error, it means the parsing of the version string // of the form "v229" failed, set it to 0 to indicate we couldn't guess // it. if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrSyntax { systemdVersion = 0 } else { return "", -1, errwrap.Wrap(errors.New("error parsing stage1's systemd version"), err) } } return flavor, systemdVersion, nil }
// KvmNetworkingToSystemd generates systemd unit files for a pod according to network configuration func KvmNetworkingToSystemd(p *stage1commontypes.Pod, n *networking.Networking) error { podRoot := common.Stage1RootfsPath(p.Root) // networking netDescriptions := kvm.GetNetworkDescriptions(n) if err := kvm.GenerateNetworkInterfaceUnits(filepath.Join(podRoot, stage1initcommon.UnitsDir), netDescriptions); err != nil { return errwrap.Wrap(errors.New("failed to transform networking to units"), err) } return nil }
// generateSysusers generates systemd sysusers files for a given app so that // corresponding entries in /etc/passwd and /etc/group are created in stage1. // This is needed to use the "User="******"Group=" options in the systemd // service files of apps. // If there're several apps defining the same UIDs/GIDs, systemd will take care // of only generating one /etc/{passwd,group} entry func generateSysusers(p *stage1commontypes.Pod, ra *schema.RuntimeApp, uid_ int, gid_ int, uidRange *user.UidRange) error { var toShift []string app := ra.App appName := ra.Name sysusersDir := path.Join(common.Stage1RootfsPath(p.Root), "usr/lib/sysusers.d") toShift = append(toShift, sysusersDir) if err := os.MkdirAll(sysusersDir, 0755); err != nil { return err } gids := append(app.SupplementaryGIDs, gid_) // Create the Unix user and group var sysusersConf []string for _, g := range gids { groupname := "gen" + strconv.Itoa(g) sysusersConf = append(sysusersConf, fmt.Sprintf("g %s %d\n", groupname, g)) } username := "******" + strconv.Itoa(uid_) sysusersConf = append(sysusersConf, fmt.Sprintf("u %s %d \"%s\"\n", username, uid_, username)) sysusersFile := path.Join(common.Stage1RootfsPath(p.Root), "usr/lib/sysusers.d", ServiceUnitName(appName)+".conf") toShift = append(toShift, sysusersFile) if err := ioutil.WriteFile(sysusersFile, []byte(strings.Join(sysusersConf, "\n")), 0640); err != nil { return err } if uidRange.Shift != 0 && uidRange.Count != 0 { for _, f := range toShift { if err := os.Chown(f, int(uidRange.Shift), int(uidRange.Shift)); err != nil { return err } } } return nil }
// deletePod cleans up files and resource associated with the pod // pod must be under exclusive lock and be in either ExitedGarbage // or Garbage state func deletePod(p *pkgPod.Pod) { podState := p.State() if podState != pkgPod.ExitedGarbage && podState != pkgPod.Garbage { stderr.Panicf("logic error: deletePod called with non-garbage pod %q (status %q)", p.UUID, p.State()) } if podState == pkgPod.ExitedGarbage { s, err := imagestore.NewStore(storeDir()) if err != nil { stderr.PrintE("cannot open store", err) return } defer s.Close() ts, err := treestore.NewStore(treeStoreDir(), s) if err != nil { stderr.PrintE("cannot open store", err) return } if globalFlags.Debug { stage0.InitDebug() } if err := mountPodStage1(ts, p); err == nil { if err = stage0.GC(p.Path(), p.UUID); err != nil { stderr.PrintE(fmt.Sprintf("problem performing stage1 GC on %q", p.UUID), err) } // an overlay fs can be mounted over itself, let's unmount it here // if we mounted it before to avoid problems when running // stage0.MountGC if p.UsesOverlay() { stage1Mnt := common.Stage1RootfsPath(p.Path()) if err := syscall.Unmount(stage1Mnt, 0); err != nil { stderr.PrintE("error unmounting stage1", err) } } } else { stderr.PrintE("skipping stage1 GC", err) } // unmount all leftover mounts if err := stage0.MountGC(p.Path(), p.UUID.String()); err != nil { stderr.PrintE(fmt.Sprintf("GC of leftover mounts for pod %q failed", p.UUID), err) return } } if err := os.RemoveAll(p.Path()); err != nil { stderr.PrintE(fmt.Sprintf("unable to remove pod %q", p.UUID), err) os.Exit(254) } }
// mountContainerV1Cgroups mounts the cgroup controllers hierarchy in the container's // namespace read-only, leaving the needed knobs in the subcgroup for each-app // read-write so systemd inside stage1 can apply isolators to them func mountContainerV1Cgroups(m fs.Mounter, p *stage1commontypes.Pod, enabledCgroups map[int][]string, subcgroup string, serviceNames []string) error { mountContext := os.Getenv(common.EnvSELinuxMountContext) stage1Root := common.Stage1RootfsPath(p.Root) if err := v1.CreateCgroups(m, stage1Root, enabledCgroups, mountContext); err != nil { return errwrap.Wrap(errors.New("error creating container cgroups"), err) } if err := v1.RemountCgroups(m, stage1Root, enabledCgroups, subcgroup, serviceNames, p.InsecureOptions.DisablePaths); err != nil { return errwrap.Wrap(errors.New("error restricting container cgroups"), err) } return nil }
// Loads nets specified by user and default one from stage1 func (e *podEnv) loadNets() ([]activeNet, error) { nets, err := loadUserNets() if err != nil { return nil, err } if !netExists(nets, "default") { defPath := path.Join(common.Stage1RootfsPath(e.podRoot), DefaultNetPath) n, err := loadNet(defPath) if err != nil { return nil, err } nets = append(nets, *n) } return nets, nil }
// PodToNspawnArgs renders a prepared Pod as a systemd-nspawn // argument list ready to be executed func PodToNspawnArgs(p *stage1commontypes.Pod) ([]string, error) { args := []string{ "--uuid=" + p.UUID.String(), "--machine=" + GetMachineID(p), "--directory=" + common.Stage1RootfsPath(p.Root), } for i := range p.Manifest.Apps { aa, err := appToNspawnArgs(p, &p.Manifest.Apps[i]) if err != nil { return nil, err } args = append(args, aa...) } return args, nil }
func gcNetworking(podID *types.UUID) error { flavor, err := os.Readlink(filepath.Join(common.Stage1RootfsPath("."), "flavor")) if err != nil { return fmt.Errorf("Failed to get stage1 flavor: %v\n", err) } n, err := networking.Load(".", podID) switch { case err == nil: n.Teardown(flavor) case os.IsNotExist(err): // probably ran without --private-net default: return fmt.Errorf("Failed loading networking state: %v", err) } return nil }
func writeShutdownService(p *stage1commontypes.Pod) error { flavor, systemdVersion, err := GetFlavor(p) if err != nil { return err } opts := []*unit.UnitOption{ unit.NewUnitOption("Unit", "Description", "Pod shutdown"), unit.NewUnitOption("Unit", "AllowIsolate", "true"), unit.NewUnitOption("Unit", "StopWhenUnneeded", "yes"), unit.NewUnitOption("Unit", "DefaultDependencies", "false"), unit.NewUnitOption("Service", "RemainAfterExit", "yes"), } shutdownVerb := "exit" // systemd <v227 doesn't allow the "exit" verb when running as PID 1, so // use "halt". // If systemdVersion is 0 it means it couldn't be guessed, assume it's new // enough for "systemctl exit". // This can happen, for example, when building rkt with: // // ./configure --with-stage1-flavors=src --with-stage1-systemd-version=master // // The patches for the "exit" verb are backported to the "coreos" flavor, so // don't rely on the systemd version on the "coreos" flavor. if flavor != "coreos" && systemdVersion != 0 && systemdVersion < 227 { shutdownVerb = "halt" } opts = append(opts, unit.NewUnitOption("Service", "ExecStop", fmt.Sprintf("/usr/bin/systemctl --force %s", shutdownVerb))) unitsPath := filepath.Join(common.Stage1RootfsPath(p.Root), UnitsDir) file, err := os.OpenFile(filepath.Join(unitsPath, "shutdown.service"), os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return errwrap.Wrap(errors.New("failed to create unit file"), err) } defer file.Close() if _, err = io.Copy(file, unit.Serialize(opts)); err != nil { return errwrap.Wrap(errors.New("failed to write unit file"), err) } return nil }
func StopApp(cfg StopConfig) error { p, err := stage1types.LoadPod(cfg.Dir, cfg.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 start application") } app := pm.Apps.Get(*cfg.AppName) if app == nil { return fmt.Errorf("error: nonexistent app %q", *cfg.AppName) } eep, err := getStage1Entrypoint(cfg.Dir, enterEntrypoint) if err != nil { return errwrap.Wrap(errors.New("error determining 'enter' entrypoint"), err) } args := []string{ cfg.UUID.String(), cfg.AppName.String(), filepath.Join(common.Stage1RootfsPath(cfg.Dir), eep), strconv.Itoa(cfg.PodPID), } if err := callEntrypoint(cfg.Dir, appStopEntrypoint, args); err != nil { return err } return nil }
// PodToNspawnArgs renders a prepared Pod as a systemd-nspawn // argument list ready to be executed func (p *Pod) PodToNspawnArgs() ([]string, error) { args := []string{ "--uuid=" + p.UUID.String(), "--machine=" + "rkt-" + p.UUID.String(), "--directory=" + common.Stage1RootfsPath(p.Root), } for _, am := range p.Apps { ra := p.Manifest.Apps.Get(am.Name) if ra == nil { panic("could not find app in pod manifest!") } aa, err := p.appToNspawnArgs(ra, am) if err != nil { return nil, fmt.Errorf("failed to construct args for app %q: %v", am.Name, err) } args = append(args, aa...) } return args, nil }
// PodToNspawnArgs renders a prepared Pod as a systemd-nspawn // argument list ready to be executed func PodToNspawnArgs(p *stage1commontypes.Pod, insecureOptions Stage1InsecureOptions) ([]string, error) { args := []string{ "--uuid=" + p.UUID.String(), "--machine=" + GetMachineID(p), "--directory=" + common.Stage1RootfsPath(p.Root), } for i := range p.Manifest.Apps { aa, err := appToNspawnArgs(p, &p.Manifest.Apps[i], insecureOptions) if err != nil { return nil, err } args = append(args, aa...) } if insecureOptions.DisableCapabilities { args = append(args, "--capability=all") } return args, nil }