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