Пример #1
0
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
}
Пример #2
0
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
}
Пример #3
0
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)
}
Пример #4
0
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)
}
Пример #5
0
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
}
Пример #6
0
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
}
Пример #7
0
// 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())
}
Пример #8
0
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)
	}
}
Пример #9
0
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
}
Пример #10
0
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
}
Пример #11
0
// 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
}
Пример #12
0
// 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
}
Пример #13
0
// 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
}