// appToNspawnArgs transforms the given app manifest, with the given associated // app image id, into a subset of applicable systemd-nspawn argument func (c *Container) appToNspawnArgs(am *schema.AppManifest, id types.Hash) ([]string, error) { args := []string{} name := am.Name.String() vols := make(map[types.ACName]types.Volume) for _, v := range c.Manifest.Volumes { for _, f := range v.Fulfills { vols[f] = v } } for _, mp := range am.MountPoints { key := mp.Name vol, ok := vols[key] if !ok { return nil, fmt.Errorf("no volume for mountpoint %q in app %q", key, name) } opt := make([]string, 4) if mp.ReadOnly { opt[0] = "--bind-ro=" } else { opt[0] = "--bind=" } opt[1] = vol.Source opt[2] = ":" opt[3] = filepath.Join(rktpath.RelAppRootfsPath(id), mp.Path) args = append(args, strings.Join(opt, "")) } return args, nil }
// appToSystemd transforms the provided app manifest into systemd units func (c *Container) appToSystemd(am *schema.AppManifest, id types.Hash) error { name := am.Name.String() execStart := strings.Join(am.Exec, " ") opts := []*unit.UnitOption{ &unit.UnitOption{"Unit", "Description", name}, &unit.UnitOption{"Unit", "DefaultDependencies", "false"}, &unit.UnitOption{"Unit", "OnFailureJobMode", "isolate"}, &unit.UnitOption{"Unit", "OnFailure", "reaper.service"}, &unit.UnitOption{"Unit", "Wants", "exit-watcher.service"}, &unit.UnitOption{"Service", "Restart", "no"}, &unit.UnitOption{"Service", "RootDirectory", rktpath.RelAppRootfsPath(id)}, &unit.UnitOption{"Service", "ExecStart", execStart}, &unit.UnitOption{"Service", "User", am.User}, &unit.UnitOption{"Service", "Group", am.Group}, } for _, eh := range am.EventHandlers { var typ string switch eh.Name { case "pre-start": typ = "ExecStartPre" case "post-stop": typ = "ExecStopPost" default: return fmt.Errorf("unrecognized eventHandler: %v", eh.Name) } exec := strings.Join(eh.Exec, " ") opts = append(opts, &unit.UnitOption{"Service", typ, exec}) } env := am.Environment env["AC_APP_NAME"] = name for ek, ev := range env { ee := fmt.Sprintf(`"%s=%s"`, ek, ev) opts = append(opts, &unit.UnitOption{"Service", "Environment", ee}) } saPorts := []types.Port{} for _, p := range am.Ports { if p.SocketActivated { saPorts = append(saPorts, p) } } if len(saPorts) > 0 { sockopts := []*unit.UnitOption{ &unit.UnitOption{"Unit", "Description", name + " socket-activated ports"}, &unit.UnitOption{"Unit", "DefaultDependencies", "false"}, &unit.UnitOption{"Socket", "BindIPv6Only", "both"}, &unit.UnitOption{"Socket", "Service", ServiceUnitName(id)}, } for _, sap := range saPorts { var proto string switch sap.Protocol { case "tcp": proto = "ListenStream" case "udp": proto = "ListenDatagram" default: return fmt.Errorf("unrecognized protocol: %v", sap.Protocol) } sockopts = append(sockopts, &unit.UnitOption{"Socket", proto, fmt.Sprintf("%v", sap.Port)}) } file, err := os.OpenFile(SocketUnitPath(c.Root, id), os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return fmt.Errorf("failed to create socket file: %v", err) } defer file.Close() if _, err = io.Copy(file, unit.Serialize(sockopts)); err != nil { return fmt.Errorf("failed to write socket unit file: %v", err) } if err = os.Symlink(path.Join("..", SocketUnitName(id)), SocketWantPath(c.Root, id)); err != nil { return fmt.Errorf("failed to link socket want: %v", err) } opts = append(opts, &unit.UnitOption{"Unit", "Requires", SocketUnitName(id)}) } file, err := os.OpenFile(ServiceUnitPath(c.Root, id), 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) } if err = os.Symlink(path.Join("..", ServiceUnitName(id)), ServiceWantPath(c.Root, id)); err != nil { return fmt.Errorf("failed to link service want: %v", err) } return nil }