Skip to content

Commit

Permalink
asserts: extend validation-set assertions to understand components
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewphelpsj authored and alfonsosanchezbeato committed Jul 23, 2024
1 parent f05326f commit 4de9b7d
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 9 deletions.
7 changes: 7 additions & 0 deletions asserts/snap_asserts.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,13 @@ func checkSnapRevisionWhat(headers map[string]interface{}, name, what string) (s
return snapRevision, nil
}

func checkOptionalSnapRevisionWhat(headers map[string]interface{}, name, what string) (snapRevision int, err error) {
if _, ok := headers[name]; !ok {
return 0, nil
}
return checkSnapRevisionWhat(headers, name, what)
}

func assembleSnapRevision(assert assertionBase) (Assertion, error) {
_, err := checkDigest(assert.headers, "snap-sha3-384", crypto.SHA3_384)
if err != nil {
Expand Down
99 changes: 91 additions & 8 deletions asserts/validation_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package asserts

import (
"errors"
"fmt"
"regexp"
"strings"
Expand Down Expand Up @@ -48,8 +49,8 @@ func presencesAsStrings(presences ...Presence) []string {

var validValidationSetSnapPresences = presencesAsStrings(PresenceRequired, PresenceOptional, PresenceInvalid)

func checkPresence(snap map[string]interface{}, which string, valid []string) (Presence, error) {
presence, err := checkOptionalStringWhat(snap, "presence", which)
func checkOptionalPresence(headers map[string]interface{}, which string, valid []string) (Presence, error) {
presence, err := checkOptionalStringWhat(headers, "presence", which)
if err != nil {
return Presence(""), err
}
Expand All @@ -59,13 +60,30 @@ func checkPresence(snap map[string]interface{}, which string, valid []string) (P
return Presence(presence), nil
}

func checkPresence(headers map[string]interface{}, which string, valid []string) (Presence, error) {
presence, err := checkExistsStringWhat(headers, "presence", which)
if err != nil {
return "", err
}
if presence != "" && !strutil.ListContains(valid, presence) {
return "", fmt.Errorf("presence %s must be one of %s", which, strings.Join(valid, "|"))
}
return Presence(presence), nil
}

// ValidationSetSnap holds the details about a snap constrained by a validation-set assertion.
type ValidationSetSnap struct {
Name string
SnapID string

Presence Presence

Revision int
Components map[string]ValidationSetComponent
}

type ValidationSetComponent struct {
Presence Presence
Revision int
}

Expand Down Expand Up @@ -95,7 +113,7 @@ func checkValidationSetSnap(snap map[string]interface{}) (*ValidationSetSnap, er
return nil, err
}

presence, err := checkPresence(snap, what, validValidationSetSnapPresences)
presence, err := checkOptionalPresence(snap, what, validValidationSetSnapPresences)
if err != nil {
return nil, err
}
Expand All @@ -112,11 +130,76 @@ func checkValidationSetSnap(snap map[string]interface{}) (*ValidationSetSnap, er
return nil, fmt.Errorf(`cannot specify revision %s at the same time as stating its presence is invalid`, what)
}

components, err := checkValidationSetComponents(snap, what)
if err != nil {
return nil, err
}

return &ValidationSetSnap{
Name: name,
SnapID: snapID,
Name: name,
SnapID: snapID,
Presence: presence,
Revision: snapRevision,
Components: components,
}, nil
}

func checkValidationSetComponents(snap map[string]interface{}, what string) (map[string]ValidationSetComponent, error) {
mapping, err := checkMapWhat(snap, "components", what)
if err != nil {
return nil, errors.New(`"components" field in "snaps" header must be a map`)
}

if len(mapping) == 0 {
return nil, nil
}

components := make(map[string]ValidationSetComponent, len(mapping))
for name, comp := range mapping {
var parsed map[string]interface{}
switch c := comp.(type) {
case map[string]interface{}:
parsed = c
case string:
parsed = map[string]interface{}{"presence": c}
default:
return nil, errors.New(`each field in "components" map must be either a map or a string`)
}

component, err := checkValidationSetComponent(parsed, name)
if err != nil {
return nil, err
}
components[name] = component
}

return components, nil
}

func checkValidationSetComponent(comp map[string]interface{}, name string) (ValidationSetComponent, error) {
if err := naming.ValidateSnap(name); err != nil {
return ValidationSetComponent{}, fmt.Errorf("invalid component name %q", name)
}

what := fmt.Sprintf("of component %q", name)

presence, err := checkPresence(comp, what, validValidationSetSnapPresences)
if err != nil {
return ValidationSetComponent{}, err
}

revision, err := checkOptionalSnapRevisionWhat(comp, "revision", what)
if err != nil {
return ValidationSetComponent{}, err
}

if revision != 0 && presence == PresenceInvalid {
return ValidationSetComponent{}, fmt.Errorf(`cannot specify component revision %s at the same time as stating its presence is invalid`, what)
}

return ValidationSetComponent{
Presence: presence,
Revision: snapRevision,
Revision: revision,
}, nil
}

Expand All @@ -125,7 +208,7 @@ func checkValidationSetSnaps(snapList interface{}) ([]*ValidationSetSnap, error)

entries, ok := snapList.([]interface{})
if !ok {
return nil, fmt.Errorf(wrongHeaderType)
return nil, errors.New(wrongHeaderType)
}

seen := make(map[string]bool, len(entries))
Expand All @@ -134,7 +217,7 @@ func checkValidationSetSnaps(snapList interface{}) ([]*ValidationSetSnap, error)
for _, entry := range entries {
snap, ok := entry.(map[string]interface{})
if !ok {
return nil, fmt.Errorf(wrongHeaderType)
return nil, errors.New(wrongHeaderType)
}
valSetSnap, err := checkValidationSetSnap(snap)
if err != nil {
Expand Down
46 changes: 45 additions & 1 deletion asserts/validation_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,16 @@ func (vss *validationSetSuite) TestDecodeInvalid(c *C) {
{"OTHER", " -\n name: baz-linux\n id: bazlinuxidididididididididididid\n", `cannot list the same snap "baz-linux" multiple times`},
{"OTHER", " -\n name: baz-linux2\n id: bazlinuxidididididididididididid\n", `cannot specify the same snap id "bazlinuxidididididididididididid" multiple times, specified for snaps "baz-linux" and "baz-linux2"`},
{"presence: optional\n", "presence:\n - opt\n", `"presence" of snap "baz-linux" must be a string`},
{"presence: optional\n", "presence: no\n", `"presence" of snap "baz-linux" must be one of must be one of required|optional|invalid`},
{"presence: optional\n", "presence: no\n", `presence of snap "baz-linux" must be one of required\|optional\|invalid`},
{"revision: 99\n", "revision: 0\n", `"revision" of snap "baz-linux" must be >=1: 0`},
{"presence: optional\n", "presence: invalid\n", `cannot specify revision of snap "baz-linux" at the same time as stating its presence is invalid`},
{"OTHER", " components:\n comp: no\n", `presence of component "comp" must be one of required\|optional\|invalid`},
{"OTHER", " components:\n comp:\n revision: 1\n presence: invalid\n", `cannot specify component revision of component "comp" at the same time as stating its presence is invalid`},
{"OTHER", " components:\n c: optional\n", `invalid component name "c"`},
{"OTHER", " components:\n comp:\n revision: 1\n", `"presence" of component "comp" is mandatory`},
{"OTHER", " components:\n comp:\n - test\n", `each field in "components" map must be either a map or a string`},
{"OTHER", " components:\n comp:\n revision: -1\n presence: optional\n", `"revision" of component "comp" must be >=1: -1`},
{"OTHER", " components: some-string", `"components" field in "snaps" header must be a map`},
}

for _, test := range invalidTests {
Expand Down Expand Up @@ -167,6 +174,43 @@ func (vss *validationSetSuite) TestSnapRevisionOptional(c *C) {
c.Check(snaps[0].Revision, Equals, 0)
}

func (vss *validationSetSuite) TestSnapComponents(c *C) {
encoded := strings.Replace(validationSetExample, "TSLINE", vss.tsLine, 1)

const components = ` components:
string-only: optional
with-revision:
revision: 10
presence: required
no-revision:
presence: invalid
`

encoded = strings.Replace(encoded, "OTHER", components, 1)

a, err := asserts.Decode([]byte(encoded))
c.Assert(err, IsNil)
c.Check(a.Type(), Equals, asserts.ValidationSetType)

valset := a.(*asserts.ValidationSet)
snaps := valset.Snaps()
c.Assert(snaps, HasLen, 1)
c.Assert(snaps[0].Components, HasLen, 3)

c.Check(snaps[0].Components, DeepEquals, map[string]asserts.ValidationSetComponent{
"string-only": {
Presence: asserts.PresenceOptional,
},
"with-revision": {
Presence: asserts.PresenceRequired,
Revision: 10,
},
"no-revision": {
Presence: asserts.PresenceInvalid,
},
})
}

func (vss *validationSetSuite) TestIsValidValidationSetName(c *C) {
names := []struct {
name string
Expand Down

0 comments on commit 4de9b7d

Please sign in to comment.