github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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 "strings" 27 28 "github.com/snapcore/snapd/asserts" 29 "github.com/snapcore/snapd/snap" 30 ) 31 32 // ValidationSetsConflictError describes an error where multiple 33 // validation sets are in conflict about snaps. 34 type ValidationSetsConflictError struct { 35 Sets map[string]*asserts.ValidationSet 36 Snaps map[string]error 37 } 38 39 func (e *ValidationSetsConflictError) Error() string { 40 buf := bytes.NewBufferString("validation sets are in conflict:") 41 for _, err := range e.Snaps { 42 fmt.Fprintf(buf, "\n- %v", err) 43 } 44 return buf.String() 45 } 46 47 // ValidationSets can hold a combination of validation-set assertions 48 // and can check for conflicts or help applying them. 49 type ValidationSets struct { 50 // sets maps sequence keys to validation-set in the combination 51 sets map[string]*asserts.ValidationSet 52 // snaps maps snap-ids to snap constraints 53 snaps map[string]*snapContraints 54 } 55 56 const presConflict asserts.Presence = "conflict" 57 58 var unspecifiedRevision = snap.R(0) 59 var invalidPresRevision = snap.R(-1) 60 61 type snapContraints struct { 62 name string 63 presence asserts.Presence 64 // revisions maps revisions to pairing of ValidationSetSnap 65 // and the originating validation-set key 66 // * unspecifiedRevision is used for constraints without a 67 // revision 68 // * invalidPresRevision is used for constraints that mark 69 // presence as invalid 70 revisions map[snap.Revision][]*revConstraint 71 } 72 73 type revConstraint struct { 74 validationSetKey string 75 asserts.ValidationSetSnap 76 } 77 78 func (c *snapContraints) conflict() *snapConflictsError { 79 if c.presence != presConflict { 80 return nil 81 } 82 83 const dontCare asserts.Presence = "" 84 whichSets := func(rcs []*revConstraint, presence asserts.Presence) []string { 85 which := make([]string, 0, len(rcs)) 86 for _, rc := range rcs { 87 if presence != dontCare && rc.Presence != presence { 88 continue 89 } 90 which = append(which, rc.validationSetKey) 91 } 92 if len(which) == 0 { 93 return nil 94 } 95 sort.Strings(which) 96 return which 97 } 98 99 byRev := make(map[snap.Revision][]string, len(c.revisions)) 100 for r := range c.revisions { 101 pres := dontCare 102 switch r { 103 case invalidPresRevision: 104 pres = asserts.PresenceInvalid 105 case unspecifiedRevision: 106 pres = asserts.PresenceRequired 107 } 108 which := whichSets(c.revisions[r], pres) 109 if len(which) != 0 { 110 byRev[r] = which 111 } 112 } 113 114 return &snapConflictsError{ 115 name: c.name, 116 revisions: byRev, 117 } 118 } 119 120 type snapConflictsError struct { 121 name string 122 // revisions maps revisions to validation-set keys of the sets 123 // that are in conflict over the revision. 124 // * unspecifiedRevision is used for validation-sets conflicting 125 // on the snap by requiring it but without a revision 126 // * invalidPresRevision is used for validation-sets that mark 127 // presence as invalid 128 // see snapContraints.revisions as well 129 revisions map[snap.Revision][]string 130 } 131 132 func (e *snapConflictsError) Error() string { 133 whichSets := func(which []string) string { 134 return fmt.Sprintf("(%s)", strings.Join(which, ",")) 135 } 136 137 msg := fmt.Sprintf("cannot constrain snap %q", e.name) 138 invalid := false 139 if invalidOnes, ok := e.revisions[invalidPresRevision]; ok { 140 msg += fmt.Sprintf(" as both invalid %s and required", whichSets(invalidOnes)) 141 invalid = true 142 } 143 144 var revnos []int 145 for r := range e.revisions { 146 if r.N >= 1 { 147 revnos = append(revnos, r.N) 148 } 149 } 150 if len(revnos) == 1 { 151 msg += fmt.Sprintf(" at revision %d %s", revnos[0], whichSets(e.revisions[snap.R(revnos[0])])) 152 } else if len(revnos) > 1 { 153 sort.Ints(revnos) 154 l := make([]string, 0, len(revnos)) 155 for _, rev := range revnos { 156 l = append(l, fmt.Sprintf("%d %s", rev, whichSets(e.revisions[snap.R(rev)]))) 157 } 158 msg += fmt.Sprintf(" at different revisions %s", strings.Join(l, ", ")) 159 } 160 161 if unspecifiedOnes, ok := e.revisions[unspecifiedRevision]; ok { 162 which := whichSets(unspecifiedOnes) 163 if which != "" { 164 if len(revnos) != 0 { 165 msg += " or" 166 } 167 if invalid { 168 msg += fmt.Sprintf(" at any revision %s", which) 169 } else { 170 msg += fmt.Sprintf(" required at any revision %s", which) 171 } 172 } 173 } 174 return msg 175 } 176 177 // NewValidationSets returns a new ValidationSets. 178 func NewValidationSets() *ValidationSets { 179 return &ValidationSets{ 180 sets: map[string]*asserts.ValidationSet{}, 181 snaps: map[string]*snapContraints{}, 182 } 183 } 184 185 func valSetKey(valset *asserts.ValidationSet) string { 186 return fmt.Sprintf("%s/%s", valset.AccountID(), valset.Name()) 187 } 188 189 // Add adds the given asserts.ValidationSet to the combination. 190 // It errors if a validation-set with the same sequence key has been 191 // added already. 192 func (v *ValidationSets) Add(valset *asserts.ValidationSet) error { 193 k := valSetKey(valset) 194 if _, ok := v.sets[k]; ok { 195 return fmt.Errorf("cannot add a second validation-set under %q", k) 196 } 197 v.sets[k] = valset 198 for _, sn := range valset.Snaps() { 199 v.addSnap(sn, k) 200 } 201 return nil 202 } 203 204 func (v *ValidationSets) addSnap(sn *asserts.ValidationSetSnap, validationSetKey string) { 205 rev := snap.R(sn.Revision) 206 if sn.Presence == asserts.PresenceInvalid { 207 rev = invalidPresRevision 208 } 209 210 rc := &revConstraint{ 211 validationSetKey: validationSetKey, 212 ValidationSetSnap: *sn, 213 } 214 215 cs := v.snaps[sn.SnapID] 216 if cs == nil { 217 v.snaps[sn.SnapID] = &snapContraints{ 218 name: sn.Name, 219 presence: sn.Presence, 220 revisions: map[snap.Revision][]*revConstraint{ 221 rev: {rc}, 222 }, 223 } 224 return 225 } 226 227 cs.revisions[rev] = append(cs.revisions[rev], rc) 228 if cs.presence == presConflict { 229 // nothing to check anymore 230 return 231 } 232 // this counts really different revisions or invalid 233 ndiff := len(cs.revisions) 234 if _, ok := cs.revisions[unspecifiedRevision]; ok { 235 ndiff -= 1 236 } 237 switch { 238 case cs.presence == asserts.PresenceOptional: 239 cs.presence = sn.Presence 240 fallthrough 241 case cs.presence == sn.Presence || sn.Presence == asserts.PresenceOptional: 242 if ndiff > 1 { 243 if cs.presence == asserts.PresenceRequired { 244 // different revisions required/invalid 245 cs.presence = presConflict 246 return 247 } 248 // multiple optional at different revisions => invalid 249 cs.presence = asserts.PresenceInvalid 250 } 251 return 252 } 253 // we are left with a combo of required and invalid => conflict 254 cs.presence = presConflict 255 return 256 } 257 258 // Conflict returns a non-nil error if the combination is in conflict, 259 // nil otherwise. 260 func (v *ValidationSets) Conflict() error { 261 sets := make(map[string]*asserts.ValidationSet) 262 snaps := make(map[string]error) 263 264 for snapID, snConstrs := range v.snaps { 265 snConflictsErr := snConstrs.conflict() 266 if snConflictsErr != nil { 267 snaps[snapID] = snConflictsErr 268 for _, valsetKeys := range snConflictsErr.revisions { 269 for _, valsetKey := range valsetKeys { 270 sets[valsetKey] = v.sets[valsetKey] 271 } 272 } 273 } 274 } 275 276 if len(snaps) != 0 { 277 return &ValidationSetsConflictError{ 278 Sets: sets, 279 Snaps: snaps, 280 } 281 } 282 return nil 283 }