github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/daemon/api_snaps_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package daemon_test
    21  
    22  import (
    23  	"bytes"
    24  	"context"
    25  	"encoding/json"
    26  	"errors"
    27  	"fmt"
    28  	"net/http"
    29  	"net/http/httptest"
    30  	"net/url"
    31  	"os"
    32  	"path/filepath"
    33  	"strings"
    34  	"time"
    35  
    36  	"gopkg.in/check.v1"
    37  
    38  	"github.com/snapcore/snapd/arch"
    39  	"github.com/snapcore/snapd/client"
    40  	"github.com/snapcore/snapd/daemon"
    41  	"github.com/snapcore/snapd/dirs"
    42  	"github.com/snapcore/snapd/overlord/assertstate"
    43  	"github.com/snapcore/snapd/overlord/auth"
    44  	"github.com/snapcore/snapd/overlord/healthstate"
    45  	"github.com/snapcore/snapd/overlord/snapstate"
    46  	"github.com/snapcore/snapd/overlord/state"
    47  	"github.com/snapcore/snapd/sandbox"
    48  	"github.com/snapcore/snapd/snap"
    49  	"github.com/snapcore/snapd/snap/channel"
    50  	"github.com/snapcore/snapd/snap/snaptest"
    51  	"github.com/snapcore/snapd/store"
    52  	"github.com/snapcore/snapd/testutil"
    53  )
    54  
    55  type snapsSuite struct {
    56  	apiBaseSuite
    57  }
    58  
    59  var _ = check.Suite(&snapsSuite{})
    60  
    61  func (s *snapsSuite) TestSnapsInfoIntegration(c *check.C) {
    62  	s.checkSnapsInfoIntegration(c, false, nil)
    63  }
    64  
    65  func (s *snapsSuite) TestSnapsInfoIntegrationSome(c *check.C) {
    66  	s.checkSnapsInfoIntegration(c, false, []string{"foo", "baz"})
    67  }
    68  
    69  func (s *snapsSuite) TestSnapsInfoIntegrationAll(c *check.C) {
    70  	s.checkSnapsInfoIntegration(c, true, nil)
    71  }
    72  
    73  func (s *snapsSuite) TestSnapsInfoIntegrationAllSome(c *check.C) {
    74  	s.checkSnapsInfoIntegration(c, true, []string{"foo", "baz"})
    75  }
    76  
    77  func snapList(rawSnaps interface{}) []map[string]interface{} {
    78  	snaps := make([]map[string]interface{}, len(rawSnaps.([]*json.RawMessage)))
    79  	for i, raw := range rawSnaps.([]*json.RawMessage) {
    80  		err := json.Unmarshal([]byte(*raw), &snaps[i])
    81  		if err != nil {
    82  			panic(err)
    83  		}
    84  	}
    85  	return snaps
    86  }
    87  
    88  func (s *snapsSuite) checkSnapsInfoIntegration(c *check.C, all bool, names []string) {
    89  	d := s.daemon(c)
    90  
    91  	type tsnap struct {
    92  		name   string
    93  		dev    string
    94  		ver    string
    95  		rev    int
    96  		active bool
    97  
    98  		wanted bool
    99  	}
   100  
   101  	tsnaps := []tsnap{
   102  		{name: "foo", dev: "bar", ver: "v0.9", rev: 1},
   103  		{name: "foo", dev: "bar", ver: "v1", rev: 5, active: true},
   104  		{name: "bar", dev: "baz", ver: "v2", rev: 10, active: true},
   105  		{name: "baz", dev: "qux", ver: "v3", rev: 15, active: true},
   106  		{name: "qux", dev: "mip", ver: "v4", rev: 20, active: true},
   107  	}
   108  	numExpected := 0
   109  
   110  	for _, snp := range tsnaps {
   111  		if all || snp.active {
   112  			if len(names) == 0 {
   113  				numExpected++
   114  				snp.wanted = true
   115  			}
   116  			for _, n := range names {
   117  				if snp.name == n {
   118  					numExpected++
   119  					snp.wanted = true
   120  					break
   121  				}
   122  			}
   123  		}
   124  		s.mkInstalledInState(c, d, snp.name, snp.dev, snp.ver, snap.R(snp.rev), snp.active, "")
   125  	}
   126  
   127  	q := url.Values{}
   128  	if all {
   129  		q.Set("select", "all")
   130  	}
   131  	if len(names) > 0 {
   132  		q.Set("snaps", strings.Join(names, ","))
   133  	}
   134  	req, err := http.NewRequest("GET", "/v2/snaps?"+q.Encode(), nil)
   135  	c.Assert(err, check.IsNil)
   136  
   137  	rsp := s.syncReq(c, req, nil)
   138  	c.Check(rsp.Status, check.Equals, 200)
   139  	c.Check(rsp.Result, check.NotNil)
   140  
   141  	snaps := snapList(rsp.Result)
   142  	c.Check(snaps, check.HasLen, numExpected)
   143  
   144  	for _, s := range tsnaps {
   145  		if !((all || s.active) && s.wanted) {
   146  			continue
   147  		}
   148  		var got map[string]interface{}
   149  		for _, got = range snaps {
   150  			if got["name"].(string) == s.name && got["revision"].(string) == snap.R(s.rev).String() {
   151  				break
   152  			}
   153  		}
   154  		c.Check(got["name"], check.Equals, s.name)
   155  		c.Check(got["version"], check.Equals, s.ver)
   156  		c.Check(got["revision"], check.Equals, snap.R(s.rev).String())
   157  		c.Check(got["developer"], check.Equals, s.dev)
   158  		c.Check(got["confinement"], check.Equals, "strict")
   159  	}
   160  }
   161  
   162  func (s *snapsSuite) TestSnapsInfoOnlyLocal(c *check.C) {
   163  	d := s.daemon(c)
   164  
   165  	s.rsnaps = []*snap.Info{{
   166  		SideInfo: snap.SideInfo{
   167  			RealName: "store",
   168  		},
   169  		Publisher: snap.StoreAccount{
   170  			ID:          "foo-id",
   171  			Username:    "foo",
   172  			DisplayName: "Foo",
   173  			Validation:  "unproven",
   174  		},
   175  	}}
   176  	s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "")
   177  	st := d.Overlord().State()
   178  	st.Lock()
   179  	st.Set("health", map[string]healthstate.HealthState{
   180  		"local": {Status: healthstate.OkayStatus},
   181  	})
   182  	st.Unlock()
   183  
   184  	req, err := http.NewRequest("GET", "/v2/snaps?sources=local", nil)
   185  	c.Assert(err, check.IsNil)
   186  
   187  	rsp := s.syncReq(c, req, nil)
   188  
   189  	c.Assert(rsp.Sources, check.DeepEquals, []string{"local"})
   190  
   191  	snaps := snapList(rsp.Result)
   192  	c.Assert(snaps, check.HasLen, 1)
   193  	c.Assert(snaps[0]["name"], check.Equals, "local")
   194  	c.Check(snaps[0]["health"], check.DeepEquals, map[string]interface{}{
   195  		"status":    "okay",
   196  		"revision":  "unset",
   197  		"timestamp": "0001-01-01T00:00:00Z",
   198  	})
   199  }
   200  
   201  func (s *snapsSuite) TestSnapsInfoAllMixedPublishers(c *check.C) {
   202  	d := s.daemon(c)
   203  
   204  	// the first 'local' is from a 'local' snap
   205  	s.mkInstalledInState(c, d, "local", "", "v1", snap.R(-1), false, "")
   206  	s.mkInstalledInState(c, d, "local", "foo", "v2", snap.R(1), false, "")
   207  	s.mkInstalledInState(c, d, "local", "foo", "v3", snap.R(2), true, "")
   208  
   209  	req, err := http.NewRequest("GET", "/v2/snaps?select=all", nil)
   210  	c.Assert(err, check.IsNil)
   211  	rsp := s.syncReq(c, req, nil)
   212  
   213  	snaps := snapList(rsp.Result)
   214  	c.Assert(snaps, check.HasLen, 3)
   215  
   216  	publisher := map[string]interface{}{
   217  		"id":           "foo-id",
   218  		"username":     "foo",
   219  		"display-name": "Foo",
   220  		"validation":   "unproven",
   221  	}
   222  
   223  	c.Check(snaps[0]["publisher"], check.IsNil)
   224  	c.Check(snaps[1]["publisher"], check.DeepEquals, publisher)
   225  	c.Check(snaps[2]["publisher"], check.DeepEquals, publisher)
   226  }
   227  
   228  func (s *snapsSuite) TestSnapsInfoAll(c *check.C) {
   229  	d := s.daemon(c)
   230  
   231  	s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(1), false, "")
   232  	s.mkInstalledInState(c, d, "local", "foo", "v2", snap.R(2), false, "")
   233  	s.mkInstalledInState(c, d, "local", "foo", "v3", snap.R(3), true, "")
   234  	s.mkInstalledInState(c, d, "local_foo", "foo", "v4", snap.R(4), true, "")
   235  	brokenInfo := s.mkInstalledInState(c, d, "local_bar", "foo", "v5", snap.R(5), true, "")
   236  	// make sure local_bar is 'broken'
   237  	err := os.Remove(filepath.Join(brokenInfo.MountDir(), "meta", "snap.yaml"))
   238  	c.Assert(err, check.IsNil)
   239  
   240  	expectedHappy := map[string]bool{
   241  		"local":     true,
   242  		"local_foo": true,
   243  		"local_bar": true,
   244  	}
   245  	for _, t := range []struct {
   246  		q        string
   247  		numSnaps int
   248  		typ      daemon.ResponseType
   249  	}{
   250  		{"?select=enabled", 3, "sync"},
   251  		{`?select=`, 3, "sync"},
   252  		{"", 3, "sync"},
   253  		{"?select=all", 5, "sync"},
   254  		{"?select=invalid-field", 0, "error"},
   255  	} {
   256  		c.Logf("trying: %v", t)
   257  		req, err := http.NewRequest("GET", fmt.Sprintf("/v2/snaps%s", t.q), nil)
   258  		c.Assert(err, check.IsNil)
   259  		rsp := s.jsonReq(c, req, nil)
   260  		c.Assert(rsp.Type, check.Equals, t.typ)
   261  
   262  		if rsp.Type != "error" {
   263  			snaps := snapList(rsp.Result)
   264  			c.Assert(snaps, check.HasLen, t.numSnaps)
   265  			seen := map[string]bool{}
   266  			for _, s := range snaps {
   267  				seen[s["name"].(string)] = true
   268  			}
   269  			c.Assert(seen, check.DeepEquals, expectedHappy)
   270  		}
   271  	}
   272  }
   273  
   274  func (s *snapsSuite) TestSnapsInfoOnlyStore(c *check.C) {
   275  	d := s.daemon(c)
   276  
   277  	s.suggestedCurrency = "EUR"
   278  
   279  	s.rsnaps = []*snap.Info{{
   280  		SideInfo: snap.SideInfo{
   281  			RealName: "store",
   282  		},
   283  		Publisher: snap.StoreAccount{
   284  			ID:          "foo-id",
   285  			Username:    "foo",
   286  			DisplayName: "Foo",
   287  			Validation:  "unproven",
   288  		},
   289  	}}
   290  	s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "")
   291  
   292  	req, err := http.NewRequest("GET", "/v2/snaps?sources=store", nil)
   293  	c.Assert(err, check.IsNil)
   294  
   295  	rsp := s.syncReq(c, req, nil)
   296  
   297  	c.Assert(rsp.Sources, check.DeepEquals, []string{"store"})
   298  
   299  	snaps := snapList(rsp.Result)
   300  	c.Assert(snaps, check.HasLen, 1)
   301  	c.Assert(snaps[0]["name"], check.Equals, "store")
   302  	c.Check(snaps[0]["prices"], check.IsNil)
   303  
   304  	c.Check(rsp.SuggestedCurrency, check.Equals, "EUR")
   305  }
   306  
   307  func (s *snapsSuite) TestSnapsInfoStoreWithAuth(c *check.C) {
   308  	d := s.daemon(c)
   309  
   310  	state := d.Overlord().State()
   311  	state.Lock()
   312  	user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"})
   313  	state.Unlock()
   314  	c.Check(err, check.IsNil)
   315  
   316  	req, err := http.NewRequest("GET", "/v2/snaps?sources=store", nil)
   317  	c.Assert(err, check.IsNil)
   318  
   319  	c.Assert(s.user, check.IsNil)
   320  
   321  	_ = s.syncReq(c, req, user)
   322  
   323  	// ensure user was set
   324  	c.Assert(s.user, check.DeepEquals, user)
   325  }
   326  
   327  func (s *snapsSuite) TestSnapsInfoLocalAndStore(c *check.C) {
   328  	d := s.daemon(c)
   329  
   330  	s.rsnaps = []*snap.Info{{
   331  		Version: "v42",
   332  		SideInfo: snap.SideInfo{
   333  			RealName: "remote",
   334  		},
   335  		Publisher: snap.StoreAccount{
   336  			ID:          "foo-id",
   337  			Username:    "foo",
   338  			DisplayName: "Foo",
   339  			Validation:  "unproven",
   340  		},
   341  	}}
   342  	s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "")
   343  
   344  	req, err := http.NewRequest("GET", "/v2/snaps?sources=local,store", nil)
   345  	c.Assert(err, check.IsNil)
   346  
   347  	rsp := s.syncReq(c, req, nil)
   348  
   349  	// presence of 'store' in sources bounces request over to /find
   350  	c.Assert(rsp.Sources, check.DeepEquals, []string{"store"})
   351  
   352  	snaps := snapList(rsp.Result)
   353  	c.Assert(snaps, check.HasLen, 1)
   354  	c.Check(snaps[0]["version"], check.Equals, "v42")
   355  
   356  	// as does a 'q'
   357  	req, err = http.NewRequest("GET", "/v2/snaps?q=what", nil)
   358  	c.Assert(err, check.IsNil)
   359  	rsp = s.syncReq(c, req, nil)
   360  	snaps = snapList(rsp.Result)
   361  	c.Assert(snaps, check.HasLen, 1)
   362  	c.Check(snaps[0]["version"], check.Equals, "v42")
   363  
   364  	// otherwise, local only
   365  	req, err = http.NewRequest("GET", "/v2/snaps", nil)
   366  	c.Assert(err, check.IsNil)
   367  	rsp = s.syncReq(c, req, nil)
   368  	snaps = snapList(rsp.Result)
   369  	c.Assert(snaps, check.HasLen, 1)
   370  	c.Check(snaps[0]["version"], check.Equals, "v1")
   371  }
   372  
   373  func (s *snapsSuite) TestSnapsInfoDefaultSources(c *check.C) {
   374  	d := s.daemon(c)
   375  
   376  	s.rsnaps = []*snap.Info{{
   377  		SideInfo: snap.SideInfo{
   378  			RealName: "remote",
   379  		},
   380  		Publisher: snap.StoreAccount{
   381  			ID:          "foo-id",
   382  			Username:    "foo",
   383  			DisplayName: "Foo",
   384  			Validation:  "unproven",
   385  		},
   386  	}}
   387  	s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "")
   388  
   389  	req, err := http.NewRequest("GET", "/v2/snaps", nil)
   390  	c.Assert(err, check.IsNil)
   391  
   392  	rsp := s.syncReq(c, req, nil)
   393  
   394  	c.Assert(rsp.Sources, check.DeepEquals, []string{"local"})
   395  	snaps := snapList(rsp.Result)
   396  	c.Assert(snaps, check.HasLen, 1)
   397  }
   398  
   399  func (s *snapsSuite) TestSnapsInfoFilterRemote(c *check.C) {
   400  	s.daemon(c)
   401  
   402  	s.rsnaps = nil
   403  
   404  	req, err := http.NewRequest("GET", "/v2/snaps?q=foo&sources=store", nil)
   405  	c.Assert(err, check.IsNil)
   406  
   407  	rsp := s.syncReq(c, req, nil)
   408  
   409  	c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "foo"})
   410  
   411  	c.Assert(rsp.Result, check.NotNil)
   412  }
   413  
   414  func (s *snapsSuite) TestPostSnapsVerifyMultiSnapInstruction(c *check.C) {
   415  	s.daemonWithOverlordMockAndStore(c)
   416  
   417  	buf := strings.NewReader(`{"action": "install","snaps":["ubuntu-core"]}`)
   418  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
   419  	c.Assert(err, check.IsNil)
   420  	req.Header.Set("Content-Type", "application/json")
   421  
   422  	rsp := s.errorReq(c, req, nil)
   423  	c.Check(rsp.Status, check.Equals, 400)
   424  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, `cannot install "ubuntu-core", please use "core" instead`)
   425  }
   426  
   427  func (s *snapsSuite) TestPostSnapsUnsupportedMultiOp(c *check.C) {
   428  	s.daemonWithOverlordMockAndStore(c)
   429  
   430  	buf := strings.NewReader(`{"action": "switch","snaps":["foo"]}`)
   431  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
   432  	c.Assert(err, check.IsNil)
   433  	req.Header.Set("Content-Type", "application/json")
   434  
   435  	rsp := s.errorReq(c, req, nil)
   436  	c.Check(rsp.Status, check.Equals, 400)
   437  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, `unsupported multi-snap operation "switch"`)
   438  }
   439  
   440  func (s *snapsSuite) TestPostSnapsNoWeirdses(c *check.C) {
   441  	s.daemonWithOverlordMockAndStore(c)
   442  
   443  	// one could add more actions here ... 🤷
   444  	for _, action := range []string{"install", "refresh", "remove"} {
   445  		for weird, v := range map[string]string{
   446  			"channel":      `"beta"`,
   447  			"revision":     `"1"`,
   448  			"devmode":      "true",
   449  			"jailmode":     "true",
   450  			"cohort-key":   `"what"`,
   451  			"leave-cohort": "true",
   452  			"purge":        "true",
   453  		} {
   454  			buf := strings.NewReader(fmt.Sprintf(`{"action": "%s","snaps":["foo","bar"], "%s": %s}`, action, weird, v))
   455  			req, err := http.NewRequest("POST", "/v2/snaps", buf)
   456  			c.Assert(err, check.IsNil)
   457  			req.Header.Set("Content-Type", "application/json")
   458  
   459  			rsp := s.errorReq(c, req, nil)
   460  			c.Check(rsp.Status, check.Equals, 400)
   461  			c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, `unsupported option provided for multi-snap operation`)
   462  		}
   463  	}
   464  }
   465  
   466  func (s *snapsSuite) TestPostSnapsOp(c *check.C) {
   467  	s.testPostSnapsOp(c, "application/json")
   468  }
   469  
   470  func (s *snapsSuite) TestPostSnapsOpMoreComplexContentType(c *check.C) {
   471  	s.testPostSnapsOp(c, "application/json; charset=utf-8")
   472  }
   473  
   474  func (s *snapsSuite) testPostSnapsOp(c *check.C, contentType string) {
   475  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(*state.State, int) error { return nil })()
   476  	defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
   477  		c.Check(names, check.HasLen, 0)
   478  		t := s.NewTask("fake-refresh-all", "Refreshing everything")
   479  		return []string{"fake1", "fake2"}, []*state.TaskSet{state.NewTaskSet(t)}, nil
   480  	})()
   481  
   482  	d := s.daemonWithOverlordMockAndStore(c)
   483  
   484  	buf := bytes.NewBufferString(`{"action": "refresh"}`)
   485  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
   486  	c.Assert(err, check.IsNil)
   487  	req.Header.Set("Content-Type", contentType)
   488  
   489  	rsp := s.asyncReq(c, req, nil)
   490  
   491  	st := d.Overlord().State()
   492  	st.Lock()
   493  	defer st.Unlock()
   494  	chg := st.Change(rsp.Change)
   495  	c.Check(chg.Summary(), check.Equals, `Refresh snaps "fake1", "fake2"`)
   496  	var apiData map[string]interface{}
   497  	c.Check(chg.Get("api-data", &apiData), check.IsNil)
   498  	c.Check(apiData["snap-names"], check.DeepEquals, []interface{}{"fake1", "fake2"})
   499  }
   500  
   501  func (s *snapsSuite) TestPostSnapsOpInvalidCharset(c *check.C) {
   502  	s.daemon(c)
   503  
   504  	buf := bytes.NewBufferString(`{"action": "refresh"}`)
   505  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
   506  	c.Assert(err, check.IsNil)
   507  	req.Header.Set("Content-Type", "application/json; charset=iso-8859-1")
   508  
   509  	rsp := s.errorReq(c, req, nil)
   510  	c.Check(rsp.Status, check.Equals, 400)
   511  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, "unknown charset in content type")
   512  }
   513  
   514  func (s *snapsSuite) TestRefreshAll(c *check.C) {
   515  	refreshSnapDecls := false
   516  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
   517  		refreshSnapDecls = true
   518  		return assertstate.RefreshSnapDeclarations(s, userID)
   519  	})()
   520  
   521  	d := s.daemon(c)
   522  
   523  	for _, tst := range []struct {
   524  		snaps []string
   525  		msg   string
   526  	}{
   527  		{nil, "Refresh all snaps: no updates"},
   528  		{[]string{"fake"}, `Refresh snap "fake"`},
   529  		{[]string{"fake1", "fake2"}, `Refresh snaps "fake1", "fake2"`},
   530  	} {
   531  		refreshSnapDecls = false
   532  
   533  		defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
   534  			c.Check(names, check.HasLen, 0)
   535  			t := s.NewTask("fake-refresh-all", "Refreshing everything")
   536  			return tst.snaps, []*state.TaskSet{state.NewTaskSet(t)}, nil
   537  		})()
   538  
   539  		inst := &daemon.SnapInstruction{Action: "refresh"}
   540  		st := d.Overlord().State()
   541  		st.Lock()
   542  		res, err := inst.DispatchForMany()(inst, st)
   543  		st.Unlock()
   544  		c.Assert(err, check.IsNil)
   545  		c.Check(res.Summary, check.Equals, tst.msg)
   546  		c.Check(refreshSnapDecls, check.Equals, true)
   547  	}
   548  }
   549  
   550  func (s *snapsSuite) TestRefreshAllNoChanges(c *check.C) {
   551  	refreshSnapDecls := false
   552  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
   553  		refreshSnapDecls = true
   554  		return assertstate.RefreshSnapDeclarations(s, userID)
   555  	})()
   556  
   557  	defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
   558  		c.Check(names, check.HasLen, 0)
   559  		return nil, nil, nil
   560  	})()
   561  
   562  	d := s.daemon(c)
   563  	inst := &daemon.SnapInstruction{Action: "refresh"}
   564  	st := d.Overlord().State()
   565  	st.Lock()
   566  	res, err := inst.DispatchForMany()(inst, st)
   567  	st.Unlock()
   568  	c.Assert(err, check.IsNil)
   569  	c.Check(res.Summary, check.Equals, `Refresh all snaps: no updates`)
   570  	c.Check(refreshSnapDecls, check.Equals, true)
   571  }
   572  
   573  func (s *snapsSuite) TestRefreshMany(c *check.C) {
   574  	refreshSnapDecls := false
   575  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
   576  		refreshSnapDecls = true
   577  		return nil
   578  	})()
   579  
   580  	defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
   581  		c.Check(names, check.HasLen, 2)
   582  		t := s.NewTask("fake-refresh-2", "Refreshing two")
   583  		return names, []*state.TaskSet{state.NewTaskSet(t)}, nil
   584  	})()
   585  
   586  	d := s.daemon(c)
   587  	inst := &daemon.SnapInstruction{Action: "refresh", Snaps: []string{"foo", "bar"}}
   588  	st := d.Overlord().State()
   589  	st.Lock()
   590  	res, err := inst.DispatchForMany()(inst, st)
   591  	st.Unlock()
   592  	c.Assert(err, check.IsNil)
   593  	c.Check(res.Summary, check.Equals, `Refresh snaps "foo", "bar"`)
   594  	c.Check(res.Affected, check.DeepEquals, inst.Snaps)
   595  	c.Check(refreshSnapDecls, check.Equals, true)
   596  }
   597  
   598  func (s *snapsSuite) TestRefreshMany1(c *check.C) {
   599  	refreshSnapDecls := false
   600  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
   601  		refreshSnapDecls = true
   602  		return nil
   603  	})()
   604  
   605  	defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
   606  		c.Check(names, check.HasLen, 1)
   607  		t := s.NewTask("fake-refresh-1", "Refreshing one")
   608  		return names, []*state.TaskSet{state.NewTaskSet(t)}, nil
   609  	})()
   610  
   611  	d := s.daemon(c)
   612  	inst := &daemon.SnapInstruction{Action: "refresh", Snaps: []string{"foo"}}
   613  	st := d.Overlord().State()
   614  	st.Lock()
   615  	res, err := inst.DispatchForMany()(inst, st)
   616  	st.Unlock()
   617  	c.Assert(err, check.IsNil)
   618  	c.Check(res.Summary, check.Equals, `Refresh snap "foo"`)
   619  	c.Check(res.Affected, check.DeepEquals, inst.Snaps)
   620  	c.Check(refreshSnapDecls, check.Equals, true)
   621  }
   622  
   623  func (s *snapsSuite) TestInstallMany(c *check.C) {
   624  	defer daemon.MockSnapstateInstallMany(func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
   625  		c.Check(names, check.HasLen, 2)
   626  		t := s.NewTask("fake-install-2", "Install two")
   627  		return names, []*state.TaskSet{state.NewTaskSet(t)}, nil
   628  	})()
   629  
   630  	d := s.daemon(c)
   631  	inst := &daemon.SnapInstruction{Action: "install", Snaps: []string{"foo", "bar"}}
   632  	st := d.Overlord().State()
   633  	st.Lock()
   634  	res, err := inst.DispatchForMany()(inst, st)
   635  	st.Unlock()
   636  	c.Assert(err, check.IsNil)
   637  	c.Check(res.Summary, check.Equals, `Install snaps "foo", "bar"`)
   638  	c.Check(res.Affected, check.DeepEquals, inst.Snaps)
   639  }
   640  
   641  func (s *snapsSuite) TestInstallManyEmptyName(c *check.C) {
   642  	defer daemon.MockSnapstateInstallMany(func(_ *state.State, _ []string, _ int) ([]string, []*state.TaskSet, error) {
   643  		return nil, nil, errors.New("should not be called")
   644  	})()
   645  	d := s.daemon(c)
   646  	inst := &daemon.SnapInstruction{Action: "install", Snaps: []string{"", "bar"}}
   647  	st := d.Overlord().State()
   648  	st.Lock()
   649  	res, err := inst.DispatchForMany()(inst, st)
   650  	st.Unlock()
   651  	c.Assert(res, check.IsNil)
   652  	c.Assert(err, check.ErrorMatches, "cannot install snap with empty name")
   653  }
   654  
   655  func (s *snapsSuite) TestRemoveMany(c *check.C) {
   656  	defer daemon.MockSnapstateRemoveMany(func(s *state.State, names []string) ([]string, []*state.TaskSet, error) {
   657  		c.Check(names, check.HasLen, 2)
   658  		t := s.NewTask("fake-remove-2", "Remove two")
   659  		return names, []*state.TaskSet{state.NewTaskSet(t)}, nil
   660  	})()
   661  
   662  	d := s.daemon(c)
   663  	inst := &daemon.SnapInstruction{Action: "remove", Snaps: []string{"foo", "bar"}}
   664  	st := d.Overlord().State()
   665  	st.Lock()
   666  	res, err := inst.DispatchForMany()(inst, st)
   667  	st.Unlock()
   668  	c.Assert(err, check.IsNil)
   669  	c.Check(res.Summary, check.Equals, `Remove snaps "foo", "bar"`)
   670  	c.Check(res.Affected, check.DeepEquals, inst.Snaps)
   671  }
   672  
   673  func (s *snapsSuite) TestSnapInfoOneIntegration(c *check.C) {
   674  	d := s.daemon(c)
   675  
   676  	// we have v0 [r5] installed
   677  	s.mkInstalledInState(c, d, "foo", "bar", "v0", snap.R(5), false, "")
   678  	// and v1 [r10] is current
   679  	s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, `title: title
   680  description: description
   681  summary: summary
   682  license: GPL-3.0
   683  base: base18
   684  apps:
   685    cmd:
   686      command: some.cmd
   687    cmd2:
   688      command: other.cmd
   689    cmd3:
   690      command: other.cmd
   691      common-id: org.foo.cmd
   692    svc1:
   693      command: somed1
   694      daemon: simple
   695    svc2:
   696      command: somed2
   697      daemon: forking
   698    svc3:
   699      command: somed3
   700      daemon: oneshot
   701    svc4:
   702      command: somed4
   703      daemon: notify
   704    svc5:
   705      command: some5
   706      timer: mon1,12:15
   707      daemon: simple
   708    svc6:
   709      command: some6
   710      daemon: simple
   711      sockets:
   712         sock:
   713           listen-stream: $SNAP_COMMON/run.sock
   714    svc7:
   715      command: some7
   716      daemon: simple
   717      sockets:
   718         other-sock:
   719           listen-stream: $SNAP_COMMON/other-run.sock
   720  `)
   721  	df := s.mkInstalledDesktopFile(c, "foo_cmd.desktop", "[Desktop]\nExec=foo.cmd %U")
   722  	s.SysctlBufs = [][]byte{
   723  		[]byte(`Type=simple
   724  Id=snap.foo.svc1.service
   725  ActiveState=fumbling
   726  UnitFileState=enabled
   727  `),
   728  		[]byte(`Type=forking
   729  Id=snap.foo.svc2.service
   730  ActiveState=active
   731  UnitFileState=disabled
   732  `),
   733  		[]byte(`Type=oneshot
   734  Id=snap.foo.svc3.service
   735  ActiveState=reloading
   736  UnitFileState=static
   737  `),
   738  		[]byte(`Type=notify
   739  Id=snap.foo.svc4.service
   740  ActiveState=inactive
   741  UnitFileState=potatoes
   742  `),
   743  		[]byte(`Type=simple
   744  Id=snap.foo.svc5.service
   745  ActiveState=inactive
   746  UnitFileState=static
   747  `),
   748  		[]byte(`Id=snap.foo.svc5.timer
   749  ActiveState=active
   750  UnitFileState=enabled
   751  `),
   752  		[]byte(`Type=simple
   753  Id=snap.foo.svc6.service
   754  ActiveState=inactive
   755  UnitFileState=static
   756  `),
   757  		[]byte(`Id=snap.foo.svc6.sock.socket
   758  ActiveState=active
   759  UnitFileState=enabled
   760  `),
   761  		[]byte(`Type=simple
   762  Id=snap.foo.svc7.service
   763  ActiveState=inactive
   764  UnitFileState=static
   765  `),
   766  		[]byte(`Id=snap.foo.svc7.other-sock.socket
   767  ActiveState=inactive
   768  UnitFileState=enabled
   769  `),
   770  	}
   771  
   772  	var snapst snapstate.SnapState
   773  	st := d.Overlord().State()
   774  	st.Lock()
   775  	st.Set("health", map[string]healthstate.HealthState{
   776  		"foo": {Status: healthstate.OkayStatus},
   777  	})
   778  	err := snapstate.Get(st, "foo", &snapst)
   779  	st.Unlock()
   780  	c.Assert(err, check.IsNil)
   781  
   782  	// modify state
   783  	snapst.TrackingChannel = "beta"
   784  	snapst.IgnoreValidation = true
   785  	snapst.CohortKey = "some-long-cohort-key"
   786  	st.Lock()
   787  	snapstate.Set(st, "foo", &snapst)
   788  	st.Unlock()
   789  
   790  	req, err := http.NewRequest("GET", "/v2/snaps/foo", nil)
   791  	c.Assert(err, check.IsNil)
   792  	rsp := s.syncReq(c, req, nil)
   793  
   794  	c.Assert(rsp.Result, check.FitsTypeOf, &client.Snap{})
   795  	m := rsp.Result.(*client.Snap)
   796  
   797  	// installed-size depends on vagaries of the filesystem, just check type
   798  	c.Check(m.InstalledSize, check.FitsTypeOf, int64(0))
   799  	m.InstalledSize = 0
   800  	// ditto install-date
   801  	c.Check(m.InstallDate, check.FitsTypeOf, time.Time{})
   802  	m.InstallDate = time.Time{}
   803  
   804  	meta := &daemon.Meta{}
   805  	expected := &daemon.Resp{
   806  		Type:   daemon.ResponseTypeSync,
   807  		Status: 200,
   808  		Result: &client.Snap{
   809  			ID:               "foo-id",
   810  			Name:             "foo",
   811  			Revision:         snap.R(10),
   812  			Version:          "v1",
   813  			Channel:          "stable",
   814  			TrackingChannel:  "beta",
   815  			IgnoreValidation: true,
   816  			Title:            "title",
   817  			Summary:          "summary",
   818  			Description:      "description",
   819  			Developer:        "bar",
   820  			Publisher: &snap.StoreAccount{
   821  				ID:          "bar-id",
   822  				Username:    "bar",
   823  				DisplayName: "Bar",
   824  				Validation:  "unproven",
   825  			},
   826  			Status:      "active",
   827  			Health:      &client.SnapHealth{Status: "okay"},
   828  			Icon:        "/v2/icons/foo/icon",
   829  			Type:        string(snap.TypeApp),
   830  			Base:        "base18",
   831  			Private:     false,
   832  			DevMode:     false,
   833  			JailMode:    false,
   834  			Confinement: string(snap.StrictConfinement),
   835  			TryMode:     false,
   836  			MountedFrom: filepath.Join(dirs.SnapBlobDir, "foo_10.snap"),
   837  			Apps: []client.AppInfo{
   838  				{
   839  					Snap: "foo", Name: "cmd",
   840  					DesktopFile: df,
   841  				}, {
   842  					// no desktop file
   843  					Snap: "foo", Name: "cmd2",
   844  				}, {
   845  					// has AppStream ID
   846  					Snap: "foo", Name: "cmd3",
   847  					CommonID: "org.foo.cmd",
   848  				}, {
   849  					// services
   850  					Snap: "foo", Name: "svc1",
   851  					Daemon:      "simple",
   852  					DaemonScope: snap.SystemDaemon,
   853  					Enabled:     true,
   854  					Active:      false,
   855  				}, {
   856  					Snap: "foo", Name: "svc2",
   857  					Daemon:      "forking",
   858  					DaemonScope: snap.SystemDaemon,
   859  					Enabled:     false,
   860  					Active:      true,
   861  				}, {
   862  					Snap: "foo", Name: "svc3",
   863  					Daemon:      "oneshot",
   864  					DaemonScope: snap.SystemDaemon,
   865  					Enabled:     true,
   866  					Active:      true,
   867  				}, {
   868  					Snap: "foo", Name: "svc4",
   869  					Daemon:      "notify",
   870  					DaemonScope: snap.SystemDaemon,
   871  					Enabled:     false,
   872  					Active:      false,
   873  				}, {
   874  					Snap: "foo", Name: "svc5",
   875  					Daemon:      "simple",
   876  					DaemonScope: snap.SystemDaemon,
   877  					Enabled:     true,
   878  					Active:      false,
   879  					Activators: []client.AppActivator{
   880  						{Name: "svc5", Type: "timer", Active: true, Enabled: true},
   881  					},
   882  				}, {
   883  					Snap: "foo", Name: "svc6",
   884  					Daemon:      "simple",
   885  					DaemonScope: snap.SystemDaemon,
   886  					Enabled:     true,
   887  					Active:      false,
   888  					Activators: []client.AppActivator{
   889  						{Name: "sock", Type: "socket", Active: true, Enabled: true},
   890  					},
   891  				}, {
   892  					Snap: "foo", Name: "svc7",
   893  					Daemon:      "simple",
   894  					DaemonScope: snap.SystemDaemon,
   895  					Enabled:     true,
   896  					Active:      false,
   897  					Activators: []client.AppActivator{
   898  						{Name: "other-sock", Type: "socket", Active: false, Enabled: true},
   899  					},
   900  				},
   901  			},
   902  			Broken:    "",
   903  			Contact:   "",
   904  			License:   "GPL-3.0",
   905  			CommonIDs: []string{"org.foo.cmd"},
   906  			CohortKey: "some-long-cohort-key",
   907  		},
   908  		Meta: meta,
   909  	}
   910  
   911  	c.Check(rsp.Result, check.DeepEquals, expected.Result)
   912  }
   913  
   914  func (s *snapsSuite) TestSnapInfoNotFound(c *check.C) {
   915  	s.daemon(c)
   916  
   917  	req, err := http.NewRequest("GET", "/v2/snaps/gfoo", nil)
   918  	c.Assert(err, check.IsNil)
   919  	c.Check(s.errorReq(c, req, nil).Status, check.Equals, 404)
   920  }
   921  
   922  func (s *snapsSuite) TestSnapInfoNoneFound(c *check.C) {
   923  	s.daemon(c)
   924  
   925  	req, err := http.NewRequest("GET", "/v2/snaps/gfoo", nil)
   926  	c.Assert(err, check.IsNil)
   927  	c.Check(s.errorReq(c, req, nil).Status, check.Equals, 404)
   928  }
   929  
   930  func (s *snapsSuite) TestSnapInfoIgnoresRemoteErrors(c *check.C) {
   931  	s.daemon(c)
   932  	s.err = errors.New("weird")
   933  
   934  	req, err := http.NewRequest("GET", "/v2/snaps/gfoo", nil)
   935  	c.Assert(err, check.IsNil)
   936  	rsp := s.errorReq(c, req, nil)
   937  	c.Check(rsp.Status, check.Equals, 404)
   938  	c.Check(rsp.Result, check.NotNil)
   939  }
   940  
   941  func (s *snapsSuite) TestMapLocalFields(c *check.C) {
   942  	media := snap.MediaInfos{
   943  		{
   944  			Type: "screenshot",
   945  			URL:  "https://example.com/shot1.svg",
   946  		}, {
   947  			Type: "icon",
   948  			URL:  "https://example.com/icon.png",
   949  		}, {
   950  			Type: "screenshot",
   951  			URL:  "https://example.com/shot2.svg",
   952  		},
   953  	}
   954  
   955  	publisher := snap.StoreAccount{
   956  		ID:          "some-dev-id",
   957  		Username:    "some-dev",
   958  		DisplayName: "Some Developer",
   959  		Validation:  "poor",
   960  	}
   961  	info := &snap.Info{
   962  		SideInfo: snap.SideInfo{
   963  			SnapID:            "some-snap-id",
   964  			RealName:          "some-snap",
   965  			EditedTitle:       "A Title",
   966  			EditedSummary:     "a summary",
   967  			EditedDescription: "the\nlong\ndescription",
   968  			Channel:           "bleeding/edge",
   969  			Contact:           "alice@example.com",
   970  			Revision:          snap.R(7),
   971  			Private:           true,
   972  		},
   973  		InstanceKey: "instance",
   974  		SnapType:    "app",
   975  		Base:        "the-base",
   976  		Version:     "v1.0",
   977  		License:     "MIT",
   978  		Broken:      "very",
   979  		Confinement: "very strict",
   980  		CommonIDs:   []string{"foo", "bar"},
   981  		Media:       media,
   982  		DownloadInfo: snap.DownloadInfo{
   983  			Size:     42,
   984  			Sha3_384: "some-sum",
   985  		},
   986  		Publisher: publisher,
   987  	}
   988  
   989  	// make InstallDate work
   990  	c.Assert(os.MkdirAll(info.MountDir(), 0755), check.IsNil)
   991  	c.Assert(os.Symlink("7", filepath.Join(info.MountDir(), "..", "current")), check.IsNil)
   992  
   993  	info.Apps = map[string]*snap.AppInfo{
   994  		"foo": {Snap: info, Name: "foo", Command: "foo"},
   995  		"bar": {Snap: info, Name: "bar", Command: "bar"},
   996  	}
   997  	about := daemon.MakeAboutSnap(info, &snapstate.SnapState{
   998  		Active:          true,
   999  		TrackingChannel: "flaky/beta",
  1000  		Current:         snap.R(7),
  1001  		Flags: snapstate.Flags{
  1002  			IgnoreValidation: true,
  1003  			DevMode:          true,
  1004  			JailMode:         true,
  1005  		},
  1006  	},
  1007  	)
  1008  
  1009  	expected := &client.Snap{
  1010  		ID:               "some-snap-id",
  1011  		Name:             "some-snap_instance",
  1012  		Summary:          "a summary",
  1013  		Description:      "the\nlong\ndescription",
  1014  		Developer:        "some-dev",
  1015  		Publisher:        &publisher,
  1016  		Icon:             "https://example.com/icon.png",
  1017  		Type:             "app",
  1018  		Base:             "the-base",
  1019  		Version:          "v1.0",
  1020  		Revision:         snap.R(7),
  1021  		Channel:          "bleeding/edge",
  1022  		TrackingChannel:  "flaky/beta",
  1023  		InstallDate:      info.InstallDate(),
  1024  		InstalledSize:    42,
  1025  		Status:           "active",
  1026  		Confinement:      "very strict",
  1027  		IgnoreValidation: true,
  1028  		DevMode:          true,
  1029  		JailMode:         true,
  1030  		Private:          true,
  1031  		Broken:           "very",
  1032  		Contact:          "alice@example.com",
  1033  		Title:            "A Title",
  1034  		License:          "MIT",
  1035  		CommonIDs:        []string{"foo", "bar"},
  1036  		MountedFrom:      filepath.Join(dirs.SnapBlobDir, "some-snap_instance_7.snap"),
  1037  		Media:            media,
  1038  		Apps: []client.AppInfo{
  1039  			{Snap: "some-snap_instance", Name: "bar"},
  1040  			{Snap: "some-snap_instance", Name: "foo"},
  1041  		},
  1042  	}
  1043  	c.Check(daemon.MapLocal(about, nil), check.DeepEquals, expected)
  1044  }
  1045  
  1046  func (s *snapsSuite) TestMapLocalOfTryResolvesSymlink(c *check.C) {
  1047  	c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), check.IsNil)
  1048  
  1049  	info := snap.Info{SideInfo: snap.SideInfo{RealName: "hello", Revision: snap.R(1)}}
  1050  	snapst := snapstate.SnapState{}
  1051  	mountFile := info.MountFile()
  1052  	about := daemon.MakeAboutSnap(&info, &snapst)
  1053  
  1054  	// if not a 'try', then MountedFrom is just MountFile()
  1055  	c.Check(daemon.MapLocal(about, nil).MountedFrom, check.Equals, mountFile)
  1056  
  1057  	// if it's a try, then MountedFrom resolves the symlink
  1058  	// (note it doesn't matter, here, whether the target of the link exists)
  1059  	snapst.TryMode = true
  1060  	c.Assert(os.Symlink("/xyzzy", mountFile), check.IsNil)
  1061  	c.Check(daemon.MapLocal(about, nil).MountedFrom, check.Equals, "/xyzzy")
  1062  
  1063  	// if the readlink fails, it's unset
  1064  	c.Assert(os.Remove(mountFile), check.IsNil)
  1065  	c.Check(daemon.MapLocal(about, nil).MountedFrom, check.Equals, "")
  1066  }
  1067  
  1068  func (s *snapsSuite) TestPostSnapBadRequest(c *check.C) {
  1069  	s.daemon(c)
  1070  
  1071  	buf := bytes.NewBufferString(`hello`)
  1072  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  1073  	c.Assert(err, check.IsNil)
  1074  
  1075  	rsp := s.errorReq(c, req, nil)
  1076  	c.Check(rsp.Status, check.Equals, 400)
  1077  	c.Check(rsp.Result, check.NotNil)
  1078  }
  1079  
  1080  func (s *snapsSuite) TestPostSnapBadAction(c *check.C) {
  1081  	s.daemon(c)
  1082  
  1083  	buf := bytes.NewBufferString(`{"action": "potato"}`)
  1084  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  1085  	c.Assert(err, check.IsNil)
  1086  
  1087  	rsp := s.errorReq(c, req, nil)
  1088  	c.Check(rsp.Status, check.Equals, 400)
  1089  	c.Check(rsp.Result, check.NotNil)
  1090  }
  1091  
  1092  func (s *snapsSuite) TestPostSnapBadChannel(c *check.C) {
  1093  	s.daemon(c)
  1094  
  1095  	buf := bytes.NewBufferString(`{"channel": "1/2/3/4"}`)
  1096  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  1097  	c.Assert(err, check.IsNil)
  1098  
  1099  	rsp := s.errorReq(c, req, nil)
  1100  	c.Check(rsp.Status, check.Equals, 400)
  1101  	c.Check(rsp.Result, check.NotNil)
  1102  }
  1103  
  1104  func (s *snapsSuite) TestPostSnap(c *check.C) {
  1105  	s.testPostSnap(c, false)
  1106  }
  1107  
  1108  func (s *snapsSuite) TestPostSnapWithChannel(c *check.C) {
  1109  	s.testPostSnap(c, true)
  1110  }
  1111  
  1112  func (s *snapsSuite) testPostSnap(c *check.C, withChannel bool) {
  1113  	d := s.daemonWithOverlordMock(c)
  1114  
  1115  	soon := 0
  1116  	var origEnsureStateSoon func(*state.State)
  1117  	origEnsureStateSoon, restore := daemon.MockEnsureStateSoon(func(st *state.State) {
  1118  		soon++
  1119  		origEnsureStateSoon(st)
  1120  	})
  1121  	defer restore()
  1122  
  1123  	checked := false
  1124  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1125  		if withChannel {
  1126  			// channel in -> channel out
  1127  			c.Check(opts.Channel, check.Equals, "xyzzy")
  1128  		} else {
  1129  			// no channel in -> no channel out
  1130  			c.Check(opts.Channel, check.Equals, "")
  1131  		}
  1132  		checked = true
  1133  
  1134  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  1135  		return state.NewTaskSet(t), nil
  1136  	})()
  1137  
  1138  	var buf *bytes.Buffer
  1139  	if withChannel {
  1140  		buf = bytes.NewBufferString(`{"action": "install", "channel": "xyzzy"}`)
  1141  	} else {
  1142  		buf = bytes.NewBufferString(`{"action": "install"}`)
  1143  	}
  1144  	req, err := http.NewRequest("POST", "/v2/snaps/foo", buf)
  1145  	c.Assert(err, check.IsNil)
  1146  
  1147  	rsp := s.asyncReq(c, req, nil)
  1148  
  1149  	st := d.Overlord().State()
  1150  	st.Lock()
  1151  	defer st.Unlock()
  1152  	chg := st.Change(rsp.Change)
  1153  	c.Assert(chg, check.NotNil)
  1154  	if withChannel {
  1155  		c.Check(chg.Summary(), check.Equals, `Install "foo" snap from "xyzzy" channel`)
  1156  	} else {
  1157  		c.Check(chg.Summary(), check.Equals, `Install "foo" snap`)
  1158  	}
  1159  	var names []string
  1160  	err = chg.Get("snap-names", &names)
  1161  	c.Assert(err, check.IsNil)
  1162  	c.Check(names, check.DeepEquals, []string{"foo"})
  1163  
  1164  	c.Check(checked, check.Equals, true)
  1165  	c.Check(soon, check.Equals, 1)
  1166  	c.Check(chg.Tasks()[0].Summary(), check.Equals, "Doing a fake install")
  1167  }
  1168  
  1169  func (s *snapsSuite) TestPostSnapVerifySnapInstruction(c *check.C) {
  1170  	s.daemonWithOverlordMock(c)
  1171  
  1172  	buf := bytes.NewBufferString(`{"action": "install"}`)
  1173  	req, err := http.NewRequest("POST", "/v2/snaps/ubuntu-core", buf)
  1174  	c.Assert(err, check.IsNil)
  1175  
  1176  	rsp := s.errorReq(c, req, nil)
  1177  	c.Check(rsp.Status, check.Equals, 400)
  1178  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, `cannot install "ubuntu-core", please use "core" instead`)
  1179  }
  1180  
  1181  func (s *snapsSuite) TestPostSnapCohortUnsupportedAction(c *check.C) {
  1182  	s.daemonWithOverlordMock(c)
  1183  	const expectedErr = "cohort-key can only be specified for install, refresh, or switch"
  1184  
  1185  	for _, action := range []string{"remove", "revert", "enable", "disable", "xyzzy"} {
  1186  		buf := strings.NewReader(fmt.Sprintf(`{"action": "%s", "cohort-key": "32"}`, action))
  1187  		req, err := http.NewRequest("POST", "/v2/snaps/some-snap", buf)
  1188  		c.Assert(err, check.IsNil)
  1189  
  1190  		rsp := s.errorReq(c, req, nil)
  1191  		c.Check(rsp.Status, check.Equals, 400, check.Commentf("%q", action))
  1192  		c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, expectedErr, check.Commentf("%q", action))
  1193  	}
  1194  }
  1195  
  1196  func (s *snapsSuite) TestPostSnapLeaveCohortUnsupportedAction(c *check.C) {
  1197  	s.daemonWithOverlordMock(c)
  1198  	const expectedErr = "leave-cohort can only be specified for refresh or switch"
  1199  
  1200  	for _, action := range []string{"install", "remove", "revert", "enable", "disable", "xyzzy"} {
  1201  		buf := strings.NewReader(fmt.Sprintf(`{"action": "%s", "leave-cohort": true}`, action))
  1202  		req, err := http.NewRequest("POST", "/v2/snaps/some-snap", buf)
  1203  		c.Assert(err, check.IsNil)
  1204  
  1205  		rsp := s.errorReq(c, req, nil)
  1206  		c.Check(rsp.Status, check.Equals, 400, check.Commentf("%q", action))
  1207  		c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, expectedErr, check.Commentf("%q", action))
  1208  	}
  1209  }
  1210  
  1211  func (s *snapsSuite) TestPostSnapCohortIncompat(c *check.C) {
  1212  	s.daemonWithOverlordMock(c)
  1213  	type T struct {
  1214  		opts   string
  1215  		errmsg string
  1216  	}
  1217  
  1218  	for i, t := range []T{
  1219  		// TODO: more?
  1220  		{`"cohort-key": "what", "revision": "42"`, `cannot specify both cohort-key and revision`},
  1221  		{`"cohort-key": "what", "leave-cohort": true`, `cannot specify both cohort-key and leave-cohort`},
  1222  	} {
  1223  		buf := strings.NewReader(fmt.Sprintf(`{"action": "refresh", %s}`, t.opts))
  1224  		req, err := http.NewRequest("POST", "/v2/snaps/some-snap", buf)
  1225  		c.Assert(err, check.IsNil, check.Commentf("%d (%s)", i, t.opts))
  1226  
  1227  		rsp := s.errorReq(c, req, nil)
  1228  		c.Check(rsp.Status, check.Equals, 400, check.Commentf("%d (%s)", i, t.opts))
  1229  		c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, t.errmsg, check.Commentf("%d (%s)", i, t.opts))
  1230  	}
  1231  }
  1232  
  1233  func (s *snapsSuite) TestPostSnapSetsUser(c *check.C) {
  1234  	d := s.daemon(c)
  1235  
  1236  	_, restore := daemon.MockEnsureStateSoon(func(st *state.State) {})
  1237  	defer restore()
  1238  
  1239  	checked := false
  1240  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1241  		c.Check(userID, check.Equals, 1)
  1242  		checked = true
  1243  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  1244  		return state.NewTaskSet(t), nil
  1245  	})()
  1246  
  1247  	state := d.Overlord().State()
  1248  	state.Lock()
  1249  	user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"})
  1250  	state.Unlock()
  1251  	c.Check(err, check.IsNil)
  1252  
  1253  	buf := bytes.NewBufferString(`{"action": "install"}`)
  1254  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  1255  	c.Assert(err, check.IsNil)
  1256  	req.Header.Set("Authorization", `Macaroon root="macaroon", discharge="discharge"`)
  1257  
  1258  	rsp := s.asyncReq(c, req, user)
  1259  
  1260  	st := d.Overlord().State()
  1261  	st.Lock()
  1262  	defer st.Unlock()
  1263  	chg := st.Change(rsp.Change)
  1264  	c.Assert(chg, check.NotNil)
  1265  	c.Check(checked, check.Equals, true)
  1266  }
  1267  
  1268  func (s *snapsSuite) TestPostSnapEnableDisableSwitchRevision(c *check.C) {
  1269  	s.daemon(c)
  1270  
  1271  	for _, action := range []string{"enable", "disable", "switch"} {
  1272  		buf := bytes.NewBufferString(`{"action": "` + action + `", "revision": "42"}`)
  1273  		req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  1274  		c.Assert(err, check.IsNil)
  1275  
  1276  		rsp := s.errorReq(c, req, nil)
  1277  		c.Check(rsp.Status, check.Equals, 400)
  1278  		c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, "takes no revision")
  1279  	}
  1280  }
  1281  
  1282  func (s *snapsSuite) TestInstall(c *check.C) {
  1283  	var calledName string
  1284  
  1285  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1286  		calledName = name
  1287  
  1288  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  1289  		return state.NewTaskSet(t), nil
  1290  	})()
  1291  
  1292  	d := s.daemon(c)
  1293  	inst := &daemon.SnapInstruction{
  1294  		Action: "install",
  1295  		// Install the snap in developer mode
  1296  		DevMode: true,
  1297  		Snaps:   []string{"fake"},
  1298  	}
  1299  
  1300  	st := d.Overlord().State()
  1301  	st.Lock()
  1302  	defer st.Unlock()
  1303  	_, _, err := inst.Dispatch()(inst, st)
  1304  	c.Check(err, check.IsNil)
  1305  	c.Check(calledName, check.Equals, "fake")
  1306  }
  1307  
  1308  func (s *snapsSuite) TestInstallDevMode(c *check.C) {
  1309  	var calledFlags snapstate.Flags
  1310  
  1311  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1312  		calledFlags = flags
  1313  
  1314  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  1315  		return state.NewTaskSet(t), nil
  1316  	})()
  1317  
  1318  	d := s.daemon(c)
  1319  	inst := &daemon.SnapInstruction{
  1320  		Action: "install",
  1321  		// Install the snap in developer mode
  1322  		DevMode: true,
  1323  		Snaps:   []string{"fake"},
  1324  	}
  1325  
  1326  	st := d.Overlord().State()
  1327  	st.Lock()
  1328  	defer st.Unlock()
  1329  	_, _, err := inst.Dispatch()(inst, st)
  1330  	c.Check(err, check.IsNil)
  1331  
  1332  	c.Check(calledFlags.DevMode, check.Equals, true)
  1333  }
  1334  
  1335  func (s *snapsSuite) TestInstallJailMode(c *check.C) {
  1336  	var calledFlags snapstate.Flags
  1337  
  1338  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1339  		calledFlags = flags
  1340  
  1341  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  1342  		return state.NewTaskSet(t), nil
  1343  	})()
  1344  
  1345  	d := s.daemon(c)
  1346  	inst := &daemon.SnapInstruction{
  1347  		Action:   "install",
  1348  		JailMode: true,
  1349  		Snaps:    []string{"fake"},
  1350  	}
  1351  
  1352  	st := d.Overlord().State()
  1353  	st.Lock()
  1354  	defer st.Unlock()
  1355  	_, _, err := inst.Dispatch()(inst, st)
  1356  	c.Check(err, check.IsNil)
  1357  
  1358  	c.Check(calledFlags.JailMode, check.Equals, true)
  1359  }
  1360  
  1361  func (s *snapsSuite) TestInstallJailModeDevModeOS(c *check.C) {
  1362  	restore := sandbox.MockForceDevMode(true)
  1363  	defer restore()
  1364  
  1365  	d := s.daemon(c)
  1366  	inst := &daemon.SnapInstruction{
  1367  		Action:   "install",
  1368  		JailMode: true,
  1369  		Snaps:    []string{"foo"},
  1370  	}
  1371  
  1372  	st := d.Overlord().State()
  1373  	st.Lock()
  1374  	defer st.Unlock()
  1375  	_, _, err := inst.Dispatch()(inst, st)
  1376  	c.Check(err, check.ErrorMatches, "this system cannot honour the jailmode flag")
  1377  }
  1378  
  1379  func (s *snapsSuite) TestInstallJailModeDevMode(c *check.C) {
  1380  	d := s.daemon(c)
  1381  	inst := &daemon.SnapInstruction{
  1382  		Action:   "install",
  1383  		DevMode:  true,
  1384  		JailMode: true,
  1385  		Snaps:    []string{"foo"},
  1386  	}
  1387  
  1388  	st := d.Overlord().State()
  1389  	st.Lock()
  1390  	defer st.Unlock()
  1391  	_, _, err := inst.Dispatch()(inst, st)
  1392  	c.Check(err, check.ErrorMatches, "cannot use devmode and jailmode flags together")
  1393  }
  1394  
  1395  func (s *snapsSuite) TestInstallCohort(c *check.C) {
  1396  	var calledName string
  1397  	var calledCohort string
  1398  
  1399  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1400  		calledName = name
  1401  		calledCohort = opts.CohortKey
  1402  
  1403  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  1404  		return state.NewTaskSet(t), nil
  1405  	})()
  1406  
  1407  	d := s.daemon(c)
  1408  	inst := &daemon.SnapInstruction{
  1409  		Action: "install",
  1410  		Snaps:  []string{"fake"},
  1411  	}
  1412  	inst.CohortKey = "To the legion of the lost ones, to the cohort of the damned."
  1413  
  1414  	st := d.Overlord().State()
  1415  	st.Lock()
  1416  	defer st.Unlock()
  1417  	msg, _, err := inst.Dispatch()(inst, st)
  1418  	c.Check(err, check.IsNil)
  1419  	c.Check(calledName, check.Equals, "fake")
  1420  	c.Check(calledCohort, check.Equals, "To the legion of the lost ones, to the cohort of the damned.")
  1421  	c.Check(msg, check.Equals, `Install "fake" snap from "…e damned." cohort`)
  1422  }
  1423  
  1424  func (s *snapsSuite) TestInstallEmptyName(c *check.C) {
  1425  	defer daemon.MockSnapstateInstall(func(ctx context.Context, _ *state.State, _ string, _ *snapstate.RevisionOptions, _ int, _ snapstate.Flags) (*state.TaskSet, error) {
  1426  		return nil, errors.New("should not be called")
  1427  	})()
  1428  
  1429  	d := s.daemon(c)
  1430  	inst := &daemon.SnapInstruction{
  1431  		Action: "install",
  1432  		Snaps:  []string{""},
  1433  	}
  1434  
  1435  	st := d.Overlord().State()
  1436  	st.Lock()
  1437  	defer st.Unlock()
  1438  	_, _, err := inst.Dispatch()(inst, st)
  1439  	c.Check(err, check.ErrorMatches, "cannot install snap with empty name")
  1440  }
  1441  
  1442  func (s *snapsSuite) TestInstallOnNonDevModeDistro(c *check.C) {
  1443  	s.testInstall(c, false, snapstate.Flags{}, snap.R(0))
  1444  }
  1445  func (s *snapsSuite) TestInstallOnDevModeDistro(c *check.C) {
  1446  	s.testInstall(c, true, snapstate.Flags{}, snap.R(0))
  1447  }
  1448  func (s *snapsSuite) TestInstallRevision(c *check.C) {
  1449  	s.testInstall(c, false, snapstate.Flags{}, snap.R(42))
  1450  }
  1451  
  1452  func (s *snapsSuite) testInstall(c *check.C, forcedDevmode bool, flags snapstate.Flags, revision snap.Revision) {
  1453  	calledFlags := snapstate.Flags{}
  1454  	installQueue := []string{}
  1455  	restore := sandbox.MockForceDevMode(forcedDevmode)
  1456  	defer restore()
  1457  
  1458  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1459  		calledFlags = flags
  1460  		installQueue = append(installQueue, name)
  1461  		c.Check(revision, check.Equals, opts.Revision)
  1462  
  1463  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  1464  		return state.NewTaskSet(t), nil
  1465  	})()
  1466  
  1467  	d := s.daemonWithFakeSnapManager(c)
  1468  
  1469  	var buf bytes.Buffer
  1470  	if revision.Unset() {
  1471  		buf.WriteString(`{"action": "install"}`)
  1472  	} else {
  1473  		fmt.Fprintf(&buf, `{"action": "install", "revision": %s}`, revision.String())
  1474  	}
  1475  	req, err := http.NewRequest("POST", "/v2/snaps/some-snap", &buf)
  1476  	c.Assert(err, check.IsNil)
  1477  
  1478  	rsp := s.asyncReq(c, req, nil)
  1479  
  1480  	st := d.Overlord().State()
  1481  	st.Lock()
  1482  	defer st.Unlock()
  1483  	chg := st.Change(rsp.Change)
  1484  	c.Assert(chg, check.NotNil)
  1485  
  1486  	c.Check(chg.Tasks(), check.HasLen, 1)
  1487  
  1488  	st.Unlock()
  1489  	s.waitTrivialChange(c, chg)
  1490  	st.Lock()
  1491  
  1492  	c.Check(chg.Status(), check.Equals, state.DoneStatus)
  1493  	c.Check(calledFlags, check.Equals, flags)
  1494  	c.Check(err, check.IsNil)
  1495  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1496  	c.Check(chg.Kind(), check.Equals, "install-snap")
  1497  	c.Check(chg.Summary(), check.Equals, `Install "some-snap" snap`)
  1498  }
  1499  
  1500  func (s *snapsSuite) TestInstallUserAgentContextCreated(c *check.C) {
  1501  	defer daemon.MockSnapstateInstall(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1502  		s.ctx = ctx
  1503  		t := st.NewTask("fake-install-snap", "Doing a fake install")
  1504  		return state.NewTaskSet(t), nil
  1505  	})()
  1506  
  1507  	s.daemonWithFakeSnapManager(c)
  1508  
  1509  	var buf bytes.Buffer
  1510  	buf.WriteString(`{"action": "install"}`)
  1511  	req, err := http.NewRequest("POST", "/v2/snaps/some-snap", &buf)
  1512  	req.RemoteAddr = "pid=100;uid=0;socket=;"
  1513  	c.Assert(err, check.IsNil)
  1514  	req.Header.Add("User-Agent", "some-agent/1.0")
  1515  
  1516  	rec := httptest.NewRecorder()
  1517  	s.serveHTTP(c, rec, req)
  1518  	c.Assert(rec.Code, check.Equals, 202)
  1519  	c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0")
  1520  }
  1521  
  1522  func (s *snapsSuite) TestInstallFails(c *check.C) {
  1523  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1524  		t := s.NewTask("fake-install-snap-error", "Install task")
  1525  		return state.NewTaskSet(t), nil
  1526  	})()
  1527  
  1528  	d := s.daemonWithFakeSnapManager(c)
  1529  	buf := bytes.NewBufferString(`{"action": "install"}`)
  1530  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  1531  	c.Assert(err, check.IsNil)
  1532  
  1533  	rsp := s.asyncReq(c, req, nil)
  1534  
  1535  	st := d.Overlord().State()
  1536  	st.Lock()
  1537  	defer st.Unlock()
  1538  	chg := st.Change(rsp.Change)
  1539  	c.Assert(chg, check.NotNil)
  1540  
  1541  	c.Check(chg.Tasks(), check.HasLen, 1)
  1542  
  1543  	st.Unlock()
  1544  	s.waitTrivialChange(c, chg)
  1545  	st.Lock()
  1546  
  1547  	c.Check(chg.Err(), check.ErrorMatches, `(?sm).*Install task \(fake-install-snap-error errored\)`)
  1548  }
  1549  
  1550  func (s *snapsSuite) TestRefresh(c *check.C) {
  1551  	var calledFlags snapstate.Flags
  1552  	calledUserID := 0
  1553  	installQueue := []string{}
  1554  	assertstateCalledUserID := 0
  1555  
  1556  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1557  		calledFlags = flags
  1558  		calledUserID = userID
  1559  		installQueue = append(installQueue, name)
  1560  
  1561  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  1562  		return state.NewTaskSet(t), nil
  1563  	})()
  1564  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1565  		assertstateCalledUserID = userID
  1566  		return nil
  1567  	})()
  1568  
  1569  	d := s.daemon(c)
  1570  	inst := &daemon.SnapInstruction{
  1571  		Action: "refresh",
  1572  		Snaps:  []string{"some-snap"},
  1573  	}
  1574  	inst.SetUserID(17)
  1575  
  1576  	st := d.Overlord().State()
  1577  	st.Lock()
  1578  	defer st.Unlock()
  1579  	summary, _, err := inst.Dispatch()(inst, st)
  1580  	c.Check(err, check.IsNil)
  1581  
  1582  	c.Check(assertstateCalledUserID, check.Equals, 17)
  1583  	c.Check(calledFlags, check.DeepEquals, snapstate.Flags{})
  1584  	c.Check(calledUserID, check.Equals, 17)
  1585  	c.Check(err, check.IsNil)
  1586  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1587  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1588  }
  1589  
  1590  func (s *snapsSuite) TestRefreshDevMode(c *check.C) {
  1591  	var calledFlags snapstate.Flags
  1592  	calledUserID := 0
  1593  	installQueue := []string{}
  1594  
  1595  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1596  		calledFlags = flags
  1597  		calledUserID = userID
  1598  		installQueue = append(installQueue, name)
  1599  
  1600  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  1601  		return state.NewTaskSet(t), nil
  1602  	})()
  1603  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1604  		return nil
  1605  	})()
  1606  
  1607  	d := s.daemon(c)
  1608  	inst := &daemon.SnapInstruction{
  1609  		Action:  "refresh",
  1610  		DevMode: true,
  1611  		Snaps:   []string{"some-snap"},
  1612  	}
  1613  	inst.SetUserID(17)
  1614  
  1615  	st := d.Overlord().State()
  1616  	st.Lock()
  1617  	defer st.Unlock()
  1618  	summary, _, err := inst.Dispatch()(inst, st)
  1619  	c.Check(err, check.IsNil)
  1620  
  1621  	flags := snapstate.Flags{}
  1622  	flags.DevMode = true
  1623  	c.Check(calledFlags, check.DeepEquals, flags)
  1624  	c.Check(calledUserID, check.Equals, 17)
  1625  	c.Check(err, check.IsNil)
  1626  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1627  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1628  }
  1629  
  1630  func (s *snapsSuite) TestRefreshClassic(c *check.C) {
  1631  	var calledFlags snapstate.Flags
  1632  
  1633  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1634  		calledFlags = flags
  1635  		return nil, nil
  1636  	})()
  1637  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1638  		return nil
  1639  	})()
  1640  
  1641  	d := s.daemon(c)
  1642  	inst := &daemon.SnapInstruction{
  1643  		Action:  "refresh",
  1644  		Classic: true,
  1645  		Snaps:   []string{"some-snap"},
  1646  	}
  1647  	inst.SetUserID(17)
  1648  
  1649  	st := d.Overlord().State()
  1650  	st.Lock()
  1651  	defer st.Unlock()
  1652  	_, _, err := inst.Dispatch()(inst, st)
  1653  	c.Check(err, check.IsNil)
  1654  
  1655  	c.Check(calledFlags, check.DeepEquals, snapstate.Flags{Classic: true})
  1656  }
  1657  
  1658  func (s *snapsSuite) TestRefreshIgnoreValidation(c *check.C) {
  1659  	var calledFlags snapstate.Flags
  1660  	calledUserID := 0
  1661  	installQueue := []string{}
  1662  
  1663  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1664  		calledFlags = flags
  1665  		calledUserID = userID
  1666  		installQueue = append(installQueue, name)
  1667  
  1668  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  1669  		return state.NewTaskSet(t), nil
  1670  	})()
  1671  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1672  		return nil
  1673  	})()
  1674  
  1675  	d := s.daemon(c)
  1676  	inst := &daemon.SnapInstruction{
  1677  		Action:           "refresh",
  1678  		IgnoreValidation: true,
  1679  		Snaps:            []string{"some-snap"},
  1680  	}
  1681  	inst.SetUserID(17)
  1682  
  1683  	st := d.Overlord().State()
  1684  	st.Lock()
  1685  	defer st.Unlock()
  1686  	summary, _, err := inst.Dispatch()(inst, st)
  1687  	c.Check(err, check.IsNil)
  1688  
  1689  	flags := snapstate.Flags{}
  1690  	flags.IgnoreValidation = true
  1691  
  1692  	c.Check(calledFlags, check.DeepEquals, flags)
  1693  	c.Check(calledUserID, check.Equals, 17)
  1694  	c.Check(err, check.IsNil)
  1695  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1696  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1697  }
  1698  
  1699  func (s *snapsSuite) TestRefreshIgnoreRunning(c *check.C) {
  1700  	var calledFlags snapstate.Flags
  1701  	installQueue := []string{}
  1702  
  1703  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1704  		calledFlags = flags
  1705  		installQueue = append(installQueue, name)
  1706  
  1707  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  1708  		return state.NewTaskSet(t), nil
  1709  	})()
  1710  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1711  		return nil
  1712  	})()
  1713  
  1714  	d := s.daemon(c)
  1715  	inst := &daemon.SnapInstruction{
  1716  		Action:        "refresh",
  1717  		IgnoreRunning: true,
  1718  		Snaps:         []string{"some-snap"},
  1719  	}
  1720  
  1721  	st := d.Overlord().State()
  1722  	st.Lock()
  1723  	defer st.Unlock()
  1724  	summary, _, err := inst.Dispatch()(inst, st)
  1725  	c.Check(err, check.IsNil)
  1726  
  1727  	flags := snapstate.Flags{}
  1728  	flags.IgnoreRunning = true
  1729  
  1730  	c.Check(calledFlags, check.DeepEquals, flags)
  1731  	c.Check(err, check.IsNil)
  1732  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1733  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1734  }
  1735  
  1736  func (s *snapsSuite) TestRefreshCohort(c *check.C) {
  1737  	cohort := ""
  1738  
  1739  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1740  		cohort = opts.CohortKey
  1741  
  1742  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  1743  		return state.NewTaskSet(t), nil
  1744  	})()
  1745  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1746  		return nil
  1747  	})()
  1748  
  1749  	d := s.daemon(c)
  1750  	inst := &daemon.SnapInstruction{
  1751  		Action: "refresh",
  1752  		Snaps:  []string{"some-snap"},
  1753  	}
  1754  	inst.CohortKey = "xyzzy"
  1755  
  1756  	st := d.Overlord().State()
  1757  	st.Lock()
  1758  	defer st.Unlock()
  1759  	summary, _, err := inst.Dispatch()(inst, st)
  1760  	c.Check(err, check.IsNil)
  1761  
  1762  	c.Check(cohort, check.Equals, "xyzzy")
  1763  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1764  }
  1765  
  1766  func (s *snapsSuite) TestRefreshLeaveCohort(c *check.C) {
  1767  	var leave *bool
  1768  
  1769  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1770  		leave = &opts.LeaveCohort
  1771  
  1772  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  1773  		return state.NewTaskSet(t), nil
  1774  	})()
  1775  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1776  		return nil
  1777  	})()
  1778  
  1779  	d := s.daemon(c)
  1780  	inst := &daemon.SnapInstruction{
  1781  		Action: "refresh",
  1782  		Snaps:  []string{"some-snap"},
  1783  	}
  1784  	inst.LeaveCohort = true
  1785  
  1786  	st := d.Overlord().State()
  1787  	st.Lock()
  1788  	defer st.Unlock()
  1789  	summary, _, err := inst.Dispatch()(inst, st)
  1790  	c.Check(err, check.IsNil)
  1791  
  1792  	c.Check(*leave, check.Equals, true)
  1793  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1794  }
  1795  
  1796  func (s *snapsSuite) TestSwitchInstruction(c *check.C) {
  1797  	var cohort, channel string
  1798  	var leave *bool
  1799  
  1800  	defer daemon.MockSnapstateSwitch(func(s *state.State, name string, opts *snapstate.RevisionOptions) (*state.TaskSet, error) {
  1801  		cohort = opts.CohortKey
  1802  		leave = &opts.LeaveCohort
  1803  		channel = opts.Channel
  1804  
  1805  		t := s.NewTask("fake-switch", "Doing a fake switch")
  1806  		return state.NewTaskSet(t), nil
  1807  	})()
  1808  
  1809  	d := s.daemon(c)
  1810  	st := d.Overlord().State()
  1811  
  1812  	type T struct {
  1813  		channel string
  1814  		cohort  string
  1815  		leave   bool
  1816  		summary string
  1817  	}
  1818  	table := []T{
  1819  		{"", "some-cohort", false, `Switch "some-snap" snap to cohort "…me-cohort"`},
  1820  		{"some-channel", "", false, `Switch "some-snap" snap to channel "some-channel"`},
  1821  		{"some-channel", "some-cohort", false, `Switch "some-snap" snap to channel "some-channel" and cohort "…me-cohort"`},
  1822  		{"", "", true, `Switch "some-snap" snap away from cohort`},
  1823  		{"some-channel", "", true, `Switch "some-snap" snap to channel "some-channel" and away from cohort`},
  1824  	}
  1825  
  1826  	for _, t := range table {
  1827  		cohort, channel = "", ""
  1828  		leave = nil
  1829  		inst := &daemon.SnapInstruction{
  1830  			Action: "switch",
  1831  			Snaps:  []string{"some-snap"},
  1832  		}
  1833  		inst.CohortKey = t.cohort
  1834  		inst.LeaveCohort = t.leave
  1835  		inst.Channel = t.channel
  1836  
  1837  		st.Lock()
  1838  		summary, _, err := inst.Dispatch()(inst, st)
  1839  		st.Unlock()
  1840  		c.Check(err, check.IsNil)
  1841  
  1842  		c.Check(cohort, check.Equals, t.cohort)
  1843  		c.Check(channel, check.Equals, t.channel)
  1844  		c.Check(summary, check.Equals, t.summary)
  1845  		c.Check(*leave, check.Equals, t.leave)
  1846  	}
  1847  }
  1848  
  1849  func (s *snapsSuite) testRevertSnap(inst *daemon.SnapInstruction, c *check.C) {
  1850  	queue := []string{}
  1851  
  1852  	instFlags, err := inst.ModeFlags()
  1853  	c.Assert(err, check.IsNil)
  1854  
  1855  	defer daemon.MockSnapstateRevert(func(s *state.State, name string, flags snapstate.Flags) (*state.TaskSet, error) {
  1856  		c.Check(flags, check.Equals, instFlags)
  1857  		queue = append(queue, name)
  1858  		return nil, nil
  1859  	})()
  1860  	defer daemon.MockSnapstateRevertToRevision(func(s *state.State, name string, rev snap.Revision, flags snapstate.Flags) (*state.TaskSet, error) {
  1861  		c.Check(flags, check.Equals, instFlags)
  1862  		queue = append(queue, fmt.Sprintf("%s (%s)", name, rev))
  1863  		return nil, nil
  1864  	})()
  1865  
  1866  	d := s.daemon(c)
  1867  	inst.Action = "revert"
  1868  	inst.Snaps = []string{"some-snap"}
  1869  
  1870  	st := d.Overlord().State()
  1871  	st.Lock()
  1872  	defer st.Unlock()
  1873  	summary, _, err := inst.Dispatch()(inst, st)
  1874  	c.Check(err, check.IsNil)
  1875  	if inst.Revision.Unset() {
  1876  		c.Check(queue, check.DeepEquals, []string{inst.Snaps[0]})
  1877  	} else {
  1878  		c.Check(queue, check.DeepEquals, []string{fmt.Sprintf("%s (%s)", inst.Snaps[0], inst.Revision)})
  1879  	}
  1880  	c.Check(summary, check.Equals, `Revert "some-snap" snap`)
  1881  }
  1882  
  1883  func (s *snapsSuite) TestRevertSnap(c *check.C) {
  1884  	s.testRevertSnap(&daemon.SnapInstruction{}, c)
  1885  }
  1886  
  1887  func (s *snapsSuite) TestRevertSnapDevMode(c *check.C) {
  1888  	s.testRevertSnap(&daemon.SnapInstruction{DevMode: true}, c)
  1889  }
  1890  
  1891  func (s *snapsSuite) TestRevertSnapJailMode(c *check.C) {
  1892  	s.testRevertSnap(&daemon.SnapInstruction{JailMode: true}, c)
  1893  }
  1894  
  1895  func (s *snapsSuite) TestRevertSnapClassic(c *check.C) {
  1896  	s.testRevertSnap(&daemon.SnapInstruction{Classic: true}, c)
  1897  }
  1898  
  1899  func (s *snapsSuite) TestRevertSnapToRevision(c *check.C) {
  1900  	inst := &daemon.SnapInstruction{}
  1901  	inst.Revision = snap.R(1)
  1902  	s.testRevertSnap(inst, c)
  1903  }
  1904  
  1905  func (s *snapsSuite) TestRevertSnapToRevisionDevMode(c *check.C) {
  1906  	inst := &daemon.SnapInstruction{}
  1907  	inst.Revision = snap.R(1)
  1908  	inst.DevMode = true
  1909  	s.testRevertSnap(inst, c)
  1910  }
  1911  
  1912  func (s *snapsSuite) TestRevertSnapToRevisionJailMode(c *check.C) {
  1913  	inst := &daemon.SnapInstruction{}
  1914  	inst.Revision = snap.R(1)
  1915  	inst.JailMode = true
  1916  	s.testRevertSnap(inst, c)
  1917  }
  1918  
  1919  func (s *snapsSuite) TestRevertSnapToRevisionClassic(c *check.C) {
  1920  	inst := &daemon.SnapInstruction{}
  1921  	inst.Revision = snap.R(1)
  1922  	inst.Classic = true
  1923  	s.testRevertSnap(inst, c)
  1924  }
  1925  
  1926  func (s *snapsSuite) TestErrToResponseNoSnapsDoesNotPanic(c *check.C) {
  1927  	si := &daemon.SnapInstruction{Action: "frobble"}
  1928  	errors := []error{
  1929  		store.ErrSnapNotFound,
  1930  		&store.RevisionNotAvailableError{},
  1931  		store.ErrNoUpdateAvailable,
  1932  		store.ErrLocalSnap,
  1933  		&snap.AlreadyInstalledError{Snap: "foo"},
  1934  		&snap.NotInstalledError{Snap: "foo"},
  1935  		&snapstate.SnapNeedsDevModeError{Snap: "foo"},
  1936  		&snapstate.SnapNeedsClassicError{Snap: "foo"},
  1937  		&snapstate.SnapNeedsClassicSystemError{Snap: "foo"},
  1938  		fakeNetError{message: "other"},
  1939  		fakeNetError{message: "timeout", timeout: true},
  1940  		fakeNetError{message: "temp", temporary: true},
  1941  		errors.New("some other error"),
  1942  	}
  1943  
  1944  	for _, err := range errors {
  1945  		rsp := si.ErrToResponse(err)
  1946  		com := check.Commentf("%v", err)
  1947  		c.Check(rsp, check.NotNil, com)
  1948  		status := rsp.(*daemon.Resp).Status
  1949  		c.Check(status/100 == 4 || status/100 == 5, check.Equals, true, com)
  1950  	}
  1951  }
  1952  
  1953  func (s *snapsSuite) TestErrToResponseForRevisionNotAvailable(c *check.C) {
  1954  	si := &daemon.SnapInstruction{Action: "frobble", Snaps: []string{"foo"}}
  1955  
  1956  	thisArch := arch.DpkgArchitecture()
  1957  
  1958  	err := &store.RevisionNotAvailableError{
  1959  		Action:  "install",
  1960  		Channel: "stable",
  1961  		Releases: []channel.Channel{
  1962  			snaptest.MustParseChannel("beta", thisArch),
  1963  		},
  1964  	}
  1965  	rsp := si.ErrToResponse(err).(*daemon.Resp)
  1966  	c.Check(rsp, check.DeepEquals, &daemon.Resp{
  1967  		Status: 404,
  1968  		Type:   daemon.ResponseTypeError,
  1969  		Result: &daemon.ErrorResult{
  1970  			Message: "no snap revision on specified channel",
  1971  			Kind:    client.ErrorKindSnapChannelNotAvailable,
  1972  			Value: map[string]interface{}{
  1973  				"snap-name":    "foo",
  1974  				"action":       "install",
  1975  				"channel":      "stable",
  1976  				"architecture": thisArch,
  1977  				"releases": []map[string]interface{}{
  1978  					{"architecture": thisArch, "channel": "beta"},
  1979  				},
  1980  			},
  1981  		},
  1982  	})
  1983  
  1984  	err = &store.RevisionNotAvailableError{
  1985  		Action:  "install",
  1986  		Channel: "stable",
  1987  		Releases: []channel.Channel{
  1988  			snaptest.MustParseChannel("beta", "other-arch"),
  1989  		},
  1990  	}
  1991  	rsp = si.ErrToResponse(err).(*daemon.Resp)
  1992  	c.Check(rsp, check.DeepEquals, &daemon.Resp{
  1993  		Status: 404,
  1994  		Type:   daemon.ResponseTypeError,
  1995  		Result: &daemon.ErrorResult{
  1996  			Message: "no snap revision on specified architecture",
  1997  			Kind:    client.ErrorKindSnapArchitectureNotAvailable,
  1998  			Value: map[string]interface{}{
  1999  				"snap-name":    "foo",
  2000  				"action":       "install",
  2001  				"channel":      "stable",
  2002  				"architecture": thisArch,
  2003  				"releases": []map[string]interface{}{
  2004  					{"architecture": "other-arch", "channel": "beta"},
  2005  				},
  2006  			},
  2007  		},
  2008  	})
  2009  
  2010  	err = &store.RevisionNotAvailableError{}
  2011  	rsp = si.ErrToResponse(err).(*daemon.Resp)
  2012  	c.Check(rsp, check.DeepEquals, &daemon.Resp{
  2013  		Status: 404,
  2014  		Type:   daemon.ResponseTypeError,
  2015  		Result: &daemon.ErrorResult{
  2016  			Message: "no snap revision available as specified",
  2017  			Kind:    client.ErrorKindSnapRevisionNotAvailable,
  2018  			Value:   "foo",
  2019  		},
  2020  	})
  2021  }
  2022  
  2023  func (s *snapsSuite) TestErrToResponseForChangeConflict(c *check.C) {
  2024  	si := &daemon.SnapInstruction{Action: "frobble", Snaps: []string{"foo"}}
  2025  
  2026  	err := &snapstate.ChangeConflictError{Snap: "foo", ChangeKind: "install"}
  2027  	rsp := si.ErrToResponse(err).(*daemon.Resp)
  2028  	c.Check(rsp, check.DeepEquals, &daemon.Resp{
  2029  		Status: 409,
  2030  		Type:   daemon.ResponseTypeError,
  2031  		Result: &daemon.ErrorResult{
  2032  			Message: `snap "foo" has "install" change in progress`,
  2033  			Kind:    client.ErrorKindSnapChangeConflict,
  2034  			Value: map[string]interface{}{
  2035  				"snap-name":   "foo",
  2036  				"change-kind": "install",
  2037  			},
  2038  		},
  2039  	})
  2040  
  2041  	// only snap
  2042  	err = &snapstate.ChangeConflictError{Snap: "foo"}
  2043  	rsp = si.ErrToResponse(err).(*daemon.Resp)
  2044  	c.Check(rsp, check.DeepEquals, &daemon.Resp{
  2045  		Status: 409,
  2046  		Type:   daemon.ResponseTypeError,
  2047  		Result: &daemon.ErrorResult{
  2048  			Message: `snap "foo" has changes in progress`,
  2049  			Kind:    client.ErrorKindSnapChangeConflict,
  2050  			Value: map[string]interface{}{
  2051  				"snap-name": "foo",
  2052  			},
  2053  		},
  2054  	})
  2055  
  2056  	// only kind
  2057  	err = &snapstate.ChangeConflictError{Message: "specific error msg", ChangeKind: "some-global-op"}
  2058  	rsp = si.ErrToResponse(err).(*daemon.Resp)
  2059  	c.Check(rsp, check.DeepEquals, &daemon.Resp{
  2060  		Status: 409,
  2061  		Type:   daemon.ResponseTypeError,
  2062  		Result: &daemon.ErrorResult{
  2063  			Message: "specific error msg",
  2064  			Kind:    client.ErrorKindSnapChangeConflict,
  2065  			Value: map[string]interface{}{
  2066  				"change-kind": "some-global-op",
  2067  			},
  2068  		},
  2069  	})
  2070  }