From 44a2a138bff96d610ec5a3f8dadee4a15da68f80 Mon Sep 17 00:00:00 2001 From: Andrew Phelps Date: Mon, 23 Sep 2024 13:46:59 -0400 Subject: [PATCH] a/snapasserts: add new method to ValidationSets to enable checking a component's constraints --- asserts/snapasserts/export_test.go | 8 + asserts/snapasserts/validation_sets.go | 185 +++++++++++++++----- asserts/snapasserts/validation_sets_test.go | 144 +++++++++++++++ 3 files changed, 295 insertions(+), 42 deletions(-) diff --git a/asserts/snapasserts/export_test.go b/asserts/snapasserts/export_test.go index d2836d894e11..c2ca64a23eff 100644 --- a/asserts/snapasserts/export_test.go +++ b/asserts/snapasserts/export_test.go @@ -19,3 +19,11 @@ package snapasserts type ByRevision = byRevision +type ContainerPresence = presence + +func NewSnapPresence(p ContainerPresence, comps map[string]ContainerPresence) SnapPresence { + return SnapPresence{ + presence: p, + components: comps, + } +} diff --git a/asserts/snapasserts/validation_sets.go b/asserts/snapasserts/validation_sets.go index 9249d5ddbae1..ccd4ab2ad016 100644 --- a/asserts/snapasserts/validation_sets.go +++ b/asserts/snapasserts/validation_sets.go @@ -968,39 +968,20 @@ func (v *ValidationSets) constraintsForSnap(snapRef naming.SnapRef) *snapConstra // returned if presence of the snap is "invalid". // The method assumes that validation sets are not in conflict. func (v *ValidationSets) CheckPresenceRequired(snapRef naming.SnapRef) ([]ValidationSetKey, snap.Revision, error) { - cstrs := v.constraintsForSnap(snapRef) - if cstrs == nil { - return nil, unspecifiedRevision, nil - } - if cstrs.presence == asserts.PresenceInvalid { - return nil, unspecifiedRevision, &PresenceConstraintError{snapRef.SnapName(), cstrs.presence} + p, err := v.Presence(snapRef) + if err != nil { + return nil, snap.Revision{}, err } - if cstrs.presence != asserts.PresenceRequired { - return nil, unspecifiedRevision, nil + + if p.Presence == asserts.PresenceInvalid { + return nil, snap.Revision{}, &PresenceConstraintError{snapRef.SnapName(), p.Presence} } - snapRev := unspecifiedRevision - var keys []ValidationSetKey - for rev, revCstr := range cstrs.revisions { - for _, rc := range revCstr { - vs := v.sets[rc.validationSetKey] - if vs == nil { - return nil, unspecifiedRevision, fmt.Errorf("internal error: no validation set for %q", rc.validationSetKey) - } - keys = append(keys, NewValidationSetKey(vs)) - // there may be constraints without revision; only set snapRev if - // it wasn't already determined. Note that if revisions are set, - // then they are the same, otherwise validation sets would be in - // conflict. - // This is an equivalent of 'if rev != unspecifiedRevision`. - if snapRev == unspecifiedRevision { - snapRev = rev - } - } + if p.Presence != asserts.PresenceRequired { + return nil, unspecifiedRevision, nil } - sort.Sort(ValidationSetKeySlice(keys)) - return keys, snapRev, nil + return p.Sets, p.Revision, nil } // CanBePresent returns true if a snap can be present in a situation in which @@ -1036,28 +1017,148 @@ func (v *ValidationSets) SnapConstrained(snapRef naming.SnapRef) bool { // presence of the snap is "optional" or "required". // The method assumes that validation sets are not in conflict. func (v *ValidationSets) CheckPresenceInvalid(snapRef naming.SnapRef) ([]ValidationSetKey, error) { - cstrs := v.constraintsForSnap(snapRef) - if cstrs == nil { + if !v.SnapConstrained(snapRef) { return nil, nil } - if cstrs.presence != asserts.PresenceInvalid { - return nil, &PresenceConstraintError{snapRef.SnapName(), cstrs.presence} + + p, err := v.Presence(snapRef) + if err != nil { + return nil, err + } + + if p.Presence != asserts.PresenceInvalid { + return nil, &PresenceConstraintError{snapRef.SnapName(), p.Presence} + } + + return p.Sets, nil +} + +type presence struct { + Presence asserts.Presence + Revision snap.Revision + Sets ValidationSetKeySlice +} + +// SnapPresence contains information about a snap's allowed presence with +// respect to a set of validation sets. +type SnapPresence struct { + presence + components map[string]presence +} + +// Constrained returns true if the snap is constrained in any way by the +// validation sets that this SnapPresence is created from. +// Ultimately, one of these things must be true for a snap to be constrained: +// - snap has a presence of either "required" or "invalid" +// - the snap's revision is pinned to a specific revision +// - either of the above are true for any of the snap's components +func (s *SnapPresence) Constrained() bool { + if s.constrained() { + return true + } + + for _, comp := range s.components { + if comp.constrained() { + return true + } + } + return false +} + +func (p *presence) constrained() bool { + return p.Presence != asserts.PresenceOptional || !p.Revision.Unset() +} + +// Component returns the presence of the given component of the snap. If this +// SnapPresence doesn't know about the component, the component will be +// considered optional and allowed to have any revision. +func (s *SnapPresence) Component(name string) presence { + if s.components == nil { + return presence{ + Presence: asserts.PresenceOptional, + } + } + + cp, ok := s.components[name] + if !ok { + return presence{ + Presence: asserts.PresenceOptional, + } + } + return cp +} + +// RequiredComponents returns a set of all of the components that are required +// to be installed when this snap is installed. +func (s *SnapPresence) RequiredComponents() map[string]presence { + required := make(map[string]presence) + for name, pres := range s.components { + if pres.Presence != asserts.PresenceRequired { + continue + } + required[name] = pres + } + return required +} + +// Presence returns a SnapPresence for the given snap. The returned struct +// contains information about the allowed presence of the snap, with respect to +// the validation sets that are known to this ValidationSets. if the snap is not +// constrained by any validation sets, the presence will be considered optional. +func (v *ValidationSets) Presence(sn naming.SnapRef) (SnapPresence, error) { + cstrs := v.constraintsForSnap(sn) + if cstrs == nil { + return SnapPresence{ + presence: presence{Presence: asserts.PresenceOptional}, + }, nil + } + + snapPresence, err := presenceFromConstraints(cstrs.constraints, v.sets) + if err != nil { + return SnapPresence{}, err + } + + comps := make(map[string]presence, len(cstrs.componentConstraints)) + for _, cstrs := range cstrs.componentConstraints { + compPresence, err := presenceFromConstraints(cstrs.constraints, v.sets) + if err != nil { + return SnapPresence{}, err + } + comps[cstrs.name] = compPresence + } + + return SnapPresence{ + presence: snapPresence, + components: comps, + }, nil +} + +func presenceFromConstraints(cstrs constraints, vsets map[string]*asserts.ValidationSet) (presence, error) { + p := presence{ + Presence: asserts.PresenceOptional, } - var keys []ValidationSetKey - for _, revCstr := range cstrs.revisions { + for rev, revCstr := range cstrs.revisions { for _, rc := range revCstr { - if rc.presence == asserts.PresenceInvalid { - vs := v.sets[rc.validationSetKey] - if vs == nil { - return nil, fmt.Errorf("internal error: no validation set for %q", rc.validationSetKey) - } - keys = append(keys, NewValidationSetKey(vs)) + vs := vsets[rc.validationSetKey] + if vs == nil { + return presence{}, fmt.Errorf("internal error: no validation set for %q", rc.validationSetKey) + } + + p.Sets = append(p.Sets, NewValidationSetKey(vs)) + + if p.Revision == unspecifiedRevision { + p.Revision = rev + } + + if p.Presence == asserts.PresenceOptional { + p.Presence = rc.presence } } } - sort.Sort(ValidationSetKeySlice(keys)) - return keys, nil + sort.Sort(ValidationSetKeySlice(p.Sets)) + + return p, nil } // ParseValidationSet parses a validation set string (account/name or account/name=sequence) diff --git a/asserts/snapasserts/validation_sets_test.go b/asserts/snapasserts/validation_sets_test.go index dee3b476e1ad..a8675d9dfdb1 100644 --- a/asserts/snapasserts/validation_sets_test.go +++ b/asserts/snapasserts/validation_sets_test.go @@ -1985,3 +1985,147 @@ func (s *validationSetsSuite) TestSnapConstrained(c *C) { Name: "unknown-snap", }), Equals, false) } + +func (s *validationSetsSuite) TestSnapPresence(c *C) { + one := assertstest.FakeAssertion(map[string]interface{}{ + "type": "validation-set", + "authority-id": "account-id", + "series": "16", + "account-id": "account-id", + "name": "one", + "sequence": "1", + "snaps": []interface{}{ + map[string]interface{}{ + "name": "snap-1", + "id": snaptest.AssertedSnapID("snap-1"), + "presence": "invalid", + }, + map[string]interface{}{ + "name": "snap-2", + "id": snaptest.AssertedSnapID("snap-2"), + "presence": "required", + }, + map[string]interface{}{ + "name": "snap-3", + "id": snaptest.AssertedSnapID("snap-3"), + "presence": "optional", + "components": map[string]interface{}{ + "comp-4": map[string]interface{}{ + "presence": "required", + }, + }, + }, + }, + }).(*asserts.ValidationSet) + + two := assertstest.FakeAssertion(map[string]interface{}{ + "type": "validation-set", + "authority-id": "account-id", + "series": "16", + "account-id": "account-id", + "name": "two", + "sequence": "1", + "snaps": []interface{}{ + map[string]interface{}{ + "name": "snap-2", + "id": snaptest.AssertedSnapID("snap-2"), + "presence": "optional", + "revision": "2", + "components": map[string]interface{}{ + "comp-2": map[string]interface{}{ + "presence": "required", + "revision": "22", + }, + "comp-3": map[string]interface{}{ + "presence": "invalid", + }, + }, + }, + }, + }).(*asserts.ValidationSet) + + sets := snapasserts.NewValidationSets() + + c.Assert(sets.Add(one), IsNil) + c.Assert(sets.Add(two), IsNil) + + onePresence, err := sets.Presence(naming.Snap("snap-1")) + c.Assert(err, IsNil) + + oneExpected := snapasserts.NewSnapPresence(snapasserts.ContainerPresence{ + Presence: asserts.PresenceInvalid, + Revision: snap.R(-1), + Sets: []snapasserts.ValidationSetKey{"16/account-id/one/1"}, + }, make(map[string]snapasserts.ContainerPresence)) + c.Check(onePresence, DeepEquals, oneExpected) + c.Check(onePresence.Constrained(), Equals, true) + + twoPresence, err := sets.Presence(naming.Snap("snap-2")) + c.Assert(err, IsNil) + + twoExpected := snapasserts.NewSnapPresence(snapasserts.ContainerPresence{ + Presence: asserts.PresenceRequired, + Revision: snap.R(2), + Sets: []snapasserts.ValidationSetKey{"16/account-id/one/1", "16/account-id/two/1"}, + }, map[string]snapasserts.ContainerPresence{ + "comp-2": { + Presence: asserts.PresenceRequired, + Revision: snap.R(22), + Sets: []snapasserts.ValidationSetKey{"16/account-id/two/1"}, + }, + "comp-3": { + Presence: asserts.PresenceInvalid, + Revision: snap.R(-1), + Sets: []snapasserts.ValidationSetKey{"16/account-id/two/1"}, + }, + }) + c.Check(twoPresence, DeepEquals, twoExpected) + c.Check(twoPresence.Constrained(), Equals, true) + + c.Check(twoExpected.Component("comp-2"), DeepEquals, snapasserts.ContainerPresence{ + Presence: asserts.PresenceRequired, + Revision: snap.R(22), + Sets: []snapasserts.ValidationSetKey{"16/account-id/two/1"}, + }) + + c.Check(twoExpected.Component("comp-4"), DeepEquals, snapasserts.ContainerPresence{ + Presence: asserts.PresenceOptional, + }) + + c.Check(twoExpected.RequiredComponents(), DeepEquals, map[string]snapasserts.ContainerPresence{ + "comp-2": { + Presence: asserts.PresenceRequired, + Revision: snap.R(22), + Sets: []snapasserts.ValidationSetKey{"16/account-id/two/1"}, + }, + }) + + threePresence, err := sets.Presence(naming.Snap("snap-3")) + c.Assert(err, IsNil) + + threeExpected := snapasserts.NewSnapPresence(snapasserts.ContainerPresence{ + Presence: asserts.PresenceOptional, + Revision: snap.R(0), + Sets: []snapasserts.ValidationSetKey{"16/account-id/one/1"}, + }, map[string]snapasserts.ContainerPresence{ + "comp-4": { + Presence: asserts.PresenceRequired, + Sets: []snapasserts.ValidationSetKey{"16/account-id/one/1"}, + }, + }) + c.Check(threePresence, DeepEquals, threeExpected) + c.Check(threePresence.Constrained(), Equals, true) + + fourPresence, err := sets.Presence(naming.Snap("snap-4")) + c.Assert(err, IsNil) + + fourExpected := snapasserts.NewSnapPresence(snapasserts.ContainerPresence{ + Presence: asserts.PresenceOptional, + }, nil) + c.Check(fourPresence, DeepEquals, fourExpected) + c.Check(fourPresence.Constrained(), Equals, false) + + c.Check(fourExpected.Component("anything"), DeepEquals, snapasserts.ContainerPresence{ + Presence: asserts.PresenceOptional, + }) +}