Skip to content

Commit

Permalink
o/devicestate: only allow creating recovery systems with vsets that r…
Browse files Browse the repository at this point in the history
…equire snaps that are at least in the model
  • Loading branch information
andrewphelpsj committed Dec 19, 2024
1 parent 48feac6 commit 97dcdbc
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 3 deletions.
24 changes: 21 additions & 3 deletions overlord/devicestate/devicestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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()

Expand Down
52 changes: 52 additions & 0 deletions overlord/devicestate/devicestate_systems_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 97dcdbc

Please sign in to comment.