github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/daemon/api_snaps_test.go (about)

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