summaryrefslogtreecommitdiff
diff options
authorPaweł Stołowski <stolowski@gmail.com>2022-06-23 13:59:49 +0200
committerPaweł Stołowski <stolowski@gmail.com>2022-06-23 13:59:49 +0200
commit48aaec2e15f64494c4d4ae1906e4e23fd6dffbe4 (patch)
treeeae1f360f8bd5d5f7fa7da07f6dcf984aa22281f
parent5c35df06c8eacc7affd195f218075396a9c3b672 (diff)
parent58826a919e2907cb185c60e2127fcbd1c397dcec (diff)
Merge branch 'validation-sets/try-enforce-validation-sets' into validation-sets/enforce-refresh
-rw-r--r--asserts/snapasserts/validation_sets.go32
-rw-r--r--asserts/snapasserts/validation_sets_test.go52
-rw-r--r--cmd/snap/cmd_validate.go34
-rw-r--r--overlord/assertstate/assertstate.go92
-rw-r--r--overlord/assertstate/assertstate_test.go351
5 files changed, 479 insertions, 82 deletions
diff --git a/asserts/snapasserts/validation_sets.go b/asserts/snapasserts/validation_sets.go
index c2d073311e..4b2a94e5a6 100644
--- a/asserts/snapasserts/validation_sets.go
+++ b/asserts/snapasserts/validation_sets.go
@@ -23,6 +23,7 @@ import (
"bytes"
"fmt"
"sort"
+ "strconv"
"strings"
"github.com/snapcore/snapd/asserts"
@@ -584,3 +585,34 @@ func (v *ValidationSets) CheckPresenceInvalid(snapRef naming.SnapRef) ([]string,
sort.Strings(keys)
return keys, nil
}
+
+// ParseValidationSet parses a validation set string (account/name or account/name=sequence)
+// and returns its individual components, or an error.
+func ParseValidationSet(arg string) (account, name string, seq int, err error) {
+ parts := strings.Split(arg, "=")
+ if len(parts) > 2 {
+ return "", "", 0, fmt.Errorf("cannot parse validation set, expected account/name=seq")
+ }
+ if len(parts) == 2 {
+ seq, err = strconv.Atoi(parts[1])
+ if err != nil {
+ return "", "", 0, fmt.Errorf("cannot parse sequence: %v", err)
+ }
+ }
+
+ parts = strings.Split(parts[0], "/")
+ if len(parts) != 2 {
+ return "", "", 0, fmt.Errorf("expected a single account/name")
+ }
+
+ account = parts[0]
+ name = parts[1]
+ if !asserts.IsValidAccountID(account) {
+ return "", "", 0, fmt.Errorf("invalid account ID %q", account)
+ }
+ if !asserts.IsValidValidationSetName(name) {
+ return "", "", 0, fmt.Errorf("invalid validation set name %q", name)
+ }
+
+ return account, name, seq, nil
+}
diff --git a/asserts/snapasserts/validation_sets_test.go b/asserts/snapasserts/validation_sets_test.go
index 62ea0f9a7c..79bb2d15b2 100644
--- a/asserts/snapasserts/validation_sets_test.go
+++ b/asserts/snapasserts/validation_sets_test.go
@@ -1012,3 +1012,55 @@ func (s *validationSetsSuite) TestIsPresenceInvalid(c *C) {
c.Assert(err, IsNil)
c.Check(vsKeys, HasLen, 0)
}
+
+func (s *validationSetsSuite) TestParseValidationSet(c *C) {
+ for _, tc := range []struct {
+ input string
+ errMsg string
+ account string
+ name string
+ sequence int
+ }{
+ {
+ input: "foo/bar",
+ account: "foo",
+ name: "bar",
+ },
+ {
+ input: "foo/bar=9",
+ account: "foo",
+ name: "bar",
+ sequence: 9,
+ },
+ {
+ input: "foo",
+ errMsg: "expected a single account/name",
+ },
+ {
+ input: "foo/bar/baz",
+ errMsg: "expected a single account/name",
+ },
+ {
+ input: "",
+ errMsg: "expected a single account/name",
+ },
+ {
+ input: "foo=1",
+ errMsg: "expected a single account/name",
+ },
+ {
+ input: "foo/bar=x",
+ errMsg: `cannot parse sequence: strconv.Atoi: parsing "x": invalid syntax`,
+ },
+ } {
+ account, name, seq, err := snapasserts.ParseValidationSet(tc.input)
+ if tc.errMsg != "" {
+ c.Assert(err, ErrorMatches, tc.errMsg)
+ } else {
+ c.Assert(err, IsNil)
+ }
+ c.Check(account, Equals, tc.account)
+ c.Check(name, Equals, tc.name)
+ c.Check(seq, Equals, tc.sequence)
+ }
+}
diff --git a/cmd/snap/cmd_validate.go b/cmd/snap/cmd_validate.go
index 94d14ed303..b53d9e348d 100644
--- a/cmd/snap/cmd_validate.go
+++ b/cmd/snap/cmd_validate.go
@@ -21,12 +21,11 @@ package main
import (
"fmt"
- "strconv"
"strings"
"github.com/jessevdk/go-flags"
- "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/snapasserts"
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/i18n"
)
@@ -69,35 +68,6 @@ func init() {
cmd.hidden = true
}
-func splitValidationSetArg(arg string) (account, name string, seq int, err error) {
- parts := strings.Split(arg, "=")
- if len(parts) > 2 {
- return "", "", 0, fmt.Errorf("cannot parse validation set, expected account/name=seq")
- }
- if len(parts) == 2 {
- seq, err = strconv.Atoi(parts[1])
- if err != nil {
- return "", "", 0, err
- }
- }
-
- parts = strings.Split(parts[0], "/")
- if len(parts) != 2 {
- return "", "", 0, fmt.Errorf("expected a single account/name")
- }
-
- account = parts[0]
- name = parts[1]
- if !asserts.IsValidAccountID(account) {
- return "", "", 0, fmt.Errorf("invalid account ID %q", account)
- }
- if !asserts.IsValidValidationSetName(name) {
- return "", "", 0, fmt.Errorf("invalid validation set name %q", name)
- }
-
- return account, name, seq, nil
-}
-
func fmtValid(res *client.ValidationSetResult) string {
if res.Valid {
return "valid"
@@ -139,7 +109,7 @@ func (cmd *cmdValidate) Execute(args []string) error {
var seq int
var err error
if cmd.Positional.ValidationSet != "" {
- accountID, name, seq, err = splitValidationSetArg(cmd.Positional.ValidationSet)
+ accountID, name, seq, err = snapasserts.ParseValidationSet(cmd.Positional.ValidationSet)
if err != nil {
return fmt.Errorf("cannot parse validation set %q: %v", cmd.Positional.ValidationSet, err)
}
diff --git a/overlord/assertstate/assertstate.go b/overlord/assertstate/assertstate.go
index defd1af12d..2cc0bac83f 100644
--- a/overlord/assertstate/assertstate.go
+++ b/overlord/assertstate/assertstate.go
@@ -25,7 +25,6 @@ package assertstate
import (
"errors"
"fmt"
- "strconv"
"strings"
"github.com/snapcore/snapd/asserts"
@@ -774,50 +773,27 @@ func validationSetAssertionForEnforce(st *state.State, accountID, name string, s
}
// TryEnforceValidationSets tries to fetch the given validation sets and enforce them (together with currently tracked validation sets) against installed snaps,
-// but doesn't update tracking information. It may return snapasserts.ValidationSetsValidationError which can be used to install/remove snaps as required
-// to satisfy validation sets constraints.
+// but doesn't update tracking information in case of an error. It may return snapasserts.ValidationSetsValidationError which can be used to install/remove snaps
+// as required to satisfy validation sets constraints.
func TryEnforceValidationSets(st *state.State, validationSets []string, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error {
deviceCtx, err := snapstate.DevicePastSeeding(st, nil)
if err != nil {
return err
}
- opts := &RefreshAssertionsOptions{IsAutoRefresh: false}
-
- // refresh all currently tracked validation set assertions (this may or may not
- // include the one requested by the caller).
- if err = RefreshValidationSetAssertions(st, userID, opts); err != nil {
- return err
- }
-
db := cachedDB(st)
pool := asserts.NewPool(db, maxGroups)
extraVsHeaders := make([]map[string]string, 0, len(validationSets))
+ newTracking := make([]*ValidationSetTracking, 0, len(validationSets))
for _, vsstr := range validationSets {
- // XXX: do we have a helper for this already?
- parts := strings.Split(vsstr, "/")
- if len(parts) != 2 {
- return fmt.Errorf("cannot parse validation set: %s", vsstr)
- }
- accountID, name := parts[0], parts[1]
- seqParts := strings.Split(name, "=")
- var sequence int
- if len(seqParts) > 2 {
- return fmt.Errorf("cannot parse validation-set sequence %s", name)
- }
- if len(seqParts) == 2 {
- sequence, err = strconv.Atoi(seqParts[1])
- if err != nil {
- return fmt.Errorf("cannot parse validation-set sequence %s: %v", seqParts[1], err)
- }
+ accountID, name, sequence, err := snapasserts.ParseValidationSet(vsstr)
+ if err != nil {
+ return err
}
- // try to get existing from the db. It will be the latest one if it was
- // tracked already and thus refreshed via RefreshValidationSetAssertions.
- // Otherwise, it may be a local assertion that was tracked in the past and
- // then forgotten, in which case we need to refresh it explicitly.
+ // try to get existing from the db
headers := map[string]string{
"series": release.Series,
"account-id": accountID,
@@ -834,28 +810,27 @@ func TryEnforceValidationSets(st *state.State, validationSets []string, userID i
Revision: asserts.RevisionNotKnown,
Pinned: sequence > 0,
}
+
+ // prepare tracking data, note current is not known yet
+ tr := &ValidationSetTracking{
+ AccountID: headers["account-id"],
+ Name: headers["name"],
+ Mode: Enforce,
+ // may be 0 meaning no pinning
+ PinnedAt: sequence,
+ }
+
extraVsHeaders = append(extraVsHeaders, headers)
+ newTracking = append(newTracking, tr)
vs, err := getSpecificSequenceOrLatest(db, headers)
// found locally
if err == nil {
- // check if we were tracking it already; if not, that
- // means we found an old assertion (it was very likely tracked in the
- // past) and we need to update it as it wasn't covered
- // by RefreshValidationSetAssertions.
- var tr ValidationSetTracking
- trerr := GetValidationSet(st, accountID, name, &tr)
- if trerr != nil && !errors.Is(trerr, state.ErrNoState) {
- return trerr
- }
- // not tracked, update the assertion
- if errors.Is(trerr, state.ErrNoState) {
- // update with pool
- atSeq.Sequence = vs.Sequence()
- atSeq.Revision = vs.Revision()
- if err := pool.AddSequenceToUpdate(atSeq, atSeq.Unique()); err != nil {
- return err
- }
+ // update with pool
+ atSeq.Sequence = vs.Sequence()
+ atSeq.Revision = vs.Revision()
+ if err := pool.AddSequenceToUpdate(atSeq, atSeq.Unique()); err != nil {
+ return err
}
} else {
if !asserts.IsNotFound(err) {
@@ -883,6 +858,8 @@ func TryEnforceValidationSets(st *state.State, validationSets []string, userID i
valsets, err := EnforcedValidationSets(st, extraVs...)
if err != nil {
+ // the returned error may be ValidationSetsValidationError which is normal and means we cannot enforce
+ // the new validation sets - the caller should resolve the error and retry.
return err
}
if err := valsets.Conflict(); err != nil {
@@ -897,13 +874,28 @@ func TryEnforceValidationSets(st *state.State, validationSets []string, userID i
return nil
}
+ opts := &RefreshAssertionsOptions{}
if err := resolvePoolNoFallback(st, pool, checkBeforeCommit, userID, deviceCtx, opts); err != nil {
return err
}
- // TODO: update tracking for all vsets
+ // no error, all validation-sets can be enforced, update tracking for all vsets
+ for i := 0; i < len(extraVsHeaders); i++ {
+ // get latest assertion from the db to determine current
+ a, err := db.FindSequence(asserts.ValidationSetType, extraVsHeaders[i], -1, -1)
+ if err != nil {
+ // this is unexpected since all asserts should be resolved and commited at this point
+ return fmt.Errorf("internal error: cannot find validation set assertion: %v", err)
+ }
+ vs := a.(*asserts.ValidationSet)
+ tr := newTracking[i]
+ tr.Current = vs.Sequence()
+ }
+ for _, tr := range newTracking {
+ UpdateValidationSet(st, tr)
+ }
- return nil
+ return addCurrentTrackingToValidationSetsHistory(st)
}
// EnforceValidationSet tries to fetch the given validation set and enforce it.
diff --git a/overlord/assertstate/assertstate_test.go b/overlord/assertstate/assertstate_test.go
index a52a5817d4..a96a2b82db 100644
--- a/overlord/assertstate/assertstate_test.go
+++ b/overlord/assertstate/assertstate_test.go
@@ -893,6 +893,10 @@ func (s *assertMgrSuite) validationSetAssert(c *C, name, sequence, revision stri
if requiredRevision != "" {
snaps[0].(map[string]interface{})["revision"] = requiredRevision
}
+ return s.validationSetAssertForSnaps(c, name, sequence, revision, snaps)
+}
+
+func (s *assertMgrSuite) validationSetAssertForSnaps(c *C, name, sequence, revision string, snaps []interface{}) *asserts.ValidationSet {
headers := map[string]interface{}{
"series": "16",
"account-id": s.dev1Acct.AccountID(),
@@ -3686,6 +3690,353 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionIgnoreValidation(c *C)
c.Check(tr, DeepEquals, *tracking)
}
+func (s *assertMgrSuite) TestTryEnforceValidationSetsAssertionsValidationError(c *C) {
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ // have a model and the store assertion available
+ storeAs := s.setupModelAndStore(c)
+ c.Assert(s.storeSigning.Add(storeAs), IsNil)
+ c.Assert(assertstate.Add(st, s.storeSigning.StoreAccountKey("")), IsNil)
+ c.Assert(assertstate.Add(st, s.dev1Acct), IsNil)
+ c.Assert(assertstate.Add(st, s.dev1AcctKey), IsNil)
+
+ // pretend we are already enforcing a validation set foo
+ snaps3 := []interface{}{
+ map[string]interface{}{
+ "id": "qOqKhntON3vR7kwEbVPsILm7bUViPDzz",
+ "name": "some-snap",
+ "presence": "required",
+ }}
+ vsetAs3 := s.validationSetAssertForSnaps(c, "foo", "1", "1", snaps3)
+ c.Assert(assertstate.Add(st, vsetAs3), IsNil)
+ assertstate.UpdateValidationSet(st, &assertstate.ValidationSetTracking{
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "foo",
+ Mode: assertstate.Enforce,
+ Current: 1,
+ })
+
+ // add validation set assertions to the store
+ snaps1 := []interface{}{
+ map[string]interface{}{
+ "id": "qOqKhntON3vR7kwEbVPsILm7bUViPDzz",
+ "name": "some-snap",
+ "presence": "required",
+ "revision": "3",
+ },
+ map[string]interface{}{
+ "id": "aAqKhntON3vR7kwEbVPsILm7bUViPDaa",
+ "name": "other-snap",
+ "presence": "required",
+ }}
+ vsetAs := s.validationSetAssertForSnaps(c, "bar", "2", "2", snaps1)
+ c.Assert(s.storeSigning.Add(vsetAs), IsNil)
+ snaps2 := []interface{}{
+ map[string]interface{}{
+ "id": "cccchntON3vR7kwEbVPsILm7bUViPDcc",
+ "name": "invalid-snap",
+ "presence": "invalid",
+ }}
+ vsetAs2 := s.validationSetAssertForSnaps(c, "baz", "1", "1", snaps2)
+ c.Assert(s.storeSigning.Add(vsetAs2), IsNil)
+
+ // try to enforce extra validation sets bar and baz. some-snap is present (and required by foo at any revision),
+ // but needs to be at revision 3 to satisfy bar. invalid-snap is present but is invalid for baz.
+ installedSnaps := []*snapasserts.InstalledSnap{
+ snapasserts.NewInstalledSnap("some-snap", "qOqKhntON3vR7kwEbVPsILm7bUViPDzz", snap.Revision{N: 1}),
+ snapasserts.NewInstalledSnap("invalid-snap", "cccchntON3vR7kwEbVPsILm7bUViPDcc", snap.Revision{N: 1}),
+ }
+ err := assertstate.TryEnforceValidationSets(st, []string{fmt.Sprintf("%s/bar", s.dev1Acct.AccountID()), fmt.Sprintf("%s/baz", s.dev1Acct.AccountID())}, 0, installedSnaps, nil)
+ verr, ok := err.(*snapasserts.ValidationSetsValidationError)
+ c.Assert(ok, Equals, true)
+ c.Check(verr.WrongRevisionSnaps, DeepEquals, map[string]map[snap.Revision][]string{
+ "some-snap": {
+ snap.R(3): []string{fmt.Sprintf("%s/bar", s.dev1Acct.AccountID())},
+ },
+ })
+ c.Check(verr.MissingSnaps, DeepEquals, map[string]map[snap.Revision][]string{
+ "other-snap": {
+ snap.R(0): []string{fmt.Sprintf("%s/bar", s.dev1Acct.AccountID())},
+ },
+ })
+ c.Check(verr.InvalidSnaps, DeepEquals, map[string][]string{"invalid-snap": {fmt.Sprintf("%s/baz", s.dev1Acct.AccountID())}})
+
+ // new assertions were not commited
+ _, err = assertstate.DB(s.state).Find(asserts.ValidationSetType, map[string]string{
+ "series": "16",
+ "account-id": s.dev1Acct.AccountID(),
+ "name": "bar",
+ "sequence": "2",
+ })
+ c.Assert(asserts.IsNotFound(err), Equals, true)
+ _, err = assertstate.DB(s.state).Find(asserts.ValidationSetType, map[string]string{
+ "series": "16",
+ "account-id": s.dev1Acct.AccountID(),
+ "name": "baz",
+ "sequence": "1",
+ })
+ c.Assert(asserts.IsNotFound(err), Equals, true)
+ c.Check(s.fakeStore.(*fakeStore).opts.IsAutoRefresh, Equals, false)
+}
+
+func (s *assertMgrSuite) TestTryEnforceValidationSetsAssertionsOK(c *C) {
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ // have a model and the store assertion available
+ storeAs := s.setupModelAndStore(c)
+ c.Assert(s.storeSigning.Add(storeAs), IsNil)
+ c.Assert(assertstate.Add(st, s.storeSigning.StoreAccountKey("")), IsNil)
+ c.Assert(assertstate.Add(st, s.dev1Acct), IsNil)
+ c.Assert(assertstate.Add(st, s.dev1AcctKey), IsNil)
+
+ // pretend we are already enforcing a validation set foo
+ snaps3 := []interface{}{
+ map[string]interface{}{
+ "id": "qOqKhntON3vR7kwEbVPsILm7bUViPDzz",
+ "name": "some-snap",
+ "presence": "required",
+ }}
+ vsetAs3 := s.validationSetAssertForSnaps(c, "foo", "1", "1", snaps3)
+ c.Assert(assertstate.Add(st, vsetAs3), IsNil)
+ assertstate.UpdateValidationSet(st, &assertstate.ValidationSetTracking{
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "foo",
+ Mode: assertstate.Enforce,
+ Current: 1,
+ })
+
+ // add validation set assertions to the store
+ snaps1 := []interface{}{
+ map[string]interface{}{
+ "id": "qOqKhntON3vR7kwEbVPsILm7bUViPDzz",
+ "name": "some-snap",
+ "presence": "required",
+ "revision": "3",
+ }}
+ vsetAs := s.validationSetAssertForSnaps(c, "bar", "2", "2", snaps1)
+ c.Assert(s.storeSigning.Add(vsetAs), IsNil)
+ snaps2 := []interface{}{
+ map[string]interface{}{
+ "id": "aAqKhntON3vR7kwEbVPsILm7bUViPDaa",
+ "name": "other-snap",
+ "presence": "optional",
+ }}
+ vsetAs2 := s.validationSetAssertForSnaps(c, "baz", "1", "1", snaps2)
+ c.Assert(s.storeSigning.Add(vsetAs2), IsNil)
+
+ installedSnaps := []*snapasserts.InstalledSnap{
+ snapasserts.NewInstalledSnap("some-snap", "qOqKhntON3vR7kwEbVPsILm7bUViPDzz", snap.Revision{N: 3}),
+ }
+ err := assertstate.TryEnforceValidationSets(st, []string{fmt.Sprintf("%s/bar", s.dev1Acct.AccountID()), fmt.Sprintf("%s/baz=1", s.dev1Acct.AccountID())}, 0, installedSnaps, nil)
+ c.Assert(err, IsNil)
+
+ // new assertions were commited
+ _, err = assertstate.DB(s.state).Find(asserts.ValidationSetType, map[string]string{
+ "series": "16",
+ "account-id": s.dev1Acct.AccountID(),
+ "name": "bar",
+ "sequence": "2",
+ })
+ c.Assert(err, IsNil)
+
+ _, err = assertstate.DB(s.state).Find(asserts.ValidationSetType, map[string]string{
+ "series": "16",
+ "account-id": s.dev1Acct.AccountID(),
+ "name": "baz",
+ "sequence": "1",
+ })
+ c.Assert(err, IsNil)
+ c.Check(s.fakeStore.(*fakeStore).opts.IsAutoRefresh, Equals, false)
+
+ // tracking was updated
+ var tr assertstate.ValidationSetTracking
+ c.Assert(assertstate.GetValidationSet(s.state, s.dev1Acct.AccountID(), "bar", &tr), IsNil)
+ c.Check(tr, DeepEquals, assertstate.ValidationSetTracking{
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "bar",
+ Mode: assertstate.Enforce,
+ Current: 2,
+ })
+ c.Assert(assertstate.GetValidationSet(s.state, s.dev1Acct.AccountID(), "baz", &tr), IsNil)
+ c.Check(tr, DeepEquals, assertstate.ValidationSetTracking{
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "baz",
+ Mode: assertstate.Enforce,
+ Current: 1,
+ PinnedAt: 1,
+ })
+
+ // and it was added to the history
+ // note, normally there would be a map with just "foo" as well, but there isn't one
+ // since we created the initial state in the test manually.
+ vshist, err := assertstate.ValidationSetsHistory(st)
+ c.Assert(err, IsNil)
+ c.Check(vshist, DeepEquals, []map[string]*assertstate.ValidationSetTracking{{
+ fmt.Sprintf("%s/foo", s.dev1Acct.AccountID()): {
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "foo",
+ Mode: assertstate.Enforce,
+ Current: 1,
+ },
+ fmt.Sprintf("%s/bar", s.dev1Acct.AccountID()): {
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "bar",
+ Mode: assertstate.Enforce,
+ Current: 2,
+ },
+ fmt.Sprintf("%s/baz", s.dev1Acct.AccountID()): {
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "baz",
+ Mode: assertstate.Enforce,
+ PinnedAt: 1,
+ Current: 1,
+ },
+ }})
+}
+
+func (s *assertMgrSuite) TestTryEnforceValidationSetsAssertionsAlreadyTrackedUpdateOK(c *C) {
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ // have a model and the store assertion available
+ storeAs := s.setupModelAndStore(c)
+ c.Assert(s.storeSigning.Add(storeAs), IsNil)
+ c.Assert(assertstate.Add(st, s.storeSigning.StoreAccountKey("")), IsNil)
+ c.Assert(assertstate.Add(st, s.dev1Acct), IsNil)
+ c.Assert(assertstate.Add(st, s.dev1AcctKey), IsNil)
+
+ // pretend we are already enforcing a validation set foo
+ snaps3 := []interface{}{
+ map[string]interface{}{
+ "id": "qOqKhntON3vR7kwEbVPsILm7bUViPDzz",
+ "name": "some-snap",
+ "presence": "required",
+ }}
+ vsetAs1 := s.validationSetAssertForSnaps(c, "foo", "1", "1", snaps3)
+ c.Assert(assertstate.Add(st, vsetAs1), IsNil)
+ assertstate.UpdateValidationSet(st, &assertstate.ValidationSetTracking{
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "foo",
+ Mode: assertstate.Enforce,
+ Current: 1,
+ })
+
+ // add validation set assertions to the store
+ snaps1 := []interface{}{
+ map[string]interface{}{
+ "id": "qOqKhntON3vR7kwEbVPsILm7bUViPDzz",
+ "name": "some-snap",
+ "presence": "required",
+ "revision": "3",
+ }}
+ vsetAs2 := s.validationSetAssertForSnaps(c, "foo", "2", "2", snaps1)
+ c.Assert(s.storeSigning.Add(vsetAs2), IsNil)
+
+ installedSnaps := []*snapasserts.InstalledSnap{
+ snapasserts.NewInstalledSnap("some-snap", "qOqKhntON3vR7kwEbVPsILm7bUViPDzz", snap.Revision{N: 3}),
+ }
+ err := assertstate.TryEnforceValidationSets(st, []string{fmt.Sprintf("%s/foo", s.dev1Acct.AccountID())}, 0, installedSnaps, nil)
+ c.Assert(err, IsNil)
+
+ // new assertion was commited
+ _, err = assertstate.DB(s.state).Find(asserts.ValidationSetType, map[string]string{
+ "series": "16",
+ "account-id": s.dev1Acct.AccountID(),
+ "name": "foo",
+ "sequence": "2",
+ })
+ c.Assert(err, IsNil)
+ c.Check(s.fakeStore.(*fakeStore).opts.IsAutoRefresh, Equals, false)
+
+ // tracking was updated
+ var tr assertstate.ValidationSetTracking
+ c.Assert(assertstate.GetValidationSet(s.state, s.dev1Acct.AccountID(), "foo", &tr), IsNil)
+ c.Check(tr, DeepEquals, assertstate.ValidationSetTracking{
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "foo",
+ Mode: assertstate.Enforce,
+ Current: 2,
+ })
+
+ // and it was added to the history
+ // note, normally there would be a map with just "foo" as well, but there isn't one
+ // since we created the initial state in the test manually.
+ vshist, err := assertstate.ValidationSetsHistory(st)
+ c.Assert(err, IsNil)
+ c.Check(vshist, DeepEquals, []map[string]*assertstate.ValidationSetTracking{{
+ fmt.Sprintf("%s/foo", s.dev1Acct.AccountID()): {
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "foo",
+ Mode: assertstate.Enforce,
+ Current: 2,
+ },
+ }})
+}
+
+func (s *assertMgrSuite) TestTryEnforceValidationSetsAssertionsConflictError(c *C) {
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ // have a model and the store assertion available
+ storeAs := s.setupModelAndStore(c)
+ c.Assert(s.storeSigning.Add(storeAs), IsNil)
+ c.Assert(assertstate.Add(st, s.storeSigning.StoreAccountKey("")), IsNil)
+ c.Assert(assertstate.Add(st, s.dev1Acct), IsNil)
+ c.Assert(assertstate.Add(st, s.dev1AcctKey), IsNil)
+
+ // pretend we are already enforcing a validation set foo
+ snaps3 := []interface{}{
+ map[string]interface{}{
+ "id": "qOqKhntON3vR7kwEbVPsILm7bUViPDzz",
+ "name": "some-snap",
+ "presence": "required",
+ }}
+ vsetAs3 := s.validationSetAssertForSnaps(c, "foo", "1", "1", snaps3)
+ c.Assert(assertstate.Add(st, vsetAs3), IsNil)
+ assertstate.UpdateValidationSet(st, &assertstate.ValidationSetTracking{
+ AccountID: s.dev1Acct.AccountID(),
+ Name: "foo",
+ Mode: assertstate.Enforce,
+ Current: 1,
+ })
+
+ // add a validation set assertion to the store
+ snaps1 := []interface{}{
+ map[string]interface{}{
+ "id": "qOqKhntON3vR7kwEbVPsILm7bUViPDzz",
+ "name": "some-snap",
+ "presence": "invalid",
+ }}
+ vsetAs := s.validationSetAssertForSnaps(c, "bar", "2", "2", snaps1)
+ c.Assert(s.storeSigning.Add(vsetAs), IsNil)
+
+ // try to enforce extra validation sets bar and baz. some-snap is present (and required by foo at any revision),
+ // but needs to be at revision 3 to satisfy bar. invalid-snap is present but is invalid for baz.
+ installedSnaps := []*snapasserts.InstalledSnap{
+ snapasserts.NewInstalledSnap("some-snap", "qOqKhntON3vR7kwEbVPsILm7bUViPDzz", snap.Revision{N: 1}),
+ }
+ err := assertstate.TryEnforceValidationSets(st, []string{fmt.Sprintf("%s/bar", s.dev1Acct.AccountID())}, 0, installedSnaps, nil)
+ _, ok := err.(*snapasserts.ValidationSetsConflictError)
+ c.Assert(ok, Equals, true)
+ c.Assert(err, ErrorMatches, `validation sets are in conflict:\n- cannot constrain snap "some-snap" as both invalid \(.*/bar\) and required at any revision \(.*/foo\).*`)
+
+ // new assertion wasn't commited
+ _, err = assertstate.DB(s.state).Find(asserts.ValidationSetType, map[string]string{
+ "series": "16",
+ "account-id": s.dev1Acct.AccountID(),
+ "name": "bar",
+ "sequence": "2",
+ })
+ c.Assert(asserts.IsNotFound(err), Equals, true)
+ c.Check(s.fakeStore.(*fakeStore).opts.IsAutoRefresh, Equals, false)
+}
+
func (s *assertMgrSuite) TestMonitorValidationSet(c *C) {
st := s.state