github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/seed/seedwriter/writer_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-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 seedwriter_test
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"testing"
    29  	"time"
    30  
    31  	. "gopkg.in/check.v1"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/asserts/assertstest"
    35  	"github.com/snapcore/snapd/osutil"
    36  	"github.com/snapcore/snapd/seed/seedtest"
    37  	"github.com/snapcore/snapd/seed/seedwriter"
    38  	"github.com/snapcore/snapd/snap"
    39  	"github.com/snapcore/snapd/snap/naming"
    40  	"github.com/snapcore/snapd/snap/snapfile"
    41  	"github.com/snapcore/snapd/snap/snaptest"
    42  	"github.com/snapcore/snapd/testutil"
    43  )
    44  
    45  func Test(t *testing.T) { TestingT(t) }
    46  
    47  type writerSuite struct {
    48  	testutil.BaseTest
    49  
    50  	// SeedSnaps helps creating and making available seed snaps
    51  	// (it provides MakeAssertedSnap etc.) for the tests.
    52  	*seedtest.SeedSnaps
    53  
    54  	opts *seedwriter.Options
    55  
    56  	db         *asserts.Database
    57  	newFetcher seedwriter.NewFetcherFunc
    58  	rf         seedwriter.RefAssertsFetcher
    59  
    60  	devAcct *asserts.Account
    61  
    62  	aRefs map[string][]*asserts.Ref
    63  }
    64  
    65  var _ = Suite(&writerSuite{})
    66  
    67  var (
    68  	brandPrivKey, _ = assertstest.GenerateKey(752)
    69  )
    70  
    71  func (s *writerSuite) SetUpTest(c *C) {
    72  	s.BaseTest.SetUpTest(c)
    73  	s.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    74  
    75  	dir := c.MkDir()
    76  	seedDir := filepath.Join(dir, "seed")
    77  	err := os.Mkdir(seedDir, 0755)
    78  	c.Assert(err, IsNil)
    79  
    80  	s.opts = &seedwriter.Options{
    81  		SeedDir: seedDir,
    82  	}
    83  
    84  	s.SeedSnaps = &seedtest.SeedSnaps{}
    85  	s.SetupAssertSigning("canonical")
    86  	s.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{
    87  		"verification": "verified",
    88  	})
    89  	assertstest.AddMany(s.StoreSigning, s.Brands.AccountsAndKeys("my-brand")...)
    90  
    91  	s.devAcct = assertstest.NewAccount(s.StoreSigning, "developer", map[string]interface{}{
    92  		"account-id": "developerid",
    93  	}, "")
    94  	assertstest.AddMany(s.StoreSigning, s.devAcct)
    95  
    96  	db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
    97  		Backstore: asserts.NewMemoryBackstore(),
    98  		Trusted:   s.StoreSigning.Trusted,
    99  	})
   100  	c.Assert(err, IsNil)
   101  	s.db = db
   102  
   103  	retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
   104  		return ref.Resolve(s.StoreSigning.Find)
   105  	}
   106  	s.newFetcher = func(save func(asserts.Assertion) error) asserts.Fetcher {
   107  		save2 := func(a asserts.Assertion) error {
   108  			// for checking
   109  			err := db.Add(a)
   110  			if err != nil {
   111  				if _, ok := err.(*asserts.RevisionError); ok {
   112  					return nil
   113  				}
   114  				return err
   115  			}
   116  			return save(a)
   117  		}
   118  		return asserts.NewFetcher(db, retrieve, save2)
   119  	}
   120  	s.rf = seedwriter.MakeRefAssertsFetcher(s.newFetcher)
   121  
   122  	s.aRefs = make(map[string][]*asserts.Ref)
   123  }
   124  
   125  var snapYaml = seedtest.MergeSampleSnapYaml(seedtest.SampleSnapYaml, map[string]string{
   126  	"cont-producer": `name: cont-producer
   127  type: app
   128  base: core18
   129  version: 1.1
   130  slots:
   131     cont:
   132       interface: content
   133       content: cont
   134  `,
   135  	"cont-consumer": `name: cont-consumer
   136  base: core18
   137  version: 1.0
   138  plugs:
   139     cont:
   140       interface: content
   141       content: cont
   142       default-provider: cont-producer
   143  `,
   144  	"required-base-core16": `name: required-base-core16
   145  type: app
   146  base: core16
   147  version: 1.0
   148  `,
   149  	"my-devmode": `name: my-devmode
   150  type: app
   151  version: 1
   152  confinement: devmode
   153  `,
   154  })
   155  
   156  const pcGadgetYaml = `
   157  volumes:
   158    pc:
   159      bootloader: grub
   160  `
   161  
   162  var snapFiles = map[string][][]string{
   163  	"pc": {
   164  		{"meta/gadget.yaml", pcGadgetYaml},
   165  	},
   166  	"pc=18": {
   167  		{"meta/gadget.yaml", pcGadgetYaml},
   168  	},
   169  }
   170  
   171  func (s *writerSuite) makeSnap(c *C, yamlKey, publisher string) {
   172  	if publisher == "" {
   173  		publisher = "canonical"
   174  	}
   175  	s.MakeAssertedSnap(c, snapYaml[yamlKey], snapFiles[yamlKey], snap.R(1), publisher, s.StoreSigning.Database)
   176  }
   177  
   178  func (s *writerSuite) makeLocalSnap(c *C, yamlKey string) (fname string) {
   179  	return snaptest.MakeTestSnapWithFiles(c, snapYaml[yamlKey], nil)
   180  }
   181  
   182  func (s *writerSuite) doFillMetaDownloadedSnap(c *C, w *seedwriter.Writer, sn *seedwriter.SeedSnap) *snap.Info {
   183  	info := s.AssertedSnapInfo(sn.SnapName())
   184  	c.Assert(info, NotNil, Commentf("%s not defined", sn.SnapName()))
   185  	err := w.SetInfo(sn, info)
   186  	c.Assert(err, IsNil)
   187  
   188  	aRefs := s.aRefs[sn.SnapName()]
   189  	if aRefs == nil {
   190  		prev := len(s.rf.Refs())
   191  		err = s.rf.Fetch(s.AssertedSnapRevision(sn.SnapName()).Ref())
   192  		c.Assert(err, IsNil)
   193  		aRefs = s.rf.Refs()[prev:]
   194  		s.aRefs[sn.SnapName()] = aRefs
   195  	}
   196  	sn.ARefs = aRefs
   197  
   198  	return info
   199  }
   200  
   201  func (s *writerSuite) fillDownloadedSnap(c *C, w *seedwriter.Writer, sn *seedwriter.SeedSnap) {
   202  	info := s.doFillMetaDownloadedSnap(c, w, sn)
   203  
   204  	c.Assert(sn.Path, Equals, filepath.Join(s.opts.SeedDir, "snaps", info.Filename()))
   205  	err := os.Rename(s.AssertedSnap(sn.SnapName()), sn.Path)
   206  	c.Assert(err, IsNil)
   207  }
   208  
   209  func (s *writerSuite) fillMetaDownloadedSnap(c *C, w *seedwriter.Writer, sn *seedwriter.SeedSnap) {
   210  	s.doFillMetaDownloadedSnap(c, w, sn)
   211  }
   212  
   213  func (s *writerSuite) TestNewDefaultChannelError(c *C) {
   214  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   215  		"display-name":   "my model",
   216  		"architecture":   "amd64",
   217  		"gadget":         "pc",
   218  		"kernel":         "pc-kernel",
   219  		"required-snaps": []interface{}{"required"},
   220  	})
   221  
   222  	s.opts.DefaultChannel = "foo/bar"
   223  	w, err := seedwriter.New(model, s.opts)
   224  	c.Assert(w, IsNil)
   225  	c.Check(err, ErrorMatches, `cannot use global default option channel: invalid risk in channel name: foo/bar`)
   226  }
   227  
   228  func (s writerSuite) TestSetOptionsSnapsErrors(c *C) {
   229  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   230  		"display-name":   "my model",
   231  		"architecture":   "amd64",
   232  		"gadget":         "pc",
   233  		"kernel":         "pc-kernel",
   234  		"required-snaps": []interface{}{"required"},
   235  	})
   236  
   237  	tests := []struct {
   238  		snaps []*seedwriter.OptionsSnap
   239  		err   string
   240  	}{
   241  		{[]*seedwriter.OptionsSnap{{Name: "foo%&"}}, `invalid snap name: "foo%&"`},
   242  		{[]*seedwriter.OptionsSnap{{Name: "foo_1"}}, `cannot use snap "foo_1", parallel snap instances are unsupported`},
   243  		{[]*seedwriter.OptionsSnap{{Name: "foo"}, {Name: "foo"}}, `snap "foo" is repeated in options`},
   244  		{[]*seedwriter.OptionsSnap{{Name: "foo", Channel: "track/foo"}}, `cannot use option channel for snap "foo": invalid risk in channel name: track/foo`},
   245  		{[]*seedwriter.OptionsSnap{{Path: "not-a-snap"}}, `local option snap "not-a-snap" does not end in .snap`},
   246  		{[]*seedwriter.OptionsSnap{{Path: "not-there.snap"}}, `local option snap "not-there.snap" does not exist`},
   247  		{[]*seedwriter.OptionsSnap{{Name: "foo", Path: "foo.snap"}}, `cannot specify both name and path for option snap "foo"`},
   248  	}
   249  
   250  	for _, t := range tests {
   251  		w, err := seedwriter.New(model, s.opts)
   252  		c.Assert(err, IsNil)
   253  
   254  		c.Check(w.SetOptionsSnaps(t.snaps), ErrorMatches, t.err)
   255  	}
   256  }
   257  
   258  func (s *writerSuite) TestSnapsToDownloadCore16(c *C) {
   259  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   260  		"display-name":   "my model",
   261  		"architecture":   "amd64",
   262  		"gadget":         "pc",
   263  		"kernel":         "pc-kernel",
   264  		"required-snaps": []interface{}{"required"},
   265  	})
   266  
   267  	w, err := seedwriter.New(model, s.opts)
   268  	c.Assert(err, IsNil)
   269  
   270  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}})
   271  	c.Assert(err, IsNil)
   272  
   273  	_, err = w.Start(s.db, s.newFetcher)
   274  	c.Assert(err, IsNil)
   275  
   276  	snaps, err := w.SnapsToDownload()
   277  	c.Assert(err, IsNil)
   278  	c.Check(snaps, HasLen, 4)
   279  
   280  	c.Check(naming.SameSnap(snaps[0], naming.Snap("core")), Equals, true)
   281  	c.Check(naming.SameSnap(snaps[1], naming.Snap("pc-kernel")), Equals, true)
   282  	c.Check(snaps[1].Channel, Equals, "stable")
   283  	c.Check(naming.SameSnap(snaps[2], naming.Snap("pc")), Equals, true)
   284  	c.Check(snaps[2].Channel, Equals, "edge")
   285  	c.Check(naming.SameSnap(snaps[3], naming.Snap("required")), Equals, true)
   286  }
   287  
   288  func (s *writerSuite) TestSnapsToDownloadOptionTrack(c *C) {
   289  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   290  		"display-name":   "my model",
   291  		"architecture":   "amd64",
   292  		"gadget":         "pc",
   293  		"kernel":         "pc-kernel",
   294  		"required-snaps": []interface{}{"required"},
   295  	})
   296  
   297  	w, err := seedwriter.New(model, s.opts)
   298  	c.Assert(err, IsNil)
   299  
   300  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "track/edge"}})
   301  	c.Assert(err, IsNil)
   302  
   303  	_, err = w.Start(s.db, s.newFetcher)
   304  	c.Assert(err, IsNil)
   305  
   306  	snaps, err := w.SnapsToDownload()
   307  	c.Assert(err, IsNil)
   308  	c.Check(snaps, HasLen, 4)
   309  
   310  	c.Check(naming.SameSnap(snaps[2], naming.Snap("pc")), Equals, true)
   311  	c.Check(snaps[2].Channel, Equals, "track/edge")
   312  }
   313  
   314  func (s *writerSuite) TestDownloadedCore16(c *C) {
   315  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   316  		"display-name":   "my model",
   317  		"architecture":   "amd64",
   318  		"gadget":         "pc",
   319  		"kernel":         "pc-kernel",
   320  		"required-snaps": []interface{}{"required"},
   321  	})
   322  
   323  	s.makeSnap(c, "core", "")
   324  	s.makeSnap(c, "pc-kernel", "")
   325  	s.makeSnap(c, "pc", "")
   326  	s.makeSnap(c, "required", "developerid")
   327  
   328  	w, err := seedwriter.New(model, s.opts)
   329  	c.Assert(err, IsNil)
   330  
   331  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}})
   332  	c.Assert(err, IsNil)
   333  
   334  	_, err = w.Start(s.db, s.newFetcher)
   335  	c.Assert(err, IsNil)
   336  
   337  	snaps, err := w.SnapsToDownload()
   338  	c.Assert(err, IsNil)
   339  	c.Check(snaps, HasLen, 4)
   340  
   341  	for _, sn := range snaps {
   342  		s.fillDownloadedSnap(c, w, sn)
   343  	}
   344  
   345  	complete, err := w.Downloaded()
   346  	c.Assert(err, IsNil)
   347  	c.Check(complete, Equals, true)
   348  
   349  	essSnaps, err := w.BootSnaps()
   350  	c.Assert(err, IsNil)
   351  	c.Check(essSnaps, DeepEquals, snaps[:3])
   352  	c.Check(naming.SameSnap(essSnaps[2], naming.Snap("pc")), Equals, true)
   353  	c.Check(essSnaps[2].Channel, Equals, "edge")
   354  }
   355  
   356  func (s *writerSuite) TestDownloadedCore18(c *C) {
   357  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   358  		"display-name":   "my model",
   359  		"architecture":   "amd64",
   360  		"base":           "core18",
   361  		"gadget":         "pc=18",
   362  		"kernel":         "pc-kernel=18",
   363  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
   364  	})
   365  
   366  	s.makeSnap(c, "snapd", "")
   367  	s.makeSnap(c, "core18", "")
   368  	s.makeSnap(c, "pc-kernel=18", "")
   369  	s.makeSnap(c, "pc=18", "")
   370  	s.makeSnap(c, "cont-producer", "developerid")
   371  	s.makeSnap(c, "cont-consumer", "developerid")
   372  
   373  	w, err := seedwriter.New(model, s.opts)
   374  	c.Assert(err, IsNil)
   375  
   376  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}})
   377  	c.Assert(err, IsNil)
   378  
   379  	_, err = w.Start(s.db, s.newFetcher)
   380  	c.Assert(err, IsNil)
   381  
   382  	snaps, err := w.SnapsToDownload()
   383  	c.Assert(err, IsNil)
   384  	c.Check(snaps, HasLen, 6)
   385  	c.Check(naming.SameSnap(snaps[0], naming.Snap("snapd")), Equals, true)
   386  	c.Check(naming.SameSnap(snaps[1], naming.Snap("pc-kernel")), Equals, true)
   387  	c.Check(naming.SameSnap(snaps[2], naming.Snap("core18")), Equals, true)
   388  	c.Check(naming.SameSnap(snaps[3], naming.Snap("pc")), Equals, true)
   389  	c.Check(snaps[3].Channel, Equals, "18/edge")
   390  
   391  	for _, sn := range snaps {
   392  		s.fillDownloadedSnap(c, w, sn)
   393  	}
   394  
   395  	complete, err := w.Downloaded()
   396  	c.Assert(err, IsNil)
   397  	c.Check(complete, Equals, true)
   398  
   399  	essSnaps, err := w.BootSnaps()
   400  	c.Assert(err, IsNil)
   401  	c.Check(essSnaps, DeepEquals, snaps[:4])
   402  
   403  	c.Check(w.Warnings(), HasLen, 0)
   404  }
   405  
   406  func (s *writerSuite) TestSnapsToDownloadCore18IncompatibleTrack(c *C) {
   407  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   408  		"display-name":   "my model",
   409  		"architecture":   "amd64",
   410  		"base":           "core18",
   411  		"gadget":         "pc=18",
   412  		"kernel":         "pc-kernel=18",
   413  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
   414  	})
   415  
   416  	s.makeSnap(c, "snapd", "")
   417  	s.makeSnap(c, "core18", "")
   418  	s.makeSnap(c, "pc-kernel=18", "")
   419  	s.makeSnap(c, "pc=18", "")
   420  	s.makeSnap(c, "cont-producer", "developerid")
   421  	s.makeSnap(c, "cont-consumer", "developerid")
   422  
   423  	w, err := seedwriter.New(model, s.opts)
   424  	c.Assert(err, IsNil)
   425  
   426  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc-kernel", Channel: "18.1"}})
   427  	c.Assert(err, IsNil)
   428  
   429  	_, err = w.Start(s.db, s.newFetcher)
   430  	c.Assert(err, IsNil)
   431  
   432  	_, err = w.SnapsToDownload()
   433  	c.Check(err, ErrorMatches, `option channel "18.1" for kernel "pc-kernel" has a track incompatible with the pinned track from model assertion: 18`)
   434  }
   435  
   436  func (s *writerSuite) TestSnapsToDownloadDefaultChannel(c *C) {
   437  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   438  		"display-name":   "my model",
   439  		"architecture":   "amd64",
   440  		"base":           "core18",
   441  		"gadget":         "pc=18",
   442  		"kernel":         "pc-kernel=18",
   443  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
   444  	})
   445  
   446  	s.makeSnap(c, "snapd", "")
   447  	s.makeSnap(c, "core18", "")
   448  	s.makeSnap(c, "pc-kernel=18", "")
   449  	s.makeSnap(c, "pc=18", "")
   450  	s.makeSnap(c, "cont-producer", "developerid")
   451  	s.makeSnap(c, "cont-consumer", "developerid")
   452  
   453  	s.opts.DefaultChannel = "candidate"
   454  	w, err := seedwriter.New(model, s.opts)
   455  	c.Assert(err, IsNil)
   456  
   457  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}})
   458  	c.Assert(err, IsNil)
   459  
   460  	_, err = w.Start(s.db, s.newFetcher)
   461  	c.Assert(err, IsNil)
   462  
   463  	snaps, err := w.SnapsToDownload()
   464  	c.Assert(err, IsNil)
   465  	c.Check(snaps, HasLen, 6)
   466  
   467  	for i, name := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} {
   468  		c.Check(naming.SameSnap(snaps[i], naming.Snap(name)), Equals, true)
   469  		channel := "candidate"
   470  		switch name {
   471  		case "pc-kernel":
   472  			channel = "18/candidate"
   473  		case "pc":
   474  			channel = "18/edge"
   475  		}
   476  		c.Check(snaps[i].Channel, Equals, channel)
   477  	}
   478  }
   479  
   480  func (s *writerSuite) upToDownloaded(c *C, model *asserts.Model, fill func(c *C, w *seedwriter.Writer, sn *seedwriter.SeedSnap)) (complete bool, w *seedwriter.Writer, err error) {
   481  	w, err = seedwriter.New(model, s.opts)
   482  	c.Assert(err, IsNil)
   483  
   484  	_, err = w.Start(s.db, s.newFetcher)
   485  	c.Assert(err, IsNil)
   486  
   487  	snaps, err := w.SnapsToDownload()
   488  	c.Assert(err, IsNil)
   489  
   490  	for _, sn := range snaps {
   491  		fill(c, w, sn)
   492  	}
   493  
   494  	complete, err = w.Downloaded()
   495  	return complete, w, err
   496  }
   497  
   498  func (s *writerSuite) TestDownloadedCheckBaseGadget(c *C) {
   499  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   500  		"display-name": "my model",
   501  		"architecture": "amd64",
   502  		"base":         "core18",
   503  		"gadget":       "pc",
   504  		"kernel":       "pc-kernel=18",
   505  	})
   506  
   507  	s.makeSnap(c, "snapd", "")
   508  	s.makeSnap(c, "core18", "")
   509  	s.makeSnap(c, "pc-kernel=18", "")
   510  	s.makeSnap(c, "pc", "")
   511  
   512  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
   513  	c.Check(err, ErrorMatches, `cannot use gadget snap because its base "" is different from model base "core18"`)
   514  
   515  }
   516  
   517  func (s *writerSuite) TestDownloadedCheckBase(c *C) {
   518  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   519  		"display-name":   "my model",
   520  		"architecture":   "amd64",
   521  		"gadget":         "pc",
   522  		"kernel":         "pc-kernel",
   523  		"required-snaps": []interface{}{"cont-producer"},
   524  	})
   525  
   526  	s.makeSnap(c, "core", "")
   527  	s.makeSnap(c, "pc-kernel", "")
   528  	s.makeSnap(c, "pc", "")
   529  	s.makeSnap(c, "cont-producer", "developerid")
   530  
   531  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
   532  	c.Check(err, ErrorMatches, `cannot add snap "cont-producer" without also adding its base "core18" explicitly`)
   533  
   534  }
   535  
   536  func (s *writerSuite) TestOutOfOrder(c *C) {
   537  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   538  		"display-name":   "my model",
   539  		"architecture":   "amd64",
   540  		"gadget":         "pc",
   541  		"kernel":         "pc-kernel",
   542  		"required-snaps": []interface{}{"required"},
   543  	})
   544  
   545  	w, err := seedwriter.New(model, s.opts)
   546  	c.Assert(err, IsNil)
   547  
   548  	c.Check(w.WriteMeta(), ErrorMatches, "internal error: seedwriter.Writer expected Start|SetOptionsSnaps to be invoked on it at this point, not WriteMeta")
   549  	c.Check(w.SeedSnaps(nil), ErrorMatches, "internal error: seedwriter.Writer expected Start|SetOptionsSnaps to be invoked on it at this point, not SeedSnaps")
   550  
   551  	_, err = w.Start(s.db, s.newFetcher)
   552  	c.Assert(err, IsNil)
   553  	_, err = w.Downloaded()
   554  	c.Check(err, ErrorMatches, "internal error: seedwriter.Writer expected SnapToDownload|LocalSnaps to be invoked on it at this point, not Downloaded")
   555  
   556  	_, err = w.BootSnaps()
   557  	c.Check(err, ErrorMatches, "internal error: seedwriter.Writer cannot query seed snaps before Downloaded signaled complete")
   558  
   559  	_, err = w.UnassertedSnaps()
   560  	c.Check(err, ErrorMatches, "internal error: seedwriter.Writer cannot query seed snaps before Downloaded signaled complete")
   561  
   562  }
   563  
   564  func (s *writerSuite) TestOutOfOrderWithLocalSnaps(c *C) {
   565  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   566  		"display-name":   "my model",
   567  		"architecture":   "amd64",
   568  		"gadget":         "pc",
   569  		"kernel":         "pc-kernel",
   570  		"required-snaps": []interface{}{"required"},
   571  	})
   572  
   573  	w, err := seedwriter.New(model, s.opts)
   574  	c.Assert(err, IsNil)
   575  
   576  	requiredFn := s.makeLocalSnap(c, "required")
   577  
   578  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}})
   579  	c.Assert(err, IsNil)
   580  
   581  	_, err = w.Start(s.db, s.newFetcher)
   582  	c.Assert(err, IsNil)
   583  
   584  	_, err = w.SnapsToDownload()
   585  	c.Check(err, ErrorMatches, `internal error: seedwriter.Writer expected LocalSnaps to be invoked on it at this point, not SnapsToDownload`)
   586  
   587  	_, err = w.LocalSnaps()
   588  	c.Assert(err, IsNil)
   589  
   590  	_, err = w.SnapsToDownload()
   591  	c.Check(err, ErrorMatches, `internal error: seedwriter.Writer expected InfoDerived to be invoked on it at this point, not SnapsToDownload`)
   592  }
   593  
   594  func (s *writerSuite) TestDownloadedInfosNotSet(c *C) {
   595  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   596  		"display-name":   "my model",
   597  		"architecture":   "amd64",
   598  		"gadget":         "pc",
   599  		"kernel":         "pc-kernel",
   600  		"required-snaps": []interface{}{"required"},
   601  	})
   602  
   603  	doNothingFill := func(*C, *seedwriter.Writer, *seedwriter.SeedSnap) {}
   604  
   605  	_, _, err := s.upToDownloaded(c, model, doNothingFill)
   606  	c.Check(err, ErrorMatches, `internal error: before seedwriter.Writer.Downloaded snap \"core\" Info should have been set`)
   607  }
   608  
   609  func (s *writerSuite) TestDownloadedUnexpectedClassicSnap(c *C) {
   610  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   611  		"display-name":   "my model",
   612  		"architecture":   "amd64",
   613  		"gadget":         "pc",
   614  		"kernel":         "pc-kernel",
   615  		"required-snaps": []interface{}{"classic-snap"},
   616  	})
   617  
   618  	s.makeSnap(c, "core", "")
   619  	s.makeSnap(c, "pc-kernel", "")
   620  	s.makeSnap(c, "pc", "")
   621  	s.makeSnap(c, "classic-snap", "developerid")
   622  
   623  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
   624  	c.Check(err, ErrorMatches, `cannot use classic snap "classic-snap" in a core system`)
   625  }
   626  
   627  func (s *writerSuite) TestDownloadedPublisherMismatchKernel(c *C) {
   628  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   629  		"display-name": "my model",
   630  		"architecture": "amd64",
   631  		"gadget":       "pc",
   632  		"kernel":       "pc-kernel",
   633  	})
   634  
   635  	s.makeSnap(c, "core", "")
   636  	s.makeSnap(c, "pc-kernel", "developerid")
   637  	s.makeSnap(c, "pc", "")
   638  
   639  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
   640  	c.Check(err, ErrorMatches, `cannot use kernel "pc-kernel" published by "developerid" for model by "my-brand"`)
   641  }
   642  
   643  func (s *writerSuite) TestDownloadedPublisherMismatchGadget(c *C) {
   644  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   645  		"display-name": "my model",
   646  		"architecture": "amd64",
   647  		"gadget":       "pc",
   648  		"kernel":       "pc-kernel",
   649  	})
   650  
   651  	s.makeSnap(c, "core", "")
   652  	s.makeSnap(c, "pc-kernel", "")
   653  	s.makeSnap(c, "pc", "developerid")
   654  
   655  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
   656  	c.Check(err, ErrorMatches, `cannot use gadget "pc" published by "developerid" for model by "my-brand"`)
   657  }
   658  
   659  func (s *writerSuite) TestDownloadedMissingDefaultProvider(c *C) {
   660  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   661  		"display-name":   "my model",
   662  		"architecture":   "amd64",
   663  		"base":           "core18",
   664  		"gadget":         "pc=18",
   665  		"kernel":         "pc-kernel=18",
   666  		"required-snaps": []interface{}{"cont-consumer"},
   667  	})
   668  
   669  	s.makeSnap(c, "snapd", "")
   670  	s.makeSnap(c, "core18", "")
   671  	s.makeSnap(c, "pc-kernel=18", "")
   672  	s.makeSnap(c, "pc=18", "")
   673  	s.makeSnap(c, "cont-consumer", "developerid")
   674  
   675  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
   676  	c.Check(err, ErrorMatches, `cannot use snap "cont-consumer" without its default content provider "cont-producer" being added explicitly`)
   677  }
   678  
   679  func (s *writerSuite) TestDownloadedCheckType(c *C) {
   680  	s.makeSnap(c, "snapd", "")
   681  	s.makeSnap(c, "core18", "")
   682  	s.makeSnap(c, "core", "")
   683  	s.makeSnap(c, "pc-kernel=18", "")
   684  	s.makeSnap(c, "pc=18", "")
   685  	s.makeSnap(c, "cont-producer", "developerid")
   686  	s.makeSnap(c, "cont-consumer", "developerid")
   687  
   688  	core18headers := map[string]interface{}{
   689  		"display-name":   "my model",
   690  		"architecture":   "amd64",
   691  		"base":           "core18",
   692  		"gadget":         "pc=18",
   693  		"kernel":         "pc-kernel=18",
   694  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
   695  	}
   696  
   697  	tests := []struct {
   698  		header        string
   699  		wrongTypeSnap string
   700  		what          string
   701  	}{
   702  		{"base", "core", "boot base"},
   703  		{"gadget", "cont-consumer", "gadget"},
   704  		{"kernel", "cont-consumer", "kernel"},
   705  		{"required-snaps", "pc", "snap"},
   706  		{"required-snaps", "pc-kernel", "snap"},
   707  	}
   708  
   709  	for _, t := range tests {
   710  		var wrongTypeSnap interface{} = t.wrongTypeSnap
   711  		if t.header == "required-snaps" {
   712  			wrongTypeSnap = []interface{}{wrongTypeSnap}
   713  		}
   714  		model := s.Brands.Model("my-brand", "my-model", core18headers, map[string]interface{}{
   715  			t.header: wrongTypeSnap,
   716  		})
   717  
   718  		_, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap)
   719  
   720  		expErr := fmt.Sprintf("%s %q has unexpected type: %v", t.what, t.wrongTypeSnap, s.AssertedSnapInfo(t.wrongTypeSnap).Type())
   721  		c.Check(err, ErrorMatches, expErr)
   722  	}
   723  }
   724  
   725  func (s *writerSuite) TestDownloadedCheckTypeSnapd(c *C) {
   726  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   727  		"display-name": "my model",
   728  		"architecture": "amd64",
   729  		"base":         "core18",
   730  		"gadget":       "pc=18",
   731  		"kernel":       "pc-kernel=18",
   732  	})
   733  
   734  	s.makeSnap(c, "snapd", "")
   735  	s.makeSnap(c, "core18", "")
   736  	s.makeSnap(c, "pc-kernel=18", "")
   737  	s.makeSnap(c, "pc=18", "")
   738  
   739  	// break type
   740  	s.AssertedSnapInfo("snapd").SnapType = snap.TypeGadget
   741  	_, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap)
   742  	c.Check(err, ErrorMatches, `snapd snap has unexpected type: gadget`)
   743  }
   744  
   745  func (s *writerSuite) TestDownloadedCheckTypeCore(c *C) {
   746  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   747  		"display-name": "my model",
   748  		"architecture": "amd64",
   749  		"gadget":       "pc",
   750  		"kernel":       "pc-kernel",
   751  	})
   752  
   753  	s.makeSnap(c, "core", "")
   754  	s.makeSnap(c, "pc-kernel", "")
   755  	s.makeSnap(c, "pc", "")
   756  
   757  	// break type
   758  	s.AssertedSnapInfo("core").SnapType = snap.TypeBase
   759  	_, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap)
   760  	c.Check(err, ErrorMatches, `core snap has unexpected type: base`)
   761  }
   762  
   763  func (s *writerSuite) TestSeedSnapsWriteMetaCore18(c *C) {
   764  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   765  		"display-name":   "my model",
   766  		"architecture":   "amd64",
   767  		"base":           "core18",
   768  		"gadget":         "pc=18",
   769  		"kernel":         "pc-kernel=18",
   770  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
   771  	})
   772  
   773  	s.makeSnap(c, "snapd", "")
   774  	s.makeSnap(c, "core18", "")
   775  	s.makeSnap(c, "pc-kernel=18", "")
   776  	s.makeSnap(c, "pc=18", "")
   777  	s.makeSnap(c, "cont-producer", "developerid")
   778  	s.makeSnap(c, "cont-consumer", "developerid")
   779  
   780  	w, err := seedwriter.New(model, s.opts)
   781  	c.Assert(err, IsNil)
   782  
   783  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}})
   784  	c.Assert(err, IsNil)
   785  
   786  	_, err = w.Start(s.db, s.newFetcher)
   787  	c.Assert(err, IsNil)
   788  
   789  	snaps, err := w.SnapsToDownload()
   790  	c.Assert(err, IsNil)
   791  	c.Check(snaps, HasLen, 6)
   792  
   793  	s.AssertedSnapInfo("cont-producer").Contact = "author@cont-producer.net"
   794  	for _, sn := range snaps {
   795  		s.fillDownloadedSnap(c, w, sn)
   796  	}
   797  
   798  	complete, err := w.Downloaded()
   799  	c.Assert(err, IsNil)
   800  	c.Check(complete, Equals, true)
   801  
   802  	err = w.SeedSnaps(nil)
   803  	c.Assert(err, IsNil)
   804  
   805  	err = w.WriteMeta()
   806  	c.Assert(err, IsNil)
   807  
   808  	// check seed
   809  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
   810  	c.Assert(err, IsNil)
   811  
   812  	c.Check(seedYaml.Snaps, HasLen, 6)
   813  
   814  	// check the files are in place
   815  	for i, name := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} {
   816  		info := s.AssertedSnapInfo(name)
   817  
   818  		fn := info.Filename()
   819  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
   820  		c.Check(p, testutil.FilePresent)
   821  
   822  		channel := "stable"
   823  		switch name {
   824  		case "pc-kernel":
   825  			channel = "18"
   826  		case "pc":
   827  			channel = "18/edge"
   828  		}
   829  
   830  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
   831  			Name:    info.SnapName(),
   832  			SnapID:  info.SnapID,
   833  			Channel: channel,
   834  			File:    fn,
   835  			Contact: info.Contact,
   836  		})
   837  	}
   838  
   839  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
   840  	c.Assert(err, IsNil)
   841  	c.Check(l, HasLen, 6)
   842  
   843  	// check assertions
   844  	seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions")
   845  	storeAccountKeyPK := s.StoreSigning.StoreAccountKey("").PublicKeyID()
   846  	brandAcctKeyPK := s.Brands.AccountKey("my-brand").PublicKeyID()
   847  
   848  	for _, fn := range []string{"model", brandAcctKeyPK + ".account-key", "my-brand.account", storeAccountKeyPK + ".account-key"} {
   849  		p := filepath.Join(seedAssertsDir, fn)
   850  		c.Check(p, testutil.FilePresent)
   851  	}
   852  
   853  	c.Check(filepath.Join(seedAssertsDir, "model"), testutil.FileEquals, asserts.Encode(model))
   854  
   855  	acct := seedtest.ReadAssertions(c, filepath.Join(seedAssertsDir, "my-brand.account"))
   856  	c.Assert(acct, HasLen, 1)
   857  	c.Check(acct[0].Type(), Equals, asserts.AccountType)
   858  	c.Check(acct[0].HeaderString("account-id"), Equals, "my-brand")
   859  
   860  	// check the snap assertions are also in place
   861  	for _, snapName := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} {
   862  		p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName)))
   863  		decl := seedtest.ReadAssertions(c, p)
   864  		c.Assert(decl, HasLen, 1)
   865  		c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType)
   866  		c.Check(decl[0].HeaderString("snap-name"), Equals, snapName)
   867  		p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384()))
   868  		rev := seedtest.ReadAssertions(c, p)
   869  		c.Assert(rev, HasLen, 1)
   870  		c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType)
   871  		c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName))
   872  	}
   873  }
   874  
   875  func (s *writerSuite) TestSeedSnapsWriteMetaCore18StoreAssertion(c *C) {
   876  	// add store assertion
   877  	storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{
   878  		"store":       "my-store",
   879  		"operator-id": "canonical",
   880  		"timestamp":   time.Now().UTC().Format(time.RFC3339),
   881  	}, nil, "")
   882  	c.Assert(err, IsNil)
   883  	err = s.StoreSigning.Add(storeAs)
   884  	c.Assert(err, IsNil)
   885  
   886  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   887  		"display-name": "my model",
   888  		"architecture": "amd64",
   889  		"base":         "core18",
   890  		"gadget":       "pc=18",
   891  		"kernel":       "pc-kernel=18",
   892  		"store":        "my-store",
   893  	})
   894  
   895  	s.makeSnap(c, "snapd", "")
   896  	s.makeSnap(c, "core18", "")
   897  	s.makeSnap(c, "pc-kernel=18", "")
   898  	s.makeSnap(c, "pc=18", "")
   899  
   900  	complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
   901  	c.Assert(err, IsNil)
   902  	c.Check(complete, Equals, true)
   903  
   904  	err = w.SeedSnaps(nil)
   905  	c.Assert(err, IsNil)
   906  
   907  	err = w.WriteMeta()
   908  	c.Assert(err, IsNil)
   909  
   910  	// check assertions
   911  	seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions")
   912  	// check the store assertion was fetched
   913  	p := filepath.Join(seedAssertsDir, "my-store.store")
   914  	c.Check(p, testutil.FilePresent)
   915  }
   916  
   917  func (s *writerSuite) TestLocalSnaps(c *C) {
   918  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   919  		"display-name":   "my model",
   920  		"architecture":   "amd64",
   921  		"base":           "core18",
   922  		"gadget":         "pc=18",
   923  		"kernel":         "pc-kernel=18",
   924  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
   925  	})
   926  
   927  	core18Fn := s.makeLocalSnap(c, "core18")
   928  	pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18")
   929  	pcFn := s.makeLocalSnap(c, "pc=18")
   930  	contConsumerFn := s.makeLocalSnap(c, "cont-consumer")
   931  
   932  	w, err := seedwriter.New(model, s.opts)
   933  	c.Assert(err, IsNil)
   934  
   935  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
   936  		{Path: core18Fn},
   937  		{Path: pcFn, Channel: "edge"},
   938  		{Path: pcKernelFn},
   939  		{Path: contConsumerFn},
   940  	})
   941  	c.Assert(err, IsNil)
   942  
   943  	_, err = w.Start(s.db, s.newFetcher)
   944  	c.Assert(err, IsNil)
   945  
   946  	localSnaps, err := w.LocalSnaps()
   947  	c.Assert(err, IsNil)
   948  	c.Assert(localSnaps, HasLen, 4)
   949  	c.Check(localSnaps[0].Path, Equals, core18Fn)
   950  	c.Check(localSnaps[1].Path, Equals, pcFn)
   951  	c.Check(localSnaps[2].Path, Equals, pcKernelFn)
   952  	c.Check(localSnaps[3].Path, Equals, contConsumerFn)
   953  }
   954  
   955  func (s *writerSuite) TestLocalSnapsCore18FullUse(c *C) {
   956  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   957  		"display-name":   "my model",
   958  		"architecture":   "amd64",
   959  		"base":           "core18",
   960  		"gadget":         "pc=18",
   961  		"kernel":         "pc-kernel=18",
   962  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
   963  	})
   964  
   965  	s.makeSnap(c, "snapd", "")
   966  	s.makeSnap(c, "cont-producer", "developerid")
   967  
   968  	core18Fn := s.makeLocalSnap(c, "core18")
   969  	pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18")
   970  	pcFn := s.makeLocalSnap(c, "pc=18")
   971  	contConsumerFn := s.makeLocalSnap(c, "cont-consumer")
   972  
   973  	w, err := seedwriter.New(model, s.opts)
   974  	c.Assert(err, IsNil)
   975  
   976  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
   977  		{Path: core18Fn},
   978  		{Name: "pc-kernel", Channel: "candidate"},
   979  		{Path: pcFn, Channel: "edge"},
   980  		{Path: pcKernelFn},
   981  		{Path: s.AssertedSnap("cont-producer")},
   982  		{Path: contConsumerFn},
   983  	})
   984  	c.Assert(err, IsNil)
   985  
   986  	tf, err := w.Start(s.db, s.newFetcher)
   987  	c.Assert(err, IsNil)
   988  
   989  	localSnaps, err := w.LocalSnaps()
   990  	c.Assert(err, IsNil)
   991  	c.Assert(localSnaps, HasLen, 5)
   992  
   993  	for _, sn := range localSnaps {
   994  		si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
   995  		if !asserts.IsNotFound(err) {
   996  			c.Assert(err, IsNil)
   997  		}
   998  		f, err := snapfile.Open(sn.Path)
   999  		c.Assert(err, IsNil)
  1000  		info, err := snap.ReadInfoFromSnapFile(f, si)
  1001  		c.Assert(err, IsNil)
  1002  		w.SetInfo(sn, info)
  1003  		sn.ARefs = aRefs
  1004  	}
  1005  
  1006  	err = w.InfoDerived()
  1007  	c.Assert(err, IsNil)
  1008  
  1009  	snaps, err := w.SnapsToDownload()
  1010  	c.Assert(err, IsNil)
  1011  	c.Check(snaps, HasLen, 1)
  1012  	c.Check(naming.SameSnap(snaps[0], naming.Snap("snapd")), Equals, true)
  1013  
  1014  	for _, sn := range snaps {
  1015  		s.fillDownloadedSnap(c, w, sn)
  1016  	}
  1017  
  1018  	complete, err := w.Downloaded()
  1019  	c.Assert(err, IsNil)
  1020  	c.Check(complete, Equals, true)
  1021  
  1022  	copySnap := func(name, src, dst string) error {
  1023  		return osutil.CopyFile(src, dst, 0)
  1024  	}
  1025  
  1026  	err = w.SeedSnaps(copySnap)
  1027  	c.Assert(err, IsNil)
  1028  
  1029  	err = w.WriteMeta()
  1030  	c.Assert(err, IsNil)
  1031  
  1032  	// check seed
  1033  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1034  	c.Assert(err, IsNil)
  1035  
  1036  	c.Check(seedYaml.Snaps, HasLen, 6)
  1037  
  1038  	assertedNum := 0
  1039  	// check the files are in place
  1040  	for i, name := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} {
  1041  		info := s.AssertedSnapInfo(name)
  1042  		unasserted := false
  1043  		if info == nil {
  1044  			info = &snap.Info{
  1045  				SuggestedName: name,
  1046  			}
  1047  			info.Revision = snap.R(-1)
  1048  			unasserted = true
  1049  		} else {
  1050  			assertedNum++
  1051  		}
  1052  
  1053  		fn := info.Filename()
  1054  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1055  		c.Check(p, testutil.FilePresent)
  1056  
  1057  		channel := ""
  1058  		if !unasserted {
  1059  			channel = "stable"
  1060  		}
  1061  
  1062  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
  1063  			Name:       info.SnapName(),
  1064  			SnapID:     info.SnapID,
  1065  			Channel:    channel,
  1066  			File:       fn,
  1067  			Unasserted: unasserted,
  1068  		})
  1069  	}
  1070  	c.Check(assertedNum, Equals, 2)
  1071  
  1072  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  1073  	c.Assert(err, IsNil)
  1074  	c.Check(l, HasLen, 6)
  1075  
  1076  	// check the snap assertions are in place
  1077  	seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions")
  1078  	for _, snapName := range []string{"snapd", "cont-producer"} {
  1079  		p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName)))
  1080  		decl := seedtest.ReadAssertions(c, p)
  1081  		c.Assert(decl, HasLen, 1)
  1082  		c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType)
  1083  		c.Check(decl[0].HeaderString("snap-name"), Equals, snapName)
  1084  		p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384()))
  1085  		rev := seedtest.ReadAssertions(c, p)
  1086  		c.Assert(rev, HasLen, 1)
  1087  		c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType)
  1088  		c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName))
  1089  	}
  1090  
  1091  	unassertedSnaps, err := w.UnassertedSnaps()
  1092  	c.Assert(err, IsNil)
  1093  	c.Check(unassertedSnaps, HasLen, 4)
  1094  	unassertedSet := naming.NewSnapSet(unassertedSnaps)
  1095  	for _, snapName := range []string{"core18", "pc-kernel", "pc", "cont-consumer"} {
  1096  		c.Check(unassertedSet.Contains(naming.Snap(snapName)), Equals, true)
  1097  	}
  1098  }
  1099  
  1100  func (s *writerSuite) TestSeedSnapsWriteMetaDefaultTrackCore18(c *C) {
  1101  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1102  		"display-name":   "my model",
  1103  		"architecture":   "amd64",
  1104  		"base":           "core18",
  1105  		"gadget":         "pc=18",
  1106  		"kernel":         "pc-kernel=18",
  1107  		"required-snaps": []interface{}{"required18"},
  1108  	})
  1109  
  1110  	s.makeSnap(c, "snapd", "")
  1111  	s.makeSnap(c, "core18", "")
  1112  	s.makeSnap(c, "pc-kernel=18", "")
  1113  	s.makeSnap(c, "pc=18", "")
  1114  	s.makeSnap(c, "required18", "developerid")
  1115  
  1116  	w, err := seedwriter.New(model, s.opts)
  1117  	c.Assert(err, IsNil)
  1118  
  1119  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "required18", Channel: "candidate"}})
  1120  	c.Assert(err, IsNil)
  1121  
  1122  	_, err = w.Start(s.db, s.newFetcher)
  1123  	c.Assert(err, IsNil)
  1124  
  1125  	snaps, err := w.SnapsToDownload()
  1126  	c.Assert(err, IsNil)
  1127  	c.Check(snaps, HasLen, 5)
  1128  
  1129  	for _, sn := range snaps {
  1130  		s.fillDownloadedSnap(c, w, sn)
  1131  		if sn.SnapName() == "required18" {
  1132  			c.Check(sn.Channel, Equals, "candidate")
  1133  			w.SetRedirectChannel(sn, "default-track/candidate")
  1134  		}
  1135  	}
  1136  
  1137  	complete, err := w.Downloaded()
  1138  	c.Assert(err, IsNil)
  1139  	c.Check(complete, Equals, true)
  1140  
  1141  	err = w.SeedSnaps(nil)
  1142  	c.Assert(err, IsNil)
  1143  
  1144  	err = w.WriteMeta()
  1145  	c.Assert(err, IsNil)
  1146  
  1147  	// check seed
  1148  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1149  	c.Assert(err, IsNil)
  1150  
  1151  	c.Check(seedYaml.Snaps, HasLen, 5)
  1152  
  1153  	info := s.AssertedSnapInfo("required18")
  1154  	fn := info.Filename()
  1155  	c.Check(filepath.Join(s.opts.SeedDir, "snaps", fn), testutil.FilePresent)
  1156  	c.Check(seedYaml.Snaps[4], DeepEquals, &seedwriter.InternalSnap16{
  1157  		Name:    info.SnapName(),
  1158  		SnapID:  info.SnapID,
  1159  		Channel: "default-track/candidate",
  1160  		File:    fn,
  1161  	})
  1162  
  1163  }
  1164  
  1165  func (s *writerSuite) TestSetRedirectChannelErrors(c *C) {
  1166  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1167  		"display-name":   "my model",
  1168  		"architecture":   "amd64",
  1169  		"base":           "core18",
  1170  		"gadget":         "pc=18",
  1171  		"kernel":         "pc-kernel=18",
  1172  		"required-snaps": []interface{}{"required18"},
  1173  	})
  1174  
  1175  	s.makeSnap(c, "snapd", "")
  1176  	s.makeSnap(c, "core18", "")
  1177  	s.makeSnap(c, "pc-kernel=18", "")
  1178  	s.makeSnap(c, "pc=18", "")
  1179  	s.makeSnap(c, "required18", "developerid")
  1180  
  1181  	w, err := seedwriter.New(model, s.opts)
  1182  	c.Assert(err, IsNil)
  1183  
  1184  	_, err = w.Start(s.db, s.newFetcher)
  1185  	c.Assert(err, IsNil)
  1186  
  1187  	snaps, err := w.SnapsToDownload()
  1188  	c.Assert(err, IsNil)
  1189  	c.Check(snaps, HasLen, 5)
  1190  
  1191  	sn := snaps[4]
  1192  	c.Assert(sn.SnapName(), Equals, "required18")
  1193  
  1194  	c.Check(w.SetRedirectChannel(sn, "default-track/stable"), ErrorMatches, `internal error: before using seedwriter.Writer.SetRedirectChannel snap "required18" Info should have been set`)
  1195  
  1196  	s.fillDownloadedSnap(c, w, sn)
  1197  
  1198  	c.Check(w.SetRedirectChannel(sn, "default-track//stable"), ErrorMatches, `invalid redirect channel for snap "required18":.*`)
  1199  }
  1200  
  1201  func (s *writerSuite) TestInfoDerivedInfosNotSet(c *C) {
  1202  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1203  		"display-name":   "my model",
  1204  		"architecture":   "amd64",
  1205  		"base":           "core18",
  1206  		"gadget":         "pc=18",
  1207  		"kernel":         "pc-kernel=18",
  1208  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1209  	})
  1210  
  1211  	core18Fn := s.makeLocalSnap(c, "core18")
  1212  	pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18")
  1213  	pcFn := s.makeLocalSnap(c, "pc=18")
  1214  
  1215  	w, err := seedwriter.New(model, s.opts)
  1216  	c.Assert(err, IsNil)
  1217  
  1218  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
  1219  		{Path: core18Fn},
  1220  		{Path: pcFn, Channel: "edge"},
  1221  		{Path: pcKernelFn},
  1222  	})
  1223  	c.Assert(err, IsNil)
  1224  
  1225  	_, err = w.Start(s.db, s.newFetcher)
  1226  	c.Assert(err, IsNil)
  1227  
  1228  	_, err = w.LocalSnaps()
  1229  	c.Assert(err, IsNil)
  1230  
  1231  	err = w.InfoDerived()
  1232  	c.Assert(err, ErrorMatches, `internal error: before seedwriter.Writer.InfoDerived snap ".*/core18.*.snap" Info should have been set`)
  1233  }
  1234  
  1235  func (s *writerSuite) TestInfoDerivedRepeatedLocalSnap(c *C) {
  1236  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1237  		"display-name":   "my model",
  1238  		"architecture":   "amd64",
  1239  		"base":           "core18",
  1240  		"gadget":         "pc=18",
  1241  		"kernel":         "pc-kernel=18",
  1242  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1243  	})
  1244  
  1245  	core18Fn := s.makeLocalSnap(c, "core18")
  1246  	pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18")
  1247  	pcFn := s.makeLocalSnap(c, "pc=18")
  1248  
  1249  	w, err := seedwriter.New(model, s.opts)
  1250  	c.Assert(err, IsNil)
  1251  
  1252  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
  1253  		{Path: core18Fn},
  1254  		{Path: pcFn, Channel: "edge"},
  1255  		{Path: pcKernelFn},
  1256  		{Path: core18Fn},
  1257  	})
  1258  	c.Assert(err, IsNil)
  1259  
  1260  	_, err = w.Start(s.db, s.newFetcher)
  1261  	c.Assert(err, IsNil)
  1262  
  1263  	localSnaps, err := w.LocalSnaps()
  1264  	c.Assert(err, IsNil)
  1265  	c.Check(localSnaps, HasLen, 4)
  1266  
  1267  	for _, sn := range localSnaps {
  1268  		f, err := snapfile.Open(sn.Path)
  1269  		c.Assert(err, IsNil)
  1270  		info, err := snap.ReadInfoFromSnapFile(f, nil)
  1271  		c.Assert(err, IsNil)
  1272  		w.SetInfo(sn, info)
  1273  	}
  1274  
  1275  	err = w.InfoDerived()
  1276  	c.Assert(err, ErrorMatches, `local snap "core18" is repeated in options`)
  1277  }
  1278  
  1279  func (s *writerSuite) TestInfoDerivedInconsistentChannel(c *C) {
  1280  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1281  		"display-name":   "my model",
  1282  		"architecture":   "amd64",
  1283  		"base":           "core18",
  1284  		"gadget":         "pc=18",
  1285  		"kernel":         "pc-kernel=18",
  1286  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1287  	})
  1288  
  1289  	core18Fn := s.makeLocalSnap(c, "core18")
  1290  	pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18")
  1291  	pcFn := s.makeLocalSnap(c, "pc=18")
  1292  
  1293  	w, err := seedwriter.New(model, s.opts)
  1294  	c.Assert(err, IsNil)
  1295  
  1296  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
  1297  		{Path: core18Fn},
  1298  		{Path: pcFn, Channel: "edge"},
  1299  		{Path: pcKernelFn},
  1300  		{Name: "pc", Channel: "beta"},
  1301  	})
  1302  	c.Assert(err, IsNil)
  1303  
  1304  	_, err = w.Start(s.db, s.newFetcher)
  1305  	c.Assert(err, IsNil)
  1306  
  1307  	localSnaps, err := w.LocalSnaps()
  1308  	c.Assert(err, IsNil)
  1309  	c.Check(localSnaps, HasLen, 3)
  1310  
  1311  	for _, sn := range localSnaps {
  1312  		f, err := snapfile.Open(sn.Path)
  1313  		c.Assert(err, IsNil)
  1314  		info, err := snap.ReadInfoFromSnapFile(f, nil)
  1315  		c.Assert(err, IsNil)
  1316  		w.SetInfo(sn, info)
  1317  	}
  1318  
  1319  	err = w.InfoDerived()
  1320  	c.Assert(err, ErrorMatches, `option snap has different channels specified: ".*/pc.*.snap"="edge" vs "pc"="beta"`)
  1321  }
  1322  
  1323  func (s *writerSuite) TestSetRedirectChannelLocalError(c *C) {
  1324  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1325  		"display-name": "my model",
  1326  		"architecture": "amd64",
  1327  		"base":         "core18",
  1328  		"gadget":       "pc=18",
  1329  		"kernel":       "pc-kernel=18",
  1330  	})
  1331  
  1332  	core18Fn := s.makeLocalSnap(c, "core18")
  1333  
  1334  	w, err := seedwriter.New(model, s.opts)
  1335  	c.Assert(err, IsNil)
  1336  
  1337  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
  1338  		{Path: core18Fn},
  1339  	})
  1340  	c.Assert(err, IsNil)
  1341  
  1342  	_, err = w.Start(s.db, s.newFetcher)
  1343  	c.Assert(err, IsNil)
  1344  
  1345  	localSnaps, err := w.LocalSnaps()
  1346  	c.Assert(err, IsNil)
  1347  	c.Check(localSnaps, HasLen, 1)
  1348  
  1349  	sn := localSnaps[0]
  1350  	f, err := snapfile.Open(sn.Path)
  1351  	c.Assert(err, IsNil)
  1352  	info, err := snap.ReadInfoFromSnapFile(f, nil)
  1353  	c.Assert(err, IsNil)
  1354  	err = w.SetInfo(sn, info)
  1355  	c.Assert(err, IsNil)
  1356  
  1357  	c.Check(w.SetRedirectChannel(sn, "foo"), ErrorMatches, `internal error: cannot set redirect channel for local snap .*`)
  1358  
  1359  }
  1360  
  1361  func (s *writerSuite) TestSeedSnapsWriteMetaClassicWithCore(c *C) {
  1362  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1363  		"classic":        "true",
  1364  		"architecture":   "amd64",
  1365  		"gadget":         "classic-gadget",
  1366  		"required-snaps": []interface{}{"required"},
  1367  	})
  1368  
  1369  	s.makeSnap(c, "core", "")
  1370  	s.makeSnap(c, "classic-gadget", "")
  1371  	s.makeSnap(c, "required", "developerid")
  1372  
  1373  	complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  1374  	c.Assert(err, IsNil)
  1375  	c.Check(complete, Equals, false)
  1376  
  1377  	snaps, err := w.SnapsToDownload()
  1378  	c.Assert(err, IsNil)
  1379  	c.Assert(snaps, HasLen, 1)
  1380  
  1381  	s.fillDownloadedSnap(c, w, snaps[0])
  1382  
  1383  	complete, err = w.Downloaded()
  1384  	c.Assert(err, IsNil)
  1385  	c.Check(complete, Equals, true)
  1386  
  1387  	_, err = w.BootSnaps()
  1388  	c.Check(err, ErrorMatches, "no snaps participating in boot on classic")
  1389  
  1390  	err = w.SeedSnaps(nil)
  1391  	c.Assert(err, IsNil)
  1392  
  1393  	err = w.WriteMeta()
  1394  	c.Assert(err, IsNil)
  1395  
  1396  	// check seed
  1397  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1398  	c.Assert(err, IsNil)
  1399  
  1400  	c.Check(seedYaml.Snaps, HasLen, 3)
  1401  
  1402  	// check the files are in place
  1403  	for i, name := range []string{"core", "classic-gadget", "required"} {
  1404  		info := s.AssertedSnapInfo(name)
  1405  
  1406  		fn := info.Filename()
  1407  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1408  		c.Check(p, testutil.FilePresent)
  1409  
  1410  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
  1411  			Name:    info.SnapName(),
  1412  			SnapID:  info.SnapID,
  1413  			Channel: "stable",
  1414  			File:    fn,
  1415  			Contact: info.Contact,
  1416  		})
  1417  	}
  1418  }
  1419  
  1420  func (s *writerSuite) TestSeedSnapsWriteMetaClassicSnapdOnly(c *C) {
  1421  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1422  		"classic":        "true",
  1423  		"architecture":   "amd64",
  1424  		"gadget":         "classic-gadget18",
  1425  		"required-snaps": []interface{}{"core18", "required18"},
  1426  	})
  1427  
  1428  	s.makeSnap(c, "snapd", "")
  1429  	s.makeSnap(c, "core18", "")
  1430  	s.makeSnap(c, "classic-gadget18", "")
  1431  	s.makeSnap(c, "required18", "developerid")
  1432  
  1433  	complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  1434  	c.Assert(err, IsNil)
  1435  	c.Check(complete, Equals, false)
  1436  
  1437  	snaps, err := w.SnapsToDownload()
  1438  	c.Assert(err, IsNil)
  1439  	c.Assert(snaps, HasLen, 1)
  1440  
  1441  	s.fillDownloadedSnap(c, w, snaps[0])
  1442  
  1443  	complete, err = w.Downloaded()
  1444  	c.Assert(err, IsNil)
  1445  	c.Check(complete, Equals, true)
  1446  
  1447  	err = w.SeedSnaps(nil)
  1448  	c.Assert(err, IsNil)
  1449  
  1450  	err = w.WriteMeta()
  1451  	c.Assert(err, IsNil)
  1452  
  1453  	// check seed
  1454  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1455  	c.Assert(err, IsNil)
  1456  	c.Assert(seedYaml.Snaps, HasLen, 4)
  1457  
  1458  	// check the files are in place
  1459  	for i, name := range []string{"snapd", "core18", "classic-gadget18", "required18"} {
  1460  		info := s.AssertedSnapInfo(name)
  1461  
  1462  		fn := info.Filename()
  1463  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1464  		c.Check(p, testutil.FilePresent)
  1465  
  1466  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
  1467  			Name:    info.SnapName(),
  1468  			SnapID:  info.SnapID,
  1469  			Channel: "stable",
  1470  			File:    fn,
  1471  			Contact: info.Contact,
  1472  		})
  1473  	}
  1474  }
  1475  
  1476  func (s *writerSuite) TestSeedSnapsWriteMetaClassicSnapdOnlyMissingCore16(c *C) {
  1477  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1478  		"classic":        "true",
  1479  		"architecture":   "amd64",
  1480  		"gadget":         "classic-gadget18",
  1481  		"required-snaps": []interface{}{"core18", "required-base-core16"},
  1482  	})
  1483  
  1484  	s.makeSnap(c, "snapd", "")
  1485  	s.makeSnap(c, "core18", "")
  1486  	s.makeSnap(c, "classic-gadget18", "")
  1487  	s.makeSnap(c, "required-base-core16", "developerid")
  1488  
  1489  	_, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap)
  1490  	c.Check(err, ErrorMatches, `cannot use "required-base-core16" requiring base "core16" without adding "core16" \(or "core"\) explicitly`)
  1491  }
  1492  
  1493  func (s *writerSuite) TestSeedSnapsWriteMetaExtraSnaps(c *C) {
  1494  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1495  		"display-name":   "my model",
  1496  		"architecture":   "amd64",
  1497  		"base":           "core18",
  1498  		"gadget":         "pc=18",
  1499  		"kernel":         "pc-kernel=18",
  1500  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1501  	})
  1502  
  1503  	s.makeSnap(c, "snapd", "")
  1504  	s.makeSnap(c, "core18", "")
  1505  	s.makeSnap(c, "pc-kernel=18", "")
  1506  	s.makeSnap(c, "pc=18", "")
  1507  	s.makeSnap(c, "cont-producer", "developerid")
  1508  	s.makeSnap(c, "cont-consumer", "developerid")
  1509  	s.makeSnap(c, "core", "")
  1510  	s.makeSnap(c, "required", "developerid")
  1511  
  1512  	w, err := seedwriter.New(model, s.opts)
  1513  	c.Assert(err, IsNil)
  1514  
  1515  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "required", Channel: "beta"}})
  1516  	c.Assert(err, IsNil)
  1517  
  1518  	_, err = w.Start(s.db, s.newFetcher)
  1519  	c.Assert(err, IsNil)
  1520  
  1521  	snaps, err := w.SnapsToDownload()
  1522  	c.Assert(err, IsNil)
  1523  	c.Assert(snaps, HasLen, 6)
  1524  
  1525  	s.AssertedSnapInfo("cont-producer").Contact = "author@cont-producer.net"
  1526  	for _, sn := range snaps {
  1527  		s.fillDownloadedSnap(c, w, sn)
  1528  	}
  1529  
  1530  	complete, err := w.Downloaded()
  1531  	c.Assert(err, IsNil)
  1532  	c.Assert(complete, Equals, false)
  1533  
  1534  	snaps, err = w.SnapsToDownload()
  1535  	c.Assert(err, IsNil)
  1536  	c.Assert(snaps, HasLen, 1)
  1537  	c.Check(naming.SameSnap(snaps[0], naming.Snap("required")), Equals, true)
  1538  
  1539  	s.fillDownloadedSnap(c, w, snaps[0])
  1540  
  1541  	complete, err = w.Downloaded()
  1542  	c.Assert(err, IsNil)
  1543  	c.Assert(complete, Equals, false)
  1544  
  1545  	snaps, err = w.SnapsToDownload()
  1546  	c.Assert(err, IsNil)
  1547  	c.Assert(snaps, HasLen, 1)
  1548  	c.Check(naming.SameSnap(snaps[0], naming.Snap("core")), Equals, true)
  1549  
  1550  	s.fillDownloadedSnap(c, w, snaps[0])
  1551  
  1552  	complete, err = w.Downloaded()
  1553  	c.Assert(err, IsNil)
  1554  	c.Assert(complete, Equals, true)
  1555  
  1556  	err = w.SeedSnaps(nil)
  1557  	c.Assert(err, IsNil)
  1558  
  1559  	err = w.WriteMeta()
  1560  	c.Assert(err, IsNil)
  1561  
  1562  	// check seed
  1563  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1564  	c.Assert(err, IsNil)
  1565  	c.Assert(seedYaml.Snaps, HasLen, 8)
  1566  
  1567  	// check the files are in place
  1568  	for i, name := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} {
  1569  		info := s.AssertedSnapInfo(name)
  1570  
  1571  		fn := info.Filename()
  1572  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1573  		c.Check(osutil.FileExists(p), Equals, true)
  1574  
  1575  		channel := "stable"
  1576  		switch name {
  1577  		case "pc-kernel", "pc":
  1578  			channel = "18"
  1579  		case "required":
  1580  			channel = "beta"
  1581  		}
  1582  
  1583  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
  1584  			Name:    info.SnapName(),
  1585  			SnapID:  info.SnapID,
  1586  			Channel: channel,
  1587  			File:    fn,
  1588  			Contact: info.Contact,
  1589  		})
  1590  	}
  1591  
  1592  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  1593  	c.Assert(err, IsNil)
  1594  	c.Check(l, HasLen, 8)
  1595  
  1596  	// check the snap assertions are also in place
  1597  	seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions")
  1598  	for _, snapName := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} {
  1599  		p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName)))
  1600  		decl := seedtest.ReadAssertions(c, p)
  1601  		c.Assert(decl, HasLen, 1)
  1602  		c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType)
  1603  		c.Check(decl[0].HeaderString("snap-name"), Equals, snapName)
  1604  		p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384()))
  1605  		rev := seedtest.ReadAssertions(c, p)
  1606  		c.Assert(rev, HasLen, 1)
  1607  		c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType)
  1608  		c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName))
  1609  	}
  1610  
  1611  	c.Check(w.Warnings(), DeepEquals, []string{
  1612  		`model has base "core18" but some snaps ("required") require "core" as base as well, for compatibility it was added implicitly, adding "core" explicitly is recommended`,
  1613  	})
  1614  }
  1615  
  1616  func (s *writerSuite) TestSeedSnapsWriteMetaLocalExtraSnaps(c *C) {
  1617  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1618  		"display-name":   "my model",
  1619  		"architecture":   "amd64",
  1620  		"base":           "core18",
  1621  		"gadget":         "pc=18",
  1622  		"kernel":         "pc-kernel=18",
  1623  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1624  	})
  1625  
  1626  	s.makeSnap(c, "snapd", "")
  1627  	s.makeSnap(c, "core18", "")
  1628  	s.makeSnap(c, "pc-kernel=18", "")
  1629  	s.makeSnap(c, "pc=18", "")
  1630  	s.makeSnap(c, "cont-producer", "developerid")
  1631  	s.makeSnap(c, "cont-consumer", "developerid")
  1632  	s.makeSnap(c, "core", "")
  1633  	requiredFn := s.makeLocalSnap(c, "required")
  1634  
  1635  	w, err := seedwriter.New(model, s.opts)
  1636  	c.Assert(err, IsNil)
  1637  
  1638  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}})
  1639  	c.Assert(err, IsNil)
  1640  
  1641  	tf, err := w.Start(s.db, s.newFetcher)
  1642  	c.Assert(err, IsNil)
  1643  
  1644  	localSnaps, err := w.LocalSnaps()
  1645  	c.Assert(err, IsNil)
  1646  	c.Assert(localSnaps, HasLen, 1)
  1647  
  1648  	for _, sn := range localSnaps {
  1649  		si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  1650  		if !asserts.IsNotFound(err) {
  1651  			c.Assert(err, IsNil)
  1652  		}
  1653  		f, err := snapfile.Open(sn.Path)
  1654  		c.Assert(err, IsNil)
  1655  		info, err := snap.ReadInfoFromSnapFile(f, si)
  1656  		c.Assert(err, IsNil)
  1657  		w.SetInfo(sn, info)
  1658  		sn.ARefs = aRefs
  1659  	}
  1660  
  1661  	err = w.InfoDerived()
  1662  	c.Assert(err, IsNil)
  1663  
  1664  	snaps, err := w.SnapsToDownload()
  1665  	c.Assert(err, IsNil)
  1666  	c.Assert(snaps, HasLen, 6)
  1667  
  1668  	s.AssertedSnapInfo("cont-producer").Contact = "author@cont-producer.net"
  1669  	for _, sn := range snaps {
  1670  		s.fillDownloadedSnap(c, w, sn)
  1671  	}
  1672  
  1673  	complete, err := w.Downloaded()
  1674  	c.Assert(err, IsNil)
  1675  	c.Assert(complete, Equals, false)
  1676  
  1677  	snaps, err = w.SnapsToDownload()
  1678  	c.Assert(err, IsNil)
  1679  	c.Assert(snaps, HasLen, 0)
  1680  
  1681  	complete, err = w.Downloaded()
  1682  	c.Assert(err, IsNil)
  1683  	c.Assert(complete, Equals, false)
  1684  
  1685  	snaps, err = w.SnapsToDownload()
  1686  	c.Assert(err, IsNil)
  1687  	c.Assert(snaps, HasLen, 1)
  1688  	c.Check(naming.SameSnap(snaps[0], naming.Snap("core")), Equals, true)
  1689  
  1690  	s.fillDownloadedSnap(c, w, snaps[0])
  1691  
  1692  	complete, err = w.Downloaded()
  1693  	c.Assert(err, IsNil)
  1694  	c.Assert(complete, Equals, true)
  1695  
  1696  	copySnap := func(name, src, dst string) error {
  1697  		return osutil.CopyFile(src, dst, 0)
  1698  	}
  1699  
  1700  	err = w.SeedSnaps(copySnap)
  1701  	c.Assert(err, IsNil)
  1702  
  1703  	err = w.WriteMeta()
  1704  	c.Assert(err, IsNil)
  1705  
  1706  	// check seed
  1707  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1708  	c.Assert(err, IsNil)
  1709  	c.Assert(seedYaml.Snaps, HasLen, 8)
  1710  
  1711  	// check the files are in place
  1712  	for i, name := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} {
  1713  		info := s.AssertedSnapInfo(name)
  1714  		unasserted := false
  1715  		if info == nil {
  1716  			info = &snap.Info{
  1717  				SuggestedName: name,
  1718  			}
  1719  			info.Revision = snap.R(-1)
  1720  			unasserted = true
  1721  		}
  1722  
  1723  		fn := info.Filename()
  1724  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1725  		c.Check(osutil.FileExists(p), Equals, true)
  1726  
  1727  		channel := ""
  1728  		if !unasserted {
  1729  			switch name {
  1730  			case "pc-kernel", "pc":
  1731  				channel = "18"
  1732  			default:
  1733  				channel = "stable"
  1734  			}
  1735  		}
  1736  
  1737  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
  1738  			Name:       info.SnapName(),
  1739  			SnapID:     info.SnapID,
  1740  			Channel:    channel,
  1741  			File:       fn,
  1742  			Contact:    info.Contact,
  1743  			Unasserted: unasserted,
  1744  		})
  1745  	}
  1746  
  1747  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  1748  	c.Assert(err, IsNil)
  1749  	c.Check(l, HasLen, 8)
  1750  
  1751  	unassertedSnaps, err := w.UnassertedSnaps()
  1752  	c.Assert(err, IsNil)
  1753  	c.Check(unassertedSnaps, HasLen, 1)
  1754  	c.Check(naming.SameSnap(unassertedSnaps[0], naming.Snap("required")), Equals, true)
  1755  }
  1756  
  1757  func (s *writerSuite) TestSeedSnapsWriteMetaCore20(c *C) {
  1758  	// add store assertion
  1759  	storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{
  1760  		"store":       "my-store",
  1761  		"operator-id": "canonical",
  1762  		"timestamp":   time.Now().UTC().Format(time.RFC3339),
  1763  	}, nil, "")
  1764  	c.Assert(err, IsNil)
  1765  	err = s.StoreSigning.Add(storeAs)
  1766  	c.Assert(err, IsNil)
  1767  
  1768  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1769  		"display-name": "my model",
  1770  		"architecture": "amd64",
  1771  		"store":        "my-store",
  1772  		"base":         "core20",
  1773  		"snaps": []interface{}{
  1774  			map[string]interface{}{
  1775  				"name":            "pc-kernel",
  1776  				"id":              s.AssertedSnapID("pc-kernel"),
  1777  				"type":            "kernel",
  1778  				"default-channel": "20",
  1779  			},
  1780  			map[string]interface{}{
  1781  				"name":            "pc",
  1782  				"id":              s.AssertedSnapID("pc"),
  1783  				"type":            "gadget",
  1784  				"default-channel": "20",
  1785  			},
  1786  			map[string]interface{}{
  1787  				"name": "core18",
  1788  				"id":   s.AssertedSnapID("core18"),
  1789  				"type": "base",
  1790  			},
  1791  			map[string]interface{}{
  1792  				"name": "cont-consumer",
  1793  				"id":   s.AssertedSnapID("cont-consumer"),
  1794  			},
  1795  			map[string]interface{}{
  1796  				"name": "cont-producer",
  1797  				"id":   s.AssertedSnapID("cont-producer"),
  1798  			},
  1799  		},
  1800  	})
  1801  
  1802  	// sanity
  1803  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  1804  
  1805  	s.makeSnap(c, "snapd", "")
  1806  	s.makeSnap(c, "core20", "")
  1807  	s.makeSnap(c, "core18", "")
  1808  	s.makeSnap(c, "pc-kernel=20", "")
  1809  	s.makeSnap(c, "pc=20", "")
  1810  	s.makeSnap(c, "cont-producer", "developerid")
  1811  	s.makeSnap(c, "cont-consumer", "developerid")
  1812  
  1813  	s.opts.Label = "20191003"
  1814  	w, err := seedwriter.New(model, s.opts)
  1815  	c.Assert(err, IsNil)
  1816  
  1817  	_, err = w.Start(s.db, s.newFetcher)
  1818  	c.Assert(err, IsNil)
  1819  
  1820  	snaps, err := w.SnapsToDownload()
  1821  	c.Assert(err, IsNil)
  1822  	c.Check(snaps, HasLen, 7)
  1823  
  1824  	s.AssertedSnapInfo("cont-producer").Contact = "author@cont-producer.net"
  1825  	s.AssertedSnapInfo("cont-consumer").Private = true
  1826  	for _, sn := range snaps {
  1827  		// check the used channel at this level because in the
  1828  		// non-dangerous case is not written anywhere (it
  1829  		// reflects the model or default)
  1830  		channel := "latest/stable"
  1831  		switch sn.SnapName() {
  1832  		case "pc", "pc-kernel":
  1833  			channel = "20"
  1834  		}
  1835  		c.Check(sn.Channel, Equals, channel)
  1836  		s.fillDownloadedSnap(c, w, sn)
  1837  	}
  1838  
  1839  	complete, err := w.Downloaded()
  1840  	c.Assert(err, IsNil)
  1841  	c.Check(complete, Equals, true)
  1842  
  1843  	err = w.SeedSnaps(nil)
  1844  	c.Assert(err, IsNil)
  1845  
  1846  	err = w.WriteMeta()
  1847  	c.Assert(err, IsNil)
  1848  
  1849  	// check seed
  1850  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  1851  	c.Check(systemDir, testutil.FilePresent)
  1852  
  1853  	// check the snaps are in place
  1854  	for _, name := range []string{"snapd", "pc-kernel", "core20", "pc", "core18", "cont-consumer", "cont-producer"} {
  1855  		info := s.AssertedSnapInfo(name)
  1856  
  1857  		fn := info.Filename()
  1858  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1859  		c.Check(p, testutil.FilePresent)
  1860  	}
  1861  
  1862  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  1863  	c.Assert(err, IsNil)
  1864  	c.Check(l, HasLen, 7)
  1865  
  1866  	// check assertions
  1867  	c.Check(filepath.Join(systemDir, "model"), testutil.FileEquals, asserts.Encode(model))
  1868  
  1869  	assertsDir := filepath.Join(systemDir, "assertions")
  1870  	modelEtc := seedtest.ReadAssertions(c, filepath.Join(assertsDir, "model-etc"))
  1871  	c.Check(modelEtc, HasLen, 4)
  1872  
  1873  	keyPKs := make(map[string]bool)
  1874  	for _, a := range modelEtc {
  1875  		switch a.Type() {
  1876  		case asserts.AccountType:
  1877  			c.Check(a.HeaderString("account-id"), Equals, "my-brand")
  1878  		case asserts.StoreType:
  1879  			c.Check(a.HeaderString("store"), Equals, "my-store")
  1880  		case asserts.AccountKeyType:
  1881  			keyPKs[a.HeaderString("public-key-sha3-384")] = true
  1882  		default:
  1883  			c.Fatalf("unexpected assertion %s", a.Type().Name)
  1884  		}
  1885  	}
  1886  	c.Check(keyPKs, DeepEquals, map[string]bool{
  1887  		s.StoreSigning.StoreAccountKey("").PublicKeyID(): true,
  1888  		s.Brands.AccountKey("my-brand").PublicKeyID():    true,
  1889  	})
  1890  
  1891  	// check snap assertions
  1892  	snapAsserts := seedtest.ReadAssertions(c, filepath.Join(assertsDir, "snaps"))
  1893  	seen := make(map[string]bool)
  1894  
  1895  	for _, a := range snapAsserts {
  1896  		uniq := a.Ref().Unique()
  1897  		if a.Type() == asserts.SnapRevisionType {
  1898  			rev := a.(*asserts.SnapRevision)
  1899  			uniq = fmt.Sprintf("%s@%d", rev.SnapID(), rev.SnapRevision())
  1900  		}
  1901  		seen[uniq] = true
  1902  	}
  1903  
  1904  	snapRevUniq := func(snapName string, revno int) string {
  1905  		return fmt.Sprintf("%s@%d", s.AssertedSnapID(snapName), revno)
  1906  	}
  1907  	snapDeclUniq := func(snapName string) string {
  1908  		return "snap-declaration/16/" + s.AssertedSnapID(snapName)
  1909  	}
  1910  
  1911  	c.Check(seen, DeepEquals, map[string]bool{
  1912  		"account/developerid":           true,
  1913  		snapDeclUniq("snapd"):           true,
  1914  		snapDeclUniq("pc-kernel"):       true,
  1915  		snapDeclUniq("pc"):              true,
  1916  		snapDeclUniq("core20"):          true,
  1917  		snapDeclUniq("core18"):          true,
  1918  		snapDeclUniq("cont-consumer"):   true,
  1919  		snapDeclUniq("cont-producer"):   true,
  1920  		snapRevUniq("snapd", 1):         true,
  1921  		snapRevUniq("pc-kernel", 1):     true,
  1922  		snapRevUniq("pc", 1):            true,
  1923  		snapRevUniq("core20", 1):        true,
  1924  		snapRevUniq("core18", 1):        true,
  1925  		snapRevUniq("cont-consumer", 1): true,
  1926  		snapRevUniq("cont-producer", 1): true,
  1927  	})
  1928  
  1929  	c.Check(filepath.Join(systemDir, "extra-snaps"), testutil.FileAbsent)
  1930  
  1931  	// check auxiliary store info
  1932  	l, err = ioutil.ReadDir(filepath.Join(systemDir, "snaps"))
  1933  	c.Assert(err, IsNil)
  1934  	c.Check(l, HasLen, 1)
  1935  
  1936  	b, err := ioutil.ReadFile(filepath.Join(systemDir, "snaps", "aux-info.json"))
  1937  	c.Assert(err, IsNil)
  1938  	var auxInfos map[string]map[string]interface{}
  1939  	err = json.Unmarshal(b, &auxInfos)
  1940  	c.Assert(err, IsNil)
  1941  	c.Check(auxInfos, DeepEquals, map[string]map[string]interface{}{
  1942  		s.AssertedSnapID("cont-consumer"): {
  1943  			"private": true,
  1944  		},
  1945  		s.AssertedSnapID("cont-producer"): {
  1946  			"contact": "author@cont-producer.net",
  1947  		},
  1948  	})
  1949  
  1950  	c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent)
  1951  }
  1952  
  1953  func (s *writerSuite) TestCore20InvalidLabel(c *C) {
  1954  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1955  		"display-name": "my model",
  1956  		"architecture": "amd64",
  1957  		"store":        "my-store",
  1958  		"base":         "core20",
  1959  		"snaps": []interface{}{
  1960  			map[string]interface{}{
  1961  				"name":            "pc-kernel",
  1962  				"id":              s.AssertedSnapID("pc-kernel"),
  1963  				"type":            "kernel",
  1964  				"default-channel": "20",
  1965  			},
  1966  			map[string]interface{}{
  1967  				"name":            "pc",
  1968  				"id":              s.AssertedSnapID("pc"),
  1969  				"type":            "gadget",
  1970  				"default-channel": "20",
  1971  			},
  1972  		},
  1973  	})
  1974  
  1975  	invalid := []string{
  1976  		"-",
  1977  		"a.b",
  1978  		"aa--b",
  1979  	}
  1980  
  1981  	for _, inv := range invalid {
  1982  		s.opts.Label = inv
  1983  		w, err := seedwriter.New(model, s.opts)
  1984  		c.Assert(w, IsNil)
  1985  		c.Check(err, ErrorMatches, fmt.Sprintf(`invalid seed system label: %q`, inv))
  1986  	}
  1987  }
  1988  
  1989  func (s *writerSuite) TestDownloadedCore20CheckBase(c *C) {
  1990  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1991  		"display-name": "my model",
  1992  		"architecture": "amd64",
  1993  		"store":        "my-store",
  1994  		"base":         "core20",
  1995  		"snaps": []interface{}{
  1996  			map[string]interface{}{
  1997  				"name":            "pc-kernel",
  1998  				"id":              s.AssertedSnapID("pc-kernel"),
  1999  				"type":            "kernel",
  2000  				"default-channel": "20",
  2001  			},
  2002  			map[string]interface{}{
  2003  				"name":            "pc",
  2004  				"id":              s.AssertedSnapID("pc"),
  2005  				"type":            "gadget",
  2006  				"default-channel": "20",
  2007  			},
  2008  			map[string]interface{}{
  2009  				"name": "cont-producer",
  2010  				"id":   s.AssertedSnapID("cont-producer"),
  2011  			},
  2012  		},
  2013  	})
  2014  
  2015  	// sanity
  2016  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2017  
  2018  	s.makeSnap(c, "snapd", "")
  2019  	s.makeSnap(c, "core20", "")
  2020  	s.makeSnap(c, "core18", "")
  2021  	s.makeSnap(c, "pc-kernel=20", "")
  2022  	s.makeSnap(c, "pc=20", "")
  2023  	s.makeSnap(c, "cont-producer", "developerid")
  2024  
  2025  	s.opts.Label = "20191003"
  2026  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  2027  	c.Check(err, ErrorMatches, `cannot add snap "cont-producer" without also adding its base "core18" explicitly`)
  2028  }
  2029  
  2030  func (s *writerSuite) TestDownloadedCore20CheckBaseModes(c *C) {
  2031  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2032  		"display-name": "my model",
  2033  		"architecture": "amd64",
  2034  		"store":        "my-store",
  2035  		"base":         "core20",
  2036  		"snaps": []interface{}{
  2037  			map[string]interface{}{
  2038  				"name":            "pc-kernel",
  2039  				"id":              s.AssertedSnapID("pc-kernel"),
  2040  				"type":            "kernel",
  2041  				"default-channel": "20",
  2042  			},
  2043  			map[string]interface{}{
  2044  				"name":            "pc",
  2045  				"id":              s.AssertedSnapID("pc"),
  2046  				"type":            "gadget",
  2047  				"default-channel": "20",
  2048  			},
  2049  			map[string]interface{}{
  2050  				"name": "core18",
  2051  				"id":   s.AssertedSnapID("core18"),
  2052  				"type": "base",
  2053  			},
  2054  			map[string]interface{}{
  2055  				"name":  "cont-producer",
  2056  				"id":    s.AssertedSnapID("cont-producer"),
  2057  				"modes": []interface{}{"run", "ephemeral"},
  2058  			},
  2059  		},
  2060  	})
  2061  
  2062  	// sanity
  2063  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2064  
  2065  	s.makeSnap(c, "snapd", "")
  2066  	s.makeSnap(c, "core20", "")
  2067  	s.makeSnap(c, "core18", "")
  2068  	s.makeSnap(c, "pc-kernel=20", "")
  2069  	s.makeSnap(c, "pc=20", "")
  2070  	s.makeSnap(c, "cont-producer", "developerid")
  2071  
  2072  	s.opts.Label = "20191003"
  2073  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  2074  	c.Check(err, ErrorMatches, `cannot add snap "cont-producer" without also adding its base "core18" explicitly for all relevant modes \(run, ephemeral\)`)
  2075  }
  2076  
  2077  func (s *writerSuite) TestDownloadedCore20CheckBaseEphemeralOK(c *C) {
  2078  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2079  		"display-name": "my model",
  2080  		"architecture": "amd64",
  2081  		"store":        "my-store",
  2082  		"base":         "core20",
  2083  		"snaps": []interface{}{
  2084  			map[string]interface{}{
  2085  				"name":            "pc-kernel",
  2086  				"id":              s.AssertedSnapID("pc-kernel"),
  2087  				"type":            "kernel",
  2088  				"default-channel": "20",
  2089  			},
  2090  			map[string]interface{}{
  2091  				"name":            "pc",
  2092  				"id":              s.AssertedSnapID("pc"),
  2093  				"type":            "gadget",
  2094  				"default-channel": "20",
  2095  			},
  2096  			map[string]interface{}{
  2097  				"name":  "core18",
  2098  				"id":    s.AssertedSnapID("core18"),
  2099  				"type":  "base",
  2100  				"modes": []interface{}{"ephemeral"},
  2101  			},
  2102  			map[string]interface{}{
  2103  				"name":  "cont-producer",
  2104  				"id":    s.AssertedSnapID("cont-producer"),
  2105  				"modes": []interface{}{"recover"},
  2106  			},
  2107  		},
  2108  	})
  2109  
  2110  	// sanity
  2111  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2112  
  2113  	s.makeSnap(c, "snapd", "")
  2114  	s.makeSnap(c, "core20", "")
  2115  	s.makeSnap(c, "core18", "")
  2116  	s.makeSnap(c, "pc-kernel=20", "")
  2117  	s.makeSnap(c, "pc=20", "")
  2118  	s.makeSnap(c, "cont-producer", "developerid")
  2119  
  2120  	s.opts.Label = "20191003"
  2121  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  2122  	c.Check(err, IsNil)
  2123  }
  2124  
  2125  func (s *writerSuite) TestDownloadedCore20CheckBaseCoreXX(c *C) {
  2126  	s.makeSnap(c, "snapd", "")
  2127  	s.makeSnap(c, "core20", "")
  2128  	s.makeSnap(c, "pc-kernel=20", "")
  2129  	s.makeSnap(c, "pc=20", "")
  2130  	s.makeSnap(c, "core", "")
  2131  	s.makeSnap(c, "required", "")
  2132  	s.makeSnap(c, "required-base-core16", "")
  2133  
  2134  	coreEnt := map[string]interface{}{
  2135  		"name": "core",
  2136  		"id":   s.AssertedSnapID("core"),
  2137  		"type": "core",
  2138  	}
  2139  	requiredEnt := map[string]interface{}{
  2140  		"name": "required",
  2141  		"id":   s.AssertedSnapID("required"),
  2142  	}
  2143  
  2144  	requiredBaseCore16Ent := map[string]interface{}{
  2145  		"name": "required-base-core16",
  2146  		"id":   s.AssertedSnapID("required-base-core16"),
  2147  	}
  2148  
  2149  	tests := []struct {
  2150  		snaps []interface{}
  2151  		err   string
  2152  	}{
  2153  		{[]interface{}{coreEnt, requiredEnt}, ""},
  2154  		{[]interface{}{coreEnt, requiredBaseCore16Ent}, ""},
  2155  		{[]interface{}{requiredEnt}, `cannot add snap "required" without also adding its base "core" explicitly`},
  2156  		{[]interface{}{requiredBaseCore16Ent}, `cannot add snap "required-base-core16" without also adding its base "core16" \(or "core"\) explicitly`},
  2157  	}
  2158  
  2159  	s.opts.Label = "20191003"
  2160  	for _, t := range tests {
  2161  		snaps := []interface{}{
  2162  			map[string]interface{}{
  2163  				"name":            "pc-kernel",
  2164  				"id":              s.AssertedSnapID("pc-kernel"),
  2165  				"type":            "kernel",
  2166  				"default-channel": "20",
  2167  			},
  2168  			map[string]interface{}{
  2169  				"name":            "pc",
  2170  				"id":              s.AssertedSnapID("pc"),
  2171  				"type":            "gadget",
  2172  				"default-channel": "20",
  2173  			},
  2174  		}
  2175  
  2176  		snaps = append(snaps, t.snaps...)
  2177  
  2178  		model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2179  			"display-name": "my model",
  2180  			"architecture": "amd64",
  2181  			"store":        "my-store",
  2182  			"base":         "core20",
  2183  			"snaps":        snaps,
  2184  		})
  2185  
  2186  		_, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap)
  2187  		if t.err == "" {
  2188  			c.Check(err, IsNil)
  2189  		} else {
  2190  			c.Check(err, ErrorMatches, t.err)
  2191  		}
  2192  	}
  2193  }
  2194  func (s *writerSuite) TestDownloadedCore20MissingDefaultProviderModes(c *C) {
  2195  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2196  		"display-name": "my model",
  2197  		"architecture": "amd64",
  2198  		"store":        "my-store",
  2199  		"base":         "core20",
  2200  		"snaps": []interface{}{
  2201  			map[string]interface{}{
  2202  				"name":            "pc-kernel",
  2203  				"id":              s.AssertedSnapID("pc-kernel"),
  2204  				"type":            "kernel",
  2205  				"default-channel": "20",
  2206  			},
  2207  			map[string]interface{}{
  2208  				"name":            "pc",
  2209  				"id":              s.AssertedSnapID("pc"),
  2210  				"type":            "gadget",
  2211  				"default-channel": "20",
  2212  			},
  2213  			map[string]interface{}{
  2214  				"name":  "core18",
  2215  				"id":    s.AssertedSnapID("core18"),
  2216  				"type":  "base",
  2217  				"modes": []interface{}{"run", "ephemeral"},
  2218  			},
  2219  			map[string]interface{}{
  2220  				"name": "cont-producer",
  2221  				"id":   s.AssertedSnapID("cont-producer"),
  2222  			},
  2223  			map[string]interface{}{
  2224  				"name":  "cont-consumer",
  2225  				"id":    s.AssertedSnapID("cont-consumer"),
  2226  				"modes": []interface{}{"recover"},
  2227  			},
  2228  		},
  2229  	})
  2230  
  2231  	// sanity
  2232  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2233  
  2234  	s.makeSnap(c, "snapd", "")
  2235  	s.makeSnap(c, "core20", "")
  2236  	s.makeSnap(c, "core18", "")
  2237  	s.makeSnap(c, "pc-kernel=20", "")
  2238  	s.makeSnap(c, "pc=20", "")
  2239  	s.makeSnap(c, "cont-producer", "developerid")
  2240  	s.makeSnap(c, "cont-consumer", "developerid")
  2241  
  2242  	s.opts.Label = "20191003"
  2243  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  2244  	c.Check(err, ErrorMatches, `cannot use snap "cont-consumer" without its default content provider "cont-producer" being added explicitly for all relevant modes \(recover\)`)
  2245  }
  2246  
  2247  func (s *writerSuite) TestCore20NonDangerousDisallowedDevmodeSnaps(c *C) {
  2248  
  2249  	s.makeSnap(c, "my-devmode", "canonical")
  2250  
  2251  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2252  		"display-name": "my model",
  2253  		"architecture": "amd64",
  2254  		"store":        "my-store",
  2255  		"base":         "core20",
  2256  		"snaps": []interface{}{
  2257  			map[string]interface{}{
  2258  				"name":            "pc-kernel",
  2259  				"id":              s.AssertedSnapID("pc-kernel"),
  2260  				"type":            "kernel",
  2261  				"default-channel": "20",
  2262  			},
  2263  			map[string]interface{}{
  2264  				"name":            "pc",
  2265  				"id":              s.AssertedSnapID("pc"),
  2266  				"type":            "gadget",
  2267  				"default-channel": "20",
  2268  			},
  2269  			map[string]interface{}{
  2270  				"name": "my-devmode",
  2271  				"id":   s.AssertedSnapID("my-devmode"),
  2272  				"type": "app",
  2273  			},
  2274  		},
  2275  	})
  2276  
  2277  	s.opts.Label = "20191107"
  2278  
  2279  	const expectedErr = `cannot override channels, add local snaps or extra snaps with a model of grade higher than dangerous`
  2280  
  2281  	w, err := seedwriter.New(model, s.opts)
  2282  	c.Assert(err, IsNil)
  2283  
  2284  	_, err = w.Start(s.db, s.newFetcher)
  2285  	c.Assert(err, IsNil)
  2286  
  2287  	localSnaps, err := w.LocalSnaps()
  2288  	c.Assert(err, IsNil)
  2289  	c.Assert(localSnaps, HasLen, 0)
  2290  
  2291  	snaps, err := w.SnapsToDownload()
  2292  	c.Check(err, IsNil)
  2293  	c.Assert(snaps, HasLen, 5)
  2294  
  2295  	c.Assert(snaps[4].SnapName(), Equals, "my-devmode")
  2296  	sn := snaps[4]
  2297  
  2298  	info := s.AssertedSnapInfo(sn.SnapName())
  2299  	c.Assert(info, NotNil, Commentf("%s not defined", sn.SnapName()))
  2300  	err = w.SetInfo(sn, info)
  2301  	c.Assert(err, ErrorMatches, "cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous")
  2302  	c.Check(sn.Info, Not(Equals), info)
  2303  }
  2304  
  2305  func (s *writerSuite) TestCore20NonDangerousDisallowedOptionsSnaps(c *C) {
  2306  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2307  		"display-name": "my model",
  2308  		"architecture": "amd64",
  2309  		"store":        "my-store",
  2310  		"base":         "core20",
  2311  		"snaps": []interface{}{
  2312  			map[string]interface{}{
  2313  				"name":            "pc-kernel",
  2314  				"id":              s.AssertedSnapID("pc-kernel"),
  2315  				"type":            "kernel",
  2316  				"default-channel": "20",
  2317  			},
  2318  			map[string]interface{}{
  2319  				"name":            "pc",
  2320  				"id":              s.AssertedSnapID("pc"),
  2321  				"type":            "gadget",
  2322  				"default-channel": "20",
  2323  			},
  2324  		},
  2325  	})
  2326  
  2327  	pcFn := s.makeLocalSnap(c, "pc")
  2328  
  2329  	s.opts.Label = "20191107"
  2330  
  2331  	tests := []struct {
  2332  		optSnap *seedwriter.OptionsSnap
  2333  	}{
  2334  		{&seedwriter.OptionsSnap{Name: "extra"}},
  2335  		{&seedwriter.OptionsSnap{Path: pcFn}},
  2336  		{&seedwriter.OptionsSnap{Name: "pc", Channel: "edge"}},
  2337  	}
  2338  
  2339  	const expectedErr = `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous`
  2340  
  2341  	for _, t := range tests {
  2342  		w, err := seedwriter.New(model, s.opts)
  2343  		c.Assert(err, IsNil)
  2344  
  2345  		err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{t.optSnap})
  2346  		if err != nil {
  2347  			c.Check(err, ErrorMatches, expectedErr)
  2348  			continue
  2349  		}
  2350  
  2351  		tf, err := w.Start(s.db, s.newFetcher)
  2352  		c.Assert(err, IsNil)
  2353  
  2354  		if t.optSnap.Path != "" {
  2355  			localSnaps, err := w.LocalSnaps()
  2356  			c.Assert(err, IsNil)
  2357  			c.Assert(localSnaps, HasLen, 1)
  2358  
  2359  			for _, sn := range localSnaps {
  2360  				si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  2361  				if !asserts.IsNotFound(err) {
  2362  					c.Assert(err, IsNil)
  2363  				}
  2364  				f, err := snapfile.Open(sn.Path)
  2365  				c.Assert(err, IsNil)
  2366  				info, err := snap.ReadInfoFromSnapFile(f, si)
  2367  				c.Assert(err, IsNil)
  2368  				w.SetInfo(sn, info)
  2369  				sn.ARefs = aRefs
  2370  			}
  2371  
  2372  			err = w.InfoDerived()
  2373  			c.Check(err, ErrorMatches, expectedErr)
  2374  			continue
  2375  		}
  2376  
  2377  		_, err = w.SnapsToDownload()
  2378  		c.Check(err, ErrorMatches, expectedErr)
  2379  	}
  2380  }
  2381  
  2382  func (s *writerSuite) TestCore20NonDangerousNoChannelOverride(c *C) {
  2383  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2384  		"display-name": "my model",
  2385  		"architecture": "amd64",
  2386  		"store":        "my-store",
  2387  		"base":         "core20",
  2388  		"snaps": []interface{}{
  2389  			map[string]interface{}{
  2390  				"name":            "pc-kernel",
  2391  				"id":              s.AssertedSnapID("pc-kernel"),
  2392  				"type":            "kernel",
  2393  				"default-channel": "20",
  2394  			},
  2395  			map[string]interface{}{
  2396  				"name":            "pc",
  2397  				"id":              s.AssertedSnapID("pc"),
  2398  				"type":            "gadget",
  2399  				"default-channel": "20",
  2400  			},
  2401  		},
  2402  	})
  2403  
  2404  	s.opts.DefaultChannel = "stable"
  2405  	s.opts.Label = "20191107"
  2406  	w, err := seedwriter.New(model, s.opts)
  2407  	c.Assert(w, IsNil)
  2408  	c.Check(err, ErrorMatches, `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous`)
  2409  }
  2410  
  2411  func (s *writerSuite) TestSeedSnapsWriteMetaCore20LocalSnaps(c *C) {
  2412  	// add store assertion
  2413  	storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{
  2414  		"store":       "my-store",
  2415  		"operator-id": "canonical",
  2416  		"timestamp":   time.Now().UTC().Format(time.RFC3339),
  2417  	}, nil, "")
  2418  	c.Assert(err, IsNil)
  2419  	err = s.StoreSigning.Add(storeAs)
  2420  	c.Assert(err, IsNil)
  2421  
  2422  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2423  		"display-name": "my model",
  2424  		"architecture": "amd64",
  2425  		"store":        "my-store",
  2426  		"base":         "core20",
  2427  		"grade":        "dangerous",
  2428  		"snaps": []interface{}{
  2429  			map[string]interface{}{
  2430  				"name":            "pc-kernel",
  2431  				"id":              s.AssertedSnapID("pc-kernel"),
  2432  				"type":            "kernel",
  2433  				"default-channel": "20",
  2434  			},
  2435  			map[string]interface{}{
  2436  				"name":            "pc",
  2437  				"id":              s.AssertedSnapID("pc"),
  2438  				"type":            "gadget",
  2439  				"default-channel": "20",
  2440  			},
  2441  			map[string]interface{}{
  2442  				"name": "required20",
  2443  				"id":   s.AssertedSnapID("required20"),
  2444  			},
  2445  		},
  2446  	})
  2447  
  2448  	// sanity
  2449  	c.Assert(model.Grade(), Equals, asserts.ModelDangerous)
  2450  
  2451  	s.makeSnap(c, "snapd", "")
  2452  	s.makeSnap(c, "core20", "")
  2453  	s.makeSnap(c, "pc-kernel=20", "")
  2454  	s.makeSnap(c, "pc=20", "")
  2455  	requiredFn := s.makeLocalSnap(c, "required20")
  2456  
  2457  	s.opts.Label = "20191030"
  2458  	w, err := seedwriter.New(model, s.opts)
  2459  	c.Assert(err, IsNil)
  2460  
  2461  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}})
  2462  	c.Assert(err, IsNil)
  2463  
  2464  	tf, err := w.Start(s.db, s.newFetcher)
  2465  	c.Assert(err, IsNil)
  2466  
  2467  	localSnaps, err := w.LocalSnaps()
  2468  	c.Assert(err, IsNil)
  2469  	c.Assert(localSnaps, HasLen, 1)
  2470  
  2471  	for _, sn := range localSnaps {
  2472  		_, _, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  2473  		c.Assert(asserts.IsNotFound(err), Equals, true)
  2474  		f, err := snapfile.Open(sn.Path)
  2475  		c.Assert(err, IsNil)
  2476  		info, err := snap.ReadInfoFromSnapFile(f, nil)
  2477  		c.Assert(err, IsNil)
  2478  		w.SetInfo(sn, info)
  2479  	}
  2480  
  2481  	err = w.InfoDerived()
  2482  	c.Assert(err, IsNil)
  2483  
  2484  	snaps, err := w.SnapsToDownload()
  2485  	c.Assert(err, IsNil)
  2486  	c.Check(snaps, HasLen, 4)
  2487  
  2488  	for _, sn := range snaps {
  2489  		// check the used channel at this level because in the
  2490  		// non-dangerous case is not written anywhere (it
  2491  		// reflects the model or default)
  2492  		channel := "latest/stable"
  2493  		switch sn.SnapName() {
  2494  		case "pc", "pc-kernel":
  2495  			channel = "20"
  2496  		}
  2497  		c.Check(sn.Channel, Equals, channel)
  2498  		s.fillDownloadedSnap(c, w, sn)
  2499  	}
  2500  
  2501  	complete, err := w.Downloaded()
  2502  	c.Assert(err, IsNil)
  2503  	c.Check(complete, Equals, true)
  2504  
  2505  	copySnap := func(name, src, dst string) error {
  2506  		return osutil.CopyFile(src, dst, 0)
  2507  	}
  2508  
  2509  	err = w.SeedSnaps(copySnap)
  2510  	c.Assert(err, IsNil)
  2511  
  2512  	err = w.WriteMeta()
  2513  	c.Assert(err, IsNil)
  2514  
  2515  	// check seed
  2516  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  2517  	c.Check(systemDir, testutil.FilePresent)
  2518  
  2519  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  2520  	c.Assert(err, IsNil)
  2521  	c.Check(l, HasLen, 4)
  2522  
  2523  	// local unasserted snap was put in system snaps dir
  2524  	c.Check(filepath.Join(systemDir, "snaps", "required20_1.0.snap"), testutil.FilePresent)
  2525  
  2526  	options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml"))
  2527  	c.Assert(err, IsNil)
  2528  
  2529  	c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{
  2530  		{
  2531  			Name:       "required20",
  2532  			SnapID:     s.AssertedSnapID("required20"),
  2533  			Unasserted: "required20_1.0.snap",
  2534  		},
  2535  	})
  2536  }
  2537  
  2538  func (s *writerSuite) TestSeedSnapsWriteMetaCore20ChannelOverrides(c *C) {
  2539  	// add store assertion
  2540  	storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{
  2541  		"store":       "my-store",
  2542  		"operator-id": "canonical",
  2543  		"timestamp":   time.Now().UTC().Format(time.RFC3339),
  2544  	}, nil, "")
  2545  	c.Assert(err, IsNil)
  2546  	err = s.StoreSigning.Add(storeAs)
  2547  	c.Assert(err, IsNil)
  2548  
  2549  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2550  		"display-name": "my model",
  2551  		"architecture": "amd64",
  2552  		"store":        "my-store",
  2553  		"base":         "core20",
  2554  		"grade":        "dangerous",
  2555  		"snaps": []interface{}{
  2556  			map[string]interface{}{
  2557  				"name":            "pc-kernel",
  2558  				"id":              s.AssertedSnapID("pc-kernel"),
  2559  				"type":            "kernel",
  2560  				"default-channel": "20",
  2561  			},
  2562  			map[string]interface{}{
  2563  				"name":            "pc",
  2564  				"id":              s.AssertedSnapID("pc"),
  2565  				"type":            "gadget",
  2566  				"default-channel": "20",
  2567  			},
  2568  			map[string]interface{}{
  2569  				"name": "required20",
  2570  				"id":   s.AssertedSnapID("required20"),
  2571  			},
  2572  		},
  2573  	})
  2574  
  2575  	// sanity
  2576  	c.Assert(model.Grade(), Equals, asserts.ModelDangerous)
  2577  
  2578  	s.makeSnap(c, "snapd", "")
  2579  	s.makeSnap(c, "core20", "")
  2580  	s.makeSnap(c, "pc-kernel=20", "")
  2581  	s.makeSnap(c, "pc=20", "")
  2582  	s.makeSnap(c, "required20", "developerid")
  2583  
  2584  	s.opts.Label = "20191030"
  2585  	s.opts.DefaultChannel = "candidate"
  2586  	w, err := seedwriter.New(model, s.opts)
  2587  	c.Assert(err, IsNil)
  2588  
  2589  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}})
  2590  	c.Assert(err, IsNil)
  2591  
  2592  	_, err = w.Start(s.db, s.newFetcher)
  2593  	c.Assert(err, IsNil)
  2594  
  2595  	snaps, err := w.SnapsToDownload()
  2596  	c.Assert(err, IsNil)
  2597  	c.Check(snaps, HasLen, 5)
  2598  
  2599  	for _, sn := range snaps {
  2600  		s.fillDownloadedSnap(c, w, sn)
  2601  	}
  2602  
  2603  	complete, err := w.Downloaded()
  2604  	c.Assert(err, IsNil)
  2605  	c.Check(complete, Equals, true)
  2606  
  2607  	err = w.SeedSnaps(nil)
  2608  	c.Assert(err, IsNil)
  2609  
  2610  	err = w.WriteMeta()
  2611  	c.Assert(err, IsNil)
  2612  
  2613  	// check seed
  2614  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  2615  	c.Check(systemDir, testutil.FilePresent)
  2616  
  2617  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  2618  	c.Assert(err, IsNil)
  2619  	c.Check(l, HasLen, 5)
  2620  
  2621  	options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml"))
  2622  	c.Assert(err, IsNil)
  2623  
  2624  	c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{
  2625  		{
  2626  			Name:    "snapd",
  2627  			SnapID:  s.AssertedSnapID("snapd"), // inferred
  2628  			Channel: "latest/candidate",
  2629  		},
  2630  		{
  2631  			Name:    "pc-kernel",
  2632  			SnapID:  s.AssertedSnapID("pc-kernel"),
  2633  			Channel: "20/candidate",
  2634  		},
  2635  		{
  2636  			Name:    "core20",
  2637  			SnapID:  s.AssertedSnapID("core20"), // inferred
  2638  			Channel: "latest/candidate",
  2639  		},
  2640  		{
  2641  			Name:    "pc",
  2642  			SnapID:  s.AssertedSnapID("pc"),
  2643  			Channel: "20/edge",
  2644  		},
  2645  		{
  2646  			Name:    "required20",
  2647  			SnapID:  s.AssertedSnapID("required20"),
  2648  			Channel: "latest/candidate",
  2649  		},
  2650  	})
  2651  }
  2652  
  2653  func (s *writerSuite) TestSeedSnapsWriteMetaCore20ModelOverrideSnapd(c *C) {
  2654  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2655  		"display-name": "my model",
  2656  		"architecture": "amd64",
  2657  		"base":         "core20",
  2658  		"snaps": []interface{}{
  2659  			map[string]interface{}{
  2660  				"name":            "snapd",
  2661  				"id":              s.AssertedSnapID("snapd"),
  2662  				"type":            "snapd",
  2663  				"default-channel": "latest/edge",
  2664  			},
  2665  			map[string]interface{}{
  2666  				"name":            "pc-kernel",
  2667  				"id":              s.AssertedSnapID("pc-kernel"),
  2668  				"type":            "kernel",
  2669  				"default-channel": "20",
  2670  			},
  2671  			map[string]interface{}{
  2672  				"name":            "pc",
  2673  				"id":              s.AssertedSnapID("pc"),
  2674  				"type":            "gadget",
  2675  				"default-channel": "20",
  2676  			}},
  2677  	})
  2678  
  2679  	// sanity
  2680  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2681  
  2682  	s.makeSnap(c, "snapd", "")
  2683  	s.makeSnap(c, "core20", "")
  2684  	s.makeSnap(c, "pc-kernel=20", "")
  2685  	s.makeSnap(c, "pc=20", "")
  2686  
  2687  	s.opts.Label = "20191121"
  2688  	w, err := seedwriter.New(model, s.opts)
  2689  	c.Assert(err, IsNil)
  2690  
  2691  	_, err = w.Start(s.db, s.newFetcher)
  2692  	c.Assert(err, IsNil)
  2693  
  2694  	snaps, err := w.SnapsToDownload()
  2695  	c.Assert(err, IsNil)
  2696  	c.Check(snaps, HasLen, 4)
  2697  
  2698  	for _, sn := range snaps {
  2699  		// check the used channel at this level because in the
  2700  		// non-dangerous case is not written anywhere (it
  2701  		// reflects the model or default)
  2702  		channel := "latest/stable"
  2703  		switch sn.SnapName() {
  2704  		case "snapd":
  2705  			channel = "latest/edge"
  2706  		case "pc", "pc-kernel":
  2707  			channel = "20"
  2708  		}
  2709  		c.Check(sn.Channel, Equals, channel)
  2710  		s.fillDownloadedSnap(c, w, sn)
  2711  	}
  2712  
  2713  	complete, err := w.Downloaded()
  2714  	c.Assert(err, IsNil)
  2715  	c.Check(complete, Equals, true)
  2716  
  2717  	err = w.SeedSnaps(nil)
  2718  	c.Assert(err, IsNil)
  2719  
  2720  	err = w.WriteMeta()
  2721  	c.Assert(err, IsNil)
  2722  
  2723  	// check seed
  2724  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  2725  	c.Check(systemDir, testutil.FilePresent)
  2726  
  2727  	// check the snaps are in place
  2728  	for _, name := range []string{"snapd", "pc-kernel", "core20", "pc"} {
  2729  		info := s.AssertedSnapInfo(name)
  2730  
  2731  		fn := info.Filename()
  2732  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  2733  		c.Check(p, testutil.FilePresent)
  2734  	}
  2735  
  2736  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  2737  	c.Assert(err, IsNil)
  2738  	c.Check(l, HasLen, 4)
  2739  
  2740  	c.Check(filepath.Join(systemDir, "extra-snaps"), testutil.FileAbsent)
  2741  	c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent)
  2742  }
  2743  
  2744  func (s *writerSuite) TestSnapsToDownloadCore20OptionalSnaps(c *C) {
  2745  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2746  		"display-name": "my model",
  2747  		"architecture": "amd64",
  2748  		"base":         "core20",
  2749  		"snaps": []interface{}{
  2750  			map[string]interface{}{
  2751  				"name":            "pc-kernel",
  2752  				"id":              s.AssertedSnapID("pc-kernel"),
  2753  				"type":            "kernel",
  2754  				"default-channel": "20",
  2755  			},
  2756  			map[string]interface{}{
  2757  				"name":            "pc",
  2758  				"id":              s.AssertedSnapID("pc"),
  2759  				"type":            "gadget",
  2760  				"default-channel": "20",
  2761  			},
  2762  			map[string]interface{}{
  2763  				"name": "core18",
  2764  				"id":   s.AssertedSnapID("core18"),
  2765  				"type": "base",
  2766  			},
  2767  			map[string]interface{}{
  2768  				"name":     "optional20-a",
  2769  				"id":       s.AssertedSnapID("optional20-a"),
  2770  				"presence": "optional",
  2771  			},
  2772  			map[string]interface{}{
  2773  				"name":     "optional20-b",
  2774  				"id":       s.AssertedSnapID("optional20-b"),
  2775  				"presence": "optional",
  2776  			}},
  2777  	})
  2778  
  2779  	// sanity
  2780  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2781  
  2782  	s.makeSnap(c, "snapd", "")
  2783  	s.makeSnap(c, "core20", "")
  2784  	s.makeSnap(c, "core18", "")
  2785  	s.makeSnap(c, "pc-kernel=20", "")
  2786  	s.makeSnap(c, "pc=20", "")
  2787  	s.makeSnap(c, "optional20-a", "developerid")
  2788  	s.makeSnap(c, "optional20-b", "developerid")
  2789  
  2790  	s.opts.Label = "20191122"
  2791  	w, err := seedwriter.New(model, s.opts)
  2792  	c.Assert(err, IsNil)
  2793  
  2794  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "optional20-b"}})
  2795  	c.Assert(err, IsNil)
  2796  
  2797  	_, err = w.Start(s.db, s.newFetcher)
  2798  	c.Assert(err, IsNil)
  2799  
  2800  	snaps, err := w.SnapsToDownload()
  2801  	c.Assert(err, IsNil)
  2802  	c.Check(snaps, HasLen, 6)
  2803  	c.Check(snaps[5].SnapName(), Equals, "optional20-b")
  2804  }
  2805  
  2806  func (s *writerSuite) TestSeedSnapsWriteMetaCore20ExtraSnaps(c *C) {
  2807  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2808  		"display-name": "my model",
  2809  		"architecture": "amd64",
  2810  		"base":         "core20",
  2811  		"grade":        "dangerous",
  2812  		"snaps": []interface{}{
  2813  			map[string]interface{}{
  2814  				"name":            "pc-kernel",
  2815  				"id":              s.AssertedSnapID("pc-kernel"),
  2816  				"type":            "kernel",
  2817  				"default-channel": "20",
  2818  			},
  2819  			map[string]interface{}{
  2820  				"name":            "pc",
  2821  				"id":              s.AssertedSnapID("pc"),
  2822  				"type":            "gadget",
  2823  				"default-channel": "20",
  2824  			}},
  2825  	})
  2826  
  2827  	// sanity
  2828  	c.Assert(model.Grade(), Equals, asserts.ModelDangerous)
  2829  
  2830  	s.makeSnap(c, "snapd", "")
  2831  	s.makeSnap(c, "core20", "")
  2832  	s.makeSnap(c, "pc-kernel=20", "")
  2833  	s.makeSnap(c, "pc=20", "")
  2834  	s.makeSnap(c, "core18", "")
  2835  	s.makeSnap(c, "cont-producer", "developerid")
  2836  	contConsumerFn := s.makeLocalSnap(c, "cont-consumer")
  2837  
  2838  	s.opts.Label = "20191122"
  2839  	w, err := seedwriter.New(model, s.opts)
  2840  	c.Assert(err, IsNil)
  2841  
  2842  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "cont-producer", Channel: "edge"}, {Name: "core18"}, {Path: contConsumerFn}})
  2843  	c.Assert(err, IsNil)
  2844  
  2845  	tf, err := w.Start(s.db, s.newFetcher)
  2846  	c.Assert(err, IsNil)
  2847  
  2848  	localSnaps, err := w.LocalSnaps()
  2849  	c.Assert(err, IsNil)
  2850  	c.Assert(localSnaps, HasLen, 1)
  2851  
  2852  	for _, sn := range localSnaps {
  2853  		_, _, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  2854  		c.Assert(asserts.IsNotFound(err), Equals, true)
  2855  		f, err := snapfile.Open(sn.Path)
  2856  		c.Assert(err, IsNil)
  2857  		info, err := snap.ReadInfoFromSnapFile(f, nil)
  2858  		c.Assert(err, IsNil)
  2859  		w.SetInfo(sn, info)
  2860  	}
  2861  
  2862  	err = w.InfoDerived()
  2863  	c.Assert(err, IsNil)
  2864  
  2865  	snaps, err := w.SnapsToDownload()
  2866  	c.Assert(err, IsNil)
  2867  	c.Check(snaps, HasLen, 4)
  2868  
  2869  	for _, sn := range snaps {
  2870  		channel := "latest/stable"
  2871  		switch sn.SnapName() {
  2872  		case "pc", "pc-kernel":
  2873  			channel = "20"
  2874  		}
  2875  		c.Check(sn.Channel, Equals, channel)
  2876  		s.fillDownloadedSnap(c, w, sn)
  2877  	}
  2878  
  2879  	complete, err := w.Downloaded()
  2880  	c.Assert(err, IsNil)
  2881  	c.Check(complete, Equals, false)
  2882  
  2883  	snaps, err = w.SnapsToDownload()
  2884  	c.Assert(err, IsNil)
  2885  	c.Assert(snaps, HasLen, 2)
  2886  	c.Check(snaps[0].SnapName(), Equals, "cont-producer")
  2887  	c.Check(snaps[1].SnapName(), Equals, "core18")
  2888  
  2889  	for _, sn := range snaps {
  2890  		channel := "latest/stable"
  2891  		switch sn.SnapName() {
  2892  		case "cont-producer":
  2893  			channel = "latest/edge"
  2894  		}
  2895  		c.Check(sn.Channel, Equals, channel)
  2896  
  2897  		info := s.doFillMetaDownloadedSnap(c, w, sn)
  2898  
  2899  		c.Assert(sn.Path, Equals, filepath.Join(s.opts.SeedDir, "systems", s.opts.Label, "snaps", info.Filename()))
  2900  		err := os.Rename(s.AssertedSnap(sn.SnapName()), sn.Path)
  2901  		c.Assert(err, IsNil)
  2902  	}
  2903  
  2904  	complete, err = w.Downloaded()
  2905  	c.Assert(err, IsNil)
  2906  	c.Check(complete, Equals, true)
  2907  
  2908  	copySnap := func(name, src, dst string) error {
  2909  		return osutil.CopyFile(src, dst, 0)
  2910  	}
  2911  
  2912  	err = w.SeedSnaps(copySnap)
  2913  	c.Assert(err, IsNil)
  2914  
  2915  	err = w.WriteMeta()
  2916  	c.Assert(err, IsNil)
  2917  
  2918  	// check seed
  2919  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  2920  	c.Check(systemDir, testutil.FilePresent)
  2921  
  2922  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  2923  	c.Assert(err, IsNil)
  2924  	c.Check(l, HasLen, 4)
  2925  
  2926  	// extra snaps were put in system snaps dir
  2927  	c.Check(filepath.Join(systemDir, "snaps", "core18_1.snap"), testutil.FilePresent)
  2928  	c.Check(filepath.Join(systemDir, "snaps", "cont-producer_1.snap"), testutil.FilePresent)
  2929  	c.Check(filepath.Join(systemDir, "snaps", "cont-consumer_1.0.snap"), testutil.FilePresent)
  2930  
  2931  	// check extra-snaps in assertions
  2932  	snapAsserts := seedtest.ReadAssertions(c, filepath.Join(systemDir, "assertions", "extra-snaps"))
  2933  	seen := make(map[string]bool)
  2934  
  2935  	for _, a := range snapAsserts {
  2936  		uniq := a.Ref().Unique()
  2937  		if a.Type() == asserts.SnapRevisionType {
  2938  			rev := a.(*asserts.SnapRevision)
  2939  			uniq = fmt.Sprintf("%s@%d", rev.SnapID(), rev.SnapRevision())
  2940  		}
  2941  		seen[uniq] = true
  2942  	}
  2943  
  2944  	snapRevUniq := func(snapName string, revno int) string {
  2945  		return fmt.Sprintf("%s@%d", s.AssertedSnapID(snapName), revno)
  2946  	}
  2947  	snapDeclUniq := func(snapName string) string {
  2948  		return "snap-declaration/16/" + s.AssertedSnapID(snapName)
  2949  	}
  2950  
  2951  	c.Check(seen, DeepEquals, map[string]bool{
  2952  		"account/developerid":           true,
  2953  		snapDeclUniq("core18"):          true,
  2954  		snapDeclUniq("cont-producer"):   true,
  2955  		snapRevUniq("core18", 1):        true,
  2956  		snapRevUniq("cont-producer", 1): true,
  2957  	})
  2958  
  2959  	options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml"))
  2960  	c.Assert(err, IsNil)
  2961  
  2962  	c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{
  2963  		{
  2964  			Name:    "cont-producer",
  2965  			SnapID:  s.AssertedSnapID("cont-producer"),
  2966  			Channel: "latest/edge",
  2967  		},
  2968  		{
  2969  			Name:    "core18",
  2970  			SnapID:  s.AssertedSnapID("core18"),
  2971  			Channel: "latest/stable",
  2972  		},
  2973  		{
  2974  			Name:       "cont-consumer",
  2975  			Unasserted: "cont-consumer_1.0.snap",
  2976  		},
  2977  	})
  2978  }
  2979  
  2980  func (s *writerSuite) TestSeedSnapsWriteMetaCore20LocalAssertedSnaps(c *C) {
  2981  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2982  		"display-name": "my model",
  2983  		"architecture": "amd64",
  2984  		"base":         "core20",
  2985  		"grade":        "dangerous",
  2986  		"snaps": []interface{}{
  2987  			map[string]interface{}{
  2988  				"name":            "pc-kernel",
  2989  				"id":              s.AssertedSnapID("pc-kernel"),
  2990  				"type":            "kernel",
  2991  				"default-channel": "20",
  2992  			},
  2993  			map[string]interface{}{
  2994  				"name":            "pc",
  2995  				"id":              s.AssertedSnapID("pc"),
  2996  				"type":            "gadget",
  2997  				"default-channel": "20",
  2998  			}},
  2999  	})
  3000  
  3001  	// sanity
  3002  	c.Assert(model.Grade(), Equals, asserts.ModelDangerous)
  3003  
  3004  	s.makeSnap(c, "snapd", "")
  3005  	s.makeSnap(c, "core20", "")
  3006  	s.makeSnap(c, "pc-kernel=20", "")
  3007  	s.makeSnap(c, "pc=20", "")
  3008  	s.makeSnap(c, "required20", "developerid")
  3009  
  3010  	s.opts.Label = "20191122"
  3011  	w, err := seedwriter.New(model, s.opts)
  3012  	c.Assert(err, IsNil)
  3013  
  3014  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: s.AssertedSnap("pc"), Channel: "edge"}, {Path: s.AssertedSnap("required20")}})
  3015  	c.Assert(err, IsNil)
  3016  
  3017  	tf, err := w.Start(s.db, s.newFetcher)
  3018  	c.Assert(err, IsNil)
  3019  
  3020  	localSnaps, err := w.LocalSnaps()
  3021  	c.Assert(err, IsNil)
  3022  	c.Assert(localSnaps, HasLen, 2)
  3023  
  3024  	for _, sn := range localSnaps {
  3025  		si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  3026  		c.Assert(err, IsNil)
  3027  		f, err := snapfile.Open(sn.Path)
  3028  		c.Assert(err, IsNil)
  3029  		info, err := snap.ReadInfoFromSnapFile(f, si)
  3030  		c.Assert(err, IsNil)
  3031  		w.SetInfo(sn, info)
  3032  		sn.ARefs = aRefs
  3033  	}
  3034  
  3035  	err = w.InfoDerived()
  3036  	c.Assert(err, IsNil)
  3037  
  3038  	snaps, err := w.SnapsToDownload()
  3039  	c.Assert(err, IsNil)
  3040  	c.Check(snaps, HasLen, 3)
  3041  
  3042  	for _, sn := range snaps {
  3043  		channel := "latest/stable"
  3044  		switch sn.SnapName() {
  3045  		case "pc", "pc-kernel":
  3046  			channel = "20"
  3047  		}
  3048  		c.Check(sn.Channel, Equals, channel)
  3049  		s.fillDownloadedSnap(c, w, sn)
  3050  	}
  3051  
  3052  	complete, err := w.Downloaded()
  3053  	c.Assert(err, IsNil)
  3054  	c.Check(complete, Equals, false)
  3055  
  3056  	snaps, err = w.SnapsToDownload()
  3057  	c.Assert(err, IsNil)
  3058  	c.Assert(snaps, HasLen, 0)
  3059  
  3060  	complete, err = w.Downloaded()
  3061  	c.Assert(err, IsNil)
  3062  	c.Check(complete, Equals, true)
  3063  
  3064  	copySnap := func(name, src, dst string) error {
  3065  		return osutil.CopyFile(src, dst, 0)
  3066  	}
  3067  
  3068  	err = w.SeedSnaps(copySnap)
  3069  	c.Assert(err, IsNil)
  3070  
  3071  	err = w.WriteMeta()
  3072  	c.Assert(err, IsNil)
  3073  
  3074  	// check seed
  3075  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  3076  	c.Check(systemDir, testutil.FilePresent)
  3077  
  3078  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  3079  	c.Assert(err, IsNil)
  3080  	c.Check(l, HasLen, 4)
  3081  
  3082  	// local asserted model snap was put in /snaps
  3083  	c.Check(filepath.Join(s.opts.SeedDir, "snaps", "pc_1.snap"), testutil.FilePresent)
  3084  	// extra snaps were put in system snaps dir
  3085  	c.Check(filepath.Join(systemDir, "snaps", "required20_1.snap"), testutil.FilePresent)
  3086  
  3087  	options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml"))
  3088  	c.Assert(err, IsNil)
  3089  
  3090  	c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{
  3091  		{
  3092  			Name:    "pc",
  3093  			SnapID:  s.AssertedSnapID("pc"),
  3094  			Channel: "20/edge",
  3095  		},
  3096  		{
  3097  			Name:    "required20",
  3098  			SnapID:  s.AssertedSnapID("required20"),
  3099  			Channel: "latest/stable",
  3100  		},
  3101  	})
  3102  }
  3103  
  3104  func (s *writerSuite) TestSeedSnapsWriteMetaCore20SignedLocalAssertedSnaps(c *C) {
  3105  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  3106  		"display-name": "my model",
  3107  		"architecture": "amd64",
  3108  		"base":         "core20",
  3109  		"grade":        "signed",
  3110  		"snaps": []interface{}{
  3111  			map[string]interface{}{
  3112  				"name":            "pc-kernel",
  3113  				"id":              s.AssertedSnapID("pc-kernel"),
  3114  				"type":            "kernel",
  3115  				"default-channel": "20",
  3116  			},
  3117  			map[string]interface{}{
  3118  				"name":            "pc",
  3119  				"id":              s.AssertedSnapID("pc"),
  3120  				"type":            "gadget",
  3121  				"default-channel": "20",
  3122  			}},
  3123  	})
  3124  
  3125  	// soundness
  3126  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  3127  
  3128  	s.makeSnap(c, "snapd", "")
  3129  	s.makeSnap(c, "core20", "")
  3130  	s.makeSnap(c, "pc-kernel=20", "")
  3131  	s.makeSnap(c, "pc=20", "")
  3132  
  3133  	s.opts.Label = "20191122"
  3134  	w, err := seedwriter.New(model, s.opts)
  3135  	c.Assert(err, IsNil)
  3136  
  3137  	// use a local asserted snap with signed, which is supported
  3138  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: s.AssertedSnap("pc")}})
  3139  	c.Assert(err, IsNil)
  3140  
  3141  	tf, err := w.Start(s.db, s.newFetcher)
  3142  	c.Assert(err, IsNil)
  3143  
  3144  	localSnaps, err := w.LocalSnaps()
  3145  	c.Assert(err, IsNil)
  3146  	c.Assert(localSnaps, HasLen, 1)
  3147  
  3148  	for _, sn := range localSnaps {
  3149  		si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  3150  		c.Assert(err, IsNil)
  3151  		f, err := snapfile.Open(sn.Path)
  3152  		c.Assert(err, IsNil)
  3153  		info, err := snap.ReadInfoFromSnapFile(f, si)
  3154  		c.Assert(err, IsNil)
  3155  		w.SetInfo(sn, info)
  3156  		sn.ARefs = aRefs
  3157  	}
  3158  
  3159  	err = w.InfoDerived()
  3160  	c.Assert(err, IsNil)
  3161  
  3162  	snaps, err := w.SnapsToDownload()
  3163  	c.Assert(err, IsNil)
  3164  	c.Check(snaps, HasLen, 3)
  3165  
  3166  	for _, sn := range snaps {
  3167  		channel := "latest/stable"
  3168  		switch sn.SnapName() {
  3169  		case "pc", "pc-kernel":
  3170  			channel = "20"
  3171  		}
  3172  		c.Check(sn.Channel, Equals, channel)
  3173  		s.fillDownloadedSnap(c, w, sn)
  3174  	}
  3175  
  3176  	complete, err := w.Downloaded()
  3177  	c.Assert(err, IsNil)
  3178  	c.Check(complete, Equals, true)
  3179  
  3180  	copySnap := func(name, src, dst string) error {
  3181  		return osutil.CopyFile(src, dst, 0)
  3182  	}
  3183  
  3184  	err = w.SeedSnaps(copySnap)
  3185  	c.Assert(err, IsNil)
  3186  
  3187  	err = w.WriteMeta()
  3188  	c.Assert(err, IsNil)
  3189  
  3190  	// check seed
  3191  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  3192  	c.Check(systemDir, testutil.FilePresent)
  3193  
  3194  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  3195  	c.Assert(err, IsNil)
  3196  	c.Check(l, HasLen, 4)
  3197  
  3198  	// local asserted model snap was put in /snaps
  3199  	c.Check(filepath.Join(s.opts.SeedDir, "snaps", "pc_1.snap"), testutil.FilePresent)
  3200  
  3201  	// no options file was created
  3202  	c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent)
  3203  }