// patch3: // - migrates pending tasks and add {start,stop}-snap-services tasks func patch3(s *state.State) error { // migrate all pending tasks and insert "{start,stop}-snap-server" for _, t := range s.Tasks() { if t.Status().Ready() { continue } if t.Kind() == "link-snap" { startSnapServices := s.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap services"))) startSnapServices.Set("snap-setup-task", t.ID()) startSnapServices.WaitFor(t) chg := t.Change() chg.AddTask(startSnapServices) } if t.Kind() == "unlink-snap" || t.Kind() == "unlink-current-snap" { stopSnapServices := s.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap services"))) stopSnapServices.Set("snap-setup-task", t.ID()) t.WaitFor(stopSnapServices) chg := t.Change() chg.AddTask(stopSnapServices) } } return nil }
// Disable sets a snap to the inactive state func Disable(s *state.State, name string) (*state.TaskSet, error) { var snapst SnapState err := Get(s, name, &snapst) if err == state.ErrNoState { return nil, fmt.Errorf("cannot find snap %q", name) } if err != nil { return nil, err } if !snapst.Active { return nil, fmt.Errorf("snap %q already disabled", name) } if err := checkChangeConflict(s, name, nil); err != nil { return nil, err } ss := &SnapSetup{ SideInfo: &snap.SideInfo{ RealName: name, Revision: snapst.Current, }, } stopSnapServices := s.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q (%s) services"), ss.Name(), snapst.Current)) stopSnapServices.Set("snap-setup", &ss) unlinkSnap := s.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) unavailable to the system"), ss.Name(), snapst.Current)) unlinkSnap.Set("snap-setup-task", stopSnapServices.ID()) unlinkSnap.WaitFor(stopSnapServices) return state.NewTaskSet(stopSnapServices, unlinkSnap), nil }
// Alias enables the provided aliases for the snap with the given name. func Alias(st *state.State, snapName string, aliases []string) (*state.TaskSet, error) { var snapst SnapState err := Get(st, snapName, &snapst) if err == state.ErrNoState { return nil, fmt.Errorf("cannot find snap %q", snapName) } if err != nil { return nil, err } if !snapst.Active { return nil, fmt.Errorf("enabling aliases for disabled snap %q not supported", snapName) } if err := checkChangeConflict(st, snapName, nil); err != nil { return nil, err } snapsup := &SnapSetup{ SideInfo: &snap.SideInfo{RealName: snapName}, } alias := st.NewTask("alias", fmt.Sprintf(i18n.G("Enable aliases for snap %q"), snapsup.Name())) alias.Set("snap-setup", &snapsup) toEnable := map[string]string{} for _, alias := range aliases { toEnable[alias] = "enabled" } alias.Set("aliases", toEnable) return state.NewTaskSet(alias), nil }
// ResetAliases resets the provided aliases for the snap with the given name to their default state, enabled for auto-aliases, disabled otherwise. func ResetAliases(st *state.State, snapName string, aliases []string) (*state.TaskSet, error) { var snapst SnapState err := Get(st, snapName, &snapst) if err == state.ErrNoState { return nil, fmt.Errorf("cannot find snap %q", snapName) } if err != nil { return nil, err } if !snapst.Active { // TODO: we might want to support this return nil, fmt.Errorf("resetting aliases to their default state for disabled snap %q not supported", snapName) } if err := checkChangeConflict(st, snapName, nil); err != nil { return nil, err } snapsup := &SnapSetup{ SideInfo: &snap.SideInfo{RealName: snapName}, } alias := st.NewTask("alias", fmt.Sprintf(i18n.G("Reset aliases for snap %q"), snapsup.Name())) alias.Set("snap-setup", &snapsup) toReset := map[string]string{} for _, alias := range aliases { toReset[alias] = "auto" } alias.Set("aliases", toReset) return state.NewTaskSet(alias), nil }
// Disconnect returns a set of tasks for disconnecting an interface. func Disconnect(s *state.State, plugSnap, plugName, slotSnap, slotName string) (*state.TaskSet, error) { 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 }
// HookTask returns a task that will run the specified hook. Note that the // initial context must properly marshal and unmarshal with encoding/json. func HookTask(st *state.State, summary string, setup *HookSetup, contextData map[string]interface{}) *state.Task { task := st.NewTask("run-hook", summary) task.Set("hook-setup", setup) // Initial data for Context.Get/Set. if len(contextData) > 0 { task.Set("hook-context", contextData) } return task }
// 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 removeInactiveRevision(s *state.State, name string, revision snap.Revision) *state.TaskSet { ss := SnapSetup{ SideInfo: &snap.SideInfo{ RealName: name, Revision: revision, }, } clearData := s.NewTask("clear-snap", fmt.Sprintf(i18n.G("Remove data for snap %q (%s)"), name, revision)) clearData.Set("snap-setup", ss) discardSnap := s.NewTask("discard-snap", fmt.Sprintf(i18n.G("Remove snap %q (%s) from the system"), name, revision)) discardSnap.WaitFor(clearData) discardSnap.Set("snap-setup-task", clearData.ID()) return state.NewTaskSet(clearData, discardSnap) }
// Enable sets a snap to the active state func Enable(s *state.State, name string) (*state.TaskSet, error) { var snapst SnapState err := Get(s, name, &snapst) if err == state.ErrNoState { return nil, fmt.Errorf("cannot find snap %q", name) } if err != nil { return nil, err } if snapst.Active { return nil, fmt.Errorf("snap %q already enabled", name) } if err := checkChangeConflict(s, name, nil); err != nil { return nil, err } ss := &SnapSetup{ SideInfo: snapst.CurrentSideInfo(), } prepareSnap := s.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s)"), ss.Name(), snapst.Current)) prepareSnap.Set("snap-setup", &ss) linkSnap := s.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system"), ss.Name(), snapst.Current)) linkSnap.Set("snap-setup", &ss) linkSnap.WaitFor(prepareSnap) startSnapServices := s.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q (%s) services"), ss.Name(), snapst.Current)) startSnapServices.Set("snap-setup", &ss) startSnapServices.WaitFor(linkSnap) return state.NewTaskSet(prepareSnap, linkSnap, startSnapServices), 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, revision snap.Revision) (*state.TaskSet, error) { var snapst SnapState err := Get(s, name, &snapst) if err != nil && err != state.ErrNoState { return nil, err } if !snapst.HasCurrent() { return nil, fmt.Errorf("cannot find snap %q", name) } if err := checkChangeConflict(s, name, nil); err != nil { return nil, err } active := snapst.Active var removeAll bool if revision.Unset() { removeAll = true revision = snapst.Current } else { removeAll = false if active { if revision == snapst.Current { msg := "cannot remove active revision %s of snap %q" if len(snapst.Sequence) > 1 { msg += " (revert first?)" } return nil, fmt.Errorf(msg, revision, name) } active = false } if !revisionInSequence(&snapst, revision) { return nil, fmt.Errorf("revision %s of snap %q is not installed", revision, name) } } info, err := Info(s, name, revision) if err != nil { return nil, err } // check if this is something that can be removed if !canRemove(info, active) { return nil, fmt.Errorf("snap %q is not removable", name) } // main/current SnapSetup ss := SnapSetup{ SideInfo: &snap.SideInfo{ RealName: name, Revision: revision, }, } // trigger remove full := state.NewTaskSet() var chain *state.TaskSet addNext := func(ts *state.TaskSet) { if chain != nil { ts.WaitAll(chain) } full.AddAll(ts) chain = ts } if active { // unlink stopSnapServices := s.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), name)) stopSnapServices.Set("snap-setup", ss) unlink := s.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q unavailable to the system"), name)) unlink.Set("snap-setup-task", stopSnapServices.ID()) unlink.WaitFor(stopSnapServices) removeSecurity := s.NewTask("remove-profiles", fmt.Sprintf(i18n.G("Remove security profile for snap %q (%s)"), name, revision)) removeSecurity.WaitFor(unlink) removeSecurity.Set("snap-setup-task", stopSnapServices.ID()) addNext(state.NewTaskSet(stopSnapServices, unlink, removeSecurity)) } if removeAll || len(snapst.Sequence) == 1 { seq := snapst.Sequence for i := len(seq) - 1; i >= 0; i-- { si := seq[i] addNext(removeInactiveRevision(s, name, si.Revision)) } discardConns := s.NewTask("discard-conns", fmt.Sprintf(i18n.G("Discard interface connections for snap %q (%s)"), name, revision)) discardConns.Set("snap-setup", &SnapSetup{ SideInfo: &snap.SideInfo{ RealName: name, }, }) addNext(state.NewTaskSet(discardConns)) } else { addNext(removeInactiveRevision(s, name, revision)) } return full, nil }
func doInstall(s *state.State, snapst *SnapState, ss *SnapSetup) (*state.TaskSet, error) { if err := checkChangeConflict(s, ss.Name(), snapst); err != nil { return nil, err } targetRevision := ss.Revision() revisionStr := "" if ss.SideInfo != nil { revisionStr = fmt.Sprintf(" (%s)", targetRevision) } // check if we already have the revision locally (alters tasks) revisionIsLocal := snapst.LastIndex(targetRevision) >= 0 var prepare, prev *state.Task fromStore := false // if we have a local revision here we go back to that if ss.SnapPath != "" || revisionIsLocal { prepare = s.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q%s"), ss.SnapPath, revisionStr)) } else { fromStore = true prepare = s.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q%s from channel %q"), ss.Name(), revisionStr, ss.Channel)) } prepare.Set("snap-setup", ss) tasks := []*state.Task{prepare} addTask := func(t *state.Task) { t.Set("snap-setup-task", prepare.ID()) t.WaitFor(prev) tasks = append(tasks, t) } prev = prepare if fromStore { // fetch and check assertions checkAsserts := s.NewTask("validate-snap", fmt.Sprintf(i18n.G("Fetch and check assertions for snap %q%s"), ss.Name(), revisionStr)) addTask(checkAsserts) prev = checkAsserts } // mount if !revisionIsLocal { mount := s.NewTask("mount-snap", fmt.Sprintf(i18n.G("Mount snap %q%s"), ss.Name(), revisionStr)) addTask(mount) prev = mount } if snapst.Active { // unlink-current-snap (will stop services for copy-data) stop := s.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), ss.Name())) addTask(stop) prev = stop unlink := s.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), ss.Name())) addTask(unlink) prev = unlink } // copy-data (needs stopped services by unlink) if !ss.Flags.Revert { copyData := s.NewTask("copy-snap-data", fmt.Sprintf(i18n.G("Copy snap %q data"), ss.Name())) addTask(copyData) prev = copyData } // security setupSecurity := s.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q%s security profiles"), ss.Name(), revisionStr)) addTask(setupSecurity) prev = setupSecurity // finalize (wrappers+current symlink) linkSnap := s.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q%s available to the system"), ss.Name(), revisionStr)) addTask(linkSnap) prev = linkSnap // run new serices startSnapServices := s.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q%s services"), ss.Name(), revisionStr)) addTask(startSnapServices) prev = startSnapServices // Do not do that if we are reverting to a local revision if snapst.HasCurrent() && !ss.Flags.Revert { seq := snapst.Sequence currentIndex := snapst.LastIndex(snapst.Current) // discard everything after "current" (we may have reverted to // a previous versions earlier) for i := currentIndex + 1; i < len(seq); i++ { si := seq[i] if si.Revision == targetRevision { // but don't discard this one; its' the thing we're switching to! continue } ts := removeInactiveRevision(s, ss.Name(), si.Revision) ts.WaitFor(prev) tasks = append(tasks, ts.Tasks()...) prev = tasks[len(tasks)-1] } // make sure we're not scheduling the removal of the target // revision in the case where the target revision is already in // the sequence. for i := 0; i < currentIndex; i++ { si := seq[i] if si.Revision == targetRevision { // we do *not* want to removeInactiveRevision of this one copy(seq[i:], seq[i+1:]) seq = seq[:len(seq)-1] currentIndex-- } } // normal garbage collect for i := 0; i <= currentIndex-2; i++ { si := seq[i] ts := removeInactiveRevision(s, ss.Name(), si.Revision) ts.WaitFor(prev) tasks = append(tasks, ts.Tasks()...) prev = tasks[len(tasks)-1] } addTask(s.NewTask("cleanup", fmt.Sprintf("Clean up %q%s install", ss.Name(), revisionStr))) } var defaults map[string]interface{} if !snapst.HasCurrent() && ss.SideInfo != nil && ss.SideInfo.SnapID != "" { gadget, err := GadgetInfo(s) if err != nil && err != state.ErrNoState { return nil, err } if err == nil { gadgetInfo, err := snap.ReadGadgetInfo(gadget) if err != nil { return nil, err } defaults = gadgetInfo.Defaults[ss.SideInfo.SnapID] } } installSet := state.NewTaskSet(tasks...) configSet := Configure(s, ss.Name(), defaults) configSet.WaitAll(installSet) installSet.AddAll(configSet) return installSet, nil }
// HookTask returns a task that will run the specified hook. func HookTask(s *state.State, taskSummary, snapName string, revision snap.Revision, hookName string) *state.Task { task := s.NewTask("run-hook", taskSummary) task.Set("hook-setup", HookSetup{Snap: snapName, Revision: revision, Hook: hookName}) return task }
func populateStateFromSeedImpl(st *state.State) ([]*state.TaskSet, error) { // check that the state is empty var seeded bool err := st.Get("seeded", &seeded) if err != nil && err != state.ErrNoState { return nil, err } if seeded { return nil, fmt.Errorf("cannot populate state: already seeded") } // ack all initial assertions if err := importAssertionsFromSeed(st); err != nil { return nil, err } seed, err := snap.ReadSeedYaml(filepath.Join(dirs.SnapSeedDir, "seed.yaml")) if err != nil { return nil, err } tsAll := []*state.TaskSet{} for i, sn := range seed.Snaps { var flags snapstate.Flags if sn.DevMode { flags.DevMode = true } path := filepath.Join(dirs.SnapSeedDir, "snaps", sn.File) var sideInfo snap.SideInfo if sn.Unasserted { sideInfo.RealName = sn.Name } else { si, err := snapasserts.DeriveSideInfo(path, assertstate.DB(st)) if err == asserts.ErrNotFound { return nil, fmt.Errorf("cannot find signatures with metadata for snap %q (%q)", sn.Name, path) } if err != nil { return nil, err } sideInfo = *si sideInfo.Private = sn.Private } ts, err := snapstate.InstallPath(st, &sideInfo, path, sn.Channel, flags) if i > 0 { ts.WaitAll(tsAll[i-1]) } if err != nil { return nil, err } tsAll = append(tsAll, ts) } if len(tsAll) == 0 { return nil, nil } ts := tsAll[len(tsAll)-1] markSeeded := st.NewTask("mark-seeded", i18n.G("Mark system seeded")) markSeeded.WaitAll(ts) tsAll = append(tsAll, state.NewTaskSet(markSeeded)) return tsAll, nil }