github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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  			EditedContact:     "mailto: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:          "mailto: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) TestInstallIgnoreValidation(c *check.C) {
  1428  	var calledFlags snapstate.Flags
  1429  	installQueue := []string{}
  1430  
  1431  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1432  		installQueue = append(installQueue, name)
  1433  		calledFlags = flags
  1434  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  1435  		return state.NewTaskSet(t), nil
  1436  	})()
  1437  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1438  		return nil
  1439  	})()
  1440  
  1441  	d := s.daemon(c)
  1442  	inst := &daemon.SnapInstruction{
  1443  		Action:           "install",
  1444  		IgnoreValidation: true,
  1445  		Snaps:            []string{"some-snap"},
  1446  	}
  1447  
  1448  	st := d.Overlord().State()
  1449  	st.Lock()
  1450  	defer st.Unlock()
  1451  	summary, _, err := inst.Dispatch()(inst, st)
  1452  	c.Check(err, check.IsNil)
  1453  
  1454  	flags := snapstate.Flags{}
  1455  	flags.IgnoreValidation = true
  1456  
  1457  	c.Check(calledFlags, check.DeepEquals, flags)
  1458  	c.Check(err, check.IsNil)
  1459  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1460  	c.Check(summary, check.Equals, `Install "some-snap" snap`)
  1461  }
  1462  
  1463  func (s *snapsSuite) TestInstallEmptyName(c *check.C) {
  1464  	defer daemon.MockSnapstateInstall(func(ctx context.Context, _ *state.State, _ string, _ *snapstate.RevisionOptions, _ int, _ snapstate.Flags) (*state.TaskSet, error) {
  1465  		return nil, errors.New("should not be called")
  1466  	})()
  1467  
  1468  	d := s.daemon(c)
  1469  	inst := &daemon.SnapInstruction{
  1470  		Action: "install",
  1471  		Snaps:  []string{""},
  1472  	}
  1473  
  1474  	st := d.Overlord().State()
  1475  	st.Lock()
  1476  	defer st.Unlock()
  1477  	_, _, err := inst.Dispatch()(inst, st)
  1478  	c.Check(err, check.ErrorMatches, "cannot install snap with empty name")
  1479  }
  1480  
  1481  func (s *snapsSuite) TestInstallOnNonDevModeDistro(c *check.C) {
  1482  	s.testInstall(c, false, snapstate.Flags{}, snap.R(0))
  1483  }
  1484  func (s *snapsSuite) TestInstallOnDevModeDistro(c *check.C) {
  1485  	s.testInstall(c, true, snapstate.Flags{}, snap.R(0))
  1486  }
  1487  func (s *snapsSuite) TestInstallRevision(c *check.C) {
  1488  	s.testInstall(c, false, snapstate.Flags{}, snap.R(42))
  1489  }
  1490  
  1491  func (s *snapsSuite) testInstall(c *check.C, forcedDevmode bool, flags snapstate.Flags, revision snap.Revision) {
  1492  	calledFlags := snapstate.Flags{}
  1493  	installQueue := []string{}
  1494  	restore := sandbox.MockForceDevMode(forcedDevmode)
  1495  	defer restore()
  1496  
  1497  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1498  		calledFlags = flags
  1499  		installQueue = append(installQueue, name)
  1500  		c.Check(revision, check.Equals, opts.Revision)
  1501  
  1502  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  1503  		return state.NewTaskSet(t), nil
  1504  	})()
  1505  
  1506  	d := s.daemonWithFakeSnapManager(c)
  1507  
  1508  	var buf bytes.Buffer
  1509  	if revision.Unset() {
  1510  		buf.WriteString(`{"action": "install"}`)
  1511  	} else {
  1512  		fmt.Fprintf(&buf, `{"action": "install", "revision": %s}`, revision.String())
  1513  	}
  1514  	req, err := http.NewRequest("POST", "/v2/snaps/some-snap", &buf)
  1515  	c.Assert(err, check.IsNil)
  1516  
  1517  	rsp := s.asyncReq(c, req, nil)
  1518  
  1519  	st := d.Overlord().State()
  1520  	st.Lock()
  1521  	defer st.Unlock()
  1522  	chg := st.Change(rsp.Change)
  1523  	c.Assert(chg, check.NotNil)
  1524  
  1525  	c.Check(chg.Tasks(), check.HasLen, 1)
  1526  
  1527  	st.Unlock()
  1528  	s.waitTrivialChange(c, chg)
  1529  	st.Lock()
  1530  
  1531  	c.Check(chg.Status(), check.Equals, state.DoneStatus)
  1532  	c.Check(calledFlags, check.Equals, flags)
  1533  	c.Check(err, check.IsNil)
  1534  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1535  	c.Check(chg.Kind(), check.Equals, "install-snap")
  1536  	c.Check(chg.Summary(), check.Equals, `Install "some-snap" snap`)
  1537  }
  1538  
  1539  func (s *snapsSuite) TestInstallUserAgentContextCreated(c *check.C) {
  1540  	defer daemon.MockSnapstateInstall(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1541  		s.ctx = ctx
  1542  		t := st.NewTask("fake-install-snap", "Doing a fake install")
  1543  		return state.NewTaskSet(t), nil
  1544  	})()
  1545  
  1546  	s.daemonWithFakeSnapManager(c)
  1547  
  1548  	var buf bytes.Buffer
  1549  	buf.WriteString(`{"action": "install"}`)
  1550  	req, err := http.NewRequest("POST", "/v2/snaps/some-snap", &buf)
  1551  	s.asRootAuth(req)
  1552  	c.Assert(err, check.IsNil)
  1553  	req.Header.Add("User-Agent", "some-agent/1.0")
  1554  
  1555  	rec := httptest.NewRecorder()
  1556  	s.serveHTTP(c, rec, req)
  1557  	c.Assert(rec.Code, check.Equals, 202)
  1558  	c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0")
  1559  }
  1560  
  1561  func (s *snapsSuite) TestInstallFails(c *check.C) {
  1562  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1563  		t := s.NewTask("fake-install-snap-error", "Install task")
  1564  		return state.NewTaskSet(t), nil
  1565  	})()
  1566  
  1567  	d := s.daemonWithFakeSnapManager(c)
  1568  	buf := bytes.NewBufferString(`{"action": "install"}`)
  1569  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  1570  	c.Assert(err, check.IsNil)
  1571  
  1572  	rsp := s.asyncReq(c, req, nil)
  1573  
  1574  	st := d.Overlord().State()
  1575  	st.Lock()
  1576  	defer st.Unlock()
  1577  	chg := st.Change(rsp.Change)
  1578  	c.Assert(chg, check.NotNil)
  1579  
  1580  	c.Check(chg.Tasks(), check.HasLen, 1)
  1581  
  1582  	st.Unlock()
  1583  	s.waitTrivialChange(c, chg)
  1584  	st.Lock()
  1585  
  1586  	c.Check(chg.Err(), check.ErrorMatches, `(?sm).*Install task \(fake-install-snap-error errored\)`)
  1587  }
  1588  
  1589  func (s *snapsSuite) TestRefresh(c *check.C) {
  1590  	var calledFlags snapstate.Flags
  1591  	calledUserID := 0
  1592  	installQueue := []string{}
  1593  	assertstateCalledUserID := 0
  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  		assertstateCalledUserID = userID
  1605  		return nil
  1606  	})()
  1607  
  1608  	d := s.daemon(c)
  1609  	inst := &daemon.SnapInstruction{
  1610  		Action: "refresh",
  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  	c.Check(assertstateCalledUserID, check.Equals, 17)
  1622  	c.Check(calledFlags, check.DeepEquals, snapstate.Flags{})
  1623  	c.Check(calledUserID, check.Equals, 17)
  1624  	c.Check(err, check.IsNil)
  1625  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1626  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1627  }
  1628  
  1629  func (s *snapsSuite) TestRefreshDevMode(c *check.C) {
  1630  	var calledFlags snapstate.Flags
  1631  	calledUserID := 0
  1632  	installQueue := []string{}
  1633  
  1634  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1635  		calledFlags = flags
  1636  		calledUserID = userID
  1637  		installQueue = append(installQueue, name)
  1638  
  1639  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  1640  		return state.NewTaskSet(t), nil
  1641  	})()
  1642  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1643  		return nil
  1644  	})()
  1645  
  1646  	d := s.daemon(c)
  1647  	inst := &daemon.SnapInstruction{
  1648  		Action:  "refresh",
  1649  		DevMode: true,
  1650  		Snaps:   []string{"some-snap"},
  1651  	}
  1652  	inst.SetUserID(17)
  1653  
  1654  	st := d.Overlord().State()
  1655  	st.Lock()
  1656  	defer st.Unlock()
  1657  	summary, _, err := inst.Dispatch()(inst, st)
  1658  	c.Check(err, check.IsNil)
  1659  
  1660  	flags := snapstate.Flags{}
  1661  	flags.DevMode = true
  1662  	c.Check(calledFlags, check.DeepEquals, flags)
  1663  	c.Check(calledUserID, check.Equals, 17)
  1664  	c.Check(err, check.IsNil)
  1665  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1666  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1667  }
  1668  
  1669  func (s *snapsSuite) TestRefreshClassic(c *check.C) {
  1670  	var calledFlags snapstate.Flags
  1671  
  1672  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1673  		calledFlags = flags
  1674  		return nil, nil
  1675  	})()
  1676  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1677  		return nil
  1678  	})()
  1679  
  1680  	d := s.daemon(c)
  1681  	inst := &daemon.SnapInstruction{
  1682  		Action:  "refresh",
  1683  		Classic: true,
  1684  		Snaps:   []string{"some-snap"},
  1685  	}
  1686  	inst.SetUserID(17)
  1687  
  1688  	st := d.Overlord().State()
  1689  	st.Lock()
  1690  	defer st.Unlock()
  1691  	_, _, err := inst.Dispatch()(inst, st)
  1692  	c.Check(err, check.IsNil)
  1693  
  1694  	c.Check(calledFlags, check.DeepEquals, snapstate.Flags{Classic: true})
  1695  }
  1696  
  1697  func (s *snapsSuite) TestRefreshIgnoreValidation(c *check.C) {
  1698  	var calledFlags snapstate.Flags
  1699  	calledUserID := 0
  1700  	installQueue := []string{}
  1701  
  1702  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1703  		calledFlags = flags
  1704  		calledUserID = userID
  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  		IgnoreValidation: true,
  1718  		Snaps:            []string{"some-snap"},
  1719  	}
  1720  	inst.SetUserID(17)
  1721  
  1722  	st := d.Overlord().State()
  1723  	st.Lock()
  1724  	defer st.Unlock()
  1725  	summary, _, err := inst.Dispatch()(inst, st)
  1726  	c.Check(err, check.IsNil)
  1727  
  1728  	flags := snapstate.Flags{}
  1729  	flags.IgnoreValidation = true
  1730  
  1731  	c.Check(calledFlags, check.DeepEquals, flags)
  1732  	c.Check(calledUserID, check.Equals, 17)
  1733  	c.Check(err, check.IsNil)
  1734  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1735  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1736  }
  1737  
  1738  func (s *snapsSuite) TestRefreshIgnoreRunning(c *check.C) {
  1739  	var calledFlags snapstate.Flags
  1740  	installQueue := []string{}
  1741  
  1742  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1743  		calledFlags = flags
  1744  		installQueue = append(installQueue, name)
  1745  
  1746  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  1747  		return state.NewTaskSet(t), nil
  1748  	})()
  1749  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1750  		return nil
  1751  	})()
  1752  
  1753  	d := s.daemon(c)
  1754  	inst := &daemon.SnapInstruction{
  1755  		Action:        "refresh",
  1756  		IgnoreRunning: true,
  1757  		Snaps:         []string{"some-snap"},
  1758  	}
  1759  
  1760  	st := d.Overlord().State()
  1761  	st.Lock()
  1762  	defer st.Unlock()
  1763  	summary, _, err := inst.Dispatch()(inst, st)
  1764  	c.Check(err, check.IsNil)
  1765  
  1766  	flags := snapstate.Flags{}
  1767  	flags.IgnoreRunning = true
  1768  
  1769  	c.Check(calledFlags, check.DeepEquals, flags)
  1770  	c.Check(err, check.IsNil)
  1771  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  1772  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1773  }
  1774  
  1775  func (s *snapsSuite) TestRefreshCohort(c *check.C) {
  1776  	cohort := ""
  1777  
  1778  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1779  		cohort = opts.CohortKey
  1780  
  1781  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  1782  		return state.NewTaskSet(t), nil
  1783  	})()
  1784  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1785  		return nil
  1786  	})()
  1787  
  1788  	d := s.daemon(c)
  1789  	inst := &daemon.SnapInstruction{
  1790  		Action: "refresh",
  1791  		Snaps:  []string{"some-snap"},
  1792  	}
  1793  	inst.CohortKey = "xyzzy"
  1794  
  1795  	st := d.Overlord().State()
  1796  	st.Lock()
  1797  	defer st.Unlock()
  1798  	summary, _, err := inst.Dispatch()(inst, st)
  1799  	c.Check(err, check.IsNil)
  1800  
  1801  	c.Check(cohort, check.Equals, "xyzzy")
  1802  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1803  }
  1804  
  1805  func (s *snapsSuite) TestRefreshLeaveCohort(c *check.C) {
  1806  	var leave *bool
  1807  
  1808  	defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  1809  		leave = &opts.LeaveCohort
  1810  
  1811  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  1812  		return state.NewTaskSet(t), nil
  1813  	})()
  1814  	defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error {
  1815  		return nil
  1816  	})()
  1817  
  1818  	d := s.daemon(c)
  1819  	inst := &daemon.SnapInstruction{
  1820  		Action: "refresh",
  1821  		Snaps:  []string{"some-snap"},
  1822  	}
  1823  	inst.LeaveCohort = true
  1824  
  1825  	st := d.Overlord().State()
  1826  	st.Lock()
  1827  	defer st.Unlock()
  1828  	summary, _, err := inst.Dispatch()(inst, st)
  1829  	c.Check(err, check.IsNil)
  1830  
  1831  	c.Check(*leave, check.Equals, true)
  1832  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  1833  }
  1834  
  1835  func (s *snapsSuite) TestSwitchInstruction(c *check.C) {
  1836  	var cohort, channel string
  1837  	var leave *bool
  1838  
  1839  	defer daemon.MockSnapstateSwitch(func(s *state.State, name string, opts *snapstate.RevisionOptions) (*state.TaskSet, error) {
  1840  		cohort = opts.CohortKey
  1841  		leave = &opts.LeaveCohort
  1842  		channel = opts.Channel
  1843  
  1844  		t := s.NewTask("fake-switch", "Doing a fake switch")
  1845  		return state.NewTaskSet(t), nil
  1846  	})()
  1847  
  1848  	d := s.daemon(c)
  1849  	st := d.Overlord().State()
  1850  
  1851  	type T struct {
  1852  		channel string
  1853  		cohort  string
  1854  		leave   bool
  1855  		summary string
  1856  	}
  1857  	table := []T{
  1858  		{"", "some-cohort", false, `Switch "some-snap" snap to cohort "…me-cohort"`},
  1859  		{"some-channel", "", false, `Switch "some-snap" snap to channel "some-channel"`},
  1860  		{"some-channel", "some-cohort", false, `Switch "some-snap" snap to channel "some-channel" and cohort "…me-cohort"`},
  1861  		{"", "", true, `Switch "some-snap" snap away from cohort`},
  1862  		{"some-channel", "", true, `Switch "some-snap" snap to channel "some-channel" and away from cohort`},
  1863  	}
  1864  
  1865  	for _, t := range table {
  1866  		cohort, channel = "", ""
  1867  		leave = nil
  1868  		inst := &daemon.SnapInstruction{
  1869  			Action: "switch",
  1870  			Snaps:  []string{"some-snap"},
  1871  		}
  1872  		inst.CohortKey = t.cohort
  1873  		inst.LeaveCohort = t.leave
  1874  		inst.Channel = t.channel
  1875  
  1876  		st.Lock()
  1877  		summary, _, err := inst.Dispatch()(inst, st)
  1878  		st.Unlock()
  1879  		c.Check(err, check.IsNil)
  1880  
  1881  		c.Check(cohort, check.Equals, t.cohort)
  1882  		c.Check(channel, check.Equals, t.channel)
  1883  		c.Check(summary, check.Equals, t.summary)
  1884  		c.Check(*leave, check.Equals, t.leave)
  1885  	}
  1886  }
  1887  
  1888  func (s *snapsSuite) testRevertSnap(inst *daemon.SnapInstruction, c *check.C) {
  1889  	queue := []string{}
  1890  
  1891  	instFlags, err := inst.ModeFlags()
  1892  	c.Assert(err, check.IsNil)
  1893  
  1894  	defer daemon.MockSnapstateRevert(func(s *state.State, name string, flags snapstate.Flags) (*state.TaskSet, error) {
  1895  		c.Check(flags, check.Equals, instFlags)
  1896  		queue = append(queue, name)
  1897  		return nil, nil
  1898  	})()
  1899  	defer daemon.MockSnapstateRevertToRevision(func(s *state.State, name string, rev snap.Revision, flags snapstate.Flags) (*state.TaskSet, error) {
  1900  		c.Check(flags, check.Equals, instFlags)
  1901  		queue = append(queue, fmt.Sprintf("%s (%s)", name, rev))
  1902  		return nil, nil
  1903  	})()
  1904  
  1905  	d := s.daemon(c)
  1906  	inst.Action = "revert"
  1907  	inst.Snaps = []string{"some-snap"}
  1908  
  1909  	st := d.Overlord().State()
  1910  	st.Lock()
  1911  	defer st.Unlock()
  1912  	summary, _, err := inst.Dispatch()(inst, st)
  1913  	c.Check(err, check.IsNil)
  1914  	if inst.Revision.Unset() {
  1915  		c.Check(queue, check.DeepEquals, []string{inst.Snaps[0]})
  1916  	} else {
  1917  		c.Check(queue, check.DeepEquals, []string{fmt.Sprintf("%s (%s)", inst.Snaps[0], inst.Revision)})
  1918  	}
  1919  	c.Check(summary, check.Equals, `Revert "some-snap" snap`)
  1920  }
  1921  
  1922  func (s *snapsSuite) TestRevertSnap(c *check.C) {
  1923  	s.testRevertSnap(&daemon.SnapInstruction{}, c)
  1924  }
  1925  
  1926  func (s *snapsSuite) TestRevertSnapDevMode(c *check.C) {
  1927  	s.testRevertSnap(&daemon.SnapInstruction{DevMode: true}, c)
  1928  }
  1929  
  1930  func (s *snapsSuite) TestRevertSnapJailMode(c *check.C) {
  1931  	s.testRevertSnap(&daemon.SnapInstruction{JailMode: true}, c)
  1932  }
  1933  
  1934  func (s *snapsSuite) TestRevertSnapClassic(c *check.C) {
  1935  	s.testRevertSnap(&daemon.SnapInstruction{Classic: true}, c)
  1936  }
  1937  
  1938  func (s *snapsSuite) TestRevertSnapToRevision(c *check.C) {
  1939  	inst := &daemon.SnapInstruction{}
  1940  	inst.Revision = snap.R(1)
  1941  	s.testRevertSnap(inst, c)
  1942  }
  1943  
  1944  func (s *snapsSuite) TestRevertSnapToRevisionDevMode(c *check.C) {
  1945  	inst := &daemon.SnapInstruction{}
  1946  	inst.Revision = snap.R(1)
  1947  	inst.DevMode = true
  1948  	s.testRevertSnap(inst, c)
  1949  }
  1950  
  1951  func (s *snapsSuite) TestRevertSnapToRevisionJailMode(c *check.C) {
  1952  	inst := &daemon.SnapInstruction{}
  1953  	inst.Revision = snap.R(1)
  1954  	inst.JailMode = true
  1955  	s.testRevertSnap(inst, c)
  1956  }
  1957  
  1958  func (s *snapsSuite) TestRevertSnapToRevisionClassic(c *check.C) {
  1959  	inst := &daemon.SnapInstruction{}
  1960  	inst.Revision = snap.R(1)
  1961  	inst.Classic = true
  1962  	s.testRevertSnap(inst, c)
  1963  }
  1964  
  1965  func (s *snapsSuite) TestErrToResponseNoSnapsDoesNotPanic(c *check.C) {
  1966  	si := &daemon.SnapInstruction{Action: "frobble"}
  1967  	errors := []error{
  1968  		store.ErrSnapNotFound,
  1969  		&store.RevisionNotAvailableError{},
  1970  		store.ErrNoUpdateAvailable,
  1971  		store.ErrLocalSnap,
  1972  		&snap.AlreadyInstalledError{Snap: "foo"},
  1973  		&snap.NotInstalledError{Snap: "foo"},
  1974  		&snapstate.SnapNeedsDevModeError{Snap: "foo"},
  1975  		&snapstate.SnapNeedsClassicError{Snap: "foo"},
  1976  		&snapstate.SnapNeedsClassicSystemError{Snap: "foo"},
  1977  		fakeNetError{message: "other"},
  1978  		fakeNetError{message: "timeout", timeout: true},
  1979  		fakeNetError{message: "temp", temporary: true},
  1980  		errors.New("some other error"),
  1981  	}
  1982  
  1983  	for _, err := range errors {
  1984  		rspe := si.ErrToResponse(err)
  1985  		com := check.Commentf("%v", err)
  1986  		c.Assert(rspe, check.NotNil, com)
  1987  		status := rspe.Status
  1988  		c.Check(status/100 == 4 || status/100 == 5, check.Equals, true, com)
  1989  	}
  1990  }
  1991  
  1992  func (s *snapsSuite) TestErrToResponseForRevisionNotAvailable(c *check.C) {
  1993  	si := &daemon.SnapInstruction{Action: "frobble", Snaps: []string{"foo"}}
  1994  
  1995  	thisArch := arch.DpkgArchitecture()
  1996  
  1997  	err := &store.RevisionNotAvailableError{
  1998  		Action:  "install",
  1999  		Channel: "stable",
  2000  		Releases: []channel.Channel{
  2001  			snaptest.MustParseChannel("beta", thisArch),
  2002  		},
  2003  	}
  2004  	rspe := si.ErrToResponse(err)
  2005  	c.Check(rspe, check.DeepEquals, &daemon.APIError{
  2006  		Status:  404,
  2007  		Message: "no snap revision on specified channel",
  2008  		Kind:    client.ErrorKindSnapChannelNotAvailable,
  2009  		Value: map[string]interface{}{
  2010  			"snap-name":    "foo",
  2011  			"action":       "install",
  2012  			"channel":      "stable",
  2013  			"architecture": thisArch,
  2014  			"releases": []map[string]interface{}{
  2015  				{"architecture": thisArch, "channel": "beta"},
  2016  			},
  2017  		},
  2018  	})
  2019  
  2020  	err = &store.RevisionNotAvailableError{
  2021  		Action:  "install",
  2022  		Channel: "stable",
  2023  		Releases: []channel.Channel{
  2024  			snaptest.MustParseChannel("beta", "other-arch"),
  2025  		},
  2026  	}
  2027  	rspe = si.ErrToResponse(err)
  2028  	c.Check(rspe, check.DeepEquals, &daemon.APIError{
  2029  		Status:  404,
  2030  		Message: "no snap revision on specified architecture",
  2031  		Kind:    client.ErrorKindSnapArchitectureNotAvailable,
  2032  		Value: map[string]interface{}{
  2033  			"snap-name":    "foo",
  2034  			"action":       "install",
  2035  			"channel":      "stable",
  2036  			"architecture": thisArch,
  2037  			"releases": []map[string]interface{}{
  2038  				{"architecture": "other-arch", "channel": "beta"},
  2039  			},
  2040  		},
  2041  	})
  2042  
  2043  	err = &store.RevisionNotAvailableError{}
  2044  	rspe = si.ErrToResponse(err)
  2045  	c.Check(rspe, check.DeepEquals, &daemon.APIError{
  2046  		Status:  404,
  2047  		Message: "no snap revision available as specified",
  2048  		Kind:    client.ErrorKindSnapRevisionNotAvailable,
  2049  		Value:   "foo",
  2050  	})
  2051  }
  2052  
  2053  func (s *snapsSuite) TestErrToResponseForChangeConflict(c *check.C) {
  2054  	si := &daemon.SnapInstruction{Action: "frobble", Snaps: []string{"foo"}}
  2055  
  2056  	err := &snapstate.ChangeConflictError{Snap: "foo", ChangeKind: "install"}
  2057  	rspe := si.ErrToResponse(err)
  2058  	c.Check(rspe, check.DeepEquals, &daemon.APIError{
  2059  		Status:  409,
  2060  		Message: `snap "foo" has "install" change in progress`,
  2061  		Kind:    client.ErrorKindSnapChangeConflict,
  2062  		Value: map[string]interface{}{
  2063  			"snap-name":   "foo",
  2064  			"change-kind": "install",
  2065  		},
  2066  	})
  2067  
  2068  	// only snap
  2069  	err = &snapstate.ChangeConflictError{Snap: "foo"}
  2070  	rspe = si.ErrToResponse(err)
  2071  	c.Check(rspe, check.DeepEquals, &daemon.APIError{
  2072  		Status:  409,
  2073  		Message: `snap "foo" has changes in progress`,
  2074  		Kind:    client.ErrorKindSnapChangeConflict,
  2075  		Value: map[string]interface{}{
  2076  			"snap-name": "foo",
  2077  		},
  2078  	})
  2079  
  2080  	// only kind
  2081  	err = &snapstate.ChangeConflictError{Message: "specific error msg", ChangeKind: "some-global-op"}
  2082  	rspe = si.ErrToResponse(err)
  2083  	c.Check(rspe, check.DeepEquals, &daemon.APIError{
  2084  		Status:  409,
  2085  		Message: "specific error msg",
  2086  		Kind:    client.ErrorKindSnapChangeConflict,
  2087  		Value: map[string]interface{}{
  2088  			"change-kind": "some-global-op",
  2089  		},
  2090  	})
  2091  }