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