github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/api_sideload_n_try_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  	"fmt"
    26  	"io/ioutil"
    27  	"net/http"
    28  	"os"
    29  	"path/filepath"
    30  	"regexp"
    31  	"time"
    32  
    33  	"gopkg.in/check.v1"
    34  
    35  	"github.com/snapcore/snapd/asserts"
    36  	"github.com/snapcore/snapd/asserts/assertstest"
    37  	"github.com/snapcore/snapd/client"
    38  	"github.com/snapcore/snapd/daemon"
    39  	"github.com/snapcore/snapd/dirs"
    40  	"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
    41  	"github.com/snapcore/snapd/overlord/snapstate"
    42  	"github.com/snapcore/snapd/overlord/state"
    43  	"github.com/snapcore/snapd/sandbox"
    44  	"github.com/snapcore/snapd/snap"
    45  	"github.com/snapcore/snapd/testutil"
    46  )
    47  
    48  var (
    49  	_ = check.Suite(&sideloadSuite{})
    50  	_ = check.Suite(&trySuite{})
    51  )
    52  
    53  type sideloadSuite struct {
    54  	apiBaseSuite
    55  }
    56  
    57  var sideLoadBodyWithoutDevMode = "" +
    58  	"----hello--\r\n" +
    59  	"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
    60  	"\r\n" +
    61  	"xyzzy\r\n" +
    62  	"----hello--\r\n" +
    63  	"Content-Disposition: form-data; name=\"dangerous\"\r\n" +
    64  	"\r\n" +
    65  	"true\r\n" +
    66  	"----hello--\r\n" +
    67  	"Content-Disposition: form-data; name=\"snap-path\"\r\n" +
    68  	"\r\n" +
    69  	"a/b/local.snap\r\n" +
    70  	"----hello--\r\n"
    71  
    72  func (s *sideloadSuite) TestSideloadSnapOnNonDevModeDistro(c *check.C) {
    73  	// try a multipart/form-data upload
    74  	body := sideLoadBodyWithoutDevMode
    75  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
    76  	chgSummary := s.sideloadCheck(c, body, head, "local", snapstate.Flags{RemoveSnapPath: true})
    77  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`)
    78  }
    79  
    80  func (s *sideloadSuite) TestSideloadSnapOnDevModeDistro(c *check.C) {
    81  	// try a multipart/form-data upload
    82  	body := sideLoadBodyWithoutDevMode
    83  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
    84  	restore := sandbox.MockForceDevMode(true)
    85  	defer restore()
    86  	flags := snapstate.Flags{RemoveSnapPath: true}
    87  	chgSummary := s.sideloadCheck(c, body, head, "local", flags)
    88  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`)
    89  }
    90  
    91  func (s *sideloadSuite) TestSideloadSnapDevMode(c *check.C) {
    92  	body := "" +
    93  		"----hello--\r\n" +
    94  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
    95  		"\r\n" +
    96  		"xyzzy\r\n" +
    97  		"----hello--\r\n" +
    98  		"Content-Disposition: form-data; name=\"devmode\"\r\n" +
    99  		"\r\n" +
   100  		"true\r\n" +
   101  		"----hello--\r\n"
   102  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
   103  	// try a multipart/form-data upload
   104  	flags := snapstate.Flags{RemoveSnapPath: true}
   105  	flags.DevMode = true
   106  	chgSummary := s.sideloadCheck(c, body, head, "local", flags)
   107  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`)
   108  }
   109  
   110  func (s *sideloadSuite) TestSideloadSnapJailMode(c *check.C) {
   111  	body := "" +
   112  		"----hello--\r\n" +
   113  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
   114  		"\r\n" +
   115  		"xyzzy\r\n" +
   116  		"----hello--\r\n" +
   117  		"Content-Disposition: form-data; name=\"jailmode\"\r\n" +
   118  		"\r\n" +
   119  		"true\r\n" +
   120  		"----hello--\r\n" +
   121  		"Content-Disposition: form-data; name=\"dangerous\"\r\n" +
   122  		"\r\n" +
   123  		"true\r\n" +
   124  		"----hello--\r\n"
   125  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
   126  	// try a multipart/form-data upload
   127  	flags := snapstate.Flags{JailMode: true, RemoveSnapPath: true}
   128  	chgSummary := s.sideloadCheck(c, body, head, "local", flags)
   129  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`)
   130  }
   131  
   132  func (s *sideloadSuite) sideloadCheck(c *check.C, content string, head map[string]string, expectedInstanceName string, expectedFlags snapstate.Flags) string {
   133  	d := s.daemonWithFakeSnapManager(c)
   134  
   135  	soon := 0
   136  	var origEnsureStateSoon func(*state.State)
   137  	origEnsureStateSoon, restore := daemon.MockEnsureStateSoon(func(st *state.State) {
   138  		soon++
   139  		origEnsureStateSoon(st)
   140  	})
   141  	defer restore()
   142  
   143  	c.Assert(expectedInstanceName != "", check.Equals, true, check.Commentf("expected instance name must be set"))
   144  	mockedName, _ := snap.SplitInstanceName(expectedInstanceName)
   145  
   146  	// setup done
   147  	installQueue := []string{}
   148  	defer daemon.MockUnsafeReadSnapInfo(func(path string) (*snap.Info, error) {
   149  		return &snap.Info{SuggestedName: mockedName}, nil
   150  	})()
   151  
   152  	defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
   153  		// NOTE: ubuntu-core is not installed in developer mode
   154  		c.Check(flags, check.Equals, snapstate.Flags{})
   155  		installQueue = append(installQueue, name)
   156  
   157  		t := s.NewTask("fake-install-snap", "Doing a fake install")
   158  		return state.NewTaskSet(t), nil
   159  	})()
   160  
   161  	defer daemon.MockSnapstateInstallPath(func(s *state.State, si *snap.SideInfo, path, name, channel string, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) {
   162  		c.Check(flags, check.DeepEquals, expectedFlags)
   163  
   164  		c.Check(path, testutil.FileEquals, "xyzzy")
   165  
   166  		c.Check(name, check.Equals, expectedInstanceName)
   167  
   168  		installQueue = append(installQueue, si.RealName+"::"+path)
   169  		t := s.NewTask("fake-install-snap", "Doing a fake install")
   170  		return state.NewTaskSet(t), &snap.Info{SuggestedName: name}, nil
   171  	})()
   172  
   173  	buf := bytes.NewBufferString(content)
   174  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
   175  	c.Assert(err, check.IsNil)
   176  	for k, v := range head {
   177  		req.Header.Set(k, v)
   178  	}
   179  
   180  	rsp := s.req(c, req, nil).(*daemon.Resp)
   181  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeAsync)
   182  	n := 1
   183  	c.Assert(installQueue, check.HasLen, n)
   184  	c.Check(installQueue[n-1], check.Matches, "local::.*/"+regexp.QuoteMeta(dirs.LocalInstallBlobTempPrefix)+".*")
   185  
   186  	st := d.Overlord().State()
   187  	st.Lock()
   188  	defer st.Unlock()
   189  	chg := st.Change(rsp.Change)
   190  	c.Assert(chg, check.NotNil)
   191  
   192  	c.Check(soon, check.Equals, 1)
   193  
   194  	c.Assert(chg.Tasks(), check.HasLen, n)
   195  
   196  	st.Unlock()
   197  	s.waitTrivialChange(c, chg)
   198  	st.Lock()
   199  
   200  	c.Check(chg.Kind(), check.Equals, "install-snap")
   201  	var names []string
   202  	err = chg.Get("snap-names", &names)
   203  	c.Assert(err, check.IsNil)
   204  	c.Check(names, check.DeepEquals, []string{expectedInstanceName})
   205  	var apiData map[string]interface{}
   206  	err = chg.Get("api-data", &apiData)
   207  	c.Assert(err, check.IsNil)
   208  	c.Check(apiData, check.DeepEquals, map[string]interface{}{
   209  		"snap-name": expectedInstanceName,
   210  	})
   211  
   212  	return chg.Summary()
   213  }
   214  
   215  func (s *sideloadSuite) TestSideloadSnapJailModeAndDevmode(c *check.C) {
   216  	body := "" +
   217  		"----hello--\r\n" +
   218  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
   219  		"\r\n" +
   220  		"xyzzy\r\n" +
   221  		"----hello--\r\n" +
   222  		"Content-Disposition: form-data; name=\"jailmode\"\r\n" +
   223  		"\r\n" +
   224  		"true\r\n" +
   225  		"----hello--\r\n" +
   226  		"Content-Disposition: form-data; name=\"devmode\"\r\n" +
   227  		"\r\n" +
   228  		"true\r\n" +
   229  		"----hello--\r\n"
   230  	s.daemonWithOverlordMockAndStore(c)
   231  
   232  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
   233  	c.Assert(err, check.IsNil)
   234  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
   235  
   236  	rsp := s.req(c, req, nil).(*daemon.Resp)
   237  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   238  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, "cannot use devmode and jailmode flags together")
   239  }
   240  
   241  func (s *sideloadSuite) TestSideloadSnapJailModeInDevModeOS(c *check.C) {
   242  	body := "" +
   243  		"----hello--\r\n" +
   244  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
   245  		"\r\n" +
   246  		"xyzzy\r\n" +
   247  		"----hello--\r\n" +
   248  		"Content-Disposition: form-data; name=\"jailmode\"\r\n" +
   249  		"\r\n" +
   250  		"true\r\n" +
   251  		"----hello--\r\n"
   252  	s.daemonWithOverlordMockAndStore(c)
   253  
   254  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
   255  	c.Assert(err, check.IsNil)
   256  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
   257  
   258  	restore := sandbox.MockForceDevMode(true)
   259  	defer restore()
   260  
   261  	rsp := s.req(c, req, nil).(*daemon.Resp)
   262  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   263  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, "this system cannot honour the jailmode flag")
   264  }
   265  
   266  func (s *sideloadSuite) TestLocalInstallSnapDeriveSideInfo(c *check.C) {
   267  	d := s.daemonWithOverlordMockAndStore(c)
   268  	// add the assertions first
   269  	st := d.Overlord().State()
   270  
   271  	dev1Acct := assertstest.NewAccount(s.StoreSigning, "devel1", nil, "")
   272  
   273  	snapDecl, err := s.StoreSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
   274  		"series":       "16",
   275  		"snap-id":      "x-id",
   276  		"snap-name":    "x",
   277  		"publisher-id": dev1Acct.AccountID(),
   278  		"timestamp":    time.Now().Format(time.RFC3339),
   279  	}, nil, "")
   280  	c.Assert(err, check.IsNil)
   281  
   282  	snapRev, err := s.StoreSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{
   283  		"snap-sha3-384": "YK0GWATaZf09g_fvspYPqm_qtaiqf-KjaNj5uMEQCjQpuXWPjqQbeBINL5H_A0Lo",
   284  		"snap-size":     "5",
   285  		"snap-id":       "x-id",
   286  		"snap-revision": "41",
   287  		"developer-id":  dev1Acct.AccountID(),
   288  		"timestamp":     time.Now().Format(time.RFC3339),
   289  	}, nil, "")
   290  	c.Assert(err, check.IsNil)
   291  
   292  	func() {
   293  		st.Lock()
   294  		defer st.Unlock()
   295  		assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""), dev1Acct, snapDecl, snapRev)
   296  	}()
   297  
   298  	body := "" +
   299  		"----hello--\r\n" +
   300  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x.snap\"\r\n" +
   301  		"\r\n" +
   302  		"xyzzy\r\n" +
   303  		"----hello--\r\n"
   304  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
   305  	c.Assert(err, check.IsNil)
   306  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
   307  
   308  	defer daemon.MockSnapstateInstallPath(func(s *state.State, si *snap.SideInfo, path, name, channel string, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) {
   309  		c.Check(flags, check.Equals, snapstate.Flags{RemoveSnapPath: true})
   310  		c.Check(si, check.DeepEquals, &snap.SideInfo{
   311  			RealName: "x",
   312  			SnapID:   "x-id",
   313  			Revision: snap.R(41),
   314  		})
   315  
   316  		return state.NewTaskSet(), &snap.Info{SuggestedName: "x"}, nil
   317  	})()
   318  
   319  	rsp := s.req(c, req, nil).(*daemon.Resp)
   320  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeAsync)
   321  
   322  	st.Lock()
   323  	defer st.Unlock()
   324  	chg := st.Change(rsp.Change)
   325  	c.Assert(chg, check.NotNil)
   326  	c.Check(chg.Summary(), check.Equals, `Install "x" snap from file "x.snap"`)
   327  	var names []string
   328  	err = chg.Get("snap-names", &names)
   329  	c.Assert(err, check.IsNil)
   330  	c.Check(names, check.DeepEquals, []string{"x"})
   331  	var apiData map[string]interface{}
   332  	err = chg.Get("api-data", &apiData)
   333  	c.Assert(err, check.IsNil)
   334  	c.Check(apiData, check.DeepEquals, map[string]interface{}{
   335  		"snap-name": "x",
   336  	})
   337  }
   338  
   339  func (s *sideloadSuite) TestSideloadSnapNoSignaturesDangerOff(c *check.C) {
   340  	body := "" +
   341  		"----hello--\r\n" +
   342  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
   343  		"\r\n" +
   344  		"xyzzy\r\n" +
   345  		"----hello--\r\n"
   346  	s.daemonWithOverlordMockAndStore(c)
   347  
   348  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
   349  	c.Assert(err, check.IsNil)
   350  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
   351  
   352  	// this is the prefix used for tempfiles for sideloading
   353  	glob := filepath.Join(os.TempDir(), "snapd-sideload-pkg-*")
   354  	glbBefore, _ := filepath.Glob(glob)
   355  	rsp := s.req(c, req, nil).(*daemon.Resp)
   356  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   357  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `cannot find signatures with metadata for snap "x"`)
   358  	glbAfter, _ := filepath.Glob(glob)
   359  	c.Check(len(glbBefore), check.Equals, len(glbAfter))
   360  }
   361  
   362  func (s *sideloadSuite) TestSideloadSnapNotValidFormFile(c *check.C) {
   363  	s.daemon(c)
   364  
   365  	// try a multipart/form-data upload with missing "name"
   366  	content := "" +
   367  		"----hello--\r\n" +
   368  		"Content-Disposition: form-data; filename=\"x\"\r\n" +
   369  		"\r\n" +
   370  		"xyzzy\r\n" +
   371  		"----hello--\r\n"
   372  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
   373  
   374  	buf := bytes.NewBufferString(content)
   375  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
   376  	c.Assert(err, check.IsNil)
   377  	for k, v := range head {
   378  		req.Header.Set(k, v)
   379  	}
   380  
   381  	rsp := s.req(c, req, nil).(*daemon.Resp)
   382  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   383  	c.Assert(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, `cannot find "snap" file field in provided multipart/form-data payload`)
   384  }
   385  
   386  func (s *sideloadSuite) TestSideloadSnapChangeConflict(c *check.C) {
   387  	body := "" +
   388  		"----hello--\r\n" +
   389  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
   390  		"\r\n" +
   391  		"xyzzy\r\n" +
   392  		"----hello--\r\n" +
   393  		"Content-Disposition: form-data; name=\"dangerous\"\r\n" +
   394  		"\r\n" +
   395  		"true\r\n" +
   396  		"----hello--\r\n"
   397  	s.daemonWithOverlordMockAndStore(c)
   398  
   399  	defer daemon.MockUnsafeReadSnapInfo(func(path string) (*snap.Info, error) {
   400  		return &snap.Info{SuggestedName: "foo"}, nil
   401  	})()
   402  
   403  	defer daemon.MockSnapstateInstallPath(func(s *state.State, si *snap.SideInfo, path, name, channel string, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) {
   404  		return nil, nil, &snapstate.ChangeConflictError{Snap: "foo"}
   405  	})()
   406  
   407  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
   408  	c.Assert(err, check.IsNil)
   409  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
   410  
   411  	rsp := s.req(c, req, nil).(*daemon.Resp)
   412  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   413  	c.Check(rsp.Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindSnapChangeConflict)
   414  }
   415  
   416  func (s *sideloadSuite) TestSideloadSnapInstanceName(c *check.C) {
   417  	// try a multipart/form-data upload
   418  	body := sideLoadBodyWithoutDevMode +
   419  		"Content-Disposition: form-data; name=\"name\"\r\n" +
   420  		"\r\n" +
   421  		"local_instance\r\n" +
   422  		"----hello--\r\n"
   423  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
   424  	chgSummary := s.sideloadCheck(c, body, head, "local_instance", snapstate.Flags{RemoveSnapPath: true})
   425  	c.Check(chgSummary, check.Equals, `Install "local_instance" snap from file "a/b/local.snap"`)
   426  }
   427  
   428  func (s *sideloadSuite) TestSideloadSnapInstanceNameNoKey(c *check.C) {
   429  	// try a multipart/form-data upload
   430  	body := sideLoadBodyWithoutDevMode +
   431  		"Content-Disposition: form-data; name=\"name\"\r\n" +
   432  		"\r\n" +
   433  		"local\r\n" +
   434  		"----hello--\r\n"
   435  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
   436  	chgSummary := s.sideloadCheck(c, body, head, "local", snapstate.Flags{RemoveSnapPath: true})
   437  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`)
   438  }
   439  
   440  func (s *sideloadSuite) TestSideloadSnapInstanceNameMismatch(c *check.C) {
   441  	s.daemonWithFakeSnapManager(c)
   442  
   443  	defer daemon.MockUnsafeReadSnapInfo(func(path string) (*snap.Info, error) {
   444  		return &snap.Info{SuggestedName: "bar"}, nil
   445  	})()
   446  
   447  	body := sideLoadBodyWithoutDevMode +
   448  		"Content-Disposition: form-data; name=\"name\"\r\n" +
   449  		"\r\n" +
   450  		"foo_instance\r\n" +
   451  		"----hello--\r\n"
   452  
   453  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
   454  	c.Assert(err, check.IsNil)
   455  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
   456  
   457  	rsp := s.req(c, req, nil).(*daemon.Resp)
   458  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   459  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `instance name "foo_instance" does not match snap name "bar"`)
   460  }
   461  
   462  func (s *sideloadSuite) TestInstallPathUnaliased(c *check.C) {
   463  	body := "" +
   464  		"----hello--\r\n" +
   465  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
   466  		"\r\n" +
   467  		"xyzzy\r\n" +
   468  		"----hello--\r\n" +
   469  		"Content-Disposition: form-data; name=\"devmode\"\r\n" +
   470  		"\r\n" +
   471  		"true\r\n" +
   472  		"----hello--\r\n" +
   473  		"Content-Disposition: form-data; name=\"unaliased\"\r\n" +
   474  		"\r\n" +
   475  		"true\r\n" +
   476  		"----hello--\r\n"
   477  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
   478  	// try a multipart/form-data upload
   479  	flags := snapstate.Flags{Unaliased: true, RemoveSnapPath: true, DevMode: true}
   480  	chgSummary := s.sideloadCheck(c, body, head, "local", flags)
   481  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`)
   482  }
   483  
   484  type trySuite struct {
   485  	apiBaseSuite
   486  }
   487  
   488  func (s *trySuite) TestTrySnap(c *check.C) {
   489  	d := s.daemonWithFakeSnapManager(c)
   490  
   491  	var err error
   492  
   493  	// mock a try dir
   494  	tryDir := c.MkDir()
   495  	snapYaml := filepath.Join(tryDir, "meta", "snap.yaml")
   496  	err = os.MkdirAll(filepath.Dir(snapYaml), 0755)
   497  	c.Assert(err, check.IsNil)
   498  	err = ioutil.WriteFile(snapYaml, []byte("name: foo\nversion: 1.0\n"), 0644)
   499  	c.Assert(err, check.IsNil)
   500  
   501  	reqForFlags := func(f snapstate.Flags) *http.Request {
   502  		b := "" +
   503  			"--hello\r\n" +
   504  			"Content-Disposition: form-data; name=\"action\"\r\n" +
   505  			"\r\n" +
   506  			"try\r\n" +
   507  			"--hello\r\n" +
   508  			"Content-Disposition: form-data; name=\"snap-path\"\r\n" +
   509  			"\r\n" +
   510  			tryDir + "\r\n" +
   511  			"--hello"
   512  
   513  		snip := "\r\n" +
   514  			"Content-Disposition: form-data; name=%q\r\n" +
   515  			"\r\n" +
   516  			"true\r\n" +
   517  			"--hello"
   518  
   519  		if f.DevMode {
   520  			b += fmt.Sprintf(snip, "devmode")
   521  		}
   522  		if f.JailMode {
   523  			b += fmt.Sprintf(snip, "jailmode")
   524  		}
   525  		if f.Classic {
   526  			b += fmt.Sprintf(snip, "classic")
   527  		}
   528  		b += "--\r\n"
   529  
   530  		req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(b))
   531  		c.Assert(err, check.IsNil)
   532  		req.Header.Set("Content-Type", "multipart/thing; boundary=hello")
   533  
   534  		return req
   535  	}
   536  
   537  	st := d.Overlord().State()
   538  	st.Lock()
   539  	defer st.Unlock()
   540  
   541  	soon := 0
   542  	var origEnsureStateSoon func(*state.State)
   543  	origEnsureStateSoon, restore := daemon.MockEnsureStateSoon(func(st *state.State) {
   544  		soon++
   545  		origEnsureStateSoon(st)
   546  	})
   547  	defer restore()
   548  
   549  	for _, t := range []struct {
   550  		flags snapstate.Flags
   551  		desc  string
   552  	}{
   553  		{snapstate.Flags{}, "core; -"},
   554  		{snapstate.Flags{DevMode: true}, "core; devmode"},
   555  		{snapstate.Flags{JailMode: true}, "core; jailmode"},
   556  		{snapstate.Flags{Classic: true}, "core; classic"},
   557  	} {
   558  		soon = 0
   559  
   560  		tryWasCalled := true
   561  		defer daemon.MockSnapstateTryPath(func(s *state.State, name, path string, flags snapstate.Flags) (*state.TaskSet, error) {
   562  			c.Check(flags, check.DeepEquals, t.flags, check.Commentf(t.desc))
   563  			tryWasCalled = true
   564  			t := s.NewTask("fake-install-snap", "Doing a fake try")
   565  			return state.NewTaskSet(t), nil
   566  		})()
   567  
   568  		defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
   569  			if name != "core" {
   570  				c.Check(flags, check.DeepEquals, t.flags, check.Commentf(t.desc))
   571  			}
   572  			t := s.NewTask("fake-install-snap", "Doing a fake install")
   573  			return state.NewTaskSet(t), nil
   574  		})()
   575  
   576  		// try the snap (without an installed core)
   577  		st.Unlock()
   578  		rsp := s.req(c, reqForFlags(t.flags), nil).(*daemon.Resp)
   579  		st.Lock()
   580  		c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeAsync, check.Commentf(t.desc))
   581  		c.Assert(tryWasCalled, check.Equals, true, check.Commentf(t.desc))
   582  
   583  		chg := st.Change(rsp.Change)
   584  		c.Assert(chg, check.NotNil, check.Commentf(t.desc))
   585  
   586  		c.Assert(chg.Tasks(), check.HasLen, 1, check.Commentf(t.desc))
   587  
   588  		st.Unlock()
   589  		s.waitTrivialChange(c, chg)
   590  		st.Lock()
   591  
   592  		c.Check(chg.Kind(), check.Equals, "try-snap", check.Commentf(t.desc))
   593  		c.Check(chg.Summary(), check.Equals, fmt.Sprintf(`Try "%s" snap from %s`, "foo", tryDir), check.Commentf(t.desc))
   594  		var names []string
   595  		err = chg.Get("snap-names", &names)
   596  		c.Assert(err, check.IsNil, check.Commentf(t.desc))
   597  		c.Check(names, check.DeepEquals, []string{"foo"}, check.Commentf(t.desc))
   598  		var apiData map[string]interface{}
   599  		err = chg.Get("api-data", &apiData)
   600  		c.Assert(err, check.IsNil, check.Commentf(t.desc))
   601  		c.Check(apiData, check.DeepEquals, map[string]interface{}{
   602  			"snap-name": "foo",
   603  		}, check.Commentf(t.desc))
   604  
   605  		c.Check(soon, check.Equals, 1, check.Commentf(t.desc))
   606  	}
   607  }
   608  
   609  func (s *trySuite) TestTrySnapRelative(c *check.C) {
   610  	d := s.daemon(c)
   611  	st := d.Overlord().State()
   612  
   613  	rsp := daemon.TrySnap(st, "relative-path", snapstate.Flags{}).(*daemon.Resp)
   614  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   615  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, "need an absolute path")
   616  }
   617  
   618  func (s *trySuite) TestTrySnapNotDir(c *check.C) {
   619  	d := s.daemon(c)
   620  	st := d.Overlord().State()
   621  
   622  	rsp := daemon.TrySnap(st, "/does/not/exist", snapstate.Flags{}).(*daemon.Resp)
   623  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   624  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, "not a snap directory")
   625  }
   626  
   627  func (s *trySuite) TestTryChangeConflict(c *check.C) {
   628  	d := s.daemonWithOverlordMockAndStore(c)
   629  	st := d.Overlord().State()
   630  
   631  	// mock a try dir
   632  	tryDir := c.MkDir()
   633  
   634  	defer daemon.MockUnsafeReadSnapInfo(func(path string) (*snap.Info, error) {
   635  		return &snap.Info{SuggestedName: "foo"}, nil
   636  	})()
   637  
   638  	defer daemon.MockSnapstateTryPath(func(s *state.State, name, path string, flags snapstate.Flags) (*state.TaskSet, error) {
   639  		return nil, &snapstate.ChangeConflictError{Snap: "foo"}
   640  	})()
   641  
   642  	rsp := daemon.TrySnap(st, tryDir, snapstate.Flags{}).(*daemon.Resp)
   643  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   644  	c.Check(rsp.Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindSnapChangeConflict)
   645  }