gopkg.in/juju/charm.v6-unstable@v6.0.0-20171026192109-50d0c219b496/bundledata.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the LGPLv3, see LICENCE file for details. 3 4 package charm 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "regexp" 14 "sort" 15 "strconv" 16 "strings" 17 18 "gopkg.in/juju/names.v2" 19 "gopkg.in/mgo.v2/bson" 20 "gopkg.in/yaml.v2" 21 ) 22 23 type noMethodsBundleData BundleData 24 25 type legacyBundleData struct { 26 noMethodsBundleData `bson:",inline" yaml:",inline" json:",inline"` 27 28 // LegacyServices holds application entries for older bundle files 29 // that have not been migrated to use the new "application" terminology. 30 LegacyServices map[string]*ApplicationSpec `json:"services" yaml:"services" bson:"services"` 31 } 32 33 func (lbd *legacyBundleData) setBundleData(bd *BundleData) error { 34 if len(lbd.Applications) > 0 && len(lbd.LegacyServices) > 0 { 35 return fmt.Errorf("cannot specify both applications and services") 36 } 37 if len(lbd.LegacyServices) > 0 { 38 // We account for the fact that the YAML may contain a legacy entry 39 // for "services" instead of "applications". 40 lbd.Applications = lbd.LegacyServices 41 lbd.unmarshaledWithServices = true 42 } 43 *bd = BundleData(lbd.noMethodsBundleData) 44 return nil 45 } 46 47 // UnmarshalJSON implements the json.Unmarshaler interface. 48 func (bd *BundleData) UnmarshalJSON(b []byte) error { 49 var bdc legacyBundleData 50 if err := json.Unmarshal(b, &bdc); err != nil { 51 return err 52 } 53 return bdc.setBundleData(bd) 54 } 55 56 // UnmarshalYAML implements the yaml.Unmarshaler interface. 57 func (bd *BundleData) UnmarshalYAML(f func(interface{}) error) error { 58 var bdc legacyBundleData 59 if err := f(&bdc); err != nil { 60 return err 61 } 62 return bdc.setBundleData(bd) 63 } 64 65 // SetBSON implements the bson.Setter interface. 66 func (bd *BundleData) SetBSON(raw bson.Raw) error { 67 // TODO(wallyworld) - bson deserialisation is not handling the inline directive, 68 // so we need to unmarshal the bundle data manually. 69 var b *noMethodsBundleData 70 if err := raw.Unmarshal(&b); err != nil { 71 return err 72 } 73 if b == nil { 74 return bson.SetZero 75 } 76 77 var bdc legacyBundleData 78 if err := raw.Unmarshal(&bdc); err != nil { 79 return err 80 } 81 // As per the above TODO, we manually set the inline data. 82 bdc.noMethodsBundleData = *b 83 return bdc.setBundleData(bd) 84 } 85 86 // BundleData holds the contents of the bundle. 87 type BundleData struct { 88 // Applications holds one entry for each application 89 // that the bundle will create, indexed by 90 // the application name. 91 Applications map[string]*ApplicationSpec `bson:"applications,omitempty" json:"applications,omitempty" yaml:"applications,omitempty"` 92 93 // Machines holds one entry for each machine referred to 94 // by unit placements. These will be mapped onto actual 95 // machines at bundle deployment time. 96 // It is an error if a machine is specified but 97 // not referred to by a unit placement directive. 98 Machines map[string]*MachineSpec `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 99 100 // Series holds the default series to use when 101 // the bundle chooses charms. 102 Series string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 103 104 // Relations holds a slice of 2-element slices, 105 // each specifying a relation between two applications. 106 // Each two-element slice holds two endpoints, 107 // each specified as either colon-separated 108 // (application, relation) pair or just an application name. 109 // The relation is made between each. If the relation 110 // name is omitted, it will be inferred from the available 111 // relations defined in the applications' charms. 112 Relations [][]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 113 114 // White listed set of tags to categorize bundles as we do charms. 115 Tags []string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 116 117 // Short paragraph explaining what the bundle is useful for. 118 Description string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 119 120 // unmarshaledWithServices holds whether the original marshaled data held a 121 // legacy "services" field rather than the "applications" field. 122 unmarshaledWithServices bool 123 } 124 125 // UnmarshaledWithServices reports whether the bundle data was 126 // unmarshaled from a representation that used the legacy "services" 127 // field rather than the "applications" field. 128 func (d *BundleData) UnmarshaledWithServices() bool { 129 return d.unmarshaledWithServices 130 } 131 132 // MachineSpec represents a notional machine that will be mapped 133 // onto an actual machine at bundle deployment time. 134 type MachineSpec struct { 135 Constraints string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 136 Annotations map[string]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 137 Series string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 138 } 139 140 // ApplicationSpec represents a single application that will 141 // be deployed as part of the bundle. 142 type ApplicationSpec struct { 143 // Charm holds the charm URL of the charm to 144 // use for the given application. 145 Charm string 146 147 // Series is the series to use when deploying a local charm, 148 // if the charm does not specify a default or the default 149 // is not the desired value. 150 // Series is not compatible with charm store charms where 151 // the series is specified in the URL. 152 Series string `bson:",omitempty" yaml:",omitempty" json:",omitempty"` 153 154 // Resources is the set of resource revisions to deploy for the 155 // application. Bundles only support charm store resources and not ones 156 // that were uploaded to the controller. 157 Resources map[string]interface{} `bson:",omitempty" yaml:",omitempty" json:",omitempty"` 158 159 // NumUnits holds the number of units of the 160 // application that will be deployed. 161 // 162 // For a subordinate application, this actually represents 163 // an arbitrary number of units depending on 164 // the application it is related to. 165 NumUnits int `bson:",omitempty" yaml:"num_units,omitempty" json:",omitempty"` 166 167 // To may hold up to NumUnits members with 168 // each member specifying a desired placement 169 // for the respective unit of the application. 170 // 171 // In regular-expression-like notation, each 172 // element matches the following pattern: 173 // 174 // (<containertype>:)?(<unit>|<machine>|new) 175 // 176 // If containertype is specified, the unit is deployed 177 // into a new container of that type, otherwise 178 // it will be "hulk-smashed" into the specified location, 179 // by co-locating it with any other units that happen to 180 // be there, which may result in unintended behavior. 181 // 182 // The second part (after the colon) specifies where 183 // the new unit should be placed - it may refer to 184 // a unit of another application specified in the bundle, 185 // a machine id specified in the machines section, 186 // or the special name "new" which specifies a newly 187 // created machine. 188 // 189 // A unit placement may be specified with an application name only, 190 // in which case its unit number is assumed to 191 // be one more than the unit number of the previous 192 // unit in the list with the same application, or zero 193 // if there were none. 194 // 195 // If there are less elements in To than NumUnits, 196 // the last element is replicated to fill it. If there 197 // are no elements (or To is omitted), "new" is replicated. 198 // 199 // For example: 200 // 201 // wordpress/0 wordpress/1 lxc:0 kvm:new 202 // 203 // specifies that the first two units get hulk-smashed 204 // onto the first two units of the wordpress application, 205 // the third unit gets allocated onto an lxc container 206 // on machine 0, and subsequent units get allocated 207 // on kvm containers on new machines. 208 // 209 // The above example is the same as this: 210 // 211 // wordpress wordpress lxc:0 kvm:new 212 To []string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 213 214 // Expose holds whether the application must be exposed. 215 Expose bool `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 216 217 // Options holds the configuration values 218 // to apply to the new application. They should 219 // be compatible with the charm configuration. 220 Options map[string]interface{} `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 221 222 // Annotations holds any annotations to apply to the 223 // application when deployed. 224 Annotations map[string]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 225 226 // Constraints holds the default constraints to apply 227 // when creating new machines for units of the application. 228 // This is ignored for units with explicit placement directives. 229 Constraints string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 230 231 // Storage holds the constraints for storage to assign 232 // to units of the application. 233 Storage map[string]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` 234 235 // EndpointBindings maps how endpoints are bound to spaces 236 EndpointBindings map[string]string `bson:"bindings,omitempty" json:"bindings,omitempty" yaml:"bindings,omitempty"` 237 } 238 239 // ReadBundleData reads bundle data from the given reader. 240 // The returned data is not verified - call Verify to ensure 241 // that it is OK. 242 func ReadBundleData(r io.Reader) (*BundleData, error) { 243 bytes, err := ioutil.ReadAll(r) 244 if err != nil { 245 return nil, err 246 } 247 var bd BundleData 248 if err := yaml.Unmarshal(bytes, &bd); err != nil { 249 return nil, fmt.Errorf("cannot unmarshal bundle data: %v", err) 250 } 251 return &bd, nil 252 } 253 254 // VerificationError holds an error generated by BundleData.Verify, 255 // holding all the verification errors found when verifying. 256 type VerificationError struct { 257 Errors []error 258 } 259 260 func (err *VerificationError) Error() string { 261 switch len(err.Errors) { 262 case 0: 263 return "no verification errors!" 264 case 1: 265 return err.Errors[0].Error() 266 } 267 return fmt.Sprintf("%s (and %d more errors)", err.Errors[0], len(err.Errors)-1) 268 } 269 270 type bundleDataVerifier struct { 271 // bundleDir is the directory containing the bundle file 272 bundleDir string 273 bd *BundleData 274 275 // machines holds the reference counts of all machines 276 // as referred to by placement directives. 277 machineRefCounts map[string]int 278 279 charms map[string]Charm 280 281 errors []error 282 verifyConstraints func(c string) error 283 verifyStorage func(s string) error 284 } 285 286 func (verifier *bundleDataVerifier) addErrorf(f string, a ...interface{}) { 287 verifier.addError(fmt.Errorf(f, a...)) 288 } 289 290 func (verifier *bundleDataVerifier) addError(err error) { 291 verifier.errors = append(verifier.errors, err) 292 } 293 294 func (verifier *bundleDataVerifier) err() error { 295 if len(verifier.errors) > 0 { 296 return &VerificationError{verifier.errors} 297 } 298 return nil 299 } 300 301 // RequiredCharms returns a sorted slice of all the charm URLs 302 // required by the bundle. 303 func (bd *BundleData) RequiredCharms() []string { 304 req := make([]string, 0, len(bd.Applications)) 305 for _, svc := range bd.Applications { 306 req = append(req, svc.Charm) 307 } 308 sort.Strings(req) 309 return req 310 } 311 312 // VerifyLocal verifies that a local bundle file is consistent. 313 // A local bundle file may contain references to charms which are 314 // referred to by a directory, either relative or absolute. 315 // 316 // bundleDir is used to construct the full path for charms specified 317 // using a relative directory path. The charm path is therefore expected 318 // to be relative to the bundle.yaml file. 319 func (bd *BundleData) VerifyLocal( 320 bundleDir string, 321 verifyConstraints func(c string) error, 322 verifyStorage func(s string) error, 323 ) error { 324 return bd.verifyBundle(bundleDir, verifyConstraints, verifyStorage, nil) 325 } 326 327 // Verify is a convenience method that calls VerifyWithCharms 328 // with a nil charms map. 329 func (bd *BundleData) Verify( 330 verifyConstraints func(c string) error, 331 verifyStorage func(s string) error, 332 ) error { 333 return bd.VerifyWithCharms(verifyConstraints, verifyStorage, nil) 334 } 335 336 // VerifyWithCharms verifies that the bundle is consistent. 337 // The verifyConstraints function is called to verify any constraints 338 // that are found. If verifyConstraints is nil, no checking 339 // of constraints will be done. Similarly, a non-nil verifyStorage 340 // function is called to verify any storage constraints. 341 // 342 // It verifies the following: 343 // 344 // - All defined machines are referred to by placement directives. 345 // - All applications referred to by placement directives are specified in the bundle. 346 // - All applications referred to by relations are specified in the bundle. 347 // - All basic constraints are valid. 348 // - All storage constraints are valid. 349 // 350 // If charms is not nil, it should hold a map with an entry for each 351 // charm url returned by bd.RequiredCharms. The verification will then 352 // also check that applications are defined with valid charms, 353 // relations are correctly made and options are defined correctly. 354 // 355 // If the verification fails, Verify returns a *VerificationError describing 356 // all the problems found. 357 func (bd *BundleData) VerifyWithCharms( 358 verifyConstraints func(c string) error, 359 verifyStorage func(s string) error, 360 charms map[string]Charm, 361 ) error { 362 return bd.verifyBundle("", verifyConstraints, verifyStorage, charms) 363 } 364 365 func (bd *BundleData) verifyBundle( 366 bundleDir string, 367 verifyConstraints func(c string) error, 368 verifyStorage func(s string) error, 369 charms map[string]Charm, 370 ) error { 371 if verifyConstraints == nil { 372 verifyConstraints = func(string) error { 373 return nil 374 } 375 } 376 if verifyStorage == nil { 377 verifyStorage = func(string) error { 378 return nil 379 } 380 } 381 verifier := &bundleDataVerifier{ 382 bundleDir: bundleDir, 383 verifyConstraints: verifyConstraints, 384 verifyStorage: verifyStorage, 385 bd: bd, 386 machineRefCounts: make(map[string]int), 387 charms: charms, 388 } 389 for id := range bd.Machines { 390 verifier.machineRefCounts[id] = 0 391 } 392 if bd.Series != "" && !IsValidSeries(bd.Series) { 393 verifier.addErrorf("bundle declares an invalid series %q", bd.Series) 394 } 395 verifier.verifyMachines() 396 verifier.verifyApplications() 397 verifier.verifyRelations() 398 verifier.verifyOptions() 399 verifier.verifyEndpointBindings() 400 401 for id, count := range verifier.machineRefCounts { 402 if count == 0 { 403 verifier.addErrorf("machine %q is not referred to by a placement directive", id) 404 } 405 } 406 return verifier.err() 407 } 408 409 var ( 410 validMachineId = regexp.MustCompile("^" + names.NumberSnippet + "$") 411 validStorageName = regexp.MustCompile("^" + names.StorageNameSnippet + "$") 412 ) 413 414 func (verifier *bundleDataVerifier) verifyMachines() { 415 for id, m := range verifier.bd.Machines { 416 if !validMachineId.MatchString(id) { 417 verifier.addErrorf("invalid machine id %q found in machines", id) 418 } 419 if m == nil { 420 continue 421 } 422 if m.Constraints != "" { 423 if err := verifier.verifyConstraints(m.Constraints); err != nil { 424 verifier.addErrorf("invalid constraints %q in machine %q: %v", m.Constraints, id, err) 425 } 426 } 427 if m.Series != "" && !IsValidSeries(m.Series) { 428 verifier.addErrorf("invalid series %s for machine %q", m.Series, id) 429 } 430 } 431 } 432 433 func (verifier *bundleDataVerifier) verifyApplications() { 434 if len(verifier.bd.Applications) == 0 { 435 verifier.addErrorf("at least one application must be specified") 436 return 437 } 438 for name, svc := range verifier.bd.Applications { 439 if svc.Charm == "" { 440 verifier.addErrorf("empty charm path") 441 } 442 // Charm may be a local directory or a charm URL. 443 var curl *URL 444 var err error 445 if strings.HasPrefix(svc.Charm, ".") || filepath.IsAbs(svc.Charm) { 446 charmPath := svc.Charm 447 if !filepath.IsAbs(charmPath) { 448 charmPath = filepath.Join(verifier.bundleDir, charmPath) 449 } 450 if _, err := os.Stat(charmPath); err != nil { 451 if os.IsNotExist(err) { 452 verifier.addErrorf("charm path in application %q does not exist: %v", name, charmPath) 453 } else { 454 verifier.addErrorf("invalid charm path in application %q: %v", name, err) 455 } 456 } 457 } else if curl, err = ParseURL(svc.Charm); err != nil { 458 verifier.addErrorf("invalid charm URL in application %q: %v", name, err) 459 } 460 461 // Check the series. 462 if curl != nil && curl.Series != "" && svc.Series != "" && curl.Series != svc.Series { 463 verifier.addErrorf("the charm URL for application %q has a series which does not match, please remove the series from the URL", name) 464 } 465 if svc.Series != "" && !IsValidSeries(svc.Series) { 466 verifier.addErrorf("application %q declares an invalid series %q", name, svc.Series) 467 } 468 469 if err := verifier.verifyConstraints(svc.Constraints); err != nil { 470 verifier.addErrorf("invalid constraints %q in application %q: %v", svc.Constraints, name, err) 471 } 472 for storageName, storageConstraints := range svc.Storage { 473 if !validStorageName.MatchString(storageName) { 474 verifier.addErrorf("invalid storage name %q in application %q", storageName, name) 475 } 476 if err := verifier.verifyStorage(storageConstraints); err != nil { 477 verifier.addErrorf("invalid storage %q in application %q: %v", storageName, name, err) 478 } 479 } 480 if verifier.charms != nil { 481 if ch, ok := verifier.charms[svc.Charm]; ok { 482 if ch.Meta().Subordinate { 483 if len(svc.To) > 0 { 484 verifier.addErrorf("application %q is subordinate but specifies unit placement", name) 485 } 486 if svc.NumUnits > 0 { 487 verifier.addErrorf("application %q is subordinate but has non-zero num_units", name) 488 } 489 } 490 } else { 491 verifier.addErrorf("application %q refers to non-existent charm %q", name, svc.Charm) 492 } 493 } 494 for resName := range svc.Resources { 495 if resName == "" { 496 verifier.addErrorf("missing resource name on application %q", name) 497 } 498 // We do not check the revisions because all values 499 // are allowed. 500 } 501 if svc.NumUnits < 0 { 502 verifier.addErrorf("negative number of units specified on application %q", name) 503 } else if len(svc.To) > svc.NumUnits { 504 verifier.addErrorf("too many units specified in unit placement for application %q", name) 505 } 506 verifier.verifyPlacement(svc.To) 507 } 508 } 509 510 func (verifier *bundleDataVerifier) verifyPlacement(to []string) { 511 for _, p := range to { 512 up, err := ParsePlacement(p) 513 if err != nil { 514 verifier.addError(err) 515 continue 516 } 517 switch { 518 case up.Application != "": 519 spec, ok := verifier.bd.Applications[up.Application] 520 if !ok { 521 verifier.addErrorf("placement %q refers to an application not defined in this bundle", p) 522 continue 523 } 524 if up.Unit >= 0 && up.Unit >= spec.NumUnits { 525 verifier.addErrorf("placement %q specifies a unit greater than the %d unit(s) started by the target application", p, spec.NumUnits) 526 } 527 case up.Machine == "new": 528 default: 529 _, ok := verifier.bd.Machines[up.Machine] 530 if !ok { 531 verifier.addErrorf("placement %q refers to a machine not defined in this bundle", p) 532 continue 533 } 534 verifier.machineRefCounts[up.Machine]++ 535 } 536 } 537 } 538 539 func (verifier *bundleDataVerifier) getCharmMetaForApplication(appName string) (*Meta, error) { 540 svc, ok := verifier.bd.Applications[appName] 541 if !ok { 542 return nil, fmt.Errorf("application %q not found", appName) 543 } 544 ch, ok := verifier.charms[svc.Charm] 545 if !ok { 546 return nil, fmt.Errorf("charm %q from application %q not found", svc.Charm, appName) 547 } 548 return ch.Meta(), nil 549 } 550 551 func (verifier *bundleDataVerifier) verifyRelations() { 552 seen := make(map[[2]endpoint]bool) 553 for _, relPair := range verifier.bd.Relations { 554 if len(relPair) != 2 { 555 verifier.addErrorf("relation %q has %d endpoint(s), not 2", relPair, len(relPair)) 556 continue 557 } 558 var epPair [2]endpoint 559 relParseErr := false 560 for i, svcRel := range relPair { 561 ep, err := parseEndpoint(svcRel) 562 if err != nil { 563 verifier.addError(err) 564 relParseErr = true 565 continue 566 } 567 if _, ok := verifier.bd.Applications[ep.application]; !ok { 568 verifier.addErrorf("relation %q refers to application %q not defined in this bundle", relPair, ep.application) 569 } 570 epPair[i] = ep 571 } 572 if relParseErr { 573 // We failed to parse at least one relation, so don't 574 // bother checking further. 575 continue 576 } 577 if epPair[0].application == epPair[1].application { 578 verifier.addErrorf("relation %q relates an application to itself", relPair) 579 } 580 // Resolve endpoint relations if necessary and we have 581 // the necessary charm information. 582 if (epPair[0].relation == "" || epPair[1].relation == "") && verifier.charms != nil { 583 iep0, iep1, err := inferEndpoints(epPair[0], epPair[1], verifier.getCharmMetaForApplication) 584 if err != nil { 585 verifier.addErrorf("cannot infer endpoint between %s and %s: %v", epPair[0], epPair[1], err) 586 } else { 587 // Change the endpoints that get recorded 588 // as seen, so we'll diagnose a duplicate 589 // relation even if one relation specifies 590 // the relations explicitly and the other does 591 // not. 592 epPair[0], epPair[1] = iep0, iep1 593 } 594 } 595 596 // Re-order pairs so that we diagnose duplicate relations 597 // whichever way they're specified. 598 if epPair[1].less(epPair[0]) { 599 epPair[1], epPair[0] = epPair[0], epPair[1] 600 } 601 if _, ok := seen[epPair]; ok { 602 verifier.addErrorf("relation %q is defined more than once", relPair) 603 } 604 if verifier.charms != nil && epPair[0].relation != "" && epPair[1].relation != "" { 605 // We have charms to verify against, and the 606 // endpoint has been fully specified or inferred. 607 verifier.verifyRelation(epPair[0], epPair[1]) 608 } 609 seen[epPair] = true 610 } 611 } 612 613 func (verifier *bundleDataVerifier) verifyEndpointBindings() { 614 for name, svc := range verifier.bd.Applications { 615 charm, ok := verifier.charms[name] 616 // Only thest the ok path here because the !ok path is tested in verifyApplications 617 if !ok { 618 continue 619 } 620 for endpoint, space := range svc.EndpointBindings { 621 _, isInProvides := charm.Meta().Provides[endpoint] 622 _, isInRequires := charm.Meta().Requires[endpoint] 623 _, isInPeers := charm.Meta().Peers[endpoint] 624 _, isInExtraBindings := charm.Meta().ExtraBindings[endpoint] 625 626 if !(isInProvides || isInRequires || isInPeers || isInExtraBindings) { 627 verifier.addErrorf( 628 "application %q wants to bind endpoint %q to space %q, "+ 629 "but the endpoint is not defined by the charm", 630 name, endpoint, space) 631 } 632 } 633 634 } 635 } 636 637 var infoRelation = Relation{ 638 Name: "juju-info", 639 Role: RoleProvider, 640 Interface: "juju-info", 641 Scope: ScopeContainer, 642 } 643 644 // verifyRelation verifies a single relation. 645 // It checks that both endpoints of the relation are 646 // defined, and that the relationship is correctly 647 // symmetrical (provider to requirer) and shares 648 // the same interface. 649 func (verifier *bundleDataVerifier) verifyRelation(ep0, ep1 endpoint) { 650 svc0 := verifier.bd.Applications[ep0.application] 651 svc1 := verifier.bd.Applications[ep1.application] 652 if svc0 == nil || svc1 == nil || svc0 == svc1 { 653 // An error will be produced by verifyRelations for this case. 654 return 655 } 656 charm0 := verifier.charms[svc0.Charm] 657 charm1 := verifier.charms[svc1.Charm] 658 if charm0 == nil || charm1 == nil { 659 // An error will be produced by verifyApplications for this case. 660 return 661 } 662 relProv0, okProv0 := charm0.Meta().Provides[ep0.relation] 663 // The juju-info relation is provided implicitly by every 664 // charm - use it if required. 665 if !okProv0 && ep0.relation == infoRelation.Name { 666 relProv0, okProv0 = infoRelation, true 667 } 668 relReq0, okReq0 := charm0.Meta().Requires[ep0.relation] 669 if !okProv0 && !okReq0 { 670 verifier.addErrorf("charm %q used by application %q does not define relation %q", svc0.Charm, ep0.application, ep0.relation) 671 } 672 relProv1, okProv1 := charm1.Meta().Provides[ep1.relation] 673 // The juju-info relation is provided implicitly by every 674 // charm - use it if required. 675 if !okProv1 && ep1.relation == infoRelation.Name { 676 relProv1, okProv1 = infoRelation, true 677 } 678 relReq1, okReq1 := charm1.Meta().Requires[ep1.relation] 679 if !okProv1 && !okReq1 { 680 verifier.addErrorf("charm %q used by application %q does not define relation %q", svc1.Charm, ep1.application, ep1.relation) 681 } 682 683 var relProv, relReq Relation 684 var epProv, epReq endpoint 685 switch { 686 case okProv0 && okReq1: 687 relProv, relReq = relProv0, relReq1 688 epProv, epReq = ep0, ep1 689 case okReq0 && okProv1: 690 relProv, relReq = relProv1, relReq0 691 epProv, epReq = ep1, ep0 692 case okProv0 && okProv1: 693 verifier.addErrorf("relation %q to %q relates provider to provider", ep0, ep1) 694 return 695 case okReq0 && okReq1: 696 verifier.addErrorf("relation %q to %q relates requirer to requirer", ep0, ep1) 697 return 698 default: 699 // Errors were added above. 700 return 701 } 702 if relProv.Interface != relReq.Interface { 703 verifier.addErrorf("mismatched interface between %q and %q (%q vs %q)", epProv, epReq, relProv.Interface, relReq.Interface) 704 } 705 } 706 707 // verifyOptions verifies that the options are correctly defined 708 // with respect to the charm config options. 709 func (verifier *bundleDataVerifier) verifyOptions() { 710 if verifier.charms == nil { 711 return 712 } 713 for appName, svc := range verifier.bd.Applications { 714 charm := verifier.charms[svc.Charm] 715 if charm == nil { 716 // An error will be produced by verifyApplications for this case. 717 continue 718 } 719 config := charm.Config() 720 for name, value := range svc.Options { 721 opt, ok := config.Options[name] 722 if !ok { 723 verifier.addErrorf("cannot validate application %q: configuration option %q not found in charm %q", appName, name, svc.Charm) 724 continue 725 } 726 _, err := opt.validate(name, value) 727 if err != nil { 728 verifier.addErrorf("cannot validate application %q: %v", appName, err) 729 } 730 } 731 } 732 } 733 734 var validApplicationRelation = regexp.MustCompile("^(" + names.ApplicationSnippet + "):(" + names.RelationSnippet + ")$") 735 736 type endpoint struct { 737 application string 738 relation string 739 } 740 741 func (ep endpoint) String() string { 742 if ep.relation == "" { 743 return ep.application 744 } 745 return fmt.Sprintf("%s:%s", ep.application, ep.relation) 746 } 747 748 func (ep1 endpoint) less(ep2 endpoint) bool { 749 if ep1.application == ep2.application { 750 return ep1.relation < ep2.relation 751 } 752 return ep1.application < ep2.application 753 } 754 755 func parseEndpoint(ep string) (endpoint, error) { 756 m := validApplicationRelation.FindStringSubmatch(ep) 757 if m != nil { 758 return endpoint{ 759 application: m[1], 760 relation: m[2], 761 }, nil 762 } 763 if !names.IsValidApplication(ep) { 764 return endpoint{}, fmt.Errorf("invalid relation syntax %q", ep) 765 } 766 return endpoint{ 767 application: ep, 768 }, nil 769 } 770 771 // endpointInfo holds information about one endpoint of a relation. 772 type endpointInfo struct { 773 applicationName string 774 Relation 775 } 776 777 // String returns the unique identifier of the relation endpoint. 778 func (ep endpointInfo) String() string { 779 return ep.applicationName + ":" + ep.Name 780 } 781 782 // canRelateTo returns whether a relation may be established between ep 783 // and other. 784 func (ep endpointInfo) canRelateTo(other endpointInfo) bool { 785 return ep.applicationName != other.applicationName && 786 ep.Interface == other.Interface && 787 ep.Role != RolePeer && 788 counterpartRole(ep.Role) == other.Role 789 } 790 791 // endpoint returns the endpoint specifier for ep. 792 func (ep endpointInfo) endpoint() endpoint { 793 return endpoint{ 794 application: ep.applicationName, 795 relation: ep.Name, 796 } 797 } 798 799 // counterpartRole returns the RelationRole that the given RelationRole 800 // can relate to. 801 func counterpartRole(r RelationRole) RelationRole { 802 switch r { 803 case RoleProvider: 804 return RoleRequirer 805 case RoleRequirer: 806 return RoleProvider 807 case RolePeer: 808 return RolePeer 809 } 810 panic(fmt.Errorf("unknown relation role %q", r)) 811 } 812 813 type UnitPlacement struct { 814 // ContainerType holds the container type of the new 815 // new unit, or empty if unspecified. 816 ContainerType string 817 818 // Machine holds the numeric machine id, or "new", 819 // or empty if the placement specifies an application. 820 Machine string 821 822 // application holds the application name, or empty if 823 // the placement specifies a machine. 824 Application string 825 826 // Unit holds the unit number of the application, or -1 827 // if unspecified. 828 Unit int 829 } 830 831 var snippetReplacer = strings.NewReplacer( 832 "container", names.ContainerTypeSnippet, 833 "number", names.NumberSnippet, 834 "application", names.ApplicationSnippet, 835 ) 836 837 // validPlacement holds regexp that matches valid placement requests. To 838 // make the expression easier to comprehend and maintain, we replace 839 // symbolic snippet references in the regexp by their actual regexps 840 // using snippetReplacer. 841 var validPlacement = regexp.MustCompile( 842 snippetReplacer.Replace( 843 "^(?:(container):)?(?:(application)(?:/(number))?|(number))$", 844 ), 845 ) 846 847 // ParsePlacement parses a unit placement directive, as 848 // specified in the To clause of an application entry in the 849 // applications section of a bundle. 850 func ParsePlacement(p string) (*UnitPlacement, error) { 851 m := validPlacement.FindStringSubmatch(p) 852 if m == nil { 853 return nil, fmt.Errorf("invalid placement syntax %q", p) 854 } 855 up := UnitPlacement{ 856 ContainerType: m[1], 857 Application: m[2], 858 Machine: m[4], 859 } 860 if unitStr := m[3]; unitStr != "" { 861 // We know that unitStr must be a valid integer because 862 // it's specified as such in the regexp. 863 up.Unit, _ = strconv.Atoi(unitStr) 864 } else { 865 up.Unit = -1 866 } 867 if up.Application == "new" { 868 if up.Unit != -1 { 869 return nil, fmt.Errorf("invalid placement syntax %q", p) 870 } 871 up.Machine, up.Application = "new", "" 872 } 873 return &up, nil 874 } 875 876 // inferEndpoints infers missing relation names from the given endpoint 877 // specifications, using the given get function to retrieve charm 878 // data if necessary. It returns the fully specified endpoints. 879 func inferEndpoints(epSpec0, epSpec1 endpoint, get func(svc string) (*Meta, error)) (endpoint, endpoint, error) { 880 if epSpec0.relation != "" && epSpec1.relation != "" { 881 // The endpoints are already specified explicitly so 882 // there is no need to fetch any charm data to infer 883 // them. 884 return epSpec0, epSpec1, nil 885 } 886 eps0, err := possibleEndpoints(epSpec0, get) 887 if err != nil { 888 return endpoint{}, endpoint{}, err 889 } 890 eps1, err := possibleEndpoints(epSpec1, get) 891 if err != nil { 892 return endpoint{}, endpoint{}, err 893 } 894 var candidates [][]endpointInfo 895 for _, ep0 := range eps0 { 896 for _, ep1 := range eps1 { 897 if ep0.canRelateTo(ep1) { 898 candidates = append(candidates, []endpointInfo{ep0, ep1}) 899 } 900 } 901 } 902 switch len(candidates) { 903 case 0: 904 return endpoint{}, endpoint{}, fmt.Errorf("no relations found") 905 case 1: 906 return candidates[0][0].endpoint(), candidates[0][1].endpoint(), nil 907 } 908 909 // There's ambiguity; try discarding implicit relations. 910 filtered := discardImplicitRelations(candidates) 911 if len(filtered) == 1 { 912 return filtered[0][0].endpoint(), filtered[0][1].endpoint(), nil 913 } 914 // The ambiguity cannot be resolved, so return an error. 915 var keys []string 916 for _, cand := range candidates { 917 keys = append(keys, fmt.Sprintf("%q", relationKey(cand))) 918 } 919 sort.Strings(keys) 920 return endpoint{}, endpoint{}, fmt.Errorf("ambiguous relation: %s %s could refer to %s", 921 epSpec0, epSpec1, strings.Join(keys, "; ")) 922 } 923 924 func discardImplicitRelations(candidates [][]endpointInfo) [][]endpointInfo { 925 var filtered [][]endpointInfo 926 outer: 927 for _, cand := range candidates { 928 for _, ep := range cand { 929 if ep.IsImplicit() { 930 continue outer 931 } 932 } 933 filtered = append(filtered, cand) 934 } 935 return filtered 936 } 937 938 // relationKey returns a string describing the relation defined by 939 // endpoints, for use in various contexts (including error messages). 940 func relationKey(endpoints []endpointInfo) string { 941 var names []string 942 for _, ep := range endpoints { 943 names = append(names, ep.String()) 944 } 945 sort.Strings(names) 946 return strings.Join(names, " ") 947 } 948 949 // possibleEndpoints returns all the endpoints that the given endpoint spec 950 // could refer to. 951 func possibleEndpoints(epSpec endpoint, get func(svc string) (*Meta, error)) ([]endpointInfo, error) { 952 meta, err := get(epSpec.application) 953 if err != nil { 954 return nil, err 955 } 956 957 var eps []endpointInfo 958 add := func(r Relation) { 959 if epSpec.relation == "" || epSpec.relation == r.Name { 960 eps = append(eps, endpointInfo{ 961 applicationName: epSpec.application, 962 Relation: r, 963 }) 964 } 965 } 966 967 for _, r := range meta.Provides { 968 add(r) 969 } 970 for _, r := range meta.Requires { 971 add(r) 972 } 973 // Every application implicitly provides a juju-info relation. 974 add(Relation{ 975 Name: "juju-info", 976 Role: RoleProvider, 977 Interface: "juju-info", 978 Scope: ScopeGlobal, 979 }) 980 return eps, nil 981 }