Skip to content

Commit

Permalink
a/snapasserts: add new method to ValidationSets to enable checking a …
Browse files Browse the repository at this point in the history
…component's constraints
  • Loading branch information
andrewphelpsj committed Oct 11, 2024
1 parent 56c4a01 commit 44a2a13
Show file tree
Hide file tree
Showing 3 changed files with 295 additions and 42 deletions.
8 changes: 8 additions & 0 deletions asserts/snapasserts/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
185 changes: 143 additions & 42 deletions asserts/snapasserts/validation_sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
144 changes: 144 additions & 0 deletions asserts/snapasserts/validation_sets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}

0 comments on commit 44a2a13

Please sign in to comment.