gitee.com/mysnapcore/mysnapd@v0.1.0/asserts/snapasserts/validation_sets.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package snapasserts
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"gitee.com/mysnapcore/mysnapd/asserts"
    30  	"gitee.com/mysnapcore/mysnapd/snap"
    31  	"gitee.com/mysnapcore/mysnapd/snap/naming"
    32  )
    33  
    34  // InstalledSnap holds the minimal details about an installed snap required to
    35  // check it against validation sets.
    36  type InstalledSnap struct {
    37  	naming.SnapRef
    38  	Revision snap.Revision
    39  }
    40  
    41  // NewInstalledSnap creates InstalledSnap.
    42  func NewInstalledSnap(name, snapID string, revision snap.Revision) *InstalledSnap {
    43  	return &InstalledSnap{
    44  		SnapRef:  naming.NewSnapRef(name, snapID),
    45  		Revision: revision,
    46  	}
    47  }
    48  
    49  // ValidationSetsConflictError describes an error where multiple
    50  // validation sets are in conflict about snaps.
    51  type ValidationSetsConflictError struct {
    52  	Sets  map[string]*asserts.ValidationSet
    53  	Snaps map[string]error
    54  }
    55  
    56  func (e *ValidationSetsConflictError) Error() string {
    57  	buf := bytes.NewBufferString("validation sets are in conflict:")
    58  	for _, err := range e.Snaps {
    59  		fmt.Fprintf(buf, "\n- %v", err)
    60  	}
    61  	return buf.String()
    62  }
    63  
    64  // ValidationSetsValidationError describes an error arising
    65  // from validation of snaps against ValidationSets.
    66  type ValidationSetsValidationError struct {
    67  	// MissingSnaps maps missing snap names to the expected revisions and respective validation sets requiring them.
    68  	// Revisions may be unset if no specific revision is required
    69  	MissingSnaps map[string]map[snap.Revision][]string
    70  	// InvalidSnaps maps snap names to the validation sets declaring them invalid.
    71  	InvalidSnaps map[string][]string
    72  	// WronRevisionSnaps maps snap names to the expected revisions and respective
    73  	// validation sets that require them.
    74  	WrongRevisionSnaps map[string]map[snap.Revision][]string
    75  	// Sets maps validation set keys to all validation sets assertions considered
    76  	// in the failed check.
    77  	Sets map[string]*asserts.ValidationSet
    78  }
    79  
    80  // ValidationSetKey is a string-backed primary key for a validation set assertion.
    81  type ValidationSetKey string
    82  
    83  // NewValidationSetKey returns a validation set key for a validation set.
    84  func NewValidationSetKey(vs *asserts.ValidationSet) ValidationSetKey {
    85  	return ValidationSetKey(strings.Join(vs.Ref().PrimaryKey, "/"))
    86  }
    87  
    88  func (k ValidationSetKey) String() string {
    89  	return string(k)
    90  }
    91  
    92  // Components returns the components of the validation set's primary key (see
    93  // assertion types in asserts/asserts.go).
    94  func (k ValidationSetKey) Components() []string {
    95  	return strings.Split(k.String(), "/")
    96  }
    97  
    98  // ValidationSetKeySlice can be used to sort slices of ValidationSetKey.
    99  type ValidationSetKeySlice []ValidationSetKey
   100  
   101  func (s ValidationSetKeySlice) Len() int           { return len(s) }
   102  func (s ValidationSetKeySlice) Less(i, j int) bool { return s[i] < s[j] }
   103  func (s ValidationSetKeySlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   104  
   105  // CommaSeparated returns the validation set keys separated by commas.
   106  func (s ValidationSetKeySlice) CommaSeparated() string {
   107  	var sb strings.Builder
   108  
   109  	for i, vsKey := range s {
   110  		sb.WriteString(vsKey.String())
   111  		if i < len(s)-1 {
   112  			sb.WriteRune(',')
   113  		}
   114  	}
   115  
   116  	return sb.String()
   117  }
   118  
   119  type byRevision []snap.Revision
   120  
   121  func (b byRevision) Len() int           { return len(b) }
   122  func (b byRevision) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
   123  func (b byRevision) Less(i, j int) bool { return b[i].N < b[j].N }
   124  
   125  func (e *ValidationSetsValidationError) Error() string {
   126  	buf := bytes.NewBufferString("validation sets assertions are not met:")
   127  	printDetails := func(header string, details map[string][]string,
   128  		printSnap func(snapName string, keys []string) string) {
   129  		if len(details) == 0 {
   130  			return
   131  		}
   132  		fmt.Fprintf(buf, "\n- %s:", header)
   133  		for snapName, validationSetKeys := range details {
   134  			fmt.Fprintf(buf, "\n  - %s", printSnap(snapName, validationSetKeys))
   135  		}
   136  	}
   137  
   138  	if len(e.MissingSnaps) > 0 {
   139  		fmt.Fprintf(buf, "\n- missing required snaps:")
   140  		for snapName, revisions := range e.MissingSnaps {
   141  			revisionsSorted := make([]snap.Revision, 0, len(revisions))
   142  			for rev := range revisions {
   143  				revisionsSorted = append(revisionsSorted, rev)
   144  			}
   145  			sort.Sort(byRevision(revisionsSorted))
   146  			t := make([]string, 0, len(revisionsSorted))
   147  			for _, rev := range revisionsSorted {
   148  				keys := revisions[rev]
   149  				if rev.Unset() {
   150  					t = append(t, fmt.Sprintf("at any revision by sets %s", strings.Join(keys, ",")))
   151  				} else {
   152  					t = append(t, fmt.Sprintf("at revision %s by sets %s", rev, strings.Join(keys, ",")))
   153  				}
   154  			}
   155  			fmt.Fprintf(buf, "\n  - %s (required %s)", snapName, strings.Join(t, ", "))
   156  		}
   157  	}
   158  
   159  	printDetails("invalid snaps", e.InvalidSnaps, func(snapName string, validationSetKeys []string) string {
   160  		return fmt.Sprintf("%s (invalid for sets %s)", snapName, strings.Join(validationSetKeys, ","))
   161  	})
   162  
   163  	if len(e.WrongRevisionSnaps) > 0 {
   164  		fmt.Fprint(buf, "\n- snaps at wrong revisions:")
   165  		for snapName, revisions := range e.WrongRevisionSnaps {
   166  			revisionsSorted := make([]snap.Revision, 0, len(revisions))
   167  			for rev := range revisions {
   168  				revisionsSorted = append(revisionsSorted, rev)
   169  			}
   170  			sort.Sort(byRevision(revisionsSorted))
   171  			t := make([]string, 0, len(revisionsSorted))
   172  			for _, rev := range revisionsSorted {
   173  				keys := revisions[rev]
   174  				t = append(t, fmt.Sprintf("at revision %s by sets %s", rev, strings.Join(keys, ",")))
   175  			}
   176  			fmt.Fprintf(buf, "\n  - %s (required %s)", snapName, strings.Join(t, ", "))
   177  		}
   178  	}
   179  
   180  	return buf.String()
   181  }
   182  
   183  // ValidationSets can hold a combination of validation-set assertions
   184  // and can check for conflicts or help applying them.
   185  type ValidationSets struct {
   186  	// sets maps sequence keys to validation-set in the combination
   187  	sets map[string]*asserts.ValidationSet
   188  	// snaps maps snap-ids to snap constraints
   189  	snaps map[string]*snapContraints
   190  }
   191  
   192  const presConflict asserts.Presence = "conflict"
   193  
   194  var unspecifiedRevision = snap.R(0)
   195  var invalidPresRevision = snap.R(-1)
   196  
   197  type snapContraints struct {
   198  	name     string
   199  	presence asserts.Presence
   200  	// revisions maps revisions to pairing of ValidationSetSnap
   201  	// and the originating validation-set key
   202  	// * unspecifiedRevision is used for constraints without a
   203  	//   revision
   204  	// * invalidPresRevision is used for constraints that mark
   205  	//   presence as invalid
   206  	revisions map[snap.Revision][]*revConstraint
   207  }
   208  
   209  type revConstraint struct {
   210  	validationSetKey string
   211  	asserts.ValidationSetSnap
   212  }
   213  
   214  func (c *snapContraints) conflict() *snapConflictsError {
   215  	if c.presence != presConflict {
   216  		return nil
   217  	}
   218  
   219  	const dontCare asserts.Presence = ""
   220  	whichSets := func(rcs []*revConstraint, presence asserts.Presence) []string {
   221  		which := make([]string, 0, len(rcs))
   222  		for _, rc := range rcs {
   223  			if presence != dontCare && rc.Presence != presence {
   224  				continue
   225  			}
   226  			which = append(which, rc.validationSetKey)
   227  		}
   228  		if len(which) == 0 {
   229  			return nil
   230  		}
   231  		sort.Strings(which)
   232  		return which
   233  	}
   234  
   235  	byRev := make(map[snap.Revision][]string, len(c.revisions))
   236  	for r := range c.revisions {
   237  		pres := dontCare
   238  		switch r {
   239  		case invalidPresRevision:
   240  			pres = asserts.PresenceInvalid
   241  		case unspecifiedRevision:
   242  			pres = asserts.PresenceRequired
   243  		}
   244  		which := whichSets(c.revisions[r], pres)
   245  		if len(which) != 0 {
   246  			byRev[r] = which
   247  		}
   248  	}
   249  
   250  	return &snapConflictsError{
   251  		name:      c.name,
   252  		revisions: byRev,
   253  	}
   254  }
   255  
   256  type snapConflictsError struct {
   257  	name string
   258  	// revisions maps revisions to validation-set keys of the sets
   259  	// that are in conflict over the revision.
   260  	// * unspecifiedRevision is used for validation-sets conflicting
   261  	//   on the snap by requiring it but without a revision
   262  	// * invalidPresRevision is used for validation-sets that mark
   263  	//   presence as invalid
   264  	// see snapContraints.revisions as well
   265  	revisions map[snap.Revision][]string
   266  }
   267  
   268  func (e *snapConflictsError) Error() string {
   269  	whichSets := func(which []string) string {
   270  		return fmt.Sprintf("(%s)", strings.Join(which, ","))
   271  	}
   272  
   273  	msg := fmt.Sprintf("cannot constrain snap %q", e.name)
   274  	invalid := false
   275  	if invalidOnes, ok := e.revisions[invalidPresRevision]; ok {
   276  		msg += fmt.Sprintf(" as both invalid %s and required", whichSets(invalidOnes))
   277  		invalid = true
   278  	}
   279  
   280  	var revnos []snap.Revision
   281  	for r := range e.revisions {
   282  		if r.N >= 1 {
   283  			revnos = append(revnos, r)
   284  		}
   285  	}
   286  	if len(revnos) == 1 {
   287  		msg += fmt.Sprintf(" at revision %s %s", revnos[0], whichSets(e.revisions[revnos[0]]))
   288  	} else if len(revnos) > 1 {
   289  		sort.Sort(byRevision(revnos))
   290  		l := make([]string, 0, len(revnos))
   291  		for _, rev := range revnos {
   292  			l = append(l, fmt.Sprintf("%s %s", rev, whichSets(e.revisions[rev])))
   293  		}
   294  		msg += fmt.Sprintf(" at different revisions %s", strings.Join(l, ", "))
   295  	}
   296  
   297  	if unspecifiedOnes, ok := e.revisions[unspecifiedRevision]; ok {
   298  		which := whichSets(unspecifiedOnes)
   299  		if which != "" {
   300  			if len(revnos) != 0 {
   301  				msg += " or"
   302  			}
   303  			if invalid {
   304  				msg += fmt.Sprintf(" at any revision %s", which)
   305  			} else {
   306  				msg += fmt.Sprintf(" required at any revision %s", which)
   307  			}
   308  		}
   309  	}
   310  	return msg
   311  }
   312  
   313  // NewValidationSets returns a new ValidationSets.
   314  func NewValidationSets() *ValidationSets {
   315  	return &ValidationSets{
   316  		sets:  map[string]*asserts.ValidationSet{},
   317  		snaps: map[string]*snapContraints{},
   318  	}
   319  }
   320  
   321  func valSetKey(valset *asserts.ValidationSet) string {
   322  	return fmt.Sprintf("%s/%s", valset.AccountID(), valset.Name())
   323  }
   324  
   325  // Add adds the given asserts.ValidationSet to the combination.
   326  // It errors if a validation-set with the same sequence key has been
   327  // added already.
   328  func (v *ValidationSets) Add(valset *asserts.ValidationSet) error {
   329  	k := valSetKey(valset)
   330  	if _, ok := v.sets[k]; ok {
   331  		return fmt.Errorf("cannot add a second validation-set under %q", k)
   332  	}
   333  	v.sets[k] = valset
   334  	for _, sn := range valset.Snaps() {
   335  		v.addSnap(sn, k)
   336  	}
   337  	return nil
   338  }
   339  
   340  func (v *ValidationSets) addSnap(sn *asserts.ValidationSetSnap, validationSetKey string) {
   341  	rev := snap.R(sn.Revision)
   342  	if sn.Presence == asserts.PresenceInvalid {
   343  		rev = invalidPresRevision
   344  	}
   345  
   346  	rc := &revConstraint{
   347  		validationSetKey:  validationSetKey,
   348  		ValidationSetSnap: *sn,
   349  	}
   350  
   351  	cs := v.snaps[sn.SnapID]
   352  	if cs == nil {
   353  		v.snaps[sn.SnapID] = &snapContraints{
   354  			name:     sn.Name,
   355  			presence: sn.Presence,
   356  			revisions: map[snap.Revision][]*revConstraint{
   357  				rev: {rc},
   358  			},
   359  		}
   360  		return
   361  	}
   362  
   363  	cs.revisions[rev] = append(cs.revisions[rev], rc)
   364  	if cs.presence == presConflict {
   365  		// nothing to check anymore
   366  		return
   367  	}
   368  	// this counts really different revisions or invalid
   369  	ndiff := len(cs.revisions)
   370  	if _, ok := cs.revisions[unspecifiedRevision]; ok {
   371  		ndiff--
   372  	}
   373  	switch {
   374  	case cs.presence == asserts.PresenceOptional:
   375  		cs.presence = sn.Presence
   376  		fallthrough
   377  	case cs.presence == sn.Presence || sn.Presence == asserts.PresenceOptional:
   378  		if ndiff > 1 {
   379  			if cs.presence == asserts.PresenceRequired {
   380  				// different revisions required/invalid
   381  				cs.presence = presConflict
   382  				return
   383  			}
   384  			// multiple optional at different revisions => invalid
   385  			cs.presence = asserts.PresenceInvalid
   386  		}
   387  		return
   388  	}
   389  	// we are left with a combo of required and invalid => conflict
   390  	cs.presence = presConflict
   391  }
   392  
   393  // Conflict returns a non-nil error if the combination is in conflict,
   394  // nil otherwise.
   395  func (v *ValidationSets) Conflict() error {
   396  	sets := make(map[string]*asserts.ValidationSet)
   397  	snaps := make(map[string]error)
   398  
   399  	for snapID, snConstrs := range v.snaps {
   400  		snConflictsErr := snConstrs.conflict()
   401  		if snConflictsErr != nil {
   402  			snaps[snapID] = snConflictsErr
   403  			for _, valsetKeys := range snConflictsErr.revisions {
   404  				for _, valsetKey := range valsetKeys {
   405  					sets[valsetKey] = v.sets[valsetKey]
   406  				}
   407  			}
   408  		}
   409  	}
   410  
   411  	if len(snaps) != 0 {
   412  		return &ValidationSetsConflictError{
   413  			Sets:  sets,
   414  			Snaps: snaps,
   415  		}
   416  	}
   417  	return nil
   418  }
   419  
   420  // CheckInstalledSnaps checks installed snaps against the validation sets.
   421  func (v *ValidationSets) CheckInstalledSnaps(snaps []*InstalledSnap, ignoreValidation map[string]bool) error {
   422  	installed := naming.NewSnapSet(nil)
   423  	for _, sn := range snaps {
   424  		installed.Add(sn)
   425  	}
   426  
   427  	// snapName -> validationSet key -> validation set
   428  	invalid := make(map[string]map[string]bool)
   429  	missing := make(map[string]map[snap.Revision]map[string]bool)
   430  	wrongrev := make(map[string]map[snap.Revision]map[string]bool)
   431  
   432  	for _, cstrs := range v.snaps {
   433  		for rev, revCstr := range cstrs.revisions {
   434  			for _, rc := range revCstr {
   435  				sn := installed.Lookup(rc)
   436  				isInstalled := sn != nil
   437  
   438  				if isInstalled && ignoreValidation[rc.Name] {
   439  					continue
   440  				}
   441  
   442  				switch {
   443  				case !isInstalled && (cstrs.presence == asserts.PresenceOptional || cstrs.presence == asserts.PresenceInvalid):
   444  					// not installed, but optional or not required
   445  				case isInstalled && cstrs.presence == asserts.PresenceInvalid:
   446  					// installed but not expected to be present
   447  					if invalid[rc.Name] == nil {
   448  						invalid[rc.Name] = make(map[string]bool)
   449  					}
   450  					invalid[rc.Name][rc.validationSetKey] = true
   451  				case isInstalled:
   452  					// presence is either optional or required
   453  					if rev != unspecifiedRevision && rev != sn.(*InstalledSnap).Revision {
   454  						// expected a different revision
   455  						if wrongrev[rc.Name] == nil {
   456  							wrongrev[rc.Name] = make(map[snap.Revision]map[string]bool)
   457  						}
   458  						if wrongrev[rc.Name][rev] == nil {
   459  							wrongrev[rc.Name][rev] = make(map[string]bool)
   460  						}
   461  						wrongrev[rc.Name][rev][rc.validationSetKey] = true
   462  					}
   463  				default:
   464  					// not installed but required.
   465  					// note, not checking ignoreValidation here because it's not a viable scenario (it's not
   466  					// possible to have enforced validation set while not having the required snap at all - it
   467  					// is only possible to have it with a wrong revision, or installed while invalid, in both
   468  					// cases through --ignore-validation flag).
   469  					if missing[rc.Name] == nil {
   470  						missing[rc.Name] = make(map[snap.Revision]map[string]bool)
   471  					}
   472  					if missing[rc.Name][rev] == nil {
   473  						missing[rc.Name][rev] = make(map[string]bool)
   474  					}
   475  					missing[rc.Name][rev][rc.validationSetKey] = true
   476  				}
   477  			}
   478  		}
   479  	}
   480  
   481  	setsToLists := func(in map[string]map[string]bool) map[string][]string {
   482  		if len(in) == 0 {
   483  			return nil
   484  		}
   485  		out := make(map[string][]string)
   486  		for snap, sets := range in {
   487  			out[snap] = make([]string, 0, len(sets))
   488  			for validationSetKey := range sets {
   489  				out[snap] = append(out[snap], validationSetKey)
   490  			}
   491  			sort.Strings(out[snap])
   492  		}
   493  		return out
   494  	}
   495  
   496  	if len(invalid) > 0 || len(missing) > 0 || len(wrongrev) > 0 {
   497  		verr := &ValidationSetsValidationError{
   498  			InvalidSnaps: setsToLists(invalid),
   499  			Sets:         v.sets,
   500  		}
   501  		if len(missing) > 0 {
   502  			verr.MissingSnaps = make(map[string]map[snap.Revision][]string)
   503  			for snapName, revs := range missing {
   504  				verr.MissingSnaps[snapName] = make(map[snap.Revision][]string)
   505  				for rev, keys := range revs {
   506  					for key := range keys {
   507  						verr.MissingSnaps[snapName][rev] = append(verr.MissingSnaps[snapName][rev], key)
   508  					}
   509  					sort.Strings(verr.MissingSnaps[snapName][rev])
   510  				}
   511  			}
   512  		}
   513  		if len(wrongrev) > 0 {
   514  			verr.WrongRevisionSnaps = make(map[string]map[snap.Revision][]string)
   515  			for snapName, revs := range wrongrev {
   516  				verr.WrongRevisionSnaps[snapName] = make(map[snap.Revision][]string)
   517  				for rev, keys := range revs {
   518  					for key := range keys {
   519  						verr.WrongRevisionSnaps[snapName][rev] = append(verr.WrongRevisionSnaps[snapName][rev], key)
   520  					}
   521  					sort.Strings(verr.WrongRevisionSnaps[snapName][rev])
   522  				}
   523  			}
   524  		}
   525  		return verr
   526  	}
   527  	return nil
   528  }
   529  
   530  // PresenceConstraintError describes an error where presence of the given snap
   531  // has unexpected value, e.g. it's "invalid" while checking for "required".
   532  type PresenceConstraintError struct {
   533  	SnapName string
   534  	Presence asserts.Presence
   535  }
   536  
   537  func (e *PresenceConstraintError) Error() string {
   538  	return fmt.Sprintf("unexpected presence %q for snap %q", e.Presence, e.SnapName)
   539  }
   540  
   541  func (v *ValidationSets) constraintsForSnap(snapRef naming.SnapRef) *snapContraints {
   542  	if snapRef.ID() != "" {
   543  		return v.snaps[snapRef.ID()]
   544  	}
   545  	// snapID not available, find by snap name
   546  	for _, cstrs := range v.snaps {
   547  		if cstrs.name == snapRef.SnapName() {
   548  			return cstrs
   549  		}
   550  	}
   551  	return nil
   552  }
   553  
   554  // CheckPresenceRequired returns the list of all validation sets that declare
   555  // presence of the given snap as required and the required revision (or
   556  // snap.R(0) if no specific revision is required). PresenceConstraintError is
   557  // returned if presence of the snap is "invalid".
   558  // The method assumes that validation sets are not in conflict.
   559  func (v *ValidationSets) CheckPresenceRequired(snapRef naming.SnapRef) ([]ValidationSetKey, snap.Revision, error) {
   560  	cstrs := v.constraintsForSnap(snapRef)
   561  	if cstrs == nil {
   562  		return nil, unspecifiedRevision, nil
   563  	}
   564  	if cstrs.presence == asserts.PresenceInvalid {
   565  		return nil, unspecifiedRevision, &PresenceConstraintError{snapRef.SnapName(), cstrs.presence}
   566  	}
   567  	if cstrs.presence != asserts.PresenceRequired {
   568  		return nil, unspecifiedRevision, nil
   569  	}
   570  
   571  	snapRev := unspecifiedRevision
   572  	var keys []ValidationSetKey
   573  	for rev, revCstr := range cstrs.revisions {
   574  		for _, rc := range revCstr {
   575  			vs := v.sets[rc.validationSetKey]
   576  			if vs == nil {
   577  				return nil, unspecifiedRevision, fmt.Errorf("internal error: no validation set for %q", rc.validationSetKey)
   578  			}
   579  			keys = append(keys, NewValidationSetKey(vs))
   580  			// there may be constraints without revision; only set snapRev if
   581  			// it wasn't already determined. Note that if revisions are set,
   582  			// then they are the same, otherwise validation sets would be in
   583  			// conflict.
   584  			// This is an equivalent of 'if rev != unspecifiedRevision`.
   585  			if snapRev == unspecifiedRevision {
   586  				snapRev = rev
   587  			}
   588  		}
   589  	}
   590  
   591  	sort.Sort(ValidationSetKeySlice(keys))
   592  	return keys, snapRev, nil
   593  }
   594  
   595  // CheckPresenceInvalid returns the list of all validation sets that declare
   596  // presence of the given snap as invalid. PresenceConstraintError is returned if
   597  // presence of the snap is "optional" or "required".
   598  // The method assumes that validation sets are not in conflict.
   599  func (v *ValidationSets) CheckPresenceInvalid(snapRef naming.SnapRef) ([]ValidationSetKey, error) {
   600  	cstrs := v.constraintsForSnap(snapRef)
   601  	if cstrs == nil {
   602  		return nil, nil
   603  	}
   604  	if cstrs.presence != asserts.PresenceInvalid {
   605  		return nil, &PresenceConstraintError{snapRef.SnapName(), cstrs.presence}
   606  	}
   607  	var keys []ValidationSetKey
   608  	for _, revCstr := range cstrs.revisions {
   609  		for _, rc := range revCstr {
   610  			if rc.Presence == asserts.PresenceInvalid {
   611  				vs := v.sets[rc.validationSetKey]
   612  				if vs == nil {
   613  					return nil, fmt.Errorf("internal error: no validation set for %q", rc.validationSetKey)
   614  				}
   615  				keys = append(keys, NewValidationSetKey(vs))
   616  			}
   617  		}
   618  	}
   619  
   620  	sort.Sort(ValidationSetKeySlice(keys))
   621  	return keys, nil
   622  }
   623  
   624  // ParseValidationSet parses a validation set string (account/name or account/name=sequence)
   625  // and returns its individual components, or an error.
   626  func ParseValidationSet(arg string) (account, name string, seq int, err error) {
   627  	errPrefix := func() string {
   628  		return fmt.Sprintf("cannot parse validation set %q", arg)
   629  	}
   630  	parts := strings.Split(arg, "=")
   631  	if len(parts) > 2 {
   632  		return "", "", 0, fmt.Errorf("%s: expected account/name=seq", errPrefix())
   633  	}
   634  	if len(parts) == 2 {
   635  		seq, err = strconv.Atoi(parts[1])
   636  		if err != nil {
   637  			return "", "", 0, fmt.Errorf("%s: invalid sequence: %v", errPrefix(), err)
   638  		}
   639  	}
   640  
   641  	parts = strings.Split(parts[0], "/")
   642  	if len(parts) != 2 {
   643  		return "", "", 0, fmt.Errorf("%s: expected a single account/name", errPrefix())
   644  	}
   645  
   646  	account = parts[0]
   647  	name = parts[1]
   648  	if !asserts.IsValidAccountID(account) {
   649  		return "", "", 0, fmt.Errorf("%s: invalid account ID %q", errPrefix(), account)
   650  	}
   651  	if !asserts.IsValidValidationSetName(name) {
   652  		return "", "", 0, fmt.Errorf("%s: invalid validation set name %q", errPrefix(), name)
   653  	}
   654  
   655  	return account, name, seq, nil
   656  }