github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/api_snaps_test.go (about)

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