github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/asserts/ifacedecls.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2017 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 asserts 21 22 import ( 23 "errors" 24 "fmt" 25 "reflect" 26 "regexp" 27 "strconv" 28 "strings" 29 "unicode" 30 31 "github.com/snapcore/snapd/snap/naming" 32 ) 33 34 // AttrMatchContext has contextual helpers for evaluating attribute constraints. 35 type AttrMatchContext interface { 36 PlugAttr(arg string) (interface{}, error) 37 SlotAttr(arg string) (interface{}, error) 38 } 39 40 const ( 41 // feature label for $SLOT()/$PLUG()/$MISSING 42 dollarAttrConstraintsFeature = "dollar-attr-constraints" 43 // feature label for on-store/on-brand/on-model 44 deviceScopeConstraintsFeature = "device-scope-constraints" 45 // feature label for plug-names/slot-names constraints 46 nameConstraintsFeature = "name-constraints" 47 ) 48 49 type attrMatcher interface { 50 match(apath string, v interface{}, ctx AttrMatchContext) error 51 52 feature(flabel string) bool 53 } 54 55 func chain(path, k string) string { 56 if path == "" { 57 return k 58 } 59 return fmt.Sprintf("%s.%s", path, k) 60 } 61 62 type compileContext struct { 63 dotted string 64 hadMap bool 65 wasAlt bool 66 } 67 68 func (cc compileContext) String() string { 69 return cc.dotted 70 } 71 72 func (cc compileContext) keyEntry(k string) compileContext { 73 return compileContext{ 74 dotted: chain(cc.dotted, k), 75 hadMap: true, 76 wasAlt: false, 77 } 78 } 79 80 func (cc compileContext) alt(alt int) compileContext { 81 return compileContext{ 82 dotted: fmt.Sprintf("%s/alt#%d/", cc.dotted, alt+1), 83 hadMap: cc.hadMap, 84 wasAlt: true, 85 } 86 } 87 88 // compileAttrMatcher compiles an attrMatcher derived from constraints, 89 func compileAttrMatcher(cc compileContext, constraints interface{}) (attrMatcher, error) { 90 switch x := constraints.(type) { 91 case map[string]interface{}: 92 return compileMapAttrMatcher(cc, x) 93 case []interface{}: 94 if cc.wasAlt { 95 return nil, fmt.Errorf("cannot nest alternative constraints directly at %q", cc) 96 } 97 return compileAltAttrMatcher(cc, x) 98 case string: 99 if !cc.hadMap { 100 return nil, fmt.Errorf("first level of non alternative constraints must be a set of key-value contraints") 101 } 102 if strings.HasPrefix(x, "$") { 103 if x == "$MISSING" { 104 return missingAttrMatcher{}, nil 105 } 106 return compileEvalAttrMatcher(cc, x) 107 } 108 return compileRegexpAttrMatcher(cc, x) 109 default: 110 return nil, fmt.Errorf("constraint %q must be a key-value map, regexp or a list of alternative constraints: %v", cc, x) 111 } 112 } 113 114 type mapAttrMatcher map[string]attrMatcher 115 116 func compileMapAttrMatcher(cc compileContext, m map[string]interface{}) (attrMatcher, error) { 117 matcher := make(mapAttrMatcher) 118 for k, constraint := range m { 119 matcher1, err := compileAttrMatcher(cc.keyEntry(k), constraint) 120 if err != nil { 121 return nil, err 122 } 123 matcher[k] = matcher1 124 } 125 return matcher, nil 126 } 127 128 func matchEntry(apath, k string, matcher1 attrMatcher, v interface{}, ctx AttrMatchContext) error { 129 apath = chain(apath, k) 130 // every entry matcher expects the attribute to be set except for $MISSING 131 if _, ok := matcher1.(missingAttrMatcher); !ok && v == nil { 132 return fmt.Errorf("attribute %q has constraints but is unset", apath) 133 } 134 if err := matcher1.match(apath, v, ctx); err != nil { 135 return err 136 } 137 return nil 138 } 139 140 func matchList(apath string, matcher attrMatcher, l []interface{}, ctx AttrMatchContext) error { 141 for i, elem := range l { 142 if err := matcher.match(chain(apath, strconv.Itoa(i)), elem, ctx); err != nil { 143 return err 144 } 145 } 146 return nil 147 } 148 149 func (matcher mapAttrMatcher) feature(flabel string) bool { 150 for _, matcher1 := range matcher { 151 if matcher1.feature(flabel) { 152 return true 153 } 154 } 155 return false 156 } 157 158 func (matcher mapAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 159 switch x := v.(type) { 160 case Attrer: 161 // we get Atter from root-level Check (apath is "") 162 for k, matcher1 := range matcher { 163 v, _ := x.Lookup(k) 164 if err := matchEntry("", k, matcher1, v, ctx); err != nil { 165 return err 166 } 167 } 168 case map[string]interface{}: // maps in attributes look like this 169 for k, matcher1 := range matcher { 170 if err := matchEntry(apath, k, matcher1, x[k], ctx); err != nil { 171 return err 172 } 173 } 174 case []interface{}: 175 return matchList(apath, matcher, x, ctx) 176 default: 177 return fmt.Errorf("attribute %q must be a map", apath) 178 } 179 return nil 180 } 181 182 type missingAttrMatcher struct{} 183 184 func (matcher missingAttrMatcher) feature(flabel string) bool { 185 return flabel == dollarAttrConstraintsFeature 186 } 187 188 func (matcher missingAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 189 if v != nil { 190 return fmt.Errorf("attribute %q is constrained to be missing but is set", apath) 191 } 192 return nil 193 } 194 195 type evalAttrMatcher struct { 196 // first iteration supports just $(SLOT|PLUG)(arg) 197 op string 198 arg string 199 } 200 201 var ( 202 validEvalAttrMatcher = regexp.MustCompile(`^\$(SLOT|PLUG)\((.+)\)$`) 203 ) 204 205 func compileEvalAttrMatcher(cc compileContext, s string) (attrMatcher, error) { 206 ops := validEvalAttrMatcher.FindStringSubmatch(s) 207 if len(ops) == 0 { 208 return nil, fmt.Errorf("cannot compile %q constraint %q: not a valid $SLOT()/$PLUG() constraint", cc, s) 209 } 210 return evalAttrMatcher{ 211 op: ops[1], 212 arg: ops[2], 213 }, nil 214 } 215 216 func (matcher evalAttrMatcher) feature(flabel string) bool { 217 return flabel == dollarAttrConstraintsFeature 218 } 219 220 func (matcher evalAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 221 if ctx == nil { 222 return fmt.Errorf("attribute %q cannot be matched without context", apath) 223 } 224 var comp func(string) (interface{}, error) 225 switch matcher.op { 226 case "SLOT": 227 comp = ctx.SlotAttr 228 case "PLUG": 229 comp = ctx.PlugAttr 230 } 231 v1, err := comp(matcher.arg) 232 if err != nil { 233 return fmt.Errorf("attribute %q constraint $%s(%s) cannot be evaluated: %v", apath, matcher.op, matcher.arg, err) 234 } 235 if !reflect.DeepEqual(v, v1) { 236 return fmt.Errorf("attribute %q does not match $%s(%s): %v != %v", apath, matcher.op, matcher.arg, v, v1) 237 } 238 return nil 239 } 240 241 type regexpAttrMatcher struct { 242 *regexp.Regexp 243 } 244 245 func compileRegexpAttrMatcher(cc compileContext, s string) (attrMatcher, error) { 246 rx, err := regexp.Compile("^(" + s + ")$") 247 if err != nil { 248 return nil, fmt.Errorf("cannot compile %q constraint %q: %v", cc, s, err) 249 } 250 return regexpAttrMatcher{rx}, nil 251 } 252 253 func (matcher regexpAttrMatcher) feature(flabel string) bool { 254 return false 255 } 256 257 func (matcher regexpAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 258 var s string 259 switch x := v.(type) { 260 case string: 261 s = x 262 case bool: 263 s = strconv.FormatBool(x) 264 case int64: 265 s = strconv.FormatInt(x, 10) 266 case []interface{}: 267 return matchList(apath, matcher, x, ctx) 268 default: 269 return fmt.Errorf("attribute %q must be a scalar or list", apath) 270 } 271 if !matcher.Regexp.MatchString(s) { 272 return fmt.Errorf("attribute %q value %q does not match %v", apath, s, matcher.Regexp) 273 } 274 return nil 275 276 } 277 278 type altAttrMatcher struct { 279 alts []attrMatcher 280 } 281 282 func compileAltAttrMatcher(cc compileContext, l []interface{}) (attrMatcher, error) { 283 alts := make([]attrMatcher, len(l)) 284 for i, constraint := range l { 285 matcher1, err := compileAttrMatcher(cc.alt(i), constraint) 286 if err != nil { 287 return nil, err 288 } 289 alts[i] = matcher1 290 } 291 return altAttrMatcher{alts}, nil 292 293 } 294 295 func (matcher altAttrMatcher) feature(flabel string) bool { 296 for _, alt := range matcher.alts { 297 if alt.feature(flabel) { 298 return true 299 } 300 } 301 return false 302 } 303 304 func (matcher altAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 305 var firstErr error 306 for _, alt := range matcher.alts { 307 err := alt.match(apath, v, ctx) 308 if err == nil { 309 return nil 310 } 311 if firstErr == nil { 312 firstErr = err 313 } 314 } 315 apathDescr := "" 316 if apath != "" { 317 apathDescr = fmt.Sprintf(" for attribute %q", apath) 318 } 319 return fmt.Errorf("no alternative%s matches: %v", apathDescr, firstErr) 320 } 321 322 // AttributeConstraints implements a set of constraints on the attributes of a slot or plug. 323 type AttributeConstraints struct { 324 matcher attrMatcher 325 } 326 327 func (ac *AttributeConstraints) feature(flabel string) bool { 328 return ac.matcher.feature(flabel) 329 } 330 331 // compileAttributeConstraints checks and compiles a mapping or list 332 // from the assertion format into AttributeConstraints. 333 func compileAttributeConstraints(constraints interface{}) (*AttributeConstraints, error) { 334 matcher, err := compileAttrMatcher(compileContext{}, constraints) 335 if err != nil { 336 return nil, err 337 } 338 return &AttributeConstraints{matcher: matcher}, nil 339 } 340 341 type fixedAttrMatcher struct { 342 result error 343 } 344 345 func (matcher fixedAttrMatcher) feature(flabel string) bool { 346 return false 347 } 348 349 func (matcher fixedAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 350 return matcher.result 351 } 352 353 var ( 354 AlwaysMatchAttributes = &AttributeConstraints{matcher: fixedAttrMatcher{nil}} 355 NeverMatchAttributes = &AttributeConstraints{matcher: fixedAttrMatcher{errors.New("not allowed")}} 356 ) 357 358 // Attrer reflects part of the Attrer interface (see interfaces.Attrer). 359 type Attrer interface { 360 Lookup(path string) (interface{}, bool) 361 } 362 363 // Check checks whether attrs don't match the constraints. 364 func (c *AttributeConstraints) Check(attrer Attrer, ctx AttrMatchContext) error { 365 return c.matcher.match("", attrer, ctx) 366 } 367 368 // SideArityConstraint specifies a constraint for the overall arity of 369 // the set of connected slots for a given plug or the set of 370 // connected plugs for a given slot. 371 // It is used to express parsed slots-per-plug and plugs-per-slot 372 // constraints. 373 // See https://forum.snapcraft.io/t/plug-slot-declaration-rules-greedy-plugs/12438 374 type SideArityConstraint struct { 375 // N can be: 376 // =>1 377 // 0 means default and is used only internally during rule 378 // compilation or on deny- rules where these constraints are 379 // not applicable 380 // -1 represents *, that means any (number of) 381 N int 382 } 383 384 // Any returns whether this represents the * (any number of) constraint. 385 func (ac SideArityConstraint) Any() bool { 386 return ac.N == -1 387 } 388 389 func compileSideArityConstraint(context *subruleContext, which string, v interface{}) (SideArityConstraint, error) { 390 var a SideArityConstraint 391 if context.installation() || !context.allow() { 392 return a, fmt.Errorf("%s cannot specify a %s constraint, they apply only to allow-*connection", context, which) 393 } 394 x, ok := v.(string) 395 if !ok || len(x) == 0 { 396 return a, fmt.Errorf("%s in %s must be an integer >=1 or *", which, context) 397 } 398 if x == "*" { 399 return SideArityConstraint{N: -1}, nil 400 } 401 n, err := atoi(x, "%s in %s", which, context) 402 switch _, syntax := err.(intSyntaxError); { 403 case err == nil && n < 1: 404 fallthrough 405 case syntax: 406 return a, fmt.Errorf("%s in %s must be an integer >=1 or *", which, context) 407 case err != nil: 408 return a, err 409 } 410 return SideArityConstraint{N: n}, nil 411 } 412 413 type sideArityConstraintsHolder interface { 414 setSlotsPerPlug(SideArityConstraint) 415 setPlugsPerSlot(SideArityConstraint) 416 417 slotsPerPlug() SideArityConstraint 418 plugsPerSlot() SideArityConstraint 419 } 420 421 func normalizeSideArityConstraints(context *subruleContext, c sideArityConstraintsHolder) { 422 if !context.allow() { 423 return 424 } 425 any := SideArityConstraint{N: -1} 426 // normalized plugs-per-slot is always * 427 c.setPlugsPerSlot(any) 428 slotsPerPlug := c.slotsPerPlug() 429 if context.autoConnection() { 430 // auto-connection slots-per-plug can be any or 1 431 if !slotsPerPlug.Any() { 432 c.setSlotsPerPlug(SideArityConstraint{N: 1}) 433 } 434 } else { 435 // connection slots-per-plug can be only any 436 c.setSlotsPerPlug(any) 437 } 438 } 439 440 var ( 441 sideArityConstraints = []string{"slots-per-plug", "plugs-per-slot"} 442 sideArityConstraintsSetters = map[string]func(sideArityConstraintsHolder, SideArityConstraint){ 443 "slots-per-plug": sideArityConstraintsHolder.setSlotsPerPlug, 444 "plugs-per-slot": sideArityConstraintsHolder.setPlugsPerSlot, 445 } 446 ) 447 448 // OnClassicConstraint specifies a constraint based whether the system is classic and optional specific distros' sets. 449 type OnClassicConstraint struct { 450 Classic bool 451 SystemIDs []string 452 } 453 454 // DeviceScopeConstraint specifies a constraints based on which brand 455 // store, brand or model the device belongs to. 456 type DeviceScopeConstraint struct { 457 Store []string 458 Brand []string 459 // Model is a list of precise "<brand>/<model>" constraints 460 Model []string 461 } 462 463 var ( 464 validStoreID = regexp.MustCompile("^[-A-Z0-9a-z_]+$") 465 validBrandSlashModel = regexp.MustCompile("^(" + 466 strings.Trim(validAccountID.String(), "^$") + 467 ")/(" + 468 strings.Trim(validModel.String(), "^$") + 469 ")$") 470 deviceScopeConstraints = map[string]*regexp.Regexp{ 471 "on-store": validStoreID, 472 "on-brand": validAccountID, 473 // on-model constraints are of the form list of 474 // <brand>/<model> strings where <brand> are account 475 // IDs as they appear in the respective model assertion 476 "on-model": validBrandSlashModel, 477 } 478 ) 479 480 func detectDeviceScopeConstraint(cMap map[string]interface{}) bool { 481 // for consistency and simplicity we support all of on-store, 482 // on-brand, and on-model to appear together. The interpretation 483 // layer will AND them as usual 484 for field := range deviceScopeConstraints { 485 if cMap[field] != nil { 486 return true 487 } 488 } 489 return false 490 } 491 492 func compileDeviceScopeConstraint(cMap map[string]interface{}, context string) (constr *DeviceScopeConstraint, err error) { 493 // initial map size of 2: we expect usual cases to have just one of the 494 // constraints or rarely 2 495 deviceConstr := make(map[string][]string, 2) 496 for field, validRegexp := range deviceScopeConstraints { 497 vals, err := checkStringListInMap(cMap, field, fmt.Sprintf("%s in %s", field, context), validRegexp) 498 if err != nil { 499 return nil, err 500 } 501 deviceConstr[field] = vals 502 } 503 504 if len(deviceConstr) == 0 { 505 return nil, fmt.Errorf("internal error: misdetected device scope constraints in %s", context) 506 } 507 return &DeviceScopeConstraint{ 508 Store: deviceConstr["on-store"], 509 Brand: deviceConstr["on-brand"], 510 Model: deviceConstr["on-model"], 511 }, nil 512 } 513 514 type nameMatcher interface { 515 match(name string, special map[string]string) error 516 } 517 518 var ( 519 // validates special name constraints like $INTERFACE 520 validSpecialNameConstraint = regexp.MustCompile("^\\$[A-Z][A-Z0-9_]*$") 521 ) 522 523 func compileNameMatcher(whichName string, v interface{}) (nameMatcher, error) { 524 s, ok := v.(string) 525 if !ok { 526 return nil, fmt.Errorf("%s constraint entry must be a regexp or special $ value", whichName) 527 } 528 if strings.HasPrefix(s, "$") { 529 if !validSpecialNameConstraint.MatchString(s) { 530 return nil, fmt.Errorf("%s constraint entry special value %q is invalid", whichName, s) 531 } 532 return specialNameMatcher{special: s}, nil 533 } 534 if strings.IndexFunc(s, unicode.IsSpace) != -1 { 535 return nil, fmt.Errorf("%s constraint entry regexp contains unexpected spaces", whichName) 536 } 537 rx, err := regexp.Compile("^(" + s + ")$") 538 if err != nil { 539 return nil, fmt.Errorf("cannot compile %s constraint entry %q: %v", whichName, s, err) 540 } 541 return regexpNameMatcher{rx}, nil 542 } 543 544 type regexpNameMatcher struct { 545 *regexp.Regexp 546 } 547 548 func (matcher regexpNameMatcher) match(name string, special map[string]string) error { 549 if !matcher.Regexp.MatchString(name) { 550 return fmt.Errorf("%q does not match %v", name, matcher.Regexp) 551 } 552 return nil 553 } 554 555 type specialNameMatcher struct { 556 special string 557 } 558 559 func (matcher specialNameMatcher) match(name string, special map[string]string) error { 560 expected := special[matcher.special] 561 if expected == "" || expected != name { 562 return fmt.Errorf("%q does not match %v", name, matcher.special) 563 } 564 return nil 565 } 566 567 // NameConstraints implements a set of constraints on the names of slots or plugs. 568 // See https://forum.snapcraft.io/t/plug-slot-rules-plug-names-slot-names-constraints/12439 569 type NameConstraints struct { 570 matchers []nameMatcher 571 } 572 573 func compileNameConstraints(whichName string, constraints interface{}) (*NameConstraints, error) { 574 l, ok := constraints.([]interface{}) 575 if !ok { 576 return nil, fmt.Errorf("%s constraints must be a list of regexps and special $ values", whichName) 577 } 578 matchers := make([]nameMatcher, 0, len(l)) 579 for _, nm := range l { 580 matcher, err := compileNameMatcher(whichName, nm) 581 if err != nil { 582 return nil, err 583 } 584 matchers = append(matchers, matcher) 585 } 586 return &NameConstraints{matchers: matchers}, nil 587 } 588 589 // Check checks whether name doesn't match the constraints. 590 func (nc *NameConstraints) Check(whichName, name string, special map[string]string) error { 591 for _, m := range nc.matchers { 592 if err := m.match(name, special); err == nil { 593 return nil 594 } 595 } 596 return fmt.Errorf("%s %q does not match constraints", whichName, name) 597 } 598 599 // rules 600 601 var ( 602 validSnapType = regexp.MustCompile("^(?:core|kernel|gadget|app)$") 603 validDistro = regexp.MustCompile("^[-0-9a-z._]+$") 604 validPublisher = regexp.MustCompile("^(?:[a-z0-9A-Z]{32}|[-a-z0-9]{2,28}|\\$[A-Z][A-Z0-9_]*)$") // account ids look like snap-ids or are nice identifiers, support our own special markers $MARKER 605 606 validIDConstraints = map[string]*regexp.Regexp{ 607 "slot-snap-type": validSnapType, 608 "slot-snap-id": naming.ValidSnapID, 609 "slot-publisher-id": validPublisher, 610 "plug-snap-type": validSnapType, 611 "plug-snap-id": naming.ValidSnapID, 612 "plug-publisher-id": validPublisher, 613 } 614 ) 615 616 func checkMapOrShortcut(v interface{}) (m map[string]interface{}, invert bool, err error) { 617 switch x := v.(type) { 618 case map[string]interface{}: 619 return x, false, nil 620 case string: 621 switch x { 622 case "true": 623 return nil, false, nil 624 case "false": 625 return nil, true, nil 626 } 627 } 628 return nil, false, errors.New("unexpected type") 629 } 630 631 type constraintsHolder interface { 632 setNameConstraints(field string, cstrs *NameConstraints) 633 setAttributeConstraints(field string, cstrs *AttributeConstraints) 634 setIDConstraints(field string, cstrs []string) 635 setOnClassicConstraint(onClassic *OnClassicConstraint) 636 setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) 637 } 638 639 func baseCompileConstraints(context *subruleContext, cDef constraintsDef, target constraintsHolder, nameConstraints, attrConstraints, idConstraints []string) error { 640 cMap := cDef.cMap 641 if cMap == nil { 642 fixed := AlwaysMatchAttributes // "true" 643 if cDef.invert { // "false" 644 fixed = NeverMatchAttributes 645 } 646 for _, field := range attrConstraints { 647 target.setAttributeConstraints(field, fixed) 648 } 649 return nil 650 } 651 defaultUsed := 0 652 for _, field := range nameConstraints { 653 v := cMap[field] 654 if v != nil { 655 nc, err := compileNameConstraints(field, v) 656 if err != nil { 657 return err 658 } 659 target.setNameConstraints(field, nc) 660 } else { 661 defaultUsed++ 662 } 663 } 664 for _, field := range attrConstraints { 665 cstrs := AlwaysMatchAttributes 666 v := cMap[field] 667 if v != nil { 668 var err error 669 cstrs, err = compileAttributeConstraints(cMap[field]) 670 if err != nil { 671 return fmt.Errorf("cannot compile %s in %s: %v", field, context, err) 672 } 673 } else { 674 defaultUsed++ 675 } 676 target.setAttributeConstraints(field, cstrs) 677 } 678 for _, field := range idConstraints { 679 lst, err := checkStringListInMap(cMap, field, fmt.Sprintf("%s in %s", field, context), validIDConstraints[field]) 680 if err != nil { 681 return err 682 } 683 if lst == nil { 684 defaultUsed++ 685 } 686 target.setIDConstraints(field, lst) 687 } 688 for _, field := range sideArityConstraints { 689 v := cMap[field] 690 if v != nil { 691 c, err := compileSideArityConstraint(context, field, v) 692 if err != nil { 693 return err 694 } 695 h, ok := target.(sideArityConstraintsHolder) 696 if !ok { 697 return fmt.Errorf("internal error: side arity constraint compiled for unexpected subrule %T", target) 698 } 699 sideArityConstraintsSetters[field](h, c) 700 } else { 701 defaultUsed++ 702 } 703 } 704 onClassic := cMap["on-classic"] 705 if onClassic == nil { 706 defaultUsed++ 707 } else { 708 var c *OnClassicConstraint 709 switch x := onClassic.(type) { 710 case string: 711 switch x { 712 case "true": 713 c = &OnClassicConstraint{Classic: true} 714 case "false": 715 c = &OnClassicConstraint{Classic: false} 716 } 717 case []interface{}: 718 lst, err := checkStringListInMap(cMap, "on-classic", fmt.Sprintf("on-classic in %s", context), validDistro) 719 if err != nil { 720 return err 721 } 722 c = &OnClassicConstraint{Classic: true, SystemIDs: lst} 723 } 724 if c == nil { 725 return fmt.Errorf("on-classic in %s must be 'true', 'false' or a list of operating system IDs", context) 726 } 727 target.setOnClassicConstraint(c) 728 } 729 if !detectDeviceScopeConstraint(cMap) { 730 defaultUsed++ 731 } else { 732 c, err := compileDeviceScopeConstraint(cMap, context.String()) 733 if err != nil { 734 return err 735 } 736 target.setDeviceScopeConstraint(c) 737 } 738 // checks whether defaults have been used for everything, which is not 739 // well-formed 740 // +1+1 accounts for defaults for missing on-classic plus missing 741 // on-store/on-brand/on-model 742 if defaultUsed == len(nameConstraints)+len(attributeConstraints)+len(idConstraints)+len(sideArityConstraints)+1+1 { 743 return fmt.Errorf("%s must specify at least one of %s, %s, %s, %s, on-classic, on-store, on-brand, on-model", context, strings.Join(nameConstraints, ", "), strings.Join(attrConstraints, ", "), strings.Join(idConstraints, ", "), strings.Join(sideArityConstraints, ", ")) 744 } 745 return nil 746 } 747 748 type rule interface { 749 setConstraints(field string, cstrs []constraintsHolder) 750 } 751 752 type constraintsDef struct { 753 cMap map[string]interface{} 754 invert bool 755 } 756 757 // subruleContext carries queryable context information about one the 758 // {allow,deny}-* subrules that end up compiled as 759 // Plug|Slot*Constraints. The information includes the parent rule, 760 // the introductory subrule key ({allow,deny}-*) and which alternative 761 // it corresponds to if any. 762 // The information is useful for constraints compilation now that we 763 // have constraints with different behavior depending on the kind of 764 // subrule that hosts them (e.g. slots-per-plug, plugs-per-slot). 765 type subruleContext struct { 766 // rule is the parent rule context description 767 rule string 768 // subrule is the subrule key 769 subrule string 770 // alt is which alternative this is (if > 0) 771 alt int 772 } 773 774 func (c *subruleContext) String() string { 775 subctxt := fmt.Sprintf("%s in %s", c.subrule, c.rule) 776 if c.alt != 0 { 777 subctxt = fmt.Sprintf("alternative %d of %s", c.alt, subctxt) 778 } 779 return subctxt 780 } 781 782 // allow returns whether the subrule is an allow-* subrule. 783 func (c *subruleContext) allow() bool { 784 return strings.HasPrefix(c.subrule, "allow-") 785 } 786 787 // installation returns whether the subrule is an *-installation subrule. 788 func (c *subruleContext) installation() bool { 789 return strings.HasSuffix(c.subrule, "-installation") 790 } 791 792 // autoConnection returns whether the subrule is an *-auto-connection subrule. 793 func (c *subruleContext) autoConnection() bool { 794 return strings.HasSuffix(c.subrule, "-auto-connection") 795 } 796 797 type subruleCompiler func(context *subruleContext, def constraintsDef) (constraintsHolder, error) 798 799 func baseCompileRule(context string, rule interface{}, target rule, subrules []string, compilers map[string]subruleCompiler, defaultOutcome, invertedOutcome map[string]interface{}) error { 800 rMap, invert, err := checkMapOrShortcut(rule) 801 if err != nil { 802 return fmt.Errorf("%s must be a map or one of the shortcuts 'true' or 'false'", context) 803 } 804 if rMap == nil { 805 rMap = defaultOutcome // "true" 806 if invert { 807 rMap = invertedOutcome // "false" 808 } 809 } 810 defaultUsed := 0 811 // compile and set subrules 812 for _, subrule := range subrules { 813 v := rMap[subrule] 814 var lst []interface{} 815 alternatives := false 816 switch x := v.(type) { 817 case nil: 818 v = defaultOutcome[subrule] 819 defaultUsed++ 820 case []interface{}: 821 alternatives = true 822 lst = x 823 } 824 if lst == nil { // v is map or a string, checked below 825 lst = []interface{}{v} 826 } 827 compiler := compilers[subrule] 828 if compiler == nil { 829 panic(fmt.Sprintf("no compiler for %s in %s", subrule, context)) 830 } 831 alts := make([]constraintsHolder, len(lst)) 832 for i, alt := range lst { 833 subctxt := &subruleContext{ 834 rule: context, 835 subrule: subrule, 836 } 837 if alternatives { 838 subctxt.alt = i + 1 839 } 840 cMap, invert, err := checkMapOrShortcut(alt) 841 if err != nil || (cMap == nil && alternatives) { 842 efmt := "%s must be a map" 843 if !alternatives { 844 efmt = "%s must be a map or one of the shortcuts 'true' or 'false'" 845 } 846 return fmt.Errorf(efmt, subctxt) 847 } 848 849 cstrs, err := compiler(subctxt, constraintsDef{ 850 cMap: cMap, 851 invert: invert, 852 }) 853 if err != nil { 854 return err 855 } 856 alts[i] = cstrs 857 } 858 target.setConstraints(subrule, alts) 859 } 860 if defaultUsed == len(subrules) { 861 return fmt.Errorf("%s must specify at least one of %s", context, strings.Join(subrules, ", ")) 862 } 863 return nil 864 } 865 866 // PlugRule holds the rule of what is allowed, wrt installation and 867 // connection, for a plug of a specific interface for a snap. 868 type PlugRule struct { 869 Interface string 870 871 AllowInstallation []*PlugInstallationConstraints 872 DenyInstallation []*PlugInstallationConstraints 873 874 AllowConnection []*PlugConnectionConstraints 875 DenyConnection []*PlugConnectionConstraints 876 877 AllowAutoConnection []*PlugConnectionConstraints 878 DenyAutoConnection []*PlugConnectionConstraints 879 } 880 881 func (r *PlugRule) feature(flabel string) bool { 882 for _, cs := range [][]*PlugInstallationConstraints{r.AllowInstallation, r.DenyInstallation} { 883 for _, c := range cs { 884 if c.feature(flabel) { 885 return true 886 } 887 } 888 } 889 890 for _, cs := range [][]*PlugConnectionConstraints{r.AllowConnection, r.DenyConnection, r.AllowAutoConnection, r.DenyAutoConnection} { 891 for _, c := range cs { 892 if c.feature(flabel) { 893 return true 894 } 895 } 896 } 897 898 return false 899 } 900 901 func castPlugInstallationConstraints(cstrs []constraintsHolder) (res []*PlugInstallationConstraints) { 902 res = make([]*PlugInstallationConstraints, len(cstrs)) 903 for i, cstr := range cstrs { 904 res[i] = cstr.(*PlugInstallationConstraints) 905 } 906 return res 907 } 908 909 func castPlugConnectionConstraints(cstrs []constraintsHolder) (res []*PlugConnectionConstraints) { 910 res = make([]*PlugConnectionConstraints, len(cstrs)) 911 for i, cstr := range cstrs { 912 res[i] = cstr.(*PlugConnectionConstraints) 913 } 914 return res 915 } 916 917 func (r *PlugRule) setConstraints(field string, cstrs []constraintsHolder) { 918 if len(cstrs) == 0 { 919 panic(fmt.Sprintf("cannot set PlugRule field %q to empty", field)) 920 } 921 switch cstrs[0].(type) { 922 case *PlugInstallationConstraints: 923 switch field { 924 case "allow-installation": 925 r.AllowInstallation = castPlugInstallationConstraints(cstrs) 926 return 927 case "deny-installation": 928 r.DenyInstallation = castPlugInstallationConstraints(cstrs) 929 return 930 } 931 case *PlugConnectionConstraints: 932 switch field { 933 case "allow-connection": 934 r.AllowConnection = castPlugConnectionConstraints(cstrs) 935 return 936 case "deny-connection": 937 r.DenyConnection = castPlugConnectionConstraints(cstrs) 938 return 939 case "allow-auto-connection": 940 r.AllowAutoConnection = castPlugConnectionConstraints(cstrs) 941 return 942 case "deny-auto-connection": 943 r.DenyAutoConnection = castPlugConnectionConstraints(cstrs) 944 return 945 } 946 } 947 panic(fmt.Sprintf("cannot set PlugRule field %q with %T elements", field, cstrs[0])) 948 } 949 950 // PlugInstallationConstraints specifies a set of constraints on an interface plug relevant to the installation of snap. 951 type PlugInstallationConstraints struct { 952 PlugSnapTypes []string 953 954 PlugNames *NameConstraints 955 956 PlugAttributes *AttributeConstraints 957 958 OnClassic *OnClassicConstraint 959 960 DeviceScope *DeviceScopeConstraint 961 } 962 963 func (c *PlugInstallationConstraints) feature(flabel string) bool { 964 if flabel == deviceScopeConstraintsFeature { 965 return c.DeviceScope != nil 966 } 967 if flabel == nameConstraintsFeature { 968 return c.PlugNames != nil 969 } 970 return c.PlugAttributes.feature(flabel) 971 } 972 973 func (c *PlugInstallationConstraints) setNameConstraints(field string, cstrs *NameConstraints) { 974 switch field { 975 case "plug-names": 976 c.PlugNames = cstrs 977 default: 978 panic("unknown PlugInstallationConstraints field " + field) 979 } 980 } 981 982 func (c *PlugInstallationConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) { 983 switch field { 984 case "plug-attributes": 985 c.PlugAttributes = cstrs 986 default: 987 panic("unknown PlugInstallationConstraints field " + field) 988 } 989 } 990 991 func (c *PlugInstallationConstraints) setIDConstraints(field string, cstrs []string) { 992 switch field { 993 case "plug-snap-type": 994 c.PlugSnapTypes = cstrs 995 default: 996 panic("unknown PlugInstallationConstraints field " + field) 997 } 998 } 999 1000 func (c *PlugInstallationConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) { 1001 c.OnClassic = onClassic 1002 } 1003 1004 func (c *PlugInstallationConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { 1005 c.DeviceScope = deviceScope 1006 } 1007 1008 func compilePlugInstallationConstraints(context *subruleContext, cDef constraintsDef) (constraintsHolder, error) { 1009 plugInstCstrs := &PlugInstallationConstraints{} 1010 err := baseCompileConstraints(context, cDef, plugInstCstrs, []string{"plug-names"}, []string{"plug-attributes"}, []string{"plug-snap-type"}) 1011 if err != nil { 1012 return nil, err 1013 } 1014 return plugInstCstrs, nil 1015 } 1016 1017 // PlugConnectionConstraints specfies a set of constraints on an 1018 // interface plug for a snap relevant to its connection or 1019 // auto-connection. 1020 type PlugConnectionConstraints struct { 1021 SlotSnapTypes []string 1022 SlotSnapIDs []string 1023 SlotPublisherIDs []string 1024 1025 PlugNames *NameConstraints 1026 SlotNames *NameConstraints 1027 1028 PlugAttributes *AttributeConstraints 1029 SlotAttributes *AttributeConstraints 1030 1031 // SlotsPerPlug defaults to 1 for auto-connection, can be * (any) 1032 SlotsPerPlug SideArityConstraint 1033 // PlugsPerSlot is always * (any) (for now) 1034 PlugsPerSlot SideArityConstraint 1035 1036 OnClassic *OnClassicConstraint 1037 1038 DeviceScope *DeviceScopeConstraint 1039 } 1040 1041 func (c *PlugConnectionConstraints) feature(flabel string) bool { 1042 if flabel == deviceScopeConstraintsFeature { 1043 return c.DeviceScope != nil 1044 } 1045 if flabel == nameConstraintsFeature { 1046 return c.PlugNames != nil || c.SlotNames != nil 1047 } 1048 return c.PlugAttributes.feature(flabel) || c.SlotAttributes.feature(flabel) 1049 } 1050 1051 func (c *PlugConnectionConstraints) setNameConstraints(field string, cstrs *NameConstraints) { 1052 switch field { 1053 case "plug-names": 1054 c.PlugNames = cstrs 1055 case "slot-names": 1056 c.SlotNames = cstrs 1057 default: 1058 panic("unknown PlugConnectionConstraints field " + field) 1059 } 1060 } 1061 1062 func (c *PlugConnectionConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) { 1063 switch field { 1064 case "plug-attributes": 1065 c.PlugAttributes = cstrs 1066 case "slot-attributes": 1067 c.SlotAttributes = cstrs 1068 default: 1069 panic("unknown PlugConnectionConstraints field " + field) 1070 } 1071 } 1072 1073 func (c *PlugConnectionConstraints) setIDConstraints(field string, cstrs []string) { 1074 switch field { 1075 case "slot-snap-type": 1076 c.SlotSnapTypes = cstrs 1077 case "slot-snap-id": 1078 c.SlotSnapIDs = cstrs 1079 case "slot-publisher-id": 1080 c.SlotPublisherIDs = cstrs 1081 default: 1082 panic("unknown PlugConnectionConstraints field " + field) 1083 } 1084 } 1085 1086 func (c *PlugConnectionConstraints) setSlotsPerPlug(a SideArityConstraint) { 1087 c.SlotsPerPlug = a 1088 } 1089 1090 func (c *PlugConnectionConstraints) setPlugsPerSlot(a SideArityConstraint) { 1091 c.PlugsPerSlot = a 1092 } 1093 1094 func (c *PlugConnectionConstraints) slotsPerPlug() SideArityConstraint { 1095 return c.SlotsPerPlug 1096 } 1097 1098 func (c *PlugConnectionConstraints) plugsPerSlot() SideArityConstraint { 1099 return c.PlugsPerSlot 1100 } 1101 1102 func (c *PlugConnectionConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) { 1103 c.OnClassic = onClassic 1104 } 1105 1106 func (c *PlugConnectionConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { 1107 c.DeviceScope = deviceScope 1108 } 1109 1110 var ( 1111 nameConstraints = []string{"plug-names", "slot-names"} 1112 attributeConstraints = []string{"plug-attributes", "slot-attributes"} 1113 plugIDConstraints = []string{"slot-snap-type", "slot-publisher-id", "slot-snap-id"} 1114 ) 1115 1116 func compilePlugConnectionConstraints(context *subruleContext, cDef constraintsDef) (constraintsHolder, error) { 1117 plugConnCstrs := &PlugConnectionConstraints{} 1118 err := baseCompileConstraints(context, cDef, plugConnCstrs, nameConstraints, attributeConstraints, plugIDConstraints) 1119 if err != nil { 1120 return nil, err 1121 } 1122 normalizeSideArityConstraints(context, plugConnCstrs) 1123 return plugConnCstrs, nil 1124 } 1125 1126 var ( 1127 defaultOutcome = map[string]interface{}{ 1128 "allow-installation": "true", 1129 "allow-connection": "true", 1130 "allow-auto-connection": "true", 1131 "deny-installation": "false", 1132 "deny-connection": "false", 1133 "deny-auto-connection": "false", 1134 } 1135 1136 invertedOutcome = map[string]interface{}{ 1137 "allow-installation": "false", 1138 "allow-connection": "false", 1139 "allow-auto-connection": "false", 1140 "deny-installation": "true", 1141 "deny-connection": "true", 1142 "deny-auto-connection": "true", 1143 } 1144 1145 ruleSubrules = []string{"allow-installation", "deny-installation", "allow-connection", "deny-connection", "allow-auto-connection", "deny-auto-connection"} 1146 ) 1147 1148 var plugRuleCompilers = map[string]subruleCompiler{ 1149 "allow-installation": compilePlugInstallationConstraints, 1150 "deny-installation": compilePlugInstallationConstraints, 1151 "allow-connection": compilePlugConnectionConstraints, 1152 "deny-connection": compilePlugConnectionConstraints, 1153 "allow-auto-connection": compilePlugConnectionConstraints, 1154 "deny-auto-connection": compilePlugConnectionConstraints, 1155 } 1156 1157 func compilePlugRule(interfaceName string, rule interface{}) (*PlugRule, error) { 1158 context := fmt.Sprintf("plug rule for interface %q", interfaceName) 1159 plugRule := &PlugRule{ 1160 Interface: interfaceName, 1161 } 1162 err := baseCompileRule(context, rule, plugRule, ruleSubrules, plugRuleCompilers, defaultOutcome, invertedOutcome) 1163 if err != nil { 1164 return nil, err 1165 } 1166 return plugRule, nil 1167 } 1168 1169 // SlotRule holds the rule of what is allowed, wrt installation and 1170 // connection, for a slot of a specific interface for a snap. 1171 type SlotRule struct { 1172 Interface string 1173 1174 AllowInstallation []*SlotInstallationConstraints 1175 DenyInstallation []*SlotInstallationConstraints 1176 1177 AllowConnection []*SlotConnectionConstraints 1178 DenyConnection []*SlotConnectionConstraints 1179 1180 AllowAutoConnection []*SlotConnectionConstraints 1181 DenyAutoConnection []*SlotConnectionConstraints 1182 } 1183 1184 func castSlotInstallationConstraints(cstrs []constraintsHolder) (res []*SlotInstallationConstraints) { 1185 res = make([]*SlotInstallationConstraints, len(cstrs)) 1186 for i, cstr := range cstrs { 1187 res[i] = cstr.(*SlotInstallationConstraints) 1188 } 1189 return res 1190 } 1191 1192 func (r *SlotRule) feature(flabel string) bool { 1193 for _, cs := range [][]*SlotInstallationConstraints{r.AllowInstallation, r.DenyInstallation} { 1194 for _, c := range cs { 1195 if c.feature(flabel) { 1196 return true 1197 } 1198 } 1199 } 1200 1201 for _, cs := range [][]*SlotConnectionConstraints{r.AllowConnection, r.DenyConnection, r.AllowAutoConnection, r.DenyAutoConnection} { 1202 for _, c := range cs { 1203 if c.feature(flabel) { 1204 return true 1205 } 1206 } 1207 } 1208 1209 return false 1210 } 1211 1212 func castSlotConnectionConstraints(cstrs []constraintsHolder) (res []*SlotConnectionConstraints) { 1213 res = make([]*SlotConnectionConstraints, len(cstrs)) 1214 for i, cstr := range cstrs { 1215 res[i] = cstr.(*SlotConnectionConstraints) 1216 } 1217 return res 1218 } 1219 1220 func (r *SlotRule) setConstraints(field string, cstrs []constraintsHolder) { 1221 if len(cstrs) == 0 { 1222 panic(fmt.Sprintf("cannot set SlotRule field %q to empty", field)) 1223 } 1224 switch cstrs[0].(type) { 1225 case *SlotInstallationConstraints: 1226 switch field { 1227 case "allow-installation": 1228 r.AllowInstallation = castSlotInstallationConstraints(cstrs) 1229 return 1230 case "deny-installation": 1231 r.DenyInstallation = castSlotInstallationConstraints(cstrs) 1232 return 1233 } 1234 case *SlotConnectionConstraints: 1235 switch field { 1236 case "allow-connection": 1237 r.AllowConnection = castSlotConnectionConstraints(cstrs) 1238 return 1239 case "deny-connection": 1240 r.DenyConnection = castSlotConnectionConstraints(cstrs) 1241 return 1242 case "allow-auto-connection": 1243 r.AllowAutoConnection = castSlotConnectionConstraints(cstrs) 1244 return 1245 case "deny-auto-connection": 1246 r.DenyAutoConnection = castSlotConnectionConstraints(cstrs) 1247 return 1248 } 1249 } 1250 panic(fmt.Sprintf("cannot set SlotRule field %q with %T elements", field, cstrs[0])) 1251 } 1252 1253 // SlotInstallationConstraints specifies a set of constraints on an 1254 // interface slot relevant to the installation of snap. 1255 type SlotInstallationConstraints struct { 1256 SlotSnapTypes []string 1257 1258 SlotNames *NameConstraints 1259 1260 SlotAttributes *AttributeConstraints 1261 1262 OnClassic *OnClassicConstraint 1263 1264 DeviceScope *DeviceScopeConstraint 1265 } 1266 1267 func (c *SlotInstallationConstraints) feature(flabel string) bool { 1268 if flabel == deviceScopeConstraintsFeature { 1269 return c.DeviceScope != nil 1270 } 1271 if flabel == nameConstraintsFeature { 1272 return c.SlotNames != nil 1273 } 1274 return c.SlotAttributes.feature(flabel) 1275 } 1276 1277 func (c *SlotInstallationConstraints) setNameConstraints(field string, cstrs *NameConstraints) { 1278 switch field { 1279 case "slot-names": 1280 c.SlotNames = cstrs 1281 default: 1282 panic("unknown SlotInstallationConstraints field " + field) 1283 } 1284 } 1285 1286 func (c *SlotInstallationConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) { 1287 switch field { 1288 case "slot-attributes": 1289 c.SlotAttributes = cstrs 1290 default: 1291 panic("unknown SlotInstallationConstraints field " + field) 1292 } 1293 } 1294 1295 func (c *SlotInstallationConstraints) setIDConstraints(field string, cstrs []string) { 1296 switch field { 1297 case "slot-snap-type": 1298 c.SlotSnapTypes = cstrs 1299 default: 1300 panic("unknown SlotInstallationConstraints field " + field) 1301 } 1302 } 1303 1304 func (c *SlotInstallationConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) { 1305 c.OnClassic = onClassic 1306 } 1307 1308 func (c *SlotInstallationConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { 1309 c.DeviceScope = deviceScope 1310 } 1311 1312 func compileSlotInstallationConstraints(context *subruleContext, cDef constraintsDef) (constraintsHolder, error) { 1313 slotInstCstrs := &SlotInstallationConstraints{} 1314 err := baseCompileConstraints(context, cDef, slotInstCstrs, []string{"slot-names"}, []string{"slot-attributes"}, []string{"slot-snap-type"}) 1315 if err != nil { 1316 return nil, err 1317 } 1318 return slotInstCstrs, nil 1319 } 1320 1321 // SlotConnectionConstraints specfies a set of constraints on an 1322 // interface slot for a snap relevant to its connection or 1323 // auto-connection. 1324 type SlotConnectionConstraints struct { 1325 PlugSnapTypes []string 1326 PlugSnapIDs []string 1327 PlugPublisherIDs []string 1328 1329 SlotNames *NameConstraints 1330 PlugNames *NameConstraints 1331 1332 SlotAttributes *AttributeConstraints 1333 PlugAttributes *AttributeConstraints 1334 1335 // SlotsPerPlug defaults to 1 for auto-connection, can be * (any) 1336 SlotsPerPlug SideArityConstraint 1337 // PlugsPerSlot is always * (any) (for now) 1338 PlugsPerSlot SideArityConstraint 1339 1340 OnClassic *OnClassicConstraint 1341 1342 DeviceScope *DeviceScopeConstraint 1343 } 1344 1345 func (c *SlotConnectionConstraints) feature(flabel string) bool { 1346 if flabel == deviceScopeConstraintsFeature { 1347 return c.DeviceScope != nil 1348 } 1349 if flabel == nameConstraintsFeature { 1350 return c.PlugNames != nil || c.SlotNames != nil 1351 } 1352 return c.PlugAttributes.feature(flabel) || c.SlotAttributes.feature(flabel) 1353 } 1354 1355 func (c *SlotConnectionConstraints) setNameConstraints(field string, cstrs *NameConstraints) { 1356 switch field { 1357 case "plug-names": 1358 c.PlugNames = cstrs 1359 case "slot-names": 1360 c.SlotNames = cstrs 1361 default: 1362 panic("unknown SlotConnectionConstraints field " + field) 1363 } 1364 } 1365 1366 func (c *SlotConnectionConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) { 1367 switch field { 1368 case "plug-attributes": 1369 c.PlugAttributes = cstrs 1370 case "slot-attributes": 1371 c.SlotAttributes = cstrs 1372 default: 1373 panic("unknown SlotConnectionConstraints field " + field) 1374 } 1375 } 1376 1377 func (c *SlotConnectionConstraints) setIDConstraints(field string, cstrs []string) { 1378 switch field { 1379 case "plug-snap-type": 1380 c.PlugSnapTypes = cstrs 1381 case "plug-snap-id": 1382 c.PlugSnapIDs = cstrs 1383 case "plug-publisher-id": 1384 c.PlugPublisherIDs = cstrs 1385 default: 1386 panic("unknown SlotConnectionConstraints field " + field) 1387 } 1388 } 1389 1390 var ( 1391 slotIDConstraints = []string{"plug-snap-type", "plug-publisher-id", "plug-snap-id"} 1392 ) 1393 1394 func (c *SlotConnectionConstraints) setSlotsPerPlug(a SideArityConstraint) { 1395 c.SlotsPerPlug = a 1396 } 1397 1398 func (c *SlotConnectionConstraints) setPlugsPerSlot(a SideArityConstraint) { 1399 c.PlugsPerSlot = a 1400 } 1401 1402 func (c *SlotConnectionConstraints) slotsPerPlug() SideArityConstraint { 1403 return c.SlotsPerPlug 1404 } 1405 1406 func (c *SlotConnectionConstraints) plugsPerSlot() SideArityConstraint { 1407 return c.PlugsPerSlot 1408 } 1409 1410 func (c *SlotConnectionConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) { 1411 c.OnClassic = onClassic 1412 } 1413 1414 func (c *SlotConnectionConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { 1415 c.DeviceScope = deviceScope 1416 } 1417 1418 func compileSlotConnectionConstraints(context *subruleContext, cDef constraintsDef) (constraintsHolder, error) { 1419 slotConnCstrs := &SlotConnectionConstraints{} 1420 err := baseCompileConstraints(context, cDef, slotConnCstrs, nameConstraints, attributeConstraints, slotIDConstraints) 1421 if err != nil { 1422 return nil, err 1423 } 1424 normalizeSideArityConstraints(context, slotConnCstrs) 1425 return slotConnCstrs, nil 1426 } 1427 1428 var slotRuleCompilers = map[string]subruleCompiler{ 1429 "allow-installation": compileSlotInstallationConstraints, 1430 "deny-installation": compileSlotInstallationConstraints, 1431 "allow-connection": compileSlotConnectionConstraints, 1432 "deny-connection": compileSlotConnectionConstraints, 1433 "allow-auto-connection": compileSlotConnectionConstraints, 1434 "deny-auto-connection": compileSlotConnectionConstraints, 1435 } 1436 1437 func compileSlotRule(interfaceName string, rule interface{}) (*SlotRule, error) { 1438 context := fmt.Sprintf("slot rule for interface %q", interfaceName) 1439 slotRule := &SlotRule{ 1440 Interface: interfaceName, 1441 } 1442 err := baseCompileRule(context, rule, slotRule, ruleSubrules, slotRuleCompilers, defaultOutcome, invertedOutcome) 1443 if err != nil { 1444 return nil, err 1445 } 1446 return slotRule, nil 1447 }