func (f *fakeStore) Snap(name, channel string, devmode bool, revision snap.Revision, user *auth.UserState) (*snap.Info, error) { f.pokeStateLock() if revision.Unset() { revision = snap.R(11) if channel == "channel-for-7" { revision.N = 7 } } info := &snap.Info{ SideInfo: snap.SideInfo{ RealName: strings.Split(name, ".")[0], Channel: channel, SnapID: "snapIDsnapidsnapidsnapidsnapidsn", Revision: revision, }, Version: name, DownloadInfo: snap.DownloadInfo{ DownloadURL: "https://some-server.com/some/path.snap", }, } f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-snap", name: name, revno: revision}) return info, nil }
func getSnapInfo(snapName string, revision snap.Revision) (*snap.Info, error) { if revision.Unset() { // User didn't supply a revision, so we need to get it via the snapd API // here because once we're inside the confinement it may be unavailable. snaps, err := Client().List([]string{snapName}) if err != nil { return nil, err } if len(snaps) == 0 { return nil, fmt.Errorf("cannot find snap %q", snapName) } if len(snaps) > 1 { return nil, fmt.Errorf(i18n.G("multiple snaps for %q: %d"), snapName, len(snaps)) } revision = snaps[0].Revision } info, err := snap.ReadInfo(snapName, &snap.SideInfo{ Revision: revision, }) if err != nil { return nil, err } return info, nil }
func infoForUpdate(s *state.State, snapst *SnapState, name, channel string, revision snap.Revision, userID int, flags Flags) (*snap.Info, error) { if revision.Unset() { // good ol' refresh info, err := updateInfo(s, snapst, channel, userID, flags) if err != nil { return nil, err } if ValidateRefreshes != nil && !flags.IgnoreValidation { _, err := ValidateRefreshes(s, []*snap.Info{info}, userID) if err != nil { return nil, err } } return info, nil } var sideInfo *snap.SideInfo for _, si := range snapst.Sequence { if si.Revision == revision { sideInfo = si break } } if sideInfo == nil { // refresh from given revision from store return snapInfo(s, name, channel, revision, userID, flags) } // refresh-to-local return readInfo(name, sideInfo) }
func (s *copydataSuite) populateData(c *C, revision snap.Revision) { datadir := filepath.Join(dirs.SnapDataDir, "hello", revision.String()) subdir := filepath.Join(datadir, "random-subdir") err := os.MkdirAll(subdir, 0755) c.Assert(err, IsNil) err = ioutil.WriteFile(filepath.Join(subdir, "canary"), []byte(fmt.Sprintln(revision)), 0644) c.Assert(err, IsNil) }
func (s copydataSuite) populateHomeData(c *C, user string, revision snap.Revision) (homedir string) { homedir = filepath.Join(s.tempdir, "home", user, "snap") homeData := filepath.Join(homedir, "hello", revision.String()) err := os.MkdirAll(homeData, 0755) c.Assert(err, IsNil) err = ioutil.WriteFile(filepath.Join(homeData, "canary.home"), []byte(fmt.Sprintln(revision)), 0644) c.Assert(err, IsNil) return }
func infoFromSnapYaml(c *C, snapYaml string, rev snap.Revision) *snap.Info { info, err := snap.InfoFromSnapYaml([]byte(snapYaml)) c.Assert(err, IsNil) if !rev.Unset() { info.SnapID = info.Name() + "-Id" info.Revision = rev } return info }
// Info returns the information about the snap with given name and revision. // Works also for a mounted candidate snap in the process of being installed. func Info(s *state.State, name string, revision snap.Revision) (*snap.Info, 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 } for i := len(snapst.Sequence) - 1; i >= 0; i-- { if si := snapst.Sequence[i]; si.Revision == revision { return readInfo(name, si) } } return nil, fmt.Errorf("cannot find snap %q at revision %s", name, revision.String()) }
func runHookAndWait(snapName string, revision snap.Revision, hookName, hookContext string, tomb *tomb.Tomb) ([]byte, error) { command := exec.Command("snap", "run", "--hook", hookName, "-r", revision.String(), snapName) // Make sure the hook has its context defined so it can communicate via the // REST API. command.Env = append(os.Environ(), fmt.Sprintf("SNAP_CONTEXT=%s", hookContext)) // Make sure we can obtain stdout and stderror. Same buffer so they're // combined. buffer := bytes.NewBuffer(nil) command.Stdout = buffer command.Stderr = buffer // Actually run the hook. if err := command.Start(); err != nil { return nil, err } hookCompleted := make(chan struct{}) var hookError error go func() { // Wait for hook to complete hookError = command.Wait() close(hookCompleted) }() select { // Hook completed; it may or may not have been successful. case <-hookCompleted: return buffer.Bytes(), hookError // Hook was aborted. case <-tomb.Dying(): if err := command.Process.Kill(); err != nil { return nil, fmt.Errorf("cannot abort hook %q: %s", hookName, err) } return nil, fmt.Errorf("hook %q aborted", hookName) } }
func getSnapInfo(snapName string, revision snap.Revision) (*snap.Info, error) { if revision.Unset() { curFn := filepath.Join(dirs.SnapMountDir, snapName, "current") realFn, err := os.Readlink(curFn) if err != nil { return nil, fmt.Errorf("cannot find current revision for snap %s: %s", snapName, err) } rev := filepath.Base(realFn) revision, err = snap.ParseRevision(rev) if err != nil { return nil, fmt.Errorf("cannot read revision %s: %s", rev, err) } } info, err := snap.ReadInfo(snapName, &snap.SideInfo{ Revision: revision, }) if err != nil { return nil, err } return info, nil }
func (f *fakeStore) ListRefresh(cands []*store.RefreshCandidate, _ *auth.UserState) ([]*snap.Info, error) { f.pokeStateLock() if len(cands) == 0 { return nil, nil } if len(cands) != 1 { panic("ListRefresh unexpectedly called with more than one candidate") } cand := cands[0] snapID := cand.SnapID if snapID == "" { return nil, nil } if snapID == "fakestore-please-error-on-refresh" { return nil, fmt.Errorf("failing as requested") } var name string if snapID == "some-snap-id" { name = "some-snap" } else { panic(fmt.Sprintf("ListRefresh: unknown snap-id: %s", snapID)) } revno := snap.R(11) if cand.Channel == "channel-for-7" { revno = snap.R(7) } info := &snap.Info{ SideInfo: snap.SideInfo{ RealName: name, Channel: cand.Channel, SnapID: cand.SnapID, Revision: revno, }, Version: name, DownloadInfo: snap.DownloadInfo{ DownloadURL: "https://some-server.com/some/path.snap", }, } var hit snap.Revision if cand.Revision != revno { hit = revno } for _, blocked := range cand.Block { if blocked == revno { hit = snap.Revision{} break } } f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-list-refresh", cand: *cand, revno: hit}) if hit.Unset() { return nil, nil } return []*snap.Info{info}, nil }
// Snap returns the snap.Info for the store hosted snap with the given name or an error. func (s *Store) Snap(name, channel string, devmode bool, revision snap.Revision, user *auth.UserState) (*snap.Info, error) { u, err := s.detailsURI.Parse(name) if err != nil { return nil, err } query := u.Query() query.Set("channel", channel) if !revision.Unset() { query.Set("revision", revision.String()) query.Set("channel", "") } // if devmode then don't restrict by confinement as either is fine // XXX: what we really want to do is have the store not specify // devmode, and have the business logic wrt what to do with // unwanted devmode further up if !devmode { query.Set("confinement", string(snap.StrictConfinement)) } u.RawQuery = query.Encode() reqOptions := &requestOptions{ Method: "GET", URL: u, Accept: halJsonContentType, } var remote snapDetails resp, err := s.retryRequestDecodeJSON(context.TODO(), s.client, reqOptions, user, &remote, nil) if err != nil { return nil, err } // check statusCode switch resp.StatusCode { case http.StatusOK: // OK case http.StatusNotFound: return nil, ErrSnapNotFound default: msg := fmt.Sprintf("get details for snap %q in channel %q", name, channel) return nil, respToError(resp, msg) } info := infoFromRemote(remote) // only get the channels when it makes sense as part of the reply if info.SnapID != "" && channel == "" && revision.Unset() { channels, err := s.fakeChannels(info.SnapID, user) if err != nil { logger.Noticef("cannot get channels: %v", err) } else { info.Channels = channels } } err = s.decorateOrders([]*snap.Info{info}, channel, user) if err != nil { logger.Noticef("cannot get user orders: %v", err) } s.extractSuggestedCurrency(resp) return info, 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 }
// Snap returns the snap.Info for the store hosted snap with the given name or an error. func (s *Store) Snap(name, channel string, devmode bool, revision snap.Revision, user *auth.UserState) (*snap.Info, error) { u, err := s.detailsURI.Parse(name) if err != nil { return nil, err } query := u.Query() if !revision.Unset() { query.Set("revision", revision.String()) query.Set("channel", "") // sidestep the channel map } else if channel != "" { query.Set("channel", channel) } // if devmode then don't restrict by confinement as either is fine // XXX: what we really want to do is have the store not specify // devmode, and have the business logic wrt what to do with // unwanted devmode further up if !devmode { query.Set("confinement", string(snap.StrictConfinement)) } u.RawQuery = query.Encode() reqOptions := &requestOptions{ Method: "GET", URL: u, Accept: halJsonContentType, } resp, err := s.doRequest(s.client, reqOptions, user) if err != nil { return nil, err } defer resp.Body.Close() // check statusCode switch resp.StatusCode { case http.StatusOK: // OK case http.StatusNotFound: return nil, ErrSnapNotFound default: msg := fmt.Sprintf("get details for snap %q in channel %q", name, channel) return nil, respToError(resp, msg) } // and decode json var remote snapDetails dec := json.NewDecoder(resp.Body) if err := dec.Decode(&remote); err != nil { return nil, err } info := infoFromRemote(remote) err = s.decorateOrders([]*snap.Info{info}, channel, user) if err != nil { logger.Noticef("cannot get user orders: %v", err) } s.extractSuggestedCurrency(resp) return info, nil }