gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/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) TestSeedSnapsWriteMetaCore16(c *C) {
   764  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   765  		"display-name": "my model",
   766  		"architecture": "amd64",
   767  		"gadget":       "pc",
   768  		"kernel":       "pc-kernel",
   769  	})
   770  
   771  	// the minimum set of snaps
   772  	s.makeSnap(c, "core", "")
   773  	s.makeSnap(c, "pc-kernel", "")
   774  	s.makeSnap(c, "pc", "")
   775  
   776  	w, err := seedwriter.New(model, s.opts)
   777  	c.Assert(err, IsNil)
   778  
   779  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}})
   780  	c.Assert(err, IsNil)
   781  
   782  	_, err = w.Start(s.db, s.newFetcher)
   783  	c.Assert(err, IsNil)
   784  
   785  	snaps, err := w.SnapsToDownload()
   786  	c.Assert(err, IsNil)
   787  	c.Check(snaps, HasLen, 3)
   788  
   789  	for _, sn := range snaps {
   790  		s.fillDownloadedSnap(c, w, sn)
   791  	}
   792  
   793  	complete, err := w.Downloaded()
   794  	c.Assert(err, IsNil)
   795  	c.Check(complete, Equals, true)
   796  
   797  	err = w.SeedSnaps(nil)
   798  	c.Assert(err, IsNil)
   799  
   800  	err = w.WriteMeta()
   801  	c.Assert(err, IsNil)
   802  
   803  	// check seed
   804  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
   805  	c.Assert(err, IsNil)
   806  
   807  	c.Check(seedYaml.Snaps, HasLen, 3)
   808  
   809  	// check the files are in place
   810  	for i, name := range []string{"core", "pc-kernel", "pc"} {
   811  		info := s.AssertedSnapInfo(name)
   812  
   813  		fn := info.Filename()
   814  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
   815  		c.Check(p, testutil.FilePresent)
   816  
   817  		channel := "stable"
   818  		if name == "pc" {
   819  			channel = "edge"
   820  		}
   821  
   822  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
   823  			Name:    info.SnapName(),
   824  			SnapID:  info.SnapID,
   825  			Channel: channel,
   826  			File:    fn,
   827  			Contact: info.Contact(),
   828  		})
   829  	}
   830  
   831  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
   832  	c.Assert(err, IsNil)
   833  	c.Check(l, HasLen, 3)
   834  
   835  	// check assertions
   836  	seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions")
   837  	storeAccountKeyPK := s.StoreSigning.StoreAccountKey("").PublicKeyID()
   838  	brandAcctKeyPK := s.Brands.AccountKey("my-brand").PublicKeyID()
   839  
   840  	for _, fn := range []string{"model", brandAcctKeyPK + ".account-key", "my-brand.account", storeAccountKeyPK + ".account-key"} {
   841  		p := filepath.Join(seedAssertsDir, fn)
   842  		c.Check(p, testutil.FilePresent)
   843  	}
   844  
   845  	c.Check(filepath.Join(seedAssertsDir, "model"), testutil.FileEquals, asserts.Encode(model))
   846  
   847  	acct := seedtest.ReadAssertions(c, filepath.Join(seedAssertsDir, "my-brand.account"))
   848  	c.Assert(acct, HasLen, 1)
   849  	c.Check(acct[0].Type(), Equals, asserts.AccountType)
   850  	c.Check(acct[0].HeaderString("account-id"), Equals, "my-brand")
   851  
   852  	// check the snap assertions are also in place
   853  	for _, snapName := range []string{"pc-kernel", "core", "pc"} {
   854  		p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName)))
   855  		decl := seedtest.ReadAssertions(c, p)
   856  		c.Assert(decl, HasLen, 1)
   857  		c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType)
   858  		c.Check(decl[0].HeaderString("snap-name"), Equals, snapName)
   859  		p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384()))
   860  		rev := seedtest.ReadAssertions(c, p)
   861  		c.Assert(rev, HasLen, 1)
   862  		c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType)
   863  		c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName))
   864  	}
   865  
   866  	// sanity check of seedtest helper
   867  	const usesSnapd = false
   868  	// core seeds do not use system labels
   869  	seedtest.ValidateSeed(c, s.opts.SeedDir, "", usesSnapd, s.StoreSigning.Trusted)
   870  }
   871  
   872  func (s *writerSuite) TestSeedSnapsWriteMetaCore18(c *C) {
   873  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
   874  		"display-name":   "my model",
   875  		"architecture":   "amd64",
   876  		"base":           "core18",
   877  		"gadget":         "pc=18",
   878  		"kernel":         "pc-kernel=18",
   879  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
   880  	})
   881  
   882  	s.makeSnap(c, "snapd", "")
   883  	s.makeSnap(c, "core18", "")
   884  	s.makeSnap(c, "pc-kernel=18", "")
   885  	s.makeSnap(c, "pc=18", "")
   886  	s.makeSnap(c, "cont-producer", "developerid")
   887  	s.makeSnap(c, "cont-consumer", "developerid")
   888  
   889  	w, err := seedwriter.New(model, s.opts)
   890  	c.Assert(err, IsNil)
   891  
   892  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}})
   893  	c.Assert(err, IsNil)
   894  
   895  	_, err = w.Start(s.db, s.newFetcher)
   896  	c.Assert(err, IsNil)
   897  
   898  	snaps, err := w.SnapsToDownload()
   899  	c.Assert(err, IsNil)
   900  	c.Check(snaps, HasLen, 6)
   901  
   902  	s.AssertedSnapInfo("cont-producer").EditedContact = "mailto:author@cont-producer.net"
   903  	for _, sn := range snaps {
   904  		s.fillDownloadedSnap(c, w, sn)
   905  	}
   906  
   907  	complete, err := w.Downloaded()
   908  	c.Assert(err, IsNil)
   909  	c.Check(complete, Equals, true)
   910  
   911  	err = w.SeedSnaps(nil)
   912  	c.Assert(err, IsNil)
   913  
   914  	err = w.WriteMeta()
   915  	c.Assert(err, IsNil)
   916  
   917  	// check seed
   918  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
   919  	c.Assert(err, IsNil)
   920  
   921  	c.Check(seedYaml.Snaps, HasLen, 6)
   922  
   923  	// check the files are in place
   924  	for i, name := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} {
   925  		info := s.AssertedSnapInfo(name)
   926  
   927  		fn := info.Filename()
   928  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
   929  		c.Check(p, testutil.FilePresent)
   930  
   931  		channel := "stable"
   932  		switch name {
   933  		case "pc-kernel":
   934  			channel = "18"
   935  		case "pc":
   936  			channel = "18/edge"
   937  		}
   938  
   939  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
   940  			Name:    info.SnapName(),
   941  			SnapID:  info.SnapID,
   942  			Channel: channel,
   943  			File:    fn,
   944  			Contact: info.Contact(),
   945  		})
   946  	}
   947  
   948  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
   949  	c.Assert(err, IsNil)
   950  	c.Check(l, HasLen, 6)
   951  
   952  	// check assertions
   953  	seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions")
   954  	storeAccountKeyPK := s.StoreSigning.StoreAccountKey("").PublicKeyID()
   955  	brandAcctKeyPK := s.Brands.AccountKey("my-brand").PublicKeyID()
   956  
   957  	for _, fn := range []string{"model", brandAcctKeyPK + ".account-key", "my-brand.account", storeAccountKeyPK + ".account-key"} {
   958  		p := filepath.Join(seedAssertsDir, fn)
   959  		c.Check(p, testutil.FilePresent)
   960  	}
   961  
   962  	c.Check(filepath.Join(seedAssertsDir, "model"), testutil.FileEquals, asserts.Encode(model))
   963  
   964  	acct := seedtest.ReadAssertions(c, filepath.Join(seedAssertsDir, "my-brand.account"))
   965  	c.Assert(acct, HasLen, 1)
   966  	c.Check(acct[0].Type(), Equals, asserts.AccountType)
   967  	c.Check(acct[0].HeaderString("account-id"), Equals, "my-brand")
   968  
   969  	// check the snap assertions are also in place
   970  	for _, snapName := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} {
   971  		p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName)))
   972  		decl := seedtest.ReadAssertions(c, p)
   973  		c.Assert(decl, HasLen, 1)
   974  		c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType)
   975  		c.Check(decl[0].HeaderString("snap-name"), Equals, snapName)
   976  		p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384()))
   977  		rev := seedtest.ReadAssertions(c, p)
   978  		c.Assert(rev, HasLen, 1)
   979  		c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType)
   980  		c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName))
   981  	}
   982  
   983  	// sanity check of seedtest helper
   984  	const usesSnapd = true
   985  	// core18 seeds do not use system labels
   986  	seedtest.ValidateSeed(c, s.opts.SeedDir, "", usesSnapd, s.StoreSigning.Trusted)
   987  }
   988  
   989  func (s *writerSuite) TestSeedSnapsWriteMetaCore18StoreAssertion(c *C) {
   990  	// add store assertion
   991  	storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{
   992  		"store":       "my-store",
   993  		"operator-id": "canonical",
   994  		"timestamp":   time.Now().UTC().Format(time.RFC3339),
   995  	}, nil, "")
   996  	c.Assert(err, IsNil)
   997  	err = s.StoreSigning.Add(storeAs)
   998  	c.Assert(err, IsNil)
   999  
  1000  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1001  		"display-name": "my model",
  1002  		"architecture": "amd64",
  1003  		"base":         "core18",
  1004  		"gadget":       "pc=18",
  1005  		"kernel":       "pc-kernel=18",
  1006  		"store":        "my-store",
  1007  	})
  1008  
  1009  	s.makeSnap(c, "snapd", "")
  1010  	s.makeSnap(c, "core18", "")
  1011  	s.makeSnap(c, "pc-kernel=18", "")
  1012  	s.makeSnap(c, "pc=18", "")
  1013  
  1014  	complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  1015  	c.Assert(err, IsNil)
  1016  	c.Check(complete, Equals, true)
  1017  
  1018  	err = w.SeedSnaps(nil)
  1019  	c.Assert(err, IsNil)
  1020  
  1021  	err = w.WriteMeta()
  1022  	c.Assert(err, IsNil)
  1023  
  1024  	// check assertions
  1025  	seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions")
  1026  	// check the store assertion was fetched
  1027  	p := filepath.Join(seedAssertsDir, "my-store.store")
  1028  	c.Check(p, testutil.FilePresent)
  1029  }
  1030  
  1031  func (s *writerSuite) TestLocalSnaps(c *C) {
  1032  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1033  		"display-name":   "my model",
  1034  		"architecture":   "amd64",
  1035  		"base":           "core18",
  1036  		"gadget":         "pc=18",
  1037  		"kernel":         "pc-kernel=18",
  1038  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1039  	})
  1040  
  1041  	core18Fn := s.makeLocalSnap(c, "core18")
  1042  	pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18")
  1043  	pcFn := s.makeLocalSnap(c, "pc=18")
  1044  	contConsumerFn := s.makeLocalSnap(c, "cont-consumer")
  1045  
  1046  	w, err := seedwriter.New(model, s.opts)
  1047  	c.Assert(err, IsNil)
  1048  
  1049  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
  1050  		{Path: core18Fn},
  1051  		{Path: pcFn, Channel: "edge"},
  1052  		{Path: pcKernelFn},
  1053  		{Path: contConsumerFn},
  1054  	})
  1055  	c.Assert(err, IsNil)
  1056  
  1057  	_, err = w.Start(s.db, s.newFetcher)
  1058  	c.Assert(err, IsNil)
  1059  
  1060  	localSnaps, err := w.LocalSnaps()
  1061  	c.Assert(err, IsNil)
  1062  	c.Assert(localSnaps, HasLen, 4)
  1063  	c.Check(localSnaps[0].Path, Equals, core18Fn)
  1064  	c.Check(localSnaps[1].Path, Equals, pcFn)
  1065  	c.Check(localSnaps[2].Path, Equals, pcKernelFn)
  1066  	c.Check(localSnaps[3].Path, Equals, contConsumerFn)
  1067  }
  1068  
  1069  func (s *writerSuite) TestLocalSnapsCore18FullUse(c *C) {
  1070  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1071  		"display-name":   "my model",
  1072  		"architecture":   "amd64",
  1073  		"base":           "core18",
  1074  		"gadget":         "pc=18",
  1075  		"kernel":         "pc-kernel=18",
  1076  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1077  	})
  1078  
  1079  	s.makeSnap(c, "snapd", "")
  1080  	s.makeSnap(c, "cont-producer", "developerid")
  1081  
  1082  	core18Fn := s.makeLocalSnap(c, "core18")
  1083  	pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18")
  1084  	pcFn := s.makeLocalSnap(c, "pc=18")
  1085  	contConsumerFn := s.makeLocalSnap(c, "cont-consumer")
  1086  
  1087  	w, err := seedwriter.New(model, s.opts)
  1088  	c.Assert(err, IsNil)
  1089  
  1090  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
  1091  		{Path: core18Fn},
  1092  		{Name: "pc-kernel", Channel: "candidate"},
  1093  		{Path: pcFn, Channel: "edge"},
  1094  		{Path: pcKernelFn},
  1095  		{Path: s.AssertedSnap("cont-producer")},
  1096  		{Path: contConsumerFn},
  1097  	})
  1098  	c.Assert(err, IsNil)
  1099  
  1100  	tf, err := w.Start(s.db, s.newFetcher)
  1101  	c.Assert(err, IsNil)
  1102  
  1103  	localSnaps, err := w.LocalSnaps()
  1104  	c.Assert(err, IsNil)
  1105  	c.Assert(localSnaps, HasLen, 5)
  1106  
  1107  	for _, sn := range localSnaps {
  1108  		si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  1109  		if !asserts.IsNotFound(err) {
  1110  			c.Assert(err, IsNil)
  1111  		}
  1112  		f, err := snapfile.Open(sn.Path)
  1113  		c.Assert(err, IsNil)
  1114  		info, err := snap.ReadInfoFromSnapFile(f, si)
  1115  		c.Assert(err, IsNil)
  1116  		w.SetInfo(sn, info)
  1117  		sn.ARefs = aRefs
  1118  	}
  1119  
  1120  	err = w.InfoDerived()
  1121  	c.Assert(err, IsNil)
  1122  
  1123  	snaps, err := w.SnapsToDownload()
  1124  	c.Assert(err, IsNil)
  1125  	c.Check(snaps, HasLen, 1)
  1126  	c.Check(naming.SameSnap(snaps[0], naming.Snap("snapd")), Equals, true)
  1127  
  1128  	for _, sn := range snaps {
  1129  		s.fillDownloadedSnap(c, w, sn)
  1130  	}
  1131  
  1132  	complete, err := w.Downloaded()
  1133  	c.Assert(err, IsNil)
  1134  	c.Check(complete, Equals, true)
  1135  
  1136  	copySnap := func(name, src, dst string) error {
  1137  		return osutil.CopyFile(src, dst, 0)
  1138  	}
  1139  
  1140  	err = w.SeedSnaps(copySnap)
  1141  	c.Assert(err, IsNil)
  1142  
  1143  	err = w.WriteMeta()
  1144  	c.Assert(err, IsNil)
  1145  
  1146  	// check seed
  1147  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1148  	c.Assert(err, IsNil)
  1149  
  1150  	c.Check(seedYaml.Snaps, HasLen, 6)
  1151  
  1152  	assertedNum := 0
  1153  	// check the files are in place
  1154  	for i, name := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} {
  1155  		info := s.AssertedSnapInfo(name)
  1156  		unasserted := false
  1157  		if info == nil {
  1158  			info = &snap.Info{
  1159  				SuggestedName: name,
  1160  			}
  1161  			info.Revision = snap.R(-1)
  1162  			unasserted = true
  1163  		} else {
  1164  			assertedNum++
  1165  		}
  1166  
  1167  		fn := info.Filename()
  1168  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1169  		c.Check(p, testutil.FilePresent)
  1170  
  1171  		channel := ""
  1172  		if !unasserted {
  1173  			channel = "stable"
  1174  		}
  1175  
  1176  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
  1177  			Name:       info.SnapName(),
  1178  			SnapID:     info.SnapID,
  1179  			Channel:    channel,
  1180  			File:       fn,
  1181  			Unasserted: unasserted,
  1182  		})
  1183  	}
  1184  	c.Check(assertedNum, Equals, 2)
  1185  
  1186  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  1187  	c.Assert(err, IsNil)
  1188  	c.Check(l, HasLen, 6)
  1189  
  1190  	// check the snap assertions are in place
  1191  	seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions")
  1192  	for _, snapName := range []string{"snapd", "cont-producer"} {
  1193  		p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName)))
  1194  		decl := seedtest.ReadAssertions(c, p)
  1195  		c.Assert(decl, HasLen, 1)
  1196  		c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType)
  1197  		c.Check(decl[0].HeaderString("snap-name"), Equals, snapName)
  1198  		p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384()))
  1199  		rev := seedtest.ReadAssertions(c, p)
  1200  		c.Assert(rev, HasLen, 1)
  1201  		c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType)
  1202  		c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName))
  1203  	}
  1204  
  1205  	unassertedSnaps, err := w.UnassertedSnaps()
  1206  	c.Assert(err, IsNil)
  1207  	c.Check(unassertedSnaps, HasLen, 4)
  1208  	unassertedSet := naming.NewSnapSet(unassertedSnaps)
  1209  	for _, snapName := range []string{"core18", "pc-kernel", "pc", "cont-consumer"} {
  1210  		c.Check(unassertedSet.Contains(naming.Snap(snapName)), Equals, true)
  1211  	}
  1212  }
  1213  
  1214  func (s *writerSuite) TestSeedSnapsWriteMetaDefaultTrackCore18(c *C) {
  1215  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1216  		"display-name":   "my model",
  1217  		"architecture":   "amd64",
  1218  		"base":           "core18",
  1219  		"gadget":         "pc=18",
  1220  		"kernel":         "pc-kernel=18",
  1221  		"required-snaps": []interface{}{"required18"},
  1222  	})
  1223  
  1224  	s.makeSnap(c, "snapd", "")
  1225  	s.makeSnap(c, "core18", "")
  1226  	s.makeSnap(c, "pc-kernel=18", "")
  1227  	s.makeSnap(c, "pc=18", "")
  1228  	s.makeSnap(c, "required18", "developerid")
  1229  
  1230  	w, err := seedwriter.New(model, s.opts)
  1231  	c.Assert(err, IsNil)
  1232  
  1233  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "required18", Channel: "candidate"}})
  1234  	c.Assert(err, IsNil)
  1235  
  1236  	_, err = w.Start(s.db, s.newFetcher)
  1237  	c.Assert(err, IsNil)
  1238  
  1239  	snaps, err := w.SnapsToDownload()
  1240  	c.Assert(err, IsNil)
  1241  	c.Check(snaps, HasLen, 5)
  1242  
  1243  	for _, sn := range snaps {
  1244  		s.fillDownloadedSnap(c, w, sn)
  1245  		if sn.SnapName() == "required18" {
  1246  			c.Check(sn.Channel, Equals, "candidate")
  1247  			w.SetRedirectChannel(sn, "default-track/candidate")
  1248  		}
  1249  	}
  1250  
  1251  	complete, err := w.Downloaded()
  1252  	c.Assert(err, IsNil)
  1253  	c.Check(complete, Equals, true)
  1254  
  1255  	err = w.SeedSnaps(nil)
  1256  	c.Assert(err, IsNil)
  1257  
  1258  	err = w.WriteMeta()
  1259  	c.Assert(err, IsNil)
  1260  
  1261  	// check seed
  1262  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1263  	c.Assert(err, IsNil)
  1264  
  1265  	c.Check(seedYaml.Snaps, HasLen, 5)
  1266  
  1267  	info := s.AssertedSnapInfo("required18")
  1268  	fn := info.Filename()
  1269  	c.Check(filepath.Join(s.opts.SeedDir, "snaps", fn), testutil.FilePresent)
  1270  	c.Check(seedYaml.Snaps[4], DeepEquals, &seedwriter.InternalSnap16{
  1271  		Name:    info.SnapName(),
  1272  		SnapID:  info.SnapID,
  1273  		Channel: "default-track/candidate",
  1274  		File:    fn,
  1275  	})
  1276  
  1277  }
  1278  
  1279  func (s *writerSuite) TestSetRedirectChannelErrors(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{}{"required18"},
  1287  	})
  1288  
  1289  	s.makeSnap(c, "snapd", "")
  1290  	s.makeSnap(c, "core18", "")
  1291  	s.makeSnap(c, "pc-kernel=18", "")
  1292  	s.makeSnap(c, "pc=18", "")
  1293  	s.makeSnap(c, "required18", "developerid")
  1294  
  1295  	w, err := seedwriter.New(model, s.opts)
  1296  	c.Assert(err, IsNil)
  1297  
  1298  	_, err = w.Start(s.db, s.newFetcher)
  1299  	c.Assert(err, IsNil)
  1300  
  1301  	snaps, err := w.SnapsToDownload()
  1302  	c.Assert(err, IsNil)
  1303  	c.Check(snaps, HasLen, 5)
  1304  
  1305  	sn := snaps[4]
  1306  	c.Assert(sn.SnapName(), Equals, "required18")
  1307  
  1308  	c.Check(w.SetRedirectChannel(sn, "default-track/stable"), ErrorMatches, `internal error: before using seedwriter.Writer.SetRedirectChannel snap "required18" Info should have been set`)
  1309  
  1310  	s.fillDownloadedSnap(c, w, sn)
  1311  
  1312  	c.Check(w.SetRedirectChannel(sn, "default-track//stable"), ErrorMatches, `invalid redirect channel for snap "required18":.*`)
  1313  }
  1314  
  1315  func (s *writerSuite) TestInfoDerivedInfosNotSet(c *C) {
  1316  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1317  		"display-name":   "my model",
  1318  		"architecture":   "amd64",
  1319  		"base":           "core18",
  1320  		"gadget":         "pc=18",
  1321  		"kernel":         "pc-kernel=18",
  1322  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1323  	})
  1324  
  1325  	core18Fn := s.makeLocalSnap(c, "core18")
  1326  	pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18")
  1327  	pcFn := s.makeLocalSnap(c, "pc=18")
  1328  
  1329  	w, err := seedwriter.New(model, s.opts)
  1330  	c.Assert(err, IsNil)
  1331  
  1332  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
  1333  		{Path: core18Fn},
  1334  		{Path: pcFn, Channel: "edge"},
  1335  		{Path: pcKernelFn},
  1336  	})
  1337  	c.Assert(err, IsNil)
  1338  
  1339  	_, err = w.Start(s.db, s.newFetcher)
  1340  	c.Assert(err, IsNil)
  1341  
  1342  	_, err = w.LocalSnaps()
  1343  	c.Assert(err, IsNil)
  1344  
  1345  	err = w.InfoDerived()
  1346  	c.Assert(err, ErrorMatches, `internal error: before seedwriter.Writer.InfoDerived snap ".*/core18.*.snap" Info should have been set`)
  1347  }
  1348  
  1349  func (s *writerSuite) TestInfoDerivedRepeatedLocalSnap(c *C) {
  1350  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1351  		"display-name":   "my model",
  1352  		"architecture":   "amd64",
  1353  		"base":           "core18",
  1354  		"gadget":         "pc=18",
  1355  		"kernel":         "pc-kernel=18",
  1356  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1357  	})
  1358  
  1359  	core18Fn := s.makeLocalSnap(c, "core18")
  1360  	pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18")
  1361  	pcFn := s.makeLocalSnap(c, "pc=18")
  1362  
  1363  	w, err := seedwriter.New(model, s.opts)
  1364  	c.Assert(err, IsNil)
  1365  
  1366  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
  1367  		{Path: core18Fn},
  1368  		{Path: pcFn, Channel: "edge"},
  1369  		{Path: pcKernelFn},
  1370  		{Path: core18Fn},
  1371  	})
  1372  	c.Assert(err, IsNil)
  1373  
  1374  	_, err = w.Start(s.db, s.newFetcher)
  1375  	c.Assert(err, IsNil)
  1376  
  1377  	localSnaps, err := w.LocalSnaps()
  1378  	c.Assert(err, IsNil)
  1379  	c.Check(localSnaps, HasLen, 4)
  1380  
  1381  	for _, sn := range localSnaps {
  1382  		f, err := snapfile.Open(sn.Path)
  1383  		c.Assert(err, IsNil)
  1384  		info, err := snap.ReadInfoFromSnapFile(f, nil)
  1385  		c.Assert(err, IsNil)
  1386  		w.SetInfo(sn, info)
  1387  	}
  1388  
  1389  	err = w.InfoDerived()
  1390  	c.Assert(err, ErrorMatches, `local snap "core18" is repeated in options`)
  1391  }
  1392  
  1393  func (s *writerSuite) TestInfoDerivedInconsistentChannel(c *C) {
  1394  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1395  		"display-name":   "my model",
  1396  		"architecture":   "amd64",
  1397  		"base":           "core18",
  1398  		"gadget":         "pc=18",
  1399  		"kernel":         "pc-kernel=18",
  1400  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1401  	})
  1402  
  1403  	core18Fn := s.makeLocalSnap(c, "core18")
  1404  	pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18")
  1405  	pcFn := s.makeLocalSnap(c, "pc=18")
  1406  
  1407  	w, err := seedwriter.New(model, s.opts)
  1408  	c.Assert(err, IsNil)
  1409  
  1410  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
  1411  		{Path: core18Fn},
  1412  		{Path: pcFn, Channel: "edge"},
  1413  		{Path: pcKernelFn},
  1414  		{Name: "pc", Channel: "beta"},
  1415  	})
  1416  	c.Assert(err, IsNil)
  1417  
  1418  	_, err = w.Start(s.db, s.newFetcher)
  1419  	c.Assert(err, IsNil)
  1420  
  1421  	localSnaps, err := w.LocalSnaps()
  1422  	c.Assert(err, IsNil)
  1423  	c.Check(localSnaps, HasLen, 3)
  1424  
  1425  	for _, sn := range localSnaps {
  1426  		f, err := snapfile.Open(sn.Path)
  1427  		c.Assert(err, IsNil)
  1428  		info, err := snap.ReadInfoFromSnapFile(f, nil)
  1429  		c.Assert(err, IsNil)
  1430  		w.SetInfo(sn, info)
  1431  	}
  1432  
  1433  	err = w.InfoDerived()
  1434  	c.Assert(err, ErrorMatches, `option snap has different channels specified: ".*/pc.*.snap"="edge" vs "pc"="beta"`)
  1435  }
  1436  
  1437  func (s *writerSuite) TestSetRedirectChannelLocalError(c *C) {
  1438  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1439  		"display-name": "my model",
  1440  		"architecture": "amd64",
  1441  		"base":         "core18",
  1442  		"gadget":       "pc=18",
  1443  		"kernel":       "pc-kernel=18",
  1444  	})
  1445  
  1446  	core18Fn := s.makeLocalSnap(c, "core18")
  1447  
  1448  	w, err := seedwriter.New(model, s.opts)
  1449  	c.Assert(err, IsNil)
  1450  
  1451  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
  1452  		{Path: core18Fn},
  1453  	})
  1454  	c.Assert(err, IsNil)
  1455  
  1456  	_, err = w.Start(s.db, s.newFetcher)
  1457  	c.Assert(err, IsNil)
  1458  
  1459  	localSnaps, err := w.LocalSnaps()
  1460  	c.Assert(err, IsNil)
  1461  	c.Check(localSnaps, HasLen, 1)
  1462  
  1463  	sn := localSnaps[0]
  1464  	f, err := snapfile.Open(sn.Path)
  1465  	c.Assert(err, IsNil)
  1466  	info, err := snap.ReadInfoFromSnapFile(f, nil)
  1467  	c.Assert(err, IsNil)
  1468  	err = w.SetInfo(sn, info)
  1469  	c.Assert(err, IsNil)
  1470  
  1471  	c.Check(w.SetRedirectChannel(sn, "foo"), ErrorMatches, `internal error: cannot set redirect channel for local snap .*`)
  1472  
  1473  }
  1474  
  1475  func (s *writerSuite) TestSeedSnapsWriteMetaClassicWithCore(c *C) {
  1476  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1477  		"classic":        "true",
  1478  		"architecture":   "amd64",
  1479  		"gadget":         "classic-gadget",
  1480  		"required-snaps": []interface{}{"required"},
  1481  	})
  1482  
  1483  	s.makeSnap(c, "core", "")
  1484  	s.makeSnap(c, "classic-gadget", "")
  1485  	s.makeSnap(c, "required", "developerid")
  1486  
  1487  	complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  1488  	c.Assert(err, IsNil)
  1489  	c.Check(complete, Equals, false)
  1490  
  1491  	snaps, err := w.SnapsToDownload()
  1492  	c.Assert(err, IsNil)
  1493  	c.Assert(snaps, HasLen, 1)
  1494  
  1495  	s.fillDownloadedSnap(c, w, snaps[0])
  1496  
  1497  	complete, err = w.Downloaded()
  1498  	c.Assert(err, IsNil)
  1499  	c.Check(complete, Equals, true)
  1500  
  1501  	_, err = w.BootSnaps()
  1502  	c.Check(err, ErrorMatches, "no snaps participating in boot on classic")
  1503  
  1504  	err = w.SeedSnaps(nil)
  1505  	c.Assert(err, IsNil)
  1506  
  1507  	err = w.WriteMeta()
  1508  	c.Assert(err, IsNil)
  1509  
  1510  	// check seed
  1511  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1512  	c.Assert(err, IsNil)
  1513  
  1514  	c.Check(seedYaml.Snaps, HasLen, 3)
  1515  
  1516  	// check the files are in place
  1517  	for i, name := range []string{"core", "classic-gadget", "required"} {
  1518  		info := s.AssertedSnapInfo(name)
  1519  
  1520  		fn := info.Filename()
  1521  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1522  		c.Check(p, testutil.FilePresent)
  1523  
  1524  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
  1525  			Name:    info.SnapName(),
  1526  			SnapID:  info.SnapID,
  1527  			Channel: "stable",
  1528  			File:    fn,
  1529  			Contact: info.Contact(),
  1530  		})
  1531  	}
  1532  }
  1533  
  1534  func (s *writerSuite) TestSeedSnapsWriteMetaClassicSnapdOnly(c *C) {
  1535  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1536  		"classic":        "true",
  1537  		"architecture":   "amd64",
  1538  		"gadget":         "classic-gadget18",
  1539  		"required-snaps": []interface{}{"core18", "required18"},
  1540  	})
  1541  
  1542  	s.makeSnap(c, "snapd", "")
  1543  	s.makeSnap(c, "core18", "")
  1544  	s.makeSnap(c, "classic-gadget18", "")
  1545  	s.makeSnap(c, "required18", "developerid")
  1546  
  1547  	complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  1548  	c.Assert(err, IsNil)
  1549  	c.Check(complete, Equals, false)
  1550  
  1551  	snaps, err := w.SnapsToDownload()
  1552  	c.Assert(err, IsNil)
  1553  	c.Assert(snaps, HasLen, 1)
  1554  
  1555  	s.fillDownloadedSnap(c, w, snaps[0])
  1556  
  1557  	complete, err = w.Downloaded()
  1558  	c.Assert(err, IsNil)
  1559  	c.Check(complete, Equals, true)
  1560  
  1561  	err = w.SeedSnaps(nil)
  1562  	c.Assert(err, IsNil)
  1563  
  1564  	err = w.WriteMeta()
  1565  	c.Assert(err, IsNil)
  1566  
  1567  	// check seed
  1568  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1569  	c.Assert(err, IsNil)
  1570  	c.Assert(seedYaml.Snaps, HasLen, 4)
  1571  
  1572  	// check the files are in place
  1573  	for i, name := range []string{"snapd", "core18", "classic-gadget18", "required18"} {
  1574  		info := s.AssertedSnapInfo(name)
  1575  
  1576  		fn := info.Filename()
  1577  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1578  		c.Check(p, testutil.FilePresent)
  1579  
  1580  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
  1581  			Name:    info.SnapName(),
  1582  			SnapID:  info.SnapID,
  1583  			Channel: "stable",
  1584  			File:    fn,
  1585  			Contact: info.Contact(),
  1586  		})
  1587  	}
  1588  }
  1589  
  1590  func (s *writerSuite) TestSeedSnapsWriteMetaClassicSnapdOnlyMissingCore16(c *C) {
  1591  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1592  		"classic":        "true",
  1593  		"architecture":   "amd64",
  1594  		"gadget":         "classic-gadget18",
  1595  		"required-snaps": []interface{}{"core18", "required-base-core16"},
  1596  	})
  1597  
  1598  	s.makeSnap(c, "snapd", "")
  1599  	s.makeSnap(c, "core18", "")
  1600  	s.makeSnap(c, "classic-gadget18", "")
  1601  	s.makeSnap(c, "required-base-core16", "developerid")
  1602  
  1603  	_, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap)
  1604  	c.Check(err, ErrorMatches, `cannot use "required-base-core16" requiring base "core16" without adding "core16" \(or "core"\) explicitly`)
  1605  }
  1606  
  1607  func (s *writerSuite) TestSeedSnapsWriteMetaExtraSnaps(c *C) {
  1608  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1609  		"display-name":   "my model",
  1610  		"architecture":   "amd64",
  1611  		"base":           "core18",
  1612  		"gadget":         "pc=18",
  1613  		"kernel":         "pc-kernel=18",
  1614  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1615  	})
  1616  
  1617  	s.makeSnap(c, "snapd", "")
  1618  	s.makeSnap(c, "core18", "")
  1619  	s.makeSnap(c, "pc-kernel=18", "")
  1620  	s.makeSnap(c, "pc=18", "")
  1621  	s.makeSnap(c, "cont-producer", "developerid")
  1622  	s.makeSnap(c, "cont-consumer", "developerid")
  1623  	s.makeSnap(c, "core", "")
  1624  	s.makeSnap(c, "required", "developerid")
  1625  
  1626  	w, err := seedwriter.New(model, s.opts)
  1627  	c.Assert(err, IsNil)
  1628  
  1629  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "required", Channel: "beta"}})
  1630  	c.Assert(err, IsNil)
  1631  
  1632  	_, err = w.Start(s.db, s.newFetcher)
  1633  	c.Assert(err, IsNil)
  1634  
  1635  	snaps, err := w.SnapsToDownload()
  1636  	c.Assert(err, IsNil)
  1637  	c.Assert(snaps, HasLen, 6)
  1638  
  1639  	s.AssertedSnapInfo("cont-producer").EditedContact = "mailto:author@cont-producer.net"
  1640  	for _, sn := range snaps {
  1641  		s.fillDownloadedSnap(c, w, sn)
  1642  	}
  1643  
  1644  	complete, err := w.Downloaded()
  1645  	c.Assert(err, IsNil)
  1646  	c.Assert(complete, Equals, false)
  1647  
  1648  	snaps, err = w.SnapsToDownload()
  1649  	c.Assert(err, IsNil)
  1650  	c.Assert(snaps, HasLen, 1)
  1651  	c.Check(naming.SameSnap(snaps[0], naming.Snap("required")), Equals, true)
  1652  
  1653  	s.fillDownloadedSnap(c, w, snaps[0])
  1654  
  1655  	complete, err = w.Downloaded()
  1656  	c.Assert(err, IsNil)
  1657  	c.Assert(complete, Equals, false)
  1658  
  1659  	snaps, err = w.SnapsToDownload()
  1660  	c.Assert(err, IsNil)
  1661  	c.Assert(snaps, HasLen, 1)
  1662  	c.Check(naming.SameSnap(snaps[0], naming.Snap("core")), Equals, true)
  1663  
  1664  	s.fillDownloadedSnap(c, w, snaps[0])
  1665  
  1666  	complete, err = w.Downloaded()
  1667  	c.Assert(err, IsNil)
  1668  	c.Assert(complete, Equals, true)
  1669  
  1670  	err = w.SeedSnaps(nil)
  1671  	c.Assert(err, IsNil)
  1672  
  1673  	err = w.WriteMeta()
  1674  	c.Assert(err, IsNil)
  1675  
  1676  	// check seed
  1677  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1678  	c.Assert(err, IsNil)
  1679  	c.Assert(seedYaml.Snaps, HasLen, 8)
  1680  
  1681  	// check the files are in place
  1682  	for i, name := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} {
  1683  		info := s.AssertedSnapInfo(name)
  1684  
  1685  		fn := info.Filename()
  1686  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1687  		c.Check(osutil.FileExists(p), Equals, true)
  1688  
  1689  		channel := "stable"
  1690  		switch name {
  1691  		case "pc-kernel", "pc":
  1692  			channel = "18"
  1693  		case "required":
  1694  			channel = "beta"
  1695  		}
  1696  
  1697  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
  1698  			Name:    info.SnapName(),
  1699  			SnapID:  info.SnapID,
  1700  			Channel: channel,
  1701  			File:    fn,
  1702  			Contact: info.Contact(),
  1703  		})
  1704  	}
  1705  
  1706  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  1707  	c.Assert(err, IsNil)
  1708  	c.Check(l, HasLen, 8)
  1709  
  1710  	// check the snap assertions are also in place
  1711  	seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions")
  1712  	for _, snapName := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} {
  1713  		p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName)))
  1714  		decl := seedtest.ReadAssertions(c, p)
  1715  		c.Assert(decl, HasLen, 1)
  1716  		c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType)
  1717  		c.Check(decl[0].HeaderString("snap-name"), Equals, snapName)
  1718  		p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384()))
  1719  		rev := seedtest.ReadAssertions(c, p)
  1720  		c.Assert(rev, HasLen, 1)
  1721  		c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType)
  1722  		c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName))
  1723  	}
  1724  
  1725  	c.Check(w.Warnings(), DeepEquals, []string{
  1726  		`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`,
  1727  	})
  1728  }
  1729  
  1730  func (s *writerSuite) TestSeedSnapsWriteMetaLocalExtraSnaps(c *C) {
  1731  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1732  		"display-name":   "my model",
  1733  		"architecture":   "amd64",
  1734  		"base":           "core18",
  1735  		"gadget":         "pc=18",
  1736  		"kernel":         "pc-kernel=18",
  1737  		"required-snaps": []interface{}{"cont-consumer", "cont-producer"},
  1738  	})
  1739  
  1740  	s.makeSnap(c, "snapd", "")
  1741  	s.makeSnap(c, "core18", "")
  1742  	s.makeSnap(c, "pc-kernel=18", "")
  1743  	s.makeSnap(c, "pc=18", "")
  1744  	s.makeSnap(c, "cont-producer", "developerid")
  1745  	s.makeSnap(c, "cont-consumer", "developerid")
  1746  	s.makeSnap(c, "core", "")
  1747  	requiredFn := s.makeLocalSnap(c, "required")
  1748  
  1749  	w, err := seedwriter.New(model, s.opts)
  1750  	c.Assert(err, IsNil)
  1751  
  1752  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}})
  1753  	c.Assert(err, IsNil)
  1754  
  1755  	tf, err := w.Start(s.db, s.newFetcher)
  1756  	c.Assert(err, IsNil)
  1757  
  1758  	localSnaps, err := w.LocalSnaps()
  1759  	c.Assert(err, IsNil)
  1760  	c.Assert(localSnaps, HasLen, 1)
  1761  
  1762  	for _, sn := range localSnaps {
  1763  		si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  1764  		if !asserts.IsNotFound(err) {
  1765  			c.Assert(err, IsNil)
  1766  		}
  1767  		f, err := snapfile.Open(sn.Path)
  1768  		c.Assert(err, IsNil)
  1769  		info, err := snap.ReadInfoFromSnapFile(f, si)
  1770  		c.Assert(err, IsNil)
  1771  		w.SetInfo(sn, info)
  1772  		sn.ARefs = aRefs
  1773  	}
  1774  
  1775  	err = w.InfoDerived()
  1776  	c.Assert(err, IsNil)
  1777  
  1778  	snaps, err := w.SnapsToDownload()
  1779  	c.Assert(err, IsNil)
  1780  	c.Assert(snaps, HasLen, 6)
  1781  
  1782  	s.AssertedSnapInfo("cont-producer").EditedContact = "mailto:author@cont-producer.net"
  1783  	for _, sn := range snaps {
  1784  		s.fillDownloadedSnap(c, w, sn)
  1785  	}
  1786  
  1787  	complete, err := w.Downloaded()
  1788  	c.Assert(err, IsNil)
  1789  	c.Assert(complete, Equals, false)
  1790  
  1791  	snaps, err = w.SnapsToDownload()
  1792  	c.Assert(err, IsNil)
  1793  	c.Assert(snaps, HasLen, 0)
  1794  
  1795  	complete, err = w.Downloaded()
  1796  	c.Assert(err, IsNil)
  1797  	c.Assert(complete, Equals, false)
  1798  
  1799  	snaps, err = w.SnapsToDownload()
  1800  	c.Assert(err, IsNil)
  1801  	c.Assert(snaps, HasLen, 1)
  1802  	c.Check(naming.SameSnap(snaps[0], naming.Snap("core")), Equals, true)
  1803  
  1804  	s.fillDownloadedSnap(c, w, snaps[0])
  1805  
  1806  	complete, err = w.Downloaded()
  1807  	c.Assert(err, IsNil)
  1808  	c.Assert(complete, Equals, true)
  1809  
  1810  	copySnap := func(name, src, dst string) error {
  1811  		return osutil.CopyFile(src, dst, 0)
  1812  	}
  1813  
  1814  	err = w.SeedSnaps(copySnap)
  1815  	c.Assert(err, IsNil)
  1816  
  1817  	err = w.WriteMeta()
  1818  	c.Assert(err, IsNil)
  1819  
  1820  	// check seed
  1821  	seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml"))
  1822  	c.Assert(err, IsNil)
  1823  	c.Assert(seedYaml.Snaps, HasLen, 8)
  1824  
  1825  	// check the files are in place
  1826  	for i, name := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} {
  1827  		info := s.AssertedSnapInfo(name)
  1828  		unasserted := false
  1829  		if info == nil {
  1830  			info = &snap.Info{
  1831  				SuggestedName: name,
  1832  			}
  1833  			info.Revision = snap.R(-1)
  1834  			unasserted = true
  1835  		}
  1836  
  1837  		fn := info.Filename()
  1838  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1839  		c.Check(osutil.FileExists(p), Equals, true)
  1840  
  1841  		channel := ""
  1842  		if !unasserted {
  1843  			switch name {
  1844  			case "pc-kernel", "pc":
  1845  				channel = "18"
  1846  			default:
  1847  				channel = "stable"
  1848  			}
  1849  		}
  1850  
  1851  		c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{
  1852  			Name:       info.SnapName(),
  1853  			SnapID:     info.SnapID,
  1854  			Channel:    channel,
  1855  			File:       fn,
  1856  			Contact:    info.Contact(),
  1857  			Unasserted: unasserted,
  1858  		})
  1859  	}
  1860  
  1861  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  1862  	c.Assert(err, IsNil)
  1863  	c.Check(l, HasLen, 8)
  1864  
  1865  	unassertedSnaps, err := w.UnassertedSnaps()
  1866  	c.Assert(err, IsNil)
  1867  	c.Check(unassertedSnaps, HasLen, 1)
  1868  	c.Check(naming.SameSnap(unassertedSnaps[0], naming.Snap("required")), Equals, true)
  1869  }
  1870  
  1871  func (s *writerSuite) TestSeedSnapsWriteMetaCore20(c *C) {
  1872  	// add store assertion
  1873  	storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{
  1874  		"store":       "my-store",
  1875  		"operator-id": "canonical",
  1876  		"timestamp":   time.Now().UTC().Format(time.RFC3339),
  1877  	}, nil, "")
  1878  	c.Assert(err, IsNil)
  1879  	err = s.StoreSigning.Add(storeAs)
  1880  	c.Assert(err, IsNil)
  1881  
  1882  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  1883  		"display-name": "my model",
  1884  		"architecture": "amd64",
  1885  		"store":        "my-store",
  1886  		"base":         "core20",
  1887  		"snaps": []interface{}{
  1888  			map[string]interface{}{
  1889  				"name":            "pc-kernel",
  1890  				"id":              s.AssertedSnapID("pc-kernel"),
  1891  				"type":            "kernel",
  1892  				"default-channel": "20",
  1893  			},
  1894  			map[string]interface{}{
  1895  				"name":            "pc",
  1896  				"id":              s.AssertedSnapID("pc"),
  1897  				"type":            "gadget",
  1898  				"default-channel": "20",
  1899  			},
  1900  			map[string]interface{}{
  1901  				"name": "core18",
  1902  				"id":   s.AssertedSnapID("core18"),
  1903  				"type": "base",
  1904  			},
  1905  			map[string]interface{}{
  1906  				"name": "cont-consumer",
  1907  				"id":   s.AssertedSnapID("cont-consumer"),
  1908  			},
  1909  			map[string]interface{}{
  1910  				"name": "cont-producer",
  1911  				"id":   s.AssertedSnapID("cont-producer"),
  1912  			},
  1913  		},
  1914  	})
  1915  
  1916  	// sanity
  1917  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  1918  
  1919  	s.makeSnap(c, "snapd", "")
  1920  	s.makeSnap(c, "core20", "")
  1921  	s.makeSnap(c, "core18", "")
  1922  	s.makeSnap(c, "pc-kernel=20", "")
  1923  	s.makeSnap(c, "pc=20", "")
  1924  	s.makeSnap(c, "cont-producer", "developerid")
  1925  	s.makeSnap(c, "cont-consumer", "developerid")
  1926  
  1927  	s.opts.Label = "20191003"
  1928  	w, err := seedwriter.New(model, s.opts)
  1929  	c.Assert(err, IsNil)
  1930  
  1931  	_, err = w.Start(s.db, s.newFetcher)
  1932  	c.Assert(err, IsNil)
  1933  
  1934  	snaps, err := w.SnapsToDownload()
  1935  	c.Assert(err, IsNil)
  1936  	c.Check(snaps, HasLen, 7)
  1937  
  1938  	s.AssertedSnapInfo("cont-producer").EditedContact = "mailto:author@cont-producer.net"
  1939  	s.AssertedSnapInfo("cont-consumer").Private = true
  1940  	for _, sn := range snaps {
  1941  		// check the used channel at this level because in the
  1942  		// non-dangerous case is not written anywhere (it
  1943  		// reflects the model or default)
  1944  		channel := "latest/stable"
  1945  		switch sn.SnapName() {
  1946  		case "pc", "pc-kernel":
  1947  			channel = "20"
  1948  		}
  1949  		c.Check(sn.Channel, Equals, channel)
  1950  		s.fillDownloadedSnap(c, w, sn)
  1951  	}
  1952  
  1953  	complete, err := w.Downloaded()
  1954  	c.Assert(err, IsNil)
  1955  	c.Check(complete, Equals, true)
  1956  
  1957  	err = w.SeedSnaps(nil)
  1958  	c.Assert(err, IsNil)
  1959  
  1960  	err = w.WriteMeta()
  1961  	c.Assert(err, IsNil)
  1962  
  1963  	// check seed
  1964  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  1965  	c.Check(systemDir, testutil.FilePresent)
  1966  
  1967  	// check the snaps are in place
  1968  	for _, name := range []string{"snapd", "pc-kernel", "core20", "pc", "core18", "cont-consumer", "cont-producer"} {
  1969  		info := s.AssertedSnapInfo(name)
  1970  
  1971  		fn := info.Filename()
  1972  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  1973  		c.Check(p, testutil.FilePresent)
  1974  	}
  1975  
  1976  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  1977  	c.Assert(err, IsNil)
  1978  	c.Check(l, HasLen, 7)
  1979  
  1980  	// check assertions
  1981  	c.Check(filepath.Join(systemDir, "model"), testutil.FileEquals, asserts.Encode(model))
  1982  
  1983  	assertsDir := filepath.Join(systemDir, "assertions")
  1984  	modelEtc := seedtest.ReadAssertions(c, filepath.Join(assertsDir, "model-etc"))
  1985  	c.Check(modelEtc, HasLen, 4)
  1986  
  1987  	keyPKs := make(map[string]bool)
  1988  	for _, a := range modelEtc {
  1989  		switch a.Type() {
  1990  		case asserts.AccountType:
  1991  			c.Check(a.HeaderString("account-id"), Equals, "my-brand")
  1992  		case asserts.StoreType:
  1993  			c.Check(a.HeaderString("store"), Equals, "my-store")
  1994  		case asserts.AccountKeyType:
  1995  			keyPKs[a.HeaderString("public-key-sha3-384")] = true
  1996  		default:
  1997  			c.Fatalf("unexpected assertion %s", a.Type().Name)
  1998  		}
  1999  	}
  2000  	c.Check(keyPKs, DeepEquals, map[string]bool{
  2001  		s.StoreSigning.StoreAccountKey("").PublicKeyID(): true,
  2002  		s.Brands.AccountKey("my-brand").PublicKeyID():    true,
  2003  	})
  2004  
  2005  	// check snap assertions
  2006  	snapAsserts := seedtest.ReadAssertions(c, filepath.Join(assertsDir, "snaps"))
  2007  	seen := make(map[string]bool)
  2008  
  2009  	for _, a := range snapAsserts {
  2010  		uniq := a.Ref().Unique()
  2011  		if a.Type() == asserts.SnapRevisionType {
  2012  			rev := a.(*asserts.SnapRevision)
  2013  			uniq = fmt.Sprintf("%s@%d", rev.SnapID(), rev.SnapRevision())
  2014  		}
  2015  		seen[uniq] = true
  2016  	}
  2017  
  2018  	snapRevUniq := func(snapName string, revno int) string {
  2019  		return fmt.Sprintf("%s@%d", s.AssertedSnapID(snapName), revno)
  2020  	}
  2021  	snapDeclUniq := func(snapName string) string {
  2022  		return "snap-declaration/16/" + s.AssertedSnapID(snapName)
  2023  	}
  2024  
  2025  	c.Check(seen, DeepEquals, map[string]bool{
  2026  		"account/developerid":           true,
  2027  		snapDeclUniq("snapd"):           true,
  2028  		snapDeclUniq("pc-kernel"):       true,
  2029  		snapDeclUniq("pc"):              true,
  2030  		snapDeclUniq("core20"):          true,
  2031  		snapDeclUniq("core18"):          true,
  2032  		snapDeclUniq("cont-consumer"):   true,
  2033  		snapDeclUniq("cont-producer"):   true,
  2034  		snapRevUniq("snapd", 1):         true,
  2035  		snapRevUniq("pc-kernel", 1):     true,
  2036  		snapRevUniq("pc", 1):            true,
  2037  		snapRevUniq("core20", 1):        true,
  2038  		snapRevUniq("core18", 1):        true,
  2039  		snapRevUniq("cont-consumer", 1): true,
  2040  		snapRevUniq("cont-producer", 1): true,
  2041  	})
  2042  
  2043  	c.Check(filepath.Join(systemDir, "extra-snaps"), testutil.FileAbsent)
  2044  
  2045  	// check auxiliary store info
  2046  	l, err = ioutil.ReadDir(filepath.Join(systemDir, "snaps"))
  2047  	c.Assert(err, IsNil)
  2048  	c.Check(l, HasLen, 1)
  2049  
  2050  	b, err := ioutil.ReadFile(filepath.Join(systemDir, "snaps", "aux-info.json"))
  2051  	c.Assert(err, IsNil)
  2052  	var auxInfos map[string]map[string]interface{}
  2053  	err = json.Unmarshal(b, &auxInfos)
  2054  	c.Assert(err, IsNil)
  2055  	c.Check(auxInfos, DeepEquals, map[string]map[string]interface{}{
  2056  		s.AssertedSnapID("cont-consumer"): {
  2057  			"private": true,
  2058  		},
  2059  		s.AssertedSnapID("cont-producer"): {
  2060  			"contact": "mailto:author@cont-producer.net",
  2061  		},
  2062  	})
  2063  
  2064  	c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent)
  2065  
  2066  	// sanity check of seedtest helper
  2067  	const usesSnapd = true
  2068  	seedtest.ValidateSeed(c, s.opts.SeedDir, s.opts.Label, usesSnapd,
  2069  		s.StoreSigning.Trusted)
  2070  }
  2071  
  2072  func (s *writerSuite) TestCore20InvalidLabel(c *C) {
  2073  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2074  		"display-name": "my model",
  2075  		"architecture": "amd64",
  2076  		"store":        "my-store",
  2077  		"base":         "core20",
  2078  		"snaps": []interface{}{
  2079  			map[string]interface{}{
  2080  				"name":            "pc-kernel",
  2081  				"id":              s.AssertedSnapID("pc-kernel"),
  2082  				"type":            "kernel",
  2083  				"default-channel": "20",
  2084  			},
  2085  			map[string]interface{}{
  2086  				"name":            "pc",
  2087  				"id":              s.AssertedSnapID("pc"),
  2088  				"type":            "gadget",
  2089  				"default-channel": "20",
  2090  			},
  2091  		},
  2092  	})
  2093  
  2094  	invalid := []string{
  2095  		"-",
  2096  		"a.b",
  2097  		"aa--b",
  2098  	}
  2099  
  2100  	for _, inv := range invalid {
  2101  		s.opts.Label = inv
  2102  		w, err := seedwriter.New(model, s.opts)
  2103  		c.Assert(w, IsNil)
  2104  		c.Check(err, ErrorMatches, fmt.Sprintf(`invalid seed system label: %q`, inv))
  2105  	}
  2106  }
  2107  
  2108  func (s *writerSuite) TestDownloadedCore20CheckBase(c *C) {
  2109  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2110  		"display-name": "my model",
  2111  		"architecture": "amd64",
  2112  		"store":        "my-store",
  2113  		"base":         "core20",
  2114  		"snaps": []interface{}{
  2115  			map[string]interface{}{
  2116  				"name":            "pc-kernel",
  2117  				"id":              s.AssertedSnapID("pc-kernel"),
  2118  				"type":            "kernel",
  2119  				"default-channel": "20",
  2120  			},
  2121  			map[string]interface{}{
  2122  				"name":            "pc",
  2123  				"id":              s.AssertedSnapID("pc"),
  2124  				"type":            "gadget",
  2125  				"default-channel": "20",
  2126  			},
  2127  			map[string]interface{}{
  2128  				"name": "cont-producer",
  2129  				"id":   s.AssertedSnapID("cont-producer"),
  2130  			},
  2131  		},
  2132  	})
  2133  
  2134  	// sanity
  2135  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2136  
  2137  	s.makeSnap(c, "snapd", "")
  2138  	s.makeSnap(c, "core20", "")
  2139  	s.makeSnap(c, "core18", "")
  2140  	s.makeSnap(c, "pc-kernel=20", "")
  2141  	s.makeSnap(c, "pc=20", "")
  2142  	s.makeSnap(c, "cont-producer", "developerid")
  2143  
  2144  	s.opts.Label = "20191003"
  2145  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  2146  	c.Check(err, ErrorMatches, `cannot add snap "cont-producer" without also adding its base "core18" explicitly`)
  2147  }
  2148  
  2149  func (s *writerSuite) TestDownloadedCore20CheckBaseModes(c *C) {
  2150  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2151  		"display-name": "my model",
  2152  		"architecture": "amd64",
  2153  		"store":        "my-store",
  2154  		"base":         "core20",
  2155  		"snaps": []interface{}{
  2156  			map[string]interface{}{
  2157  				"name":            "pc-kernel",
  2158  				"id":              s.AssertedSnapID("pc-kernel"),
  2159  				"type":            "kernel",
  2160  				"default-channel": "20",
  2161  			},
  2162  			map[string]interface{}{
  2163  				"name":            "pc",
  2164  				"id":              s.AssertedSnapID("pc"),
  2165  				"type":            "gadget",
  2166  				"default-channel": "20",
  2167  			},
  2168  			map[string]interface{}{
  2169  				"name": "core18",
  2170  				"id":   s.AssertedSnapID("core18"),
  2171  				"type": "base",
  2172  			},
  2173  			map[string]interface{}{
  2174  				"name":  "cont-producer",
  2175  				"id":    s.AssertedSnapID("cont-producer"),
  2176  				"modes": []interface{}{"run", "ephemeral"},
  2177  			},
  2178  		},
  2179  	})
  2180  
  2181  	// sanity
  2182  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2183  
  2184  	s.makeSnap(c, "snapd", "")
  2185  	s.makeSnap(c, "core20", "")
  2186  	s.makeSnap(c, "core18", "")
  2187  	s.makeSnap(c, "pc-kernel=20", "")
  2188  	s.makeSnap(c, "pc=20", "")
  2189  	s.makeSnap(c, "cont-producer", "developerid")
  2190  
  2191  	s.opts.Label = "20191003"
  2192  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  2193  	c.Check(err, ErrorMatches, `cannot add snap "cont-producer" without also adding its base "core18" explicitly for all relevant modes \(run, ephemeral\)`)
  2194  }
  2195  
  2196  func (s *writerSuite) TestDownloadedCore20CheckBaseEphemeralOK(c *C) {
  2197  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2198  		"display-name": "my model",
  2199  		"architecture": "amd64",
  2200  		"store":        "my-store",
  2201  		"base":         "core20",
  2202  		"snaps": []interface{}{
  2203  			map[string]interface{}{
  2204  				"name":            "pc-kernel",
  2205  				"id":              s.AssertedSnapID("pc-kernel"),
  2206  				"type":            "kernel",
  2207  				"default-channel": "20",
  2208  			},
  2209  			map[string]interface{}{
  2210  				"name":            "pc",
  2211  				"id":              s.AssertedSnapID("pc"),
  2212  				"type":            "gadget",
  2213  				"default-channel": "20",
  2214  			},
  2215  			map[string]interface{}{
  2216  				"name":  "core18",
  2217  				"id":    s.AssertedSnapID("core18"),
  2218  				"type":  "base",
  2219  				"modes": []interface{}{"ephemeral"},
  2220  			},
  2221  			map[string]interface{}{
  2222  				"name":  "cont-producer",
  2223  				"id":    s.AssertedSnapID("cont-producer"),
  2224  				"modes": []interface{}{"recover"},
  2225  			},
  2226  		},
  2227  	})
  2228  
  2229  	// sanity
  2230  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2231  
  2232  	s.makeSnap(c, "snapd", "")
  2233  	s.makeSnap(c, "core20", "")
  2234  	s.makeSnap(c, "core18", "")
  2235  	s.makeSnap(c, "pc-kernel=20", "")
  2236  	s.makeSnap(c, "pc=20", "")
  2237  	s.makeSnap(c, "cont-producer", "developerid")
  2238  
  2239  	s.opts.Label = "20191003"
  2240  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  2241  	c.Check(err, IsNil)
  2242  }
  2243  
  2244  func (s *writerSuite) TestDownloadedCore20CheckBaseCoreXX(c *C) {
  2245  	s.makeSnap(c, "snapd", "")
  2246  	s.makeSnap(c, "core20", "")
  2247  	s.makeSnap(c, "pc-kernel=20", "")
  2248  	s.makeSnap(c, "pc=20", "")
  2249  	s.makeSnap(c, "core", "")
  2250  	s.makeSnap(c, "required", "")
  2251  	s.makeSnap(c, "required-base-core16", "")
  2252  
  2253  	coreEnt := map[string]interface{}{
  2254  		"name": "core",
  2255  		"id":   s.AssertedSnapID("core"),
  2256  		"type": "core",
  2257  	}
  2258  	requiredEnt := map[string]interface{}{
  2259  		"name": "required",
  2260  		"id":   s.AssertedSnapID("required"),
  2261  	}
  2262  
  2263  	requiredBaseCore16Ent := map[string]interface{}{
  2264  		"name": "required-base-core16",
  2265  		"id":   s.AssertedSnapID("required-base-core16"),
  2266  	}
  2267  
  2268  	tests := []struct {
  2269  		snaps []interface{}
  2270  		err   string
  2271  	}{
  2272  		{[]interface{}{coreEnt, requiredEnt}, ""},
  2273  		{[]interface{}{coreEnt, requiredBaseCore16Ent}, ""},
  2274  		{[]interface{}{requiredEnt}, `cannot add snap "required" without also adding its base "core" explicitly`},
  2275  		{[]interface{}{requiredBaseCore16Ent}, `cannot add snap "required-base-core16" without also adding its base "core16" \(or "core"\) explicitly`},
  2276  	}
  2277  
  2278  	baseLabel := "20191003"
  2279  	for idx, t := range tests {
  2280  		s.opts.Label = fmt.Sprintf("%s%d", baseLabel, idx)
  2281  		snaps := []interface{}{
  2282  			map[string]interface{}{
  2283  				"name":            "pc-kernel",
  2284  				"id":              s.AssertedSnapID("pc-kernel"),
  2285  				"type":            "kernel",
  2286  				"default-channel": "20",
  2287  			},
  2288  			map[string]interface{}{
  2289  				"name":            "pc",
  2290  				"id":              s.AssertedSnapID("pc"),
  2291  				"type":            "gadget",
  2292  				"default-channel": "20",
  2293  			},
  2294  		}
  2295  
  2296  		snaps = append(snaps, t.snaps...)
  2297  
  2298  		model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2299  			"display-name": "my model",
  2300  			"architecture": "amd64",
  2301  			"store":        "my-store",
  2302  			"base":         "core20",
  2303  			"snaps":        snaps,
  2304  		})
  2305  
  2306  		_, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap)
  2307  		if t.err == "" {
  2308  			c.Check(err, IsNil)
  2309  		} else {
  2310  			c.Check(err, ErrorMatches, t.err)
  2311  		}
  2312  	}
  2313  }
  2314  func (s *writerSuite) TestDownloadedCore20MissingDefaultProviderModes(c *C) {
  2315  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2316  		"display-name": "my model",
  2317  		"architecture": "amd64",
  2318  		"store":        "my-store",
  2319  		"base":         "core20",
  2320  		"snaps": []interface{}{
  2321  			map[string]interface{}{
  2322  				"name":            "pc-kernel",
  2323  				"id":              s.AssertedSnapID("pc-kernel"),
  2324  				"type":            "kernel",
  2325  				"default-channel": "20",
  2326  			},
  2327  			map[string]interface{}{
  2328  				"name":            "pc",
  2329  				"id":              s.AssertedSnapID("pc"),
  2330  				"type":            "gadget",
  2331  				"default-channel": "20",
  2332  			},
  2333  			map[string]interface{}{
  2334  				"name":  "core18",
  2335  				"id":    s.AssertedSnapID("core18"),
  2336  				"type":  "base",
  2337  				"modes": []interface{}{"run", "ephemeral"},
  2338  			},
  2339  			map[string]interface{}{
  2340  				"name": "cont-producer",
  2341  				"id":   s.AssertedSnapID("cont-producer"),
  2342  			},
  2343  			map[string]interface{}{
  2344  				"name":  "cont-consumer",
  2345  				"id":    s.AssertedSnapID("cont-consumer"),
  2346  				"modes": []interface{}{"recover"},
  2347  			},
  2348  		},
  2349  	})
  2350  
  2351  	// sanity
  2352  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2353  
  2354  	s.makeSnap(c, "snapd", "")
  2355  	s.makeSnap(c, "core20", "")
  2356  	s.makeSnap(c, "core18", "")
  2357  	s.makeSnap(c, "pc-kernel=20", "")
  2358  	s.makeSnap(c, "pc=20", "")
  2359  	s.makeSnap(c, "cont-producer", "developerid")
  2360  	s.makeSnap(c, "cont-consumer", "developerid")
  2361  
  2362  	s.opts.Label = "20191003"
  2363  	_, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap)
  2364  	c.Check(err, ErrorMatches, `cannot use snap "cont-consumer" without its default content provider "cont-producer" being added explicitly for all relevant modes \(recover\)`)
  2365  }
  2366  
  2367  func (s *writerSuite) TestCore20NonDangerousDisallowedDevmodeSnaps(c *C) {
  2368  
  2369  	s.makeSnap(c, "my-devmode", "canonical")
  2370  
  2371  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2372  		"display-name": "my model",
  2373  		"architecture": "amd64",
  2374  		"store":        "my-store",
  2375  		"base":         "core20",
  2376  		"snaps": []interface{}{
  2377  			map[string]interface{}{
  2378  				"name":            "pc-kernel",
  2379  				"id":              s.AssertedSnapID("pc-kernel"),
  2380  				"type":            "kernel",
  2381  				"default-channel": "20",
  2382  			},
  2383  			map[string]interface{}{
  2384  				"name":            "pc",
  2385  				"id":              s.AssertedSnapID("pc"),
  2386  				"type":            "gadget",
  2387  				"default-channel": "20",
  2388  			},
  2389  			map[string]interface{}{
  2390  				"name": "my-devmode",
  2391  				"id":   s.AssertedSnapID("my-devmode"),
  2392  				"type": "app",
  2393  			},
  2394  		},
  2395  	})
  2396  
  2397  	s.opts.Label = "20191107"
  2398  
  2399  	w, err := seedwriter.New(model, s.opts)
  2400  	c.Assert(err, IsNil)
  2401  
  2402  	_, err = w.Start(s.db, s.newFetcher)
  2403  	c.Assert(err, IsNil)
  2404  
  2405  	localSnaps, err := w.LocalSnaps()
  2406  	c.Assert(err, IsNil)
  2407  	c.Assert(localSnaps, HasLen, 0)
  2408  
  2409  	snaps, err := w.SnapsToDownload()
  2410  	c.Check(err, IsNil)
  2411  	c.Assert(snaps, HasLen, 5)
  2412  
  2413  	c.Assert(snaps[4].SnapName(), Equals, "my-devmode")
  2414  	sn := snaps[4]
  2415  
  2416  	info := s.AssertedSnapInfo(sn.SnapName())
  2417  	c.Assert(info, NotNil, Commentf("%s not defined", sn.SnapName()))
  2418  	err = w.SetInfo(sn, info)
  2419  	c.Assert(err, ErrorMatches, "cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous")
  2420  	c.Check(sn.Info, Not(Equals), info)
  2421  }
  2422  
  2423  func (s *writerSuite) TestCore20NonDangerousDisallowedOptionsSnaps(c *C) {
  2424  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2425  		"display-name": "my model",
  2426  		"architecture": "amd64",
  2427  		"store":        "my-store",
  2428  		"base":         "core20",
  2429  		"snaps": []interface{}{
  2430  			map[string]interface{}{
  2431  				"name":            "pc-kernel",
  2432  				"id":              s.AssertedSnapID("pc-kernel"),
  2433  				"type":            "kernel",
  2434  				"default-channel": "20",
  2435  			},
  2436  			map[string]interface{}{
  2437  				"name":            "pc",
  2438  				"id":              s.AssertedSnapID("pc"),
  2439  				"type":            "gadget",
  2440  				"default-channel": "20",
  2441  			},
  2442  		},
  2443  	})
  2444  
  2445  	pcFn := s.makeLocalSnap(c, "pc")
  2446  
  2447  	baseLabel := "20191107"
  2448  
  2449  	tests := []struct {
  2450  		optSnap *seedwriter.OptionsSnap
  2451  	}{
  2452  		{&seedwriter.OptionsSnap{Name: "extra"}},
  2453  		{&seedwriter.OptionsSnap{Path: pcFn}},
  2454  		{&seedwriter.OptionsSnap{Name: "pc", Channel: "edge"}},
  2455  	}
  2456  
  2457  	const expectedErr = `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous`
  2458  
  2459  	for idx, t := range tests {
  2460  		s.opts.Label = fmt.Sprintf("%s%d", baseLabel, idx)
  2461  		w, err := seedwriter.New(model, s.opts)
  2462  		c.Assert(err, IsNil)
  2463  
  2464  		err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{t.optSnap})
  2465  		if err != nil {
  2466  			c.Check(err, ErrorMatches, expectedErr)
  2467  			continue
  2468  		}
  2469  
  2470  		tf, err := w.Start(s.db, s.newFetcher)
  2471  		c.Assert(err, IsNil)
  2472  
  2473  		if t.optSnap.Path != "" {
  2474  			localSnaps, err := w.LocalSnaps()
  2475  			c.Assert(err, IsNil)
  2476  			c.Assert(localSnaps, HasLen, 1)
  2477  
  2478  			for _, sn := range localSnaps {
  2479  				si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  2480  				if !asserts.IsNotFound(err) {
  2481  					c.Assert(err, IsNil)
  2482  				}
  2483  				f, err := snapfile.Open(sn.Path)
  2484  				c.Assert(err, IsNil)
  2485  				info, err := snap.ReadInfoFromSnapFile(f, si)
  2486  				c.Assert(err, IsNil)
  2487  				w.SetInfo(sn, info)
  2488  				sn.ARefs = aRefs
  2489  			}
  2490  
  2491  			err = w.InfoDerived()
  2492  			c.Check(err, ErrorMatches, expectedErr)
  2493  			continue
  2494  		}
  2495  
  2496  		_, err = w.SnapsToDownload()
  2497  		c.Check(err, ErrorMatches, expectedErr)
  2498  	}
  2499  }
  2500  
  2501  func (s *writerSuite) TestCore20NonDangerousNoChannelOverride(c *C) {
  2502  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2503  		"display-name": "my model",
  2504  		"architecture": "amd64",
  2505  		"store":        "my-store",
  2506  		"base":         "core20",
  2507  		"snaps": []interface{}{
  2508  			map[string]interface{}{
  2509  				"name":            "pc-kernel",
  2510  				"id":              s.AssertedSnapID("pc-kernel"),
  2511  				"type":            "kernel",
  2512  				"default-channel": "20",
  2513  			},
  2514  			map[string]interface{}{
  2515  				"name":            "pc",
  2516  				"id":              s.AssertedSnapID("pc"),
  2517  				"type":            "gadget",
  2518  				"default-channel": "20",
  2519  			},
  2520  		},
  2521  	})
  2522  
  2523  	s.opts.DefaultChannel = "stable"
  2524  	s.opts.Label = "20191107"
  2525  	w, err := seedwriter.New(model, s.opts)
  2526  	c.Assert(w, IsNil)
  2527  	c.Check(err, ErrorMatches, `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous`)
  2528  }
  2529  
  2530  func (s *writerSuite) TestSeedSnapsWriteMetaCore20LocalSnaps(c *C) {
  2531  	// add store assertion
  2532  	storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{
  2533  		"store":       "my-store",
  2534  		"operator-id": "canonical",
  2535  		"timestamp":   time.Now().UTC().Format(time.RFC3339),
  2536  	}, nil, "")
  2537  	c.Assert(err, IsNil)
  2538  	err = s.StoreSigning.Add(storeAs)
  2539  	c.Assert(err, IsNil)
  2540  
  2541  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2542  		"display-name": "my model",
  2543  		"architecture": "amd64",
  2544  		"store":        "my-store",
  2545  		"base":         "core20",
  2546  		"grade":        "dangerous",
  2547  		"snaps": []interface{}{
  2548  			map[string]interface{}{
  2549  				"name":            "pc-kernel",
  2550  				"id":              s.AssertedSnapID("pc-kernel"),
  2551  				"type":            "kernel",
  2552  				"default-channel": "20",
  2553  			},
  2554  			map[string]interface{}{
  2555  				"name":            "pc",
  2556  				"id":              s.AssertedSnapID("pc"),
  2557  				"type":            "gadget",
  2558  				"default-channel": "20",
  2559  			},
  2560  			map[string]interface{}{
  2561  				"name": "required20",
  2562  				"id":   s.AssertedSnapID("required20"),
  2563  			},
  2564  		},
  2565  	})
  2566  
  2567  	// sanity
  2568  	c.Assert(model.Grade(), Equals, asserts.ModelDangerous)
  2569  
  2570  	s.makeSnap(c, "snapd", "")
  2571  	s.makeSnap(c, "core20", "")
  2572  	s.makeSnap(c, "pc-kernel=20", "")
  2573  	s.makeSnap(c, "pc=20", "")
  2574  	requiredFn := s.makeLocalSnap(c, "required20")
  2575  
  2576  	s.opts.Label = "20191030"
  2577  	w, err := seedwriter.New(model, s.opts)
  2578  	c.Assert(err, IsNil)
  2579  
  2580  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}})
  2581  	c.Assert(err, IsNil)
  2582  
  2583  	tf, err := w.Start(s.db, s.newFetcher)
  2584  	c.Assert(err, IsNil)
  2585  
  2586  	localSnaps, err := w.LocalSnaps()
  2587  	c.Assert(err, IsNil)
  2588  	c.Assert(localSnaps, HasLen, 1)
  2589  
  2590  	for _, sn := range localSnaps {
  2591  		_, _, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  2592  		c.Assert(asserts.IsNotFound(err), Equals, true)
  2593  		f, err := snapfile.Open(sn.Path)
  2594  		c.Assert(err, IsNil)
  2595  		info, err := snap.ReadInfoFromSnapFile(f, nil)
  2596  		c.Assert(err, IsNil)
  2597  		w.SetInfo(sn, info)
  2598  	}
  2599  
  2600  	err = w.InfoDerived()
  2601  	c.Assert(err, IsNil)
  2602  
  2603  	snaps, err := w.SnapsToDownload()
  2604  	c.Assert(err, IsNil)
  2605  	c.Check(snaps, HasLen, 4)
  2606  
  2607  	for _, sn := range snaps {
  2608  		// check the used channel at this level because in the
  2609  		// non-dangerous case is not written anywhere (it
  2610  		// reflects the model or default)
  2611  		channel := "latest/stable"
  2612  		switch sn.SnapName() {
  2613  		case "pc", "pc-kernel":
  2614  			channel = "20"
  2615  		}
  2616  		c.Check(sn.Channel, Equals, channel)
  2617  		s.fillDownloadedSnap(c, w, sn)
  2618  	}
  2619  
  2620  	complete, err := w.Downloaded()
  2621  	c.Assert(err, IsNil)
  2622  	c.Check(complete, Equals, true)
  2623  
  2624  	copySnap := func(name, src, dst string) error {
  2625  		return osutil.CopyFile(src, dst, 0)
  2626  	}
  2627  
  2628  	err = w.SeedSnaps(copySnap)
  2629  	c.Assert(err, IsNil)
  2630  
  2631  	err = w.WriteMeta()
  2632  	c.Assert(err, IsNil)
  2633  
  2634  	// check seed
  2635  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  2636  	c.Check(systemDir, testutil.FilePresent)
  2637  
  2638  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  2639  	c.Assert(err, IsNil)
  2640  	c.Check(l, HasLen, 4)
  2641  
  2642  	// local unasserted snap was put in system snaps dir
  2643  	c.Check(filepath.Join(systemDir, "snaps", "required20_1.0.snap"), testutil.FilePresent)
  2644  
  2645  	options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml"))
  2646  	c.Assert(err, IsNil)
  2647  
  2648  	c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{
  2649  		{
  2650  			Name:       "required20",
  2651  			SnapID:     s.AssertedSnapID("required20"),
  2652  			Unasserted: "required20_1.0.snap",
  2653  		},
  2654  	})
  2655  }
  2656  
  2657  func (s *writerSuite) TestSeedSnapsWriteMetaCore20ChannelOverrides(c *C) {
  2658  	// add store assertion
  2659  	storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{
  2660  		"store":       "my-store",
  2661  		"operator-id": "canonical",
  2662  		"timestamp":   time.Now().UTC().Format(time.RFC3339),
  2663  	}, nil, "")
  2664  	c.Assert(err, IsNil)
  2665  	err = s.StoreSigning.Add(storeAs)
  2666  	c.Assert(err, IsNil)
  2667  
  2668  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2669  		"display-name": "my model",
  2670  		"architecture": "amd64",
  2671  		"store":        "my-store",
  2672  		"base":         "core20",
  2673  		"grade":        "dangerous",
  2674  		"snaps": []interface{}{
  2675  			map[string]interface{}{
  2676  				"name":            "pc-kernel",
  2677  				"id":              s.AssertedSnapID("pc-kernel"),
  2678  				"type":            "kernel",
  2679  				"default-channel": "20",
  2680  			},
  2681  			map[string]interface{}{
  2682  				"name":            "pc",
  2683  				"id":              s.AssertedSnapID("pc"),
  2684  				"type":            "gadget",
  2685  				"default-channel": "20",
  2686  			},
  2687  			map[string]interface{}{
  2688  				"name": "required20",
  2689  				"id":   s.AssertedSnapID("required20"),
  2690  			},
  2691  		},
  2692  	})
  2693  
  2694  	// sanity
  2695  	c.Assert(model.Grade(), Equals, asserts.ModelDangerous)
  2696  
  2697  	s.makeSnap(c, "snapd", "")
  2698  	s.makeSnap(c, "core20", "")
  2699  	s.makeSnap(c, "pc-kernel=20", "")
  2700  	s.makeSnap(c, "pc=20", "")
  2701  	s.makeSnap(c, "required20", "developerid")
  2702  
  2703  	s.opts.Label = "20191030"
  2704  	s.opts.DefaultChannel = "candidate"
  2705  	w, err := seedwriter.New(model, s.opts)
  2706  	c.Assert(err, IsNil)
  2707  
  2708  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}})
  2709  	c.Assert(err, IsNil)
  2710  
  2711  	_, err = w.Start(s.db, s.newFetcher)
  2712  	c.Assert(err, IsNil)
  2713  
  2714  	snaps, err := w.SnapsToDownload()
  2715  	c.Assert(err, IsNil)
  2716  	c.Check(snaps, HasLen, 5)
  2717  
  2718  	for _, sn := range snaps {
  2719  		s.fillDownloadedSnap(c, w, sn)
  2720  	}
  2721  
  2722  	complete, err := w.Downloaded()
  2723  	c.Assert(err, IsNil)
  2724  	c.Check(complete, Equals, true)
  2725  
  2726  	err = w.SeedSnaps(nil)
  2727  	c.Assert(err, IsNil)
  2728  
  2729  	err = w.WriteMeta()
  2730  	c.Assert(err, IsNil)
  2731  
  2732  	// check seed
  2733  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  2734  	c.Check(systemDir, testutil.FilePresent)
  2735  
  2736  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  2737  	c.Assert(err, IsNil)
  2738  	c.Check(l, HasLen, 5)
  2739  
  2740  	options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml"))
  2741  	c.Assert(err, IsNil)
  2742  
  2743  	c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{
  2744  		{
  2745  			Name:    "snapd",
  2746  			SnapID:  s.AssertedSnapID("snapd"), // inferred
  2747  			Channel: "latest/candidate",
  2748  		},
  2749  		{
  2750  			Name:    "pc-kernel",
  2751  			SnapID:  s.AssertedSnapID("pc-kernel"),
  2752  			Channel: "20/candidate",
  2753  		},
  2754  		{
  2755  			Name:    "core20",
  2756  			SnapID:  s.AssertedSnapID("core20"), // inferred
  2757  			Channel: "latest/candidate",
  2758  		},
  2759  		{
  2760  			Name:    "pc",
  2761  			SnapID:  s.AssertedSnapID("pc"),
  2762  			Channel: "20/edge",
  2763  		},
  2764  		{
  2765  			Name:    "required20",
  2766  			SnapID:  s.AssertedSnapID("required20"),
  2767  			Channel: "latest/candidate",
  2768  		},
  2769  	})
  2770  }
  2771  
  2772  func (s *writerSuite) TestSeedSnapsWriteMetaCore20ModelOverrideSnapd(c *C) {
  2773  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2774  		"display-name": "my model",
  2775  		"architecture": "amd64",
  2776  		"base":         "core20",
  2777  		"snaps": []interface{}{
  2778  			map[string]interface{}{
  2779  				"name":            "snapd",
  2780  				"id":              s.AssertedSnapID("snapd"),
  2781  				"type":            "snapd",
  2782  				"default-channel": "latest/edge",
  2783  			},
  2784  			map[string]interface{}{
  2785  				"name":            "pc-kernel",
  2786  				"id":              s.AssertedSnapID("pc-kernel"),
  2787  				"type":            "kernel",
  2788  				"default-channel": "20",
  2789  			},
  2790  			map[string]interface{}{
  2791  				"name":            "pc",
  2792  				"id":              s.AssertedSnapID("pc"),
  2793  				"type":            "gadget",
  2794  				"default-channel": "20",
  2795  			}},
  2796  	})
  2797  
  2798  	// sanity
  2799  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2800  
  2801  	s.makeSnap(c, "snapd", "")
  2802  	s.makeSnap(c, "core20", "")
  2803  	s.makeSnap(c, "pc-kernel=20", "")
  2804  	s.makeSnap(c, "pc=20", "")
  2805  
  2806  	s.opts.Label = "20191121"
  2807  	w, err := seedwriter.New(model, s.opts)
  2808  	c.Assert(err, IsNil)
  2809  
  2810  	_, err = w.Start(s.db, s.newFetcher)
  2811  	c.Assert(err, IsNil)
  2812  
  2813  	snaps, err := w.SnapsToDownload()
  2814  	c.Assert(err, IsNil)
  2815  	c.Check(snaps, HasLen, 4)
  2816  
  2817  	for _, sn := range snaps {
  2818  		// check the used channel at this level because in the
  2819  		// non-dangerous case is not written anywhere (it
  2820  		// reflects the model or default)
  2821  		channel := "latest/stable"
  2822  		switch sn.SnapName() {
  2823  		case "snapd":
  2824  			channel = "latest/edge"
  2825  		case "pc", "pc-kernel":
  2826  			channel = "20"
  2827  		}
  2828  		c.Check(sn.Channel, Equals, channel)
  2829  		s.fillDownloadedSnap(c, w, sn)
  2830  	}
  2831  
  2832  	complete, err := w.Downloaded()
  2833  	c.Assert(err, IsNil)
  2834  	c.Check(complete, Equals, true)
  2835  
  2836  	err = w.SeedSnaps(nil)
  2837  	c.Assert(err, IsNil)
  2838  
  2839  	err = w.WriteMeta()
  2840  	c.Assert(err, IsNil)
  2841  
  2842  	// check seed
  2843  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  2844  	c.Check(systemDir, testutil.FilePresent)
  2845  
  2846  	// check the snaps are in place
  2847  	for _, name := range []string{"snapd", "pc-kernel", "core20", "pc"} {
  2848  		info := s.AssertedSnapInfo(name)
  2849  
  2850  		fn := info.Filename()
  2851  		p := filepath.Join(s.opts.SeedDir, "snaps", fn)
  2852  		c.Check(p, testutil.FilePresent)
  2853  	}
  2854  
  2855  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  2856  	c.Assert(err, IsNil)
  2857  	c.Check(l, HasLen, 4)
  2858  
  2859  	c.Check(filepath.Join(systemDir, "extra-snaps"), testutil.FileAbsent)
  2860  	c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent)
  2861  }
  2862  
  2863  func (s *writerSuite) TestSnapsToDownloadCore20OptionalSnaps(c *C) {
  2864  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2865  		"display-name": "my model",
  2866  		"architecture": "amd64",
  2867  		"base":         "core20",
  2868  		"snaps": []interface{}{
  2869  			map[string]interface{}{
  2870  				"name":            "pc-kernel",
  2871  				"id":              s.AssertedSnapID("pc-kernel"),
  2872  				"type":            "kernel",
  2873  				"default-channel": "20",
  2874  			},
  2875  			map[string]interface{}{
  2876  				"name":            "pc",
  2877  				"id":              s.AssertedSnapID("pc"),
  2878  				"type":            "gadget",
  2879  				"default-channel": "20",
  2880  			},
  2881  			map[string]interface{}{
  2882  				"name": "core18",
  2883  				"id":   s.AssertedSnapID("core18"),
  2884  				"type": "base",
  2885  			},
  2886  			map[string]interface{}{
  2887  				"name":     "optional20-a",
  2888  				"id":       s.AssertedSnapID("optional20-a"),
  2889  				"presence": "optional",
  2890  			},
  2891  			map[string]interface{}{
  2892  				"name":     "optional20-b",
  2893  				"id":       s.AssertedSnapID("optional20-b"),
  2894  				"presence": "optional",
  2895  			}},
  2896  	})
  2897  
  2898  	// sanity
  2899  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  2900  
  2901  	s.makeSnap(c, "snapd", "")
  2902  	s.makeSnap(c, "core20", "")
  2903  	s.makeSnap(c, "core18", "")
  2904  	s.makeSnap(c, "pc-kernel=20", "")
  2905  	s.makeSnap(c, "pc=20", "")
  2906  	s.makeSnap(c, "optional20-a", "developerid")
  2907  	s.makeSnap(c, "optional20-b", "developerid")
  2908  
  2909  	s.opts.Label = "20191122"
  2910  	w, err := seedwriter.New(model, s.opts)
  2911  	c.Assert(err, IsNil)
  2912  
  2913  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "optional20-b"}})
  2914  	c.Assert(err, IsNil)
  2915  
  2916  	_, err = w.Start(s.db, s.newFetcher)
  2917  	c.Assert(err, IsNil)
  2918  
  2919  	snaps, err := w.SnapsToDownload()
  2920  	c.Assert(err, IsNil)
  2921  	c.Check(snaps, HasLen, 6)
  2922  	c.Check(snaps[5].SnapName(), Equals, "optional20-b")
  2923  }
  2924  
  2925  func (s *writerSuite) TestSeedSnapsWriteMetaCore20ExtraSnaps(c *C) {
  2926  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  2927  		"display-name": "my model",
  2928  		"architecture": "amd64",
  2929  		"base":         "core20",
  2930  		"grade":        "dangerous",
  2931  		"snaps": []interface{}{
  2932  			map[string]interface{}{
  2933  				"name":            "pc-kernel",
  2934  				"id":              s.AssertedSnapID("pc-kernel"),
  2935  				"type":            "kernel",
  2936  				"default-channel": "20",
  2937  			},
  2938  			map[string]interface{}{
  2939  				"name":            "pc",
  2940  				"id":              s.AssertedSnapID("pc"),
  2941  				"type":            "gadget",
  2942  				"default-channel": "20",
  2943  			}},
  2944  	})
  2945  
  2946  	// sanity
  2947  	c.Assert(model.Grade(), Equals, asserts.ModelDangerous)
  2948  
  2949  	s.makeSnap(c, "snapd", "")
  2950  	s.makeSnap(c, "core20", "")
  2951  	s.makeSnap(c, "pc-kernel=20", "")
  2952  	s.makeSnap(c, "pc=20", "")
  2953  	s.makeSnap(c, "core18", "")
  2954  	s.makeSnap(c, "cont-producer", "developerid")
  2955  	contConsumerFn := s.makeLocalSnap(c, "cont-consumer")
  2956  
  2957  	s.opts.Label = "20191122"
  2958  	w, err := seedwriter.New(model, s.opts)
  2959  	c.Assert(err, IsNil)
  2960  
  2961  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "cont-producer", Channel: "edge"}, {Name: "core18"}, {Path: contConsumerFn}})
  2962  	c.Assert(err, IsNil)
  2963  
  2964  	tf, err := w.Start(s.db, s.newFetcher)
  2965  	c.Assert(err, IsNil)
  2966  
  2967  	localSnaps, err := w.LocalSnaps()
  2968  	c.Assert(err, IsNil)
  2969  	c.Assert(localSnaps, HasLen, 1)
  2970  
  2971  	for _, sn := range localSnaps {
  2972  		_, _, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  2973  		c.Assert(asserts.IsNotFound(err), Equals, true)
  2974  		f, err := snapfile.Open(sn.Path)
  2975  		c.Assert(err, IsNil)
  2976  		info, err := snap.ReadInfoFromSnapFile(f, nil)
  2977  		c.Assert(err, IsNil)
  2978  		w.SetInfo(sn, info)
  2979  	}
  2980  
  2981  	err = w.InfoDerived()
  2982  	c.Assert(err, IsNil)
  2983  
  2984  	snaps, err := w.SnapsToDownload()
  2985  	c.Assert(err, IsNil)
  2986  	c.Check(snaps, HasLen, 4)
  2987  
  2988  	for _, sn := range snaps {
  2989  		channel := "latest/stable"
  2990  		switch sn.SnapName() {
  2991  		case "pc", "pc-kernel":
  2992  			channel = "20"
  2993  		}
  2994  		c.Check(sn.Channel, Equals, channel)
  2995  		s.fillDownloadedSnap(c, w, sn)
  2996  	}
  2997  
  2998  	complete, err := w.Downloaded()
  2999  	c.Assert(err, IsNil)
  3000  	c.Check(complete, Equals, false)
  3001  
  3002  	snaps, err = w.SnapsToDownload()
  3003  	c.Assert(err, IsNil)
  3004  	c.Assert(snaps, HasLen, 2)
  3005  	c.Check(snaps[0].SnapName(), Equals, "cont-producer")
  3006  	c.Check(snaps[1].SnapName(), Equals, "core18")
  3007  
  3008  	for _, sn := range snaps {
  3009  		channel := "latest/stable"
  3010  		switch sn.SnapName() {
  3011  		case "cont-producer":
  3012  			channel = "latest/edge"
  3013  		}
  3014  		c.Check(sn.Channel, Equals, channel)
  3015  
  3016  		info := s.doFillMetaDownloadedSnap(c, w, sn)
  3017  
  3018  		c.Assert(sn.Path, Equals, filepath.Join(s.opts.SeedDir, "systems", s.opts.Label, "snaps", info.Filename()))
  3019  		err := os.Rename(s.AssertedSnap(sn.SnapName()), sn.Path)
  3020  		c.Assert(err, IsNil)
  3021  	}
  3022  
  3023  	complete, err = w.Downloaded()
  3024  	c.Assert(err, IsNil)
  3025  	c.Check(complete, Equals, true)
  3026  
  3027  	copySnap := func(name, src, dst string) error {
  3028  		return osutil.CopyFile(src, dst, 0)
  3029  	}
  3030  
  3031  	err = w.SeedSnaps(copySnap)
  3032  	c.Assert(err, IsNil)
  3033  
  3034  	err = w.WriteMeta()
  3035  	c.Assert(err, IsNil)
  3036  
  3037  	// check seed
  3038  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  3039  	c.Check(systemDir, testutil.FilePresent)
  3040  
  3041  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  3042  	c.Assert(err, IsNil)
  3043  	c.Check(l, HasLen, 4)
  3044  
  3045  	// extra snaps were put in system snaps dir
  3046  	c.Check(filepath.Join(systemDir, "snaps", "core18_1.snap"), testutil.FilePresent)
  3047  	c.Check(filepath.Join(systemDir, "snaps", "cont-producer_1.snap"), testutil.FilePresent)
  3048  	c.Check(filepath.Join(systemDir, "snaps", "cont-consumer_1.0.snap"), testutil.FilePresent)
  3049  
  3050  	// check extra-snaps in assertions
  3051  	snapAsserts := seedtest.ReadAssertions(c, filepath.Join(systemDir, "assertions", "extra-snaps"))
  3052  	seen := make(map[string]bool)
  3053  
  3054  	for _, a := range snapAsserts {
  3055  		uniq := a.Ref().Unique()
  3056  		if a.Type() == asserts.SnapRevisionType {
  3057  			rev := a.(*asserts.SnapRevision)
  3058  			uniq = fmt.Sprintf("%s@%d", rev.SnapID(), rev.SnapRevision())
  3059  		}
  3060  		seen[uniq] = true
  3061  	}
  3062  
  3063  	snapRevUniq := func(snapName string, revno int) string {
  3064  		return fmt.Sprintf("%s@%d", s.AssertedSnapID(snapName), revno)
  3065  	}
  3066  	snapDeclUniq := func(snapName string) string {
  3067  		return "snap-declaration/16/" + s.AssertedSnapID(snapName)
  3068  	}
  3069  
  3070  	c.Check(seen, DeepEquals, map[string]bool{
  3071  		"account/developerid":           true,
  3072  		snapDeclUniq("core18"):          true,
  3073  		snapDeclUniq("cont-producer"):   true,
  3074  		snapRevUniq("core18", 1):        true,
  3075  		snapRevUniq("cont-producer", 1): true,
  3076  	})
  3077  
  3078  	options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml"))
  3079  	c.Assert(err, IsNil)
  3080  
  3081  	c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{
  3082  		{
  3083  			Name:    "cont-producer",
  3084  			SnapID:  s.AssertedSnapID("cont-producer"),
  3085  			Channel: "latest/edge",
  3086  		},
  3087  		{
  3088  			Name:    "core18",
  3089  			SnapID:  s.AssertedSnapID("core18"),
  3090  			Channel: "latest/stable",
  3091  		},
  3092  		{
  3093  			Name:       "cont-consumer",
  3094  			Unasserted: "cont-consumer_1.0.snap",
  3095  		},
  3096  	})
  3097  }
  3098  
  3099  func (s *writerSuite) TestSeedSnapsWriteMetaCore20LocalAssertedSnaps(c *C) {
  3100  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  3101  		"display-name": "my model",
  3102  		"architecture": "amd64",
  3103  		"base":         "core20",
  3104  		"grade":        "dangerous",
  3105  		"snaps": []interface{}{
  3106  			map[string]interface{}{
  3107  				"name":            "pc-kernel",
  3108  				"id":              s.AssertedSnapID("pc-kernel"),
  3109  				"type":            "kernel",
  3110  				"default-channel": "20",
  3111  			},
  3112  			map[string]interface{}{
  3113  				"name":            "pc",
  3114  				"id":              s.AssertedSnapID("pc"),
  3115  				"type":            "gadget",
  3116  				"default-channel": "20",
  3117  			}},
  3118  	})
  3119  
  3120  	// sanity
  3121  	c.Assert(model.Grade(), Equals, asserts.ModelDangerous)
  3122  
  3123  	s.makeSnap(c, "snapd", "")
  3124  	s.makeSnap(c, "core20", "")
  3125  	s.makeSnap(c, "pc-kernel=20", "")
  3126  	s.makeSnap(c, "pc=20", "")
  3127  	s.makeSnap(c, "required20", "developerid")
  3128  
  3129  	s.opts.Label = "20191122"
  3130  	w, err := seedwriter.New(model, s.opts)
  3131  	c.Assert(err, IsNil)
  3132  
  3133  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: s.AssertedSnap("pc"), Channel: "edge"}, {Path: s.AssertedSnap("required20")}})
  3134  	c.Assert(err, IsNil)
  3135  
  3136  	tf, err := w.Start(s.db, s.newFetcher)
  3137  	c.Assert(err, IsNil)
  3138  
  3139  	localSnaps, err := w.LocalSnaps()
  3140  	c.Assert(err, IsNil)
  3141  	c.Assert(localSnaps, HasLen, 2)
  3142  
  3143  	for _, sn := range localSnaps {
  3144  		si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  3145  		c.Assert(err, IsNil)
  3146  		f, err := snapfile.Open(sn.Path)
  3147  		c.Assert(err, IsNil)
  3148  		info, err := snap.ReadInfoFromSnapFile(f, si)
  3149  		c.Assert(err, IsNil)
  3150  		w.SetInfo(sn, info)
  3151  		sn.ARefs = aRefs
  3152  	}
  3153  
  3154  	err = w.InfoDerived()
  3155  	c.Assert(err, IsNil)
  3156  
  3157  	snaps, err := w.SnapsToDownload()
  3158  	c.Assert(err, IsNil)
  3159  	c.Check(snaps, HasLen, 3)
  3160  
  3161  	for _, sn := range snaps {
  3162  		channel := "latest/stable"
  3163  		switch sn.SnapName() {
  3164  		case "pc", "pc-kernel":
  3165  			channel = "20"
  3166  		}
  3167  		c.Check(sn.Channel, Equals, channel)
  3168  		s.fillDownloadedSnap(c, w, sn)
  3169  	}
  3170  
  3171  	complete, err := w.Downloaded()
  3172  	c.Assert(err, IsNil)
  3173  	c.Check(complete, Equals, false)
  3174  
  3175  	snaps, err = w.SnapsToDownload()
  3176  	c.Assert(err, IsNil)
  3177  	c.Assert(snaps, HasLen, 0)
  3178  
  3179  	complete, err = w.Downloaded()
  3180  	c.Assert(err, IsNil)
  3181  	c.Check(complete, Equals, true)
  3182  
  3183  	copySnap := func(name, src, dst string) error {
  3184  		return osutil.CopyFile(src, dst, 0)
  3185  	}
  3186  
  3187  	err = w.SeedSnaps(copySnap)
  3188  	c.Assert(err, IsNil)
  3189  
  3190  	err = w.WriteMeta()
  3191  	c.Assert(err, IsNil)
  3192  
  3193  	// check seed
  3194  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  3195  	c.Check(systemDir, testutil.FilePresent)
  3196  
  3197  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  3198  	c.Assert(err, IsNil)
  3199  	c.Check(l, HasLen, 4)
  3200  
  3201  	// local asserted model snap was put in /snaps
  3202  	c.Check(filepath.Join(s.opts.SeedDir, "snaps", "pc_1.snap"), testutil.FilePresent)
  3203  	// extra snaps were put in system snaps dir
  3204  	c.Check(filepath.Join(systemDir, "snaps", "required20_1.snap"), testutil.FilePresent)
  3205  
  3206  	options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml"))
  3207  	c.Assert(err, IsNil)
  3208  
  3209  	c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{
  3210  		{
  3211  			Name:    "pc",
  3212  			SnapID:  s.AssertedSnapID("pc"),
  3213  			Channel: "20/edge",
  3214  		},
  3215  		{
  3216  			Name:    "required20",
  3217  			SnapID:  s.AssertedSnapID("required20"),
  3218  			Channel: "latest/stable",
  3219  		},
  3220  	})
  3221  }
  3222  
  3223  func (s *writerSuite) TestSeedSnapsWriteMetaCore20SignedLocalAssertedSnaps(c *C) {
  3224  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  3225  		"display-name": "my model",
  3226  		"architecture": "amd64",
  3227  		"base":         "core20",
  3228  		"grade":        "signed",
  3229  		"snaps": []interface{}{
  3230  			map[string]interface{}{
  3231  				"name":            "pc-kernel",
  3232  				"id":              s.AssertedSnapID("pc-kernel"),
  3233  				"type":            "kernel",
  3234  				"default-channel": "20",
  3235  			},
  3236  			map[string]interface{}{
  3237  				"name":            "pc",
  3238  				"id":              s.AssertedSnapID("pc"),
  3239  				"type":            "gadget",
  3240  				"default-channel": "20",
  3241  			}},
  3242  	})
  3243  
  3244  	// soundness
  3245  	c.Assert(model.Grade(), Equals, asserts.ModelSigned)
  3246  
  3247  	s.makeSnap(c, "snapd", "")
  3248  	s.makeSnap(c, "core20", "")
  3249  	s.makeSnap(c, "pc-kernel=20", "")
  3250  	s.makeSnap(c, "pc=20", "")
  3251  
  3252  	s.opts.Label = "20191122"
  3253  	w, err := seedwriter.New(model, s.opts)
  3254  	c.Assert(err, IsNil)
  3255  
  3256  	// use a local asserted snap with signed, which is supported
  3257  	err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: s.AssertedSnap("pc")}})
  3258  	c.Assert(err, IsNil)
  3259  
  3260  	tf, err := w.Start(s.db, s.newFetcher)
  3261  	c.Assert(err, IsNil)
  3262  
  3263  	localSnaps, err := w.LocalSnaps()
  3264  	c.Assert(err, IsNil)
  3265  	c.Assert(localSnaps, HasLen, 1)
  3266  
  3267  	for _, sn := range localSnaps {
  3268  		si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db)
  3269  		c.Assert(err, IsNil)
  3270  		f, err := snapfile.Open(sn.Path)
  3271  		c.Assert(err, IsNil)
  3272  		info, err := snap.ReadInfoFromSnapFile(f, si)
  3273  		c.Assert(err, IsNil)
  3274  		w.SetInfo(sn, info)
  3275  		sn.ARefs = aRefs
  3276  	}
  3277  
  3278  	err = w.InfoDerived()
  3279  	c.Assert(err, IsNil)
  3280  
  3281  	snaps, err := w.SnapsToDownload()
  3282  	c.Assert(err, IsNil)
  3283  	c.Check(snaps, HasLen, 3)
  3284  
  3285  	for _, sn := range snaps {
  3286  		channel := "latest/stable"
  3287  		switch sn.SnapName() {
  3288  		case "pc", "pc-kernel":
  3289  			channel = "20"
  3290  		}
  3291  		c.Check(sn.Channel, Equals, channel)
  3292  		s.fillDownloadedSnap(c, w, sn)
  3293  	}
  3294  
  3295  	complete, err := w.Downloaded()
  3296  	c.Assert(err, IsNil)
  3297  	c.Check(complete, Equals, true)
  3298  
  3299  	copySnap := func(name, src, dst string) error {
  3300  		return osutil.CopyFile(src, dst, 0)
  3301  	}
  3302  
  3303  	err = w.SeedSnaps(copySnap)
  3304  	c.Assert(err, IsNil)
  3305  
  3306  	err = w.WriteMeta()
  3307  	c.Assert(err, IsNil)
  3308  
  3309  	// check seed
  3310  	systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label)
  3311  	c.Check(systemDir, testutil.FilePresent)
  3312  
  3313  	l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps"))
  3314  	c.Assert(err, IsNil)
  3315  	c.Check(l, HasLen, 4)
  3316  
  3317  	// local asserted model snap was put in /snaps
  3318  	c.Check(filepath.Join(s.opts.SeedDir, "snaps", "pc_1.snap"), testutil.FilePresent)
  3319  
  3320  	// no options file was created
  3321  	c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent)
  3322  }
  3323  
  3324  func (s *writerSuite) TestSeedSnapsWriteCore20ErrWhenDirExists(c *C) {
  3325  	model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
  3326  		"display-name": "my model",
  3327  		"architecture": "amd64",
  3328  		"base":         "core20",
  3329  		"grade":        "signed",
  3330  		"snaps": []interface{}{
  3331  			map[string]interface{}{
  3332  				"name":            "pc-kernel",
  3333  				"id":              s.AssertedSnapID("pc-kernel"),
  3334  				"type":            "kernel",
  3335  				"default-channel": "20",
  3336  			},
  3337  			map[string]interface{}{
  3338  				"name":            "pc",
  3339  				"id":              s.AssertedSnapID("pc"),
  3340  				"type":            "gadget",
  3341  				"default-channel": "20",
  3342  			}},
  3343  	})
  3344  
  3345  	err := os.MkdirAll(filepath.Join(s.opts.SeedDir, "systems", "1234"), 0755)
  3346  	c.Assert(err, IsNil)
  3347  	s.opts.Label = "1234"
  3348  	w, err := seedwriter.New(model, s.opts)
  3349  	c.Assert(err, IsNil)
  3350  	c.Assert(w, NotNil)
  3351  
  3352  	_, err = w.Start(s.db, s.newFetcher)
  3353  	c.Assert(err, ErrorMatches, `system "1234" already exists`)
  3354  	c.Assert(seedwriter.IsSytemDirectoryExistsError(err), Equals, true)
  3355  }