// NewUser tracks a new authenticated user and saves its details in the state func NewUser(st *state.State, username, macaroon string, discharges []string) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err == state.ErrNoState { authStateData = AuthState{} } else if err != nil { return nil, err } sort.Strings(discharges) authStateData.LastID++ authenticatedUser := UserState{ ID: authStateData.LastID, Username: username, Macaroon: macaroon, Discharges: discharges, StoreMacaroon: macaroon, StoreDischarges: discharges, } authStateData.Users = append(authStateData.Users, authenticatedUser) st.Set("auth", authStateData) return &authenticatedUser, nil }
// allLocalSnapInfos returns the information about the all current snaps and their SnapStates. func allLocalSnapInfos(st *state.State) ([]aboutSnap, error) { st.Lock() defer st.Unlock() snapStates, err := snapstate.All(st) if err != nil { return nil, err } about := make([]aboutSnap, 0, len(snapStates)) var firstErr error for name, snapState := range snapStates { info, err := snap.ReadInfo(name, snapState.Current()) if err != nil { // XXX: aggregate instead? if firstErr == nil { firstErr = err } continue } about = append(about, aboutSnap{info, snapState}) } return about, firstErr }
// CheckMacaroon returns the UserState for the given macaroon/discharges credentials func CheckMacaroon(st *state.State, macaroon string, discharges []string) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err != nil { return nil, ErrInvalidAuth } NextUser: for _, user := range authStateData.Users { if user.Macaroon != macaroon { continue } if len(user.Discharges) != len(discharges) { continue } // sort discharges (stored users' discharges are already sorted) sort.Strings(discharges) for i, d := range user.Discharges { if d != discharges[i] { continue NextUser } } return &user, nil } return nil, ErrInvalidAuth }
// Disconnect returns a set of tasks for disconnecting an interface. func Disconnect(s *state.State, plugSnap, plugName, slotSnap, slotName string) (*state.TaskSet, error) { // TODO: Remove the intent-to-connect from the state so that we no longer // automatically try to reconnect on reboot. summary := fmt.Sprintf(i18n.G("Disconnect %s:%s from %s:%s"), plugSnap, plugName, slotSnap, slotName) task := s.NewTask("disconnect", summary) task.Set("slot", interfaces.SlotRef{Snap: slotSnap, Name: slotName}) task.Set("plug", interfaces.PlugRef{Snap: plugSnap, Name: plugName}) return state.NewTaskSet(task), nil }
// Connect returns a set of tasks for connecting an interface. // func Connect(s *state.State, plugSnap, plugName, slotSnap, slotName string) (*state.TaskSet, error) { // TODO: Store the intent-to-connect in the state so that we automatically // try to reconnect on reboot (reconnection can fail or can connect with // different parameters so we cannot store the actual connection details). summary := fmt.Sprintf(i18n.G("Connect %s:%s to %s:%s"), plugSnap, plugName, slotSnap, slotName) task := s.NewTask("connect", summary) task.Set("slot", interfaces.SlotRef{Snap: slotSnap, Name: slotName}) task.Set("plug", interfaces.PlugRef{Snap: plugSnap, Name: plugName}) return state.NewTaskSet(task), nil }
func getConns(st *state.State) (map[string]connState, error) { // Get information about connections from the state var conns map[string]connState err := st.Get("conns", &conns) if err != nil && err != state.ErrNoState { return nil, fmt.Errorf("cannot obtain data about existing connections: %s", err) } if conns == nil { conns = make(map[string]connState) } return conns, nil }
// User returns a user from the state given its ID func User(st *state.State, id int) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err != nil { return nil, err } for _, user := range authStateData.Users { if user.ID == id { return &user, nil } } return nil, fmt.Errorf("invalid user") }
// All retrieves return a map from name to SnapState for all current snaps in the system state. func All(s *state.State) (map[string]*SnapState, error) { // XXX: result is a map because sideloaded snaps carry no name // atm in their sideinfos var stateMap map[string]*SnapState if err := s.Get("snaps", &stateMap); err != nil && err != state.ErrNoState { return nil, err } curStates := make(map[string]*SnapState, len(stateMap)) for snapName, snapState := range stateMap { if snapState.Current() != nil { curStates[snapName] = snapState } } return curStates, nil }
func checkChangeConflict(s *state.State, snapName string) error { for _, task := range s.Tasks() { k := task.Kind() chg := task.Change() if (k == "link-snap" || k == "unlink-snap") && (chg == nil || !chg.Status().Ready()) { ss, err := TaskSnapSetup(task) if err != nil { return fmt.Errorf("internal error: cannot obtain snap setup from task: %s", task.Summary()) } if ss.Name == snapName { return fmt.Errorf("snap %q has changes in progress", snapName) } } } return nil }
// Get retrieves the SnapState of the given snap. func Get(s *state.State, name string, snapst *SnapState) error { var snaps map[string]*json.RawMessage err := s.Get("snaps", &snaps) if err != nil { return err } raw, ok := snaps[name] if !ok { return state.ErrNoState } err = json.Unmarshal([]byte(*raw), &snapst) if err != nil { return fmt.Errorf("cannot unmarshal snap state: %v", err) } return nil }
// ActiveInfos returns information about all active snaps. func ActiveInfos(s *state.State) ([]*snap.Info, error) { var stateMap map[string]*SnapState var infos []*snap.Info if err := s.Get("snaps", &stateMap); err != nil && err != state.ErrNoState { return nil, err } for snapName, snapState := range stateMap { if !snapState.Active { continue } snapInfo, err := readInfo(snapName, snapState.Current()) if err != nil { logger.Noticef("cannot retrieve info for snap %q: %s", snapName, err) continue } infos = append(infos, snapInfo) } return infos, nil }
// Set sets the SnapState of the given snap, overwriting any earlier state. func Set(s *state.State, name string, snapst *SnapState) { var snaps map[string]*json.RawMessage err := s.Get("snaps", &snaps) if err != nil && err != state.ErrNoState { panic("internal error: cannot unmarshal snaps state: " + err.Error()) } if snaps == nil { snaps = make(map[string]*json.RawMessage) } if snapst == nil || (len(snapst.Sequence) == 0 && snapst.Candidate == nil) { delete(snaps, name) } else { data, err := json.Marshal(snapst) if err != nil { panic("internal error: cannot marshal snap state: " + err.Error()) } raw := json.RawMessage(data) snaps[name] = &raw } s.Set("snaps", snaps) }
// RemoveUser removes a user from the state given its ID func RemoveUser(st *state.State, userID int) error { var authStateData AuthState err := st.Get("auth", &authStateData) if err != nil { return err } for i := range authStateData.Users { if authStateData.Users[i].ID == userID { // delete without preserving order n := len(authStateData.Users) - 1 authStateData.Users[i] = authStateData.Users[n] authStateData.Users[n] = UserState{} authStateData.Users = authStateData.Users[:n] st.Set("auth", authStateData) return nil } } return fmt.Errorf("invalid user") }
// localSnapInfo returns the information about the current snap for the given name plus the SnapState with the active flag and other snap revisions. func localSnapInfo(st *state.State, name string) (info *snap.Info, active bool, err error) { st.Lock() defer st.Unlock() var snapst snapstate.SnapState err = snapstate.Get(st, name, &snapst) if err != nil && err != state.ErrNoState { return nil, false, fmt.Errorf("cannot consult state: %v", err) } cur := snapst.Current() if cur == nil { return nil, false, nil } info, err = snap.ReadInfo(name, cur) if err != nil { return nil, false, fmt.Errorf("cannot read snap details: %v", err) } return info, snapst.Active, nil }
func verifyInstallUpdateTasks(c *C, curActive bool, ts *state.TaskSet, st *state.State) { i := 0 n := 5 if curActive { n++ } c.Assert(ts.Tasks(), HasLen, n) // all tasks are accounted c.Assert(st.Tasks(), HasLen, n) c.Assert(ts.Tasks()[i].Kind(), Equals, "download-snap") i++ c.Assert(ts.Tasks()[i].Kind(), Equals, "mount-snap") i++ if curActive { c.Assert(ts.Tasks()[i].Kind(), Equals, "unlink-current-snap") i++ } c.Assert(ts.Tasks()[i].Kind(), Equals, "copy-snap-data") i++ c.Assert(ts.Tasks()[i].Kind(), Equals, "setup-profiles") i++ c.Assert(ts.Tasks()[i].Kind(), Equals, "link-snap") }
func newRunnerManager(s *state.State) *runnerManager { rm := &runnerManager{ runner: state.NewTaskRunner(s), } rm.runner.AddHandler("runMgr1", func(t *state.Task, _ *tomb.Tomb) error { s := t.State() s.Lock() defer s.Unlock() s.Set("runMgr1Mark", 1) return nil }, nil) rm.runner.AddHandler("runMgr2", func(t *state.Task, _ *tomb.Tomb) error { s := t.State() s.Lock() defer s.Unlock() s.Set("runMgr2Mark", 1) return nil }, nil) rm.runner.AddHandler("runMgrEnsureBefore", func(t *state.Task, _ *tomb.Tomb) error { s := t.State() s.Lock() defer s.Unlock() s.EnsureBefore(20 * time.Millisecond) return nil }, nil) return rm }
func doInstall(s *state.State, curActive bool, snapName, snapPath, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { if err := checkChangeConflict(s, snapName); err != nil { return nil, err } if snapPath == "" && channel == "" { channel = "stable" } var prepare *state.Task ss := SnapSetup{ Channel: channel, UserID: userID, Flags: int(flags), } ss.Name = snapName ss.SnapPath = snapPath if snapPath != "" { prepare = s.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q"), snapPath)) } else { prepare = s.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q from channel %q"), snapName, channel)) } prepare.Set("snap-setup", ss) tasks := []*state.Task{prepare} addTask := func(t *state.Task) { t.Set("snap-setup-task", prepare.ID()) tasks = append(tasks, t) } // mount mount := s.NewTask("mount-snap", fmt.Sprintf(i18n.G("Mount snap %q"), snapName)) addTask(mount) mount.WaitFor(prepare) precopy := mount if curActive { // unlink-current-snap (will stop services for copy-data) unlink := s.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), snapName)) addTask(unlink) unlink.WaitFor(mount) precopy = unlink } // copy-data (needs stopped services by unlink) copyData := s.NewTask("copy-snap-data", fmt.Sprintf(i18n.G("Copy snap %q data"), snapName)) addTask(copyData) copyData.WaitFor(precopy) // security setupSecurity := s.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q security profiles"), snapName)) addTask(setupSecurity) setupSecurity.WaitFor(copyData) // finalize (wrappers+current symlink) linkSnap := s.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q available to the system"), snapName)) addTask(linkSnap) linkSnap.WaitFor(setupSecurity) return state.NewTaskSet(tasks...), nil }
// Remove returns a set of tasks for removing snap. // Note that the state must be locked by the caller. func Remove(s *state.State, name string, flags snappy.RemoveFlags) (*state.TaskSet, error) { if err := checkChangeConflict(s, name); err != nil { return nil, err } var snapst SnapState err := Get(s, name, &snapst) if err != nil && err != state.ErrNoState { return nil, err } cur := snapst.Current() if cur == nil { return nil, fmt.Errorf("cannot find snap %q", name) } revision := snapst.Current().Revision active := snapst.Active info, err := Info(s, name, revision) if err != nil { return nil, err } ss := SnapSetup{ Name: name, Revision: revision, Flags: int(flags), } // check if this is something that can be removed if !backend.CanRemove(info, active) { return nil, fmt.Errorf("snap %q is not removable", ss.Name) } // trigger remove discardSnap := s.NewTask("discard-snap", fmt.Sprintf(i18n.G("Remove snap %q from the system"), name)) discardSnap.Set("snap-setup", ss) discardSnapID := discardSnap.ID() tasks := ([]*state.Task)(nil) var chain *state.Task addNext := func(t *state.Task) { if chain != nil { t.WaitFor(chain) } if t.ID() != discardSnapID { t.Set("snap-setup-task", discardSnapID) } tasks = append(tasks, t) chain = t } if active { unlink := s.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q unavailable to the system"), name)) addNext(unlink) } removeSecurity := s.NewTask("remove-profiles", fmt.Sprintf(i18n.G("Remove security profile for snap %q"), name)) addNext(removeSecurity) clearData := s.NewTask("clear-snap", fmt.Sprintf(i18n.G("Remove data for snap %q"), name)) addNext(clearData) addNext(discardSnap) if len(snapst.Sequence) == 1 { discardConns := s.NewTask("discard-conns", fmt.Sprintf(i18n.G("Discard interface connections for snap %q"), name)) addNext(discardConns) } return state.NewTaskSet(tasks...), nil }
func setConns(st *state.State, conns map[string]connState) { st.Set("conns", conns) }