diff --git a/seed/seedwriter/writer.go b/seed/seedwriter/writer.go index fc5a7151eae..69e0bba0681 100644 --- a/seed/seedwriter/writer.go +++ b/seed/seedwriter/writer.go @@ -427,9 +427,26 @@ func (w *Writer) SetOptionsSnaps(optSnaps []*OptionsSnap) error { if err != nil { return fmt.Errorf("cannot use option channel for snap %q: %v", whichSnap, err) } - if err := w.policy.checkSnapChannel(ch, whichSnap); err != nil { + + // Check whether the channel defined in the model assertion + // is same as the option snap(--snap=SNAP_NAME=CHANNEL) + modSnaps, err := w.modSnaps() + if err != nil { return err } + for _, modSnap := range modSnaps { + if sn.Name != modSnap.Name { + continue + } + + if modSnap.Presence == "optional" && sn.Channel == modSnap.DefaultChannel { + continue + } else { + if err := w.policy.checkSnapChannel(ch, whichSnap); err != nil { + return err + } + } + } } if local { if w.localSnaps == nil { diff --git a/seed/seedwriter/writer_test.go b/seed/seedwriter/writer_test.go index 78fb2c0f9ce..9bc0fe48928 100644 --- a/seed/seedwriter/writer_test.go +++ b/seed/seedwriter/writer_test.go @@ -3316,6 +3316,102 @@ func (s *writerSuite) TestSnapsToDownloadCore20OptionalSnaps(c *C) { c.Check(snaps[5].SnapName(), Equals, "optional20-b") } +func (s *writerSuite) TestSnapsToDownloadCore20OptionalSnapsWithSameChannel(c *C) { + model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ + "display-name": "my model", + "architecture": "amd64", + "base": "core20", + "snaps": []interface{}{ + map[string]interface{}{ + "name": "pc-kernel", + "id": s.AssertedSnapID("pc-kernel"), + "type": "kernel", + "default-channel": "20", + }, + map[string]interface{}{ + "name": "pc", + "id": s.AssertedSnapID("pc"), + "type": "gadget", + "default-channel": "20", + }, + map[string]interface{}{ + "name": "optional20-a", + "id": s.AssertedSnapID("optional20-a"), + "presence": "optional", + "default-channel": "20", + }, + }, + }) + + // validity + c.Assert(model.Grade(), Equals, asserts.ModelSigned) + + s.makeSnap(c, "snapd", "") + s.makeSnap(c, "core20", "") + s.makeSnap(c, "pc-kernel=20", "") + s.makeSnap(c, "pc=20", "") + s.makeSnap(c, "optional20-a", "developerid") + + s.opts.Label = "20191122" + w, err := seedwriter.New(model, s.opts) + c.Assert(err, IsNil) + + err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "optional20-a", Channel: "20"}}) + c.Assert(err, IsNil) + + err = w.Start(s.db, s.rf) + c.Assert(err, IsNil) + + snaps, err := w.SnapsToDownload() + c.Assert(err, IsNil) + c.Check(snaps, HasLen, 5) + c.Check(snaps[4].SnapName(), Equals, "optional20-a") +} + +func (s *writerSuite) TestSnapsToDownloadCore20OptionalSnapsWithDifferentChannel(c *C) { + model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ + "display-name": "my model", + "architecture": "amd64", + "base": "core20", + "snaps": []interface{}{ + map[string]interface{}{ + "name": "pc-kernel", + "id": s.AssertedSnapID("pc-kernel"), + "type": "kernel", + "default-channel": "20", + }, + map[string]interface{}{ + "name": "pc", + "id": s.AssertedSnapID("pc"), + "type": "gadget", + "default-channel": "20", + }, + map[string]interface{}{ + "name": "optional20-a", + "id": s.AssertedSnapID("optional20-a"), + "presence": "optional", + "default-channel": "20", + }, + }, + }) + + // validity + c.Assert(model.Grade(), Equals, asserts.ModelSigned) + + s.makeSnap(c, "snapd", "") + s.makeSnap(c, "core20", "") + s.makeSnap(c, "pc-kernel=20", "") + s.makeSnap(c, "pc=20", "") + s.makeSnap(c, "optional20-a", "developerid") + + s.opts.Label = "20191122" + w, err := seedwriter.New(model, s.opts) + c.Assert(err, IsNil) + + err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "optional20-a", Channel: "22"}}) + c.Check(err, ErrorMatches, `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous`) +} + func (s *writerSuite) TestSeedSnapsWriteMetaCore20ExtraSnaps(c *C) { model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ "display-name": "my model",