diff --git a/overlord/devicestate/devicestate.go b/overlord/devicestate/devicestate.go index 2bf710825d7..f91c815f4f7 100644 --- a/overlord/devicestate/devicestate.go +++ b/overlord/devicestate/devicestate.go @@ -1675,6 +1675,21 @@ func RemoveRecoverySystem(st *state.State, label string) (*state.Change, error) return chg, nil } +func checkForRequiredSnapsNotPresentInModel(model *asserts.Model, vSets *snapasserts.ValidationSets) error { + snapsInModel := make(map[string]bool, len(model.AllSnaps())) + for _, sn := range model.AllSnaps() { + snapsInModel[sn.SnapName()] = true + } + + for _, sn := range vSets.RequiredSnaps() { + if !snapsInModel[sn] { + return fmt.Errorf("missing required snap in model: %s", sn) + } + } + + return nil +} + // CreateRecoverySystem creates a new recovery system with the given label. See // CreateRecoverySystemOptions for details on the options that can be provided. func CreateRecoverySystem(st *state.State, label string, opts CreateRecoverySystemOptions) (chg *state.Change, err error) { @@ -1725,9 +1740,12 @@ func CreateRecoverySystem(st *state.State, label string, opts CreateRecoverySyst return nil, err } - // TODO: check that all snaps and components that are required by validation - // sets are also required in the model. this matches the behavior of - // remodeling. + // the task that creates the recovery system doesn't know anything about + // validation sets, so we cannot create systems with snaps that are not in + // the model. + if err := checkForRequiredSnapsNotPresentInModel(model, valsets); err != nil { + return nil, err + } tracker := snap.NewSelfContainedSetPrereqTracker() diff --git a/overlord/devicestate/devicestate_systems_test.go b/overlord/devicestate/devicestate_systems_test.go index 2b32ff35a0d..6098fca05dd 100644 --- a/overlord/devicestate/devicestate_systems_test.go +++ b/overlord/devicestate/devicestate_systems_test.go @@ -1470,6 +1470,58 @@ func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemNotSe c.Check(chg, IsNil) } +func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoveryRequiredInVsetNotInModel(c *C) { + devicestate.SetBootOkRan(s.mgr, true) + + s.state.Lock() + defer s.state.Unlock() + + s.model = s.brands.Model("canonical", "pc-20", map[string]interface{}{ + "architecture": "amd64", + "grade": "dangerous", + "base": "core20", + "snaps": []interface{}{ + map[string]interface{}{ + "name": "pc-kernel", + "id": s.ss.AssertedSnapID("pc-kernel"), + "type": "kernel", + "default-channel": "20", + }, + map[string]interface{}{ + "name": "pc", + "id": s.ss.AssertedSnapID("pc"), + "type": "gadget", + "default-channel": "20", + }, + }, + "revision": "2", + }) + + vset, err := s.brands.Signing("canonical").Sign(asserts.ValidationSetType, map[string]interface{}{ + "type": "validation-set", + "authority-id": "canonical", + "series": "16", + "account-id": "canonical", + "name": "vset-1", + "sequence": "1", + "snaps": []interface{}{ + map[string]interface{}{ + "name": "required-snap", + "id": s.ss.AssertedSnapID("other"), + "presence": "required", + }, + }, + "timestamp": time.Now().UTC().Format(time.RFC3339), + }, nil, "") + c.Assert(err, IsNil) + + chg, err := devicestate.CreateRecoverySystem(s.state, "1234", devicestate.CreateRecoverySystemOptions{ + ValidationSets: []*asserts.ValidationSet{vset.(*asserts.ValidationSet)}, + }) + c.Assert(err, ErrorMatches, `missing required snap in model: required-snap`) + c.Check(chg, IsNil) +} + func (s *deviceMgrSystemsCreateSuite) makeSnapInState(c *C, name string, rev snap.Revision, extraFiles [][]string, components map[string]snap.Revision) *snap.Info { snapID := s.ss.AssertedSnapID(name) if rev.Unset() || rev.Local() {