github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/application/bundle.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package application 5 6 import ( 7 "encoding/base64" 8 "fmt" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "reflect" 13 "strings" 14 "time" 15 16 "github.com/juju/bundlechanges" 17 "github.com/juju/cmd" 18 "github.com/juju/collections/set" 19 "github.com/juju/errors" 20 "github.com/juju/utils" 21 "github.com/kr/pretty" 22 "gopkg.in/juju/charm.v6" 23 "gopkg.in/juju/charmrepo.v3" 24 csparams "gopkg.in/juju/charmrepo.v3/csclient/params" 25 "gopkg.in/juju/names.v2" 26 "gopkg.in/macaroon.v2-unstable" 27 "gopkg.in/yaml.v2" 28 29 "github.com/juju/juju/api" 30 "github.com/juju/juju/api/application" 31 "github.com/juju/juju/apiserver/params" 32 "github.com/juju/juju/charmstore" 33 "github.com/juju/juju/core/constraints" 34 "github.com/juju/juju/core/devices" 35 "github.com/juju/juju/core/instance" 36 "github.com/juju/juju/core/lxdprofile" 37 "github.com/juju/juju/core/model" 38 "github.com/juju/juju/environs/config" 39 "github.com/juju/juju/resource/resourceadapters" 40 "github.com/juju/juju/state/multiwatcher" 41 "github.com/juju/juju/state/watcher" 42 "github.com/juju/juju/storage" 43 ) 44 45 var watchAll = func(c *api.Client) (allWatcher, error) { 46 return c.WatchAll() 47 } 48 49 type allWatcher interface { 50 Next() ([]multiwatcher.Delta, error) 51 Stop() error 52 } 53 54 // deploymentLogger is used to notify clients about the bundle deployment 55 // progress. 56 type deploymentLogger interface { 57 // Infof formats and logs the given message. 58 Infof(string, ...interface{}) 59 } 60 61 // composeBundle adds the overlays and bundle includes into the passed bundle 62 // data struct. 63 func composeBundle(data *charm.BundleData, ctx *cmd.Context, bundleDir string, overlayFileNames []string) error { 64 if err := processBundleOverlay(data, overlayFileNames...); err != nil { 65 return errors.Annotate(err, "unable to process overlays") 66 } 67 if bundleDir == "" { 68 bundleDir = ctx.Dir 69 } 70 if err := processBundleIncludes(bundleDir, data); err != nil { 71 return errors.Annotate(err, "unable to process includes") 72 } 73 return nil 74 } 75 76 func verifyBundle(data *charm.BundleData, bundleDir string) error { 77 verifyConstraints := func(s string) error { 78 _, err := constraints.Parse(s) 79 return err 80 } 81 verifyStorage := func(s string) error { 82 _, err := storage.ParseConstraints(s) 83 return err 84 } 85 verifyDevices := func(s string) error { 86 _, err := devices.ParseConstraints(s) 87 return err 88 } 89 var verifyError error 90 if bundleDir == "" { 91 verifyError = data.Verify(verifyConstraints, verifyStorage, verifyDevices) 92 } else { 93 verifyError = data.VerifyLocal(bundleDir, verifyConstraints, verifyStorage, verifyDevices) 94 } 95 96 if verr, ok := errors.Cause(verifyError).(*charm.VerificationError); ok { 97 errs := make([]string, len(verr.Errors)) 98 for i, err := range verr.Errors { 99 errs[i] = err.Error() 100 } 101 return errors.New("the provided bundle has the following errors:\n" + strings.Join(errs, "\n")) 102 } 103 return errors.Trace(verifyError) 104 } 105 106 // deployBundle deploys the given bundle data using the given API client and 107 // charm store client. The deployment is not transactional, and its progress is 108 // notified using the given deployment logger. 109 func deployBundle( 110 bundleDir string, 111 data *charm.BundleData, 112 bundleURL *charm.URL, 113 bundleOverlayFile []string, 114 channel csparams.Channel, 115 apiRoot DeployAPI, 116 ctx *cmd.Context, 117 bundleStorage map[string]map[string]storage.Constraints, 118 bundleDevices map[string]map[string]devices.Constraints, 119 dryRun bool, 120 useExistingMachines bool, 121 bundleMachines map[string]string, 122 ) (map[*charm.URL]*macaroon.Macaroon, error) { 123 124 if err := composeBundle(data, ctx, bundleDir, bundleOverlayFile); err != nil { 125 return nil, errors.Trace(err) 126 } 127 if err := verifyBundle(data, bundleDir); err != nil { 128 return nil, errors.Trace(err) 129 } 130 131 // TODO: move bundle parsing and checking into the handler. 132 h := makeBundleHandler(dryRun, bundleDir, channel, apiRoot, ctx, data, bundleURL, bundleStorage, bundleDevices) 133 if err := h.makeModel(useExistingMachines, bundleMachines); err != nil { 134 return nil, errors.Trace(err) 135 } 136 if err := h.resolveCharmsAndEndpoints(); err != nil { 137 return nil, errors.Trace(err) 138 } 139 if err := h.getChanges(); err != nil { 140 return nil, errors.Trace(err) 141 } 142 if err := h.handleChanges(); err != nil { 143 return nil, errors.Trace(err) 144 } 145 return h.macaroons, nil 146 147 } 148 149 // bundleHandler provides helpers and the state required to deploy a bundle. 150 type bundleHandler struct { 151 dryRun bool 152 153 // bundleDir is the path where the bundle file is located for local bundles. 154 bundleDir string 155 // changes holds the changes to be applied in order to deploy the bundle. 156 changes []bundlechanges.Change 157 158 // applications are all the applications defined in the bundle. 159 // Used primarily for iterating over sorted values. 160 applications set.Strings 161 162 // results collects data resulting from applying changes. Keys identify 163 // changes, values result from interacting with the environment, and are 164 // stored so that they can be potentially reused later, for instance for 165 // resolving a dynamic placeholder included in a change. Specifically, the 166 // following values are stored: 167 // - when adding a charm, the fully resolved charm is stored; 168 // - when deploying an application, the application name is stored; 169 // - when adding a machine, the resulting machine id is stored; 170 // - when adding a unit, either the id of the machine holding the unit or 171 // the unit name can be stored. The latter happens when a machine is 172 // implicitly created by adding a unit without a machine spec. 173 results map[string]string 174 175 // channel identifies the default channel to use for the bundle. 176 channel csparams.Channel 177 178 // api is used to interact with the environment. 179 api DeployAPI 180 181 // bundleStorage contains a mapping of application-specific storage 182 // constraints. For each application, the storage constraints in the 183 // map will replace or augment the storage constraints specified 184 // in the bundle itself. 185 bundleStorage map[string]map[string]storage.Constraints 186 187 // bundleDevices contains a mapping of application-specific device 188 // constraints. For each application, the device constraints in the 189 // map will replace or augment the device constraints specified 190 // in the bundle itself. 191 bundleDevices map[string]map[string]devices.Constraints 192 193 // ctx is the command context, which is used to output messages to the 194 // user, so that the user can keep track of the bundle deployment 195 // progress. 196 ctx *cmd.Context 197 198 // data is the original bundle data that we want to deploy. 199 data *charm.BundleData 200 201 // bundleURL is the URL of the bundle when deploying a bundle from the 202 // charmstore, nil otherwise. 203 bundleURL *charm.URL 204 205 // unitStatus reflects the environment status and maps unit names to their 206 // corresponding machine identifiers. This is kept updated by both change 207 // handlers (addCharm, addApplication etc.) and by updateUnitStatus. 208 unitStatus map[string]string 209 210 modelConfig *config.Config 211 212 model *bundlechanges.Model 213 214 macaroons map[*charm.URL]*macaroon.Macaroon 215 channels map[*charm.URL]csparams.Channel 216 217 // watcher holds an environment mega-watcher used to keep the environment 218 // status up to date. 219 watcher allWatcher 220 221 // warnedLXC indicates whether or not we have warned the user that the 222 // bundle they're deploying uses lxc containers, which will be treated as 223 // LXD. This flag keeps us from writing the warning more than once per 224 // bundle. 225 warnedLXC bool 226 } 227 228 func makeBundleHandler( 229 dryRun bool, 230 bundleDir string, 231 channel csparams.Channel, 232 api DeployAPI, 233 ctx *cmd.Context, 234 data *charm.BundleData, 235 bundleURL *charm.URL, 236 bundleStorage map[string]map[string]storage.Constraints, 237 bundleDevices map[string]map[string]devices.Constraints, 238 ) *bundleHandler { 239 applications := set.NewStrings() 240 for name := range data.Applications { 241 applications.Add(name) 242 } 243 return &bundleHandler{ 244 dryRun: dryRun, 245 bundleDir: bundleDir, 246 applications: applications, 247 results: make(map[string]string), 248 channel: channel, 249 api: api, 250 bundleStorage: bundleStorage, 251 bundleDevices: bundleDevices, 252 ctx: ctx, 253 data: data, 254 bundleURL: bundleURL, 255 unitStatus: make(map[string]string), 256 macaroons: make(map[*charm.URL]*macaroon.Macaroon), 257 channels: make(map[*charm.URL]csparams.Channel), 258 } 259 } 260 261 func (h *bundleHandler) makeModel( 262 useExistingMachines bool, 263 bundleMachines map[string]string, 264 ) error { 265 // Initialize the unit status. 266 status, err := h.api.Status(nil) 267 if err != nil { 268 return errors.Annotate(err, "cannot get model status") 269 } 270 h.model, err = buildModelRepresentation(status, h.api, useExistingMachines, bundleMachines) 271 if err != nil { 272 return errors.Trace(err) 273 } 274 logger.Debugf("model: %s", pretty.Sprint(h.model)) 275 276 for _, appData := range status.Applications { 277 for unit, unitData := range appData.Units { 278 h.unitStatus[unit] = unitData.Machine 279 } 280 } 281 282 h.modelConfig, err = getModelConfig(h.api) 283 if err != nil { 284 return err 285 } 286 return nil 287 } 288 289 // resolveCharmsAndEndpoints will go through the bundle and 290 // resolve the charm URLs. From the model the charm names are 291 // fully qualified, meaning they have a source and revision id. 292 // Effectively the logic this method follows is: 293 // * if the bundle specifies a local charm, and the application 294 // exists already, then override the charm URL in the bundle 295 // spec to match the charm name from the model. We don't 296 // upgrade local charms as part of a bundle deploy. 297 // * the charm URL is resolved and the bundle spec is replaced 298 // with the fully resolved charm URL - i.e.: with rev id. 299 // * check all endpoints, and if any of them have implicit endpoints, 300 // and if they do, resolve the implicitness in order to compare 301 // with relations in the model. 302 func (h *bundleHandler) resolveCharmsAndEndpoints() error { 303 304 deployedApps := set.NewStrings() 305 306 for _, name := range h.applications.SortedValues() { 307 spec := h.data.Applications[name] 308 app := h.model.GetApplication(name) 309 if app != nil { 310 deployedApps.Add(name) 311 312 if h.isLocalCharm(spec.Charm) { 313 logger.Debugf("%s exists in model uses a local charm, replacing with %q", name, app.Charm) 314 // Replace with charm from model 315 spec.Charm = app.Charm 316 continue 317 } 318 // If the charm matches, don't bother resolving. 319 if spec.Charm == app.Charm { 320 continue 321 } 322 } 323 324 if h.isLocalCharm(spec.Charm) { 325 continue 326 } 327 328 h.ctx.Infof("Resolving charm: %s", spec.Charm) 329 ch, err := charm.ParseURL(spec.Charm) 330 if err != nil { 331 return errors.Trace(err) 332 } 333 url, _, _, err := resolveCharm(h.api.ResolveWithChannel, ch) 334 if err != nil { 335 return errors.Annotatef(err, "cannot resolve URL %q", spec.Charm) 336 } 337 if url.Series == "bundle" { 338 return errors.Errorf("expected charm URL, got bundle URL %q", spec.Charm) 339 } 340 341 spec.Charm = url.String() 342 } 343 344 // TODO(thumper): the InferEndpoints code is deeply wedged in the 345 // persistence layer and needs to be extracted. This is a multi-day 346 // effort, so for now the bundle handling is doing no implicit endpoint 347 // handling. 348 return nil 349 } 350 351 func (h *bundleHandler) getChanges() error { 352 bundleURL := "" 353 if h.bundleURL != nil { 354 bundleURL = h.bundleURL.String() 355 } 356 changes, err := bundlechanges.FromData( 357 bundlechanges.ChangesConfig{ 358 Bundle: h.data, 359 BundleURL: bundleURL, 360 Model: h.model, 361 Logger: logger, 362 }) 363 if err != nil { 364 return errors.Trace(err) 365 } 366 367 h.changes = changes 368 return nil 369 } 370 371 func (h *bundleHandler) handleChanges() error { 372 var err error 373 // Instantiate a watcher used to follow the deployment progress. 374 h.watcher, err = h.api.WatchAll() 375 if err != nil { 376 return errors.Annotate(err, "cannot watch model") 377 } 378 defer h.watcher.Stop() 379 380 if len(h.changes) == 0 { 381 h.ctx.Infof("No changes to apply.") 382 return nil 383 } 384 385 if h.dryRun { 386 fmt.Fprintf(h.ctx.Stdout, "Changes to deploy bundle:\n") 387 } else { 388 fmt.Fprintf(h.ctx.Stdout, "Executing changes:\n") 389 } 390 391 // Deploy the bundle. 392 for i, change := range h.changes { 393 fmt.Fprintf(h.ctx.Stdout, "- %s\n", change.Description()) 394 logger.Tracef("%d: change %s", i, pretty.Sprint(change)) 395 switch change := change.(type) { 396 case *bundlechanges.AddCharmChange: 397 err = h.addCharm(change) 398 case *bundlechanges.AddMachineChange: 399 err = h.addMachine(change) 400 case *bundlechanges.AddRelationChange: 401 err = h.addRelation(change) 402 case *bundlechanges.AddApplicationChange: 403 err = h.addApplication(change) 404 case *bundlechanges.ScaleChange: 405 err = h.scaleApplication(change) 406 case *bundlechanges.AddUnitChange: 407 err = h.addUnit(change) 408 case *bundlechanges.ExposeChange: 409 err = h.exposeApplication(change) 410 case *bundlechanges.SetAnnotationsChange: 411 err = h.setAnnotations(change) 412 case *bundlechanges.UpgradeCharmChange: 413 err = h.upgradeCharm(change) 414 case *bundlechanges.SetOptionsChange: 415 err = h.setOptions(change) 416 case *bundlechanges.SetConstraintsChange: 417 err = h.setConstraints(change) 418 default: 419 return errors.Errorf("unknown change type: %T", change) 420 } 421 if err != nil { 422 return errors.Trace(err) 423 } 424 } 425 426 if !h.dryRun { 427 h.ctx.Infof("Deploy of bundle completed.") 428 } 429 430 return nil 431 } 432 433 func (h *bundleHandler) isLocalCharm(name string) bool { 434 return strings.HasPrefix(name, ".") || filepath.IsAbs(name) 435 } 436 437 // addCharm adds a charm to the environment. 438 func (h *bundleHandler) addCharm(change *bundlechanges.AddCharmChange) error { 439 if h.dryRun { 440 return nil 441 } 442 id := change.Id() 443 p := change.Params 444 // First attempt to interpret as a local path. 445 if h.isLocalCharm(p.Charm) { 446 charmPath := p.Charm 447 if !filepath.IsAbs(charmPath) { 448 charmPath = filepath.Join(h.bundleDir, charmPath) 449 } 450 451 series := p.Series 452 if series == "" { 453 series = h.data.Series 454 } 455 ch, curl, err := charmrepo.NewCharmAtPath(charmPath, series) 456 if err != nil && !os.IsNotExist(err) { 457 return errors.Annotatef(err, "cannot deploy local charm at %q", charmPath) 458 } 459 if err == nil { 460 if err := lxdprofile.ValidateCharmLXDProfile(ch); err != nil { 461 return errors.Annotatef(err, "cannot deploy local charm at %q", charmPath) 462 } 463 if curl, err = h.api.AddLocalCharm(curl, ch, false); err != nil { 464 return err 465 } 466 logger.Debugf("added charm %s", curl) 467 h.results[id] = curl.String() 468 return nil 469 } 470 } 471 472 // Not a local charm, so grab from the store. 473 ch, err := charm.ParseURL(p.Charm) 474 if err != nil { 475 return errors.Trace(err) 476 } 477 478 url, channel, _, err := resolveCharm(h.api.ResolveWithChannel, ch) 479 if err != nil { 480 return errors.Annotatef(err, "cannot resolve URL %q", p.Charm) 481 } 482 if url.Series == "bundle" { 483 return errors.Errorf("expected charm URL, got bundle URL %q", p.Charm) 484 } 485 var macaroon *macaroon.Macaroon 486 url, macaroon, err = addCharmFromURL(h.api, url, channel, false) 487 if err != nil { 488 return errors.Annotatef(err, "cannot add charm %q", p.Charm) 489 } 490 logger.Debugf("added charm %s", url) 491 h.results[id] = url.String() 492 h.macaroons[url] = macaroon 493 h.channels[url] = channel 494 return nil 495 } 496 497 func (h *bundleHandler) makeResourceMap(storeResources map[string]int, localResources map[string]string) map[string]string { 498 resources := make(map[string]string) 499 for resName, path := range localResources { 500 if !filepath.IsAbs(path) { 501 path = filepath.Clean(filepath.Join(h.bundleDir, path)) 502 } 503 resources[resName] = path 504 } 505 for resName, revision := range storeResources { 506 resources[resName] = fmt.Sprint(revision) 507 } 508 return resources 509 } 510 511 // addApplication deploys an application with no units. 512 func (h *bundleHandler) addApplication(change *bundlechanges.AddApplicationChange) error { 513 // TODO: add verbose output for details 514 if h.dryRun { 515 return nil 516 } 517 518 p := change.Params 519 cURL, err := charm.ParseURL(resolve(p.Charm, h.results)) 520 if err != nil { 521 return errors.Trace(err) 522 } 523 524 chID := charmstore.CharmID{ 525 URL: cURL, 526 Channel: h.channels[cURL], 527 } 528 macaroon := h.macaroons[cURL] 529 530 h.results[change.Id()] = p.Application 531 ch := chID.URL.String() 532 533 // Handle application configuration. 534 configYAML := "" 535 if len(p.Options) > 0 { 536 config, err := yaml.Marshal(map[string]map[string]interface{}{p.Application: p.Options}) 537 if err != nil { 538 return errors.Annotatef(err, "cannot marshal options for application %q", p.Application) 539 } 540 configYAML = string(config) 541 } 542 // Handle application constraints. 543 cons, err := constraints.Parse(p.Constraints) 544 if err != nil { 545 // This should never happen, as the bundle is already verified. 546 return errors.Annotate(err, "invalid constraints for application") 547 } 548 storageConstraints := h.bundleStorage[p.Application] 549 if len(p.Storage) > 0 { 550 if storageConstraints == nil { 551 storageConstraints = make(map[string]storage.Constraints) 552 } 553 for k, v := range p.Storage { 554 if _, ok := storageConstraints[k]; ok { 555 // Storage constraints overridden 556 // on the command line. 557 continue 558 } 559 cons, err := storage.ParseConstraints(v) 560 if err != nil { 561 return errors.Annotate(err, "invalid storage constraints") 562 } 563 storageConstraints[k] = cons 564 } 565 } 566 deviceConstraints := h.bundleDevices[p.Application] 567 if len(p.Devices) > 0 { 568 if deviceConstraints == nil { 569 deviceConstraints = make(map[string]devices.Constraints) 570 } 571 for k, v := range p.Devices { 572 if _, ok := deviceConstraints[k]; ok { 573 // Device constraints overridden 574 // on the command line. 575 continue 576 } 577 cons, err := devices.ParseConstraints(v) 578 if err != nil { 579 return errors.Annotate(err, "invalid device constraints") 580 } 581 deviceConstraints[k] = cons 582 } 583 } 584 resources := h.makeResourceMap(p.Resources, p.LocalResources) 585 charmInfo, err := h.api.CharmInfo(ch) 586 if err != nil { 587 return errors.Trace(err) 588 } 589 590 if err := lxdprofile.ValidateCharmInfoLXDProfile(charmInfo); err != nil { 591 return errors.Trace(err) 592 } 593 594 resNames2IDs, err := resourceadapters.DeployResources( 595 p.Application, 596 chID, 597 macaroon, 598 resources, 599 charmInfo.Meta.Resources, 600 h.api, 601 ) 602 if err != nil { 603 return errors.Trace(err) 604 } 605 606 // Figure out what series we need to deploy with. 607 supportedSeries := charmInfo.Meta.Series 608 if len(supportedSeries) == 0 && chID.URL.Series != "" { 609 supportedSeries = []string{chID.URL.Series} 610 } 611 selector := seriesSelector{ 612 seriesFlag: p.Series, 613 charmURLSeries: chID.URL.Series, 614 supportedSeries: supportedSeries, 615 conf: h.modelConfig, 616 fromBundle: true, 617 } 618 series, err := selector.charmSeries() 619 if err != nil { 620 return errors.Trace(err) 621 } 622 623 // Only Kubernetes bundles send the unit count and placement with the deploy API call. 624 numUnits := 0 625 var placement []*instance.Placement 626 if h.data.Type == "kubernetes" { 627 numUnits = p.NumUnits 628 if p.Placement != "" { 629 p := &instance.Placement{ 630 Scope: h.modelConfig.UUID(), 631 Directive: p.Placement, 632 } 633 placement = []*instance.Placement{p} 634 } 635 } 636 // Deploy the application. 637 if err := h.api.Deploy(application.DeployArgs{ 638 CharmID: chID, 639 Cons: cons, 640 ApplicationName: p.Application, 641 Series: series, 642 NumUnits: numUnits, 643 Placement: placement, 644 ConfigYAML: configYAML, 645 Storage: storageConstraints, 646 Devices: deviceConstraints, 647 Resources: resNames2IDs, 648 EndpointBindings: p.EndpointBindings, 649 }); err != nil { 650 return errors.Annotatef(err, "cannot deploy application %q", p.Application) 651 } 652 h.writeAddedResources(resNames2IDs) 653 654 return nil 655 } 656 657 func (h *bundleHandler) writeAddedResources(resNames2IDs map[string]string) { 658 // Make sure the resources are output in a defined order. 659 names := set.NewStrings() 660 for resName := range resNames2IDs { 661 names.Add(resName) 662 } 663 for _, name := range names.SortedValues() { 664 h.ctx.Infof(" added resource %s", name) 665 } 666 } 667 668 // scaleApplication updates the number of units for an application. 669 func (h *bundleHandler) scaleApplication(change *bundlechanges.ScaleChange) error { 670 if h.dryRun { 671 return nil 672 } 673 674 p := change.Params 675 676 result, err := h.api.ScaleApplication(application.ScaleApplicationParams{ 677 ApplicationName: p.Application, 678 Scale: p.Scale, 679 }) 680 if err == nil && result.Error != nil { 681 err = result.Error 682 } 683 if err != nil { 684 return errors.Annotatef(err, "cannot scale application %q", p.Application) 685 } 686 return nil 687 } 688 689 // addMachine creates a new top-level machine or container in the environment. 690 func (h *bundleHandler) addMachine(change *bundlechanges.AddMachineChange) error { 691 p := change.Params 692 var verbose []string 693 if p.Series != "" { 694 verbose = append(verbose, fmt.Sprintf("with series %q", p.Series)) 695 } 696 if p.Constraints != "" { 697 verbose = append(verbose, fmt.Sprintf("with constraints %q", p.Constraints)) 698 } 699 if output := strings.Join(verbose, ", "); output != "" { 700 h.ctx.Verbosef(" %s", output) 701 } 702 if h.dryRun { 703 return nil 704 } 705 706 deployedApps := func() string { 707 apps := h.applicationsForMachineChange(change.Id()) 708 // Note that we *should* always have at least one application 709 // that justifies the creation of this machine. But just in 710 // case, check (see https://pad.lv/1773357). 711 if len(apps) == 0 { 712 h.ctx.Warningf("no applications found for machine change %q", change.Id()) 713 return "nothing" 714 } 715 msg := apps[0] + " unit" 716 717 if count := len(apps); count != 1 { 718 msg = strings.Join(apps[:count-1], ", ") + " and " + apps[count-1] + " units" 719 } 720 return msg 721 } 722 723 cons, err := constraints.Parse(p.Constraints) 724 if err != nil { 725 // This should never happen, as the bundle is already verified. 726 return errors.Annotate(err, "invalid constraints for machine") 727 } 728 machineParams := params.AddMachineParams{ 729 Constraints: cons, 730 Series: p.Series, 731 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 732 } 733 if ct := p.ContainerType; ct != "" { 734 // TODO(thumper): move the warning and translation into the bundle reading code. 735 736 // for backwards compatibility with 1.x bundles, we treat lxc 737 // placement directives as lxd. 738 if ct == "lxc" { 739 if !h.warnedLXC { 740 h.ctx.Infof("Bundle has one or more containers specified as lxc. lxc containers are deprecated in Juju 2.0. lxd containers will be deployed instead.") 741 h.warnedLXC = true 742 } 743 ct = string(instance.LXD) 744 } 745 containerType, err := instance.ParseContainerType(ct) 746 if err != nil { 747 return errors.Annotatef(err, "cannot create machine for holding %s", deployedApps()) 748 } 749 machineParams.ContainerType = containerType 750 if p.ParentId != "" { 751 logger.Debugf("p.ParentId: %q", p.ParentId) 752 id, err := h.resolveMachine(p.ParentId) 753 if err != nil { 754 return errors.Annotatef(err, "cannot retrieve parent placement for %s", deployedApps()) 755 } 756 // Never create nested containers for deployment. 757 machineParams.ParentId = h.topLevelMachine(id) 758 } 759 } 760 logger.Debugf("machineParams: %s", pretty.Sprint(machineParams)) 761 r, err := h.api.AddMachines([]params.AddMachineParams{machineParams}) 762 if err != nil { 763 return errors.Annotatef(err, "cannot create machine for holding %s", deployedApps()) 764 } 765 if r[0].Error != nil { 766 return errors.Annotatef(r[0].Error, "cannot create machine for holding %s", deployedApps()) 767 } 768 machine := r[0].Machine 769 if p.ContainerType == "" { 770 logger.Debugf("created new machine %s for holding %s", machine, deployedApps()) 771 } else if p.ParentId == "" { 772 logger.Debugf("created %s container in new machine for holding %s", machine, deployedApps()) 773 } else { 774 logger.Debugf("created %s container in machine %s for holding %s", machine, machineParams.ParentId, deployedApps()) 775 } 776 h.results[change.Id()] = machine 777 return nil 778 } 779 780 // addRelation creates a relationship between two applications. 781 func (h *bundleHandler) addRelation(change *bundlechanges.AddRelationChange) error { 782 if h.dryRun { 783 return nil 784 } 785 p := change.Params 786 ep1 := resolveRelation(p.Endpoint1, h.results) 787 ep2 := resolveRelation(p.Endpoint2, h.results) 788 // TODO(wallyworld) - CMR support in bundles 789 _, err := h.api.AddRelation([]string{ep1, ep2}, nil) 790 if err != nil { 791 // TODO(thumper): remove this error check when we add resolving 792 // implicit relations. 793 if params.IsCodeAlreadyExists(err) { 794 return nil 795 } 796 return errors.Annotatef(err, "cannot add relation between %q and %q", ep1, ep2) 797 798 } 799 return nil 800 } 801 802 // addUnit adds a single unit to an application already present in the environment. 803 func (h *bundleHandler) addUnit(change *bundlechanges.AddUnitChange) error { 804 if h.dryRun { 805 return nil 806 } 807 808 p := change.Params 809 applicationName := resolve(p.Application, h.results) 810 var err error 811 var placementArg []*instance.Placement 812 targetMachine := p.To 813 if targetMachine != "" { 814 logger.Debugf("addUnit: placement %q", targetMachine) 815 // The placement maybe "container:machine" 816 container := "" 817 if parts := strings.Split(targetMachine, ":"); len(parts) > 1 { 818 container = parts[0] 819 targetMachine = parts[1] 820 } 821 targetMachine, err = h.resolveMachine(targetMachine) 822 if err != nil { 823 // Should never happen. 824 return errors.Annotatef(err, "cannot retrieve placement for %q unit", applicationName) 825 } 826 directive := targetMachine 827 if container != "" { 828 directive = container + ":" + directive 829 } 830 placement, err := parsePlacement(directive) 831 if err != nil { 832 return errors.Errorf("invalid --to parameter %q", directive) 833 } 834 logger.Debugf(" resolved: placement %q", directive) 835 placementArg = append(placementArg, placement) 836 } 837 r, err := h.api.AddUnits(application.AddUnitsParams{ 838 ApplicationName: applicationName, 839 NumUnits: 1, 840 Placement: placementArg, 841 }) 842 if err != nil { 843 return errors.Annotatef(err, "cannot add unit for application %q", applicationName) 844 } 845 unit := r[0] 846 if targetMachine == "" { 847 logger.Debugf("added %s unit to new machine", unit) 848 // In this case, the unit name is stored in results instead of the 849 // machine id, which is lazily evaluated later only if required. 850 // This way we avoid waiting for watcher updates. 851 h.results[change.Id()] = unit 852 } else { 853 logger.Debugf("added %s unit to new machine", unit) 854 h.results[change.Id()] = targetMachine 855 } 856 857 // Note that the targetMachine can be empty for now, resulting in a partially 858 // incomplete unit status. That's ok as the missing info is provided later 859 // when it is required. 860 h.unitStatus[unit] = targetMachine 861 return nil 862 } 863 864 // upgradeCharm will get the application to use the new charm. 865 func (h *bundleHandler) upgradeCharm(change *bundlechanges.UpgradeCharmChange) error { 866 if h.dryRun { 867 return nil 868 } 869 870 p := change.Params 871 cURL, err := charm.ParseURL(resolve(p.Charm, h.results)) 872 if err != nil { 873 return errors.Trace(err) 874 } 875 876 chID := charmstore.CharmID{ 877 URL: cURL, 878 Channel: h.channels[cURL], 879 } 880 macaroon := h.macaroons[cURL] 881 882 resources := h.makeResourceMap(p.Resources, p.LocalResources) 883 884 resourceLister, err := resourceadapters.NewAPIClient(h.api) 885 if err != nil { 886 return errors.Trace(err) 887 } 888 filtered, err := getUpgradeResources(h.api, resourceLister, p.Application, cURL, resources) 889 if err != nil { 890 return errors.Trace(err) 891 } 892 var resNames2IDs map[string]string 893 if len(filtered) != 0 { 894 resNames2IDs, err = resourceadapters.DeployResources( 895 p.Application, 896 chID, 897 macaroon, 898 resources, 899 filtered, 900 h.api, 901 ) 902 if err != nil { 903 return errors.Trace(err) 904 } 905 } 906 907 cfg := application.SetCharmConfig{ 908 ApplicationName: p.Application, 909 CharmID: chID, 910 ResourceIDs: resNames2IDs, 911 } 912 // Bundles only ever deal with the current generation. 913 if err := h.api.SetCharm(model.GenerationCurrent, cfg); err != nil { 914 return errors.Trace(err) 915 } 916 h.writeAddedResources(resNames2IDs) 917 918 return nil 919 } 920 921 // setOptions updates application configuration settings. 922 func (h *bundleHandler) setOptions(change *bundlechanges.SetOptionsChange) error { 923 p := change.Params 924 h.ctx.Verbosef(" setting options:") 925 for key, value := range p.Options { 926 switch value.(type) { 927 case string: 928 h.ctx.Verbosef(" %s: %q", key, value) 929 default: 930 h.ctx.Verbosef(" %s: %v", key, value) 931 } 932 } 933 if h.dryRun { 934 return nil 935 } 936 937 // We know that there wouldn't be any setOptions if there were no options. 938 cfg, err := yaml.Marshal(map[string]map[string]interface{}{p.Application: p.Options}) 939 if err != nil { 940 return errors.Annotatef(err, "cannot marshal options for application %q", p.Application) 941 } 942 943 if err := h.api.Update(params.ApplicationUpdate{ 944 ApplicationName: p.Application, 945 SettingsYAML: string(cfg), 946 Generation: model.GenerationCurrent, 947 }); err != nil { 948 return errors.Annotatef(err, "cannot update options for application %q", p.Application) 949 } 950 951 return nil 952 } 953 954 // setConstraints updates application constraints. 955 func (h *bundleHandler) setConstraints(change *bundlechanges.SetConstraintsChange) error { 956 if h.dryRun { 957 return nil 958 } 959 p := change.Params 960 // We know that p.Constraints is a valid constraints type due to the validation. 961 cons, _ := constraints.Parse(p.Constraints) 962 if err := h.api.SetConstraints(p.Application, cons); err != nil { 963 // This should never happen, as the bundle is already verified. 964 return errors.Annotatef(err, "cannot update constraints for application %q", p.Application) 965 } 966 967 return nil 968 } 969 970 // exposeApplication exposes an application. 971 func (h *bundleHandler) exposeApplication(change *bundlechanges.ExposeChange) error { 972 if h.dryRun { 973 return nil 974 } 975 976 application := resolve(change.Params.Application, h.results) 977 if err := h.api.Expose(application); err != nil { 978 return errors.Annotatef(err, "cannot expose application %s", application) 979 } 980 return nil 981 } 982 983 // setAnnotations sets annotations for an application or a machine. 984 func (h *bundleHandler) setAnnotations(change *bundlechanges.SetAnnotationsChange) error { 985 p := change.Params 986 h.ctx.Verbosef(" setting annotations:") 987 for key, value := range p.Annotations { 988 h.ctx.Verbosef(" %s: %q", key, value) 989 } 990 if h.dryRun { 991 return nil 992 } 993 eid := resolve(p.Id, h.results) 994 var tag string 995 switch p.EntityType { 996 case bundlechanges.MachineType: 997 tag = names.NewMachineTag(eid).String() 998 case bundlechanges.ApplicationType: 999 tag = names.NewApplicationTag(eid).String() 1000 default: 1001 return errors.Errorf("unexpected annotation entity type %q", p.EntityType) 1002 } 1003 result, err := h.api.SetAnnotation(map[string]map[string]string{tag: p.Annotations}) 1004 if err == nil && len(result) > 0 { 1005 err = result[0].Error 1006 } 1007 if err != nil { 1008 return errors.Annotatef(err, "cannot set annotations for %s %q", p.EntityType, eid) 1009 } 1010 return nil 1011 } 1012 1013 // applicationsForMachineChange returns the names of the applications for which an 1014 // "addMachine" change is required, as adding machines is required to place 1015 // units, and units belong to applications. 1016 // Receive the id of the "addMachine" change. 1017 func (h *bundleHandler) applicationsForMachineChange(changeId string) []string { 1018 applications := set.NewStrings() 1019 mainloop: 1020 for _, change := range h.changes { 1021 for _, required := range change.Requires() { 1022 if required != changeId { 1023 continue 1024 } 1025 switch change := change.(type) { 1026 case *bundlechanges.AddMachineChange: 1027 // The original machine is a container, and its parent is 1028 // another "addMachines" change. Search again using the 1029 // parent id. 1030 for _, application := range h.applicationsForMachineChange(change.Id()) { 1031 applications.Add(application) 1032 } 1033 continue mainloop 1034 case *bundlechanges.AddUnitChange: 1035 // We have found the "addUnit" change, which refers to a 1036 // application: now resolve the application holding the unit. 1037 application := resolve(change.Params.Application, h.results) 1038 applications.Add(application) 1039 continue mainloop 1040 case *bundlechanges.SetAnnotationsChange: 1041 // A machine change is always required to set machine 1042 // annotations, but this isn't the interesting change here. 1043 continue mainloop 1044 default: 1045 // Should never happen. 1046 panic(fmt.Sprintf("unexpected change %T", change)) 1047 } 1048 } 1049 } 1050 return applications.SortedValues() 1051 } 1052 1053 // updateUnitStatusPeriod is the time duration used to wait for a mega-watcher 1054 // change to be available. 1055 var updateUnitStatusPeriod = watcher.Period + 5*time.Second 1056 1057 // updateUnitStatus uses the mega-watcher to update units and machines info 1058 // (h.unitStatus) so that it reflects the current environment status. 1059 // This function must be called assuming new delta changes are available or 1060 // will be available within the watcher time period. Otherwise, the function 1061 // unblocks and an error is returned. 1062 func (h *bundleHandler) updateUnitStatus() error { 1063 var delta []multiwatcher.Delta 1064 var err error 1065 ch := make(chan struct{}) 1066 go func() { 1067 delta, err = h.watcher.Next() 1068 close(ch) 1069 }() 1070 select { 1071 case <-ch: 1072 if err != nil { 1073 return errors.Annotate(err, "cannot update model status") 1074 } 1075 for _, d := range delta { 1076 switch entityInfo := d.Entity.(type) { 1077 case *multiwatcher.UnitInfo: 1078 h.unitStatus[entityInfo.Name] = entityInfo.MachineId 1079 } 1080 } 1081 case <-time.After(updateUnitStatusPeriod): 1082 // TODO(fwereade): 2016-03-17 lp:1558657 1083 return errors.New("timeout while trying to get new changes from the watcher") 1084 } 1085 return nil 1086 } 1087 1088 // resolveMachine returns the machine id resolving the given unit or machine 1089 // placeholder. 1090 func (h *bundleHandler) resolveMachine(placeholder string) (string, error) { 1091 logger.Debugf("resolveMachine(%q)", placeholder) 1092 machineOrUnit := resolve(placeholder, h.results) 1093 if !names.IsValidUnit(machineOrUnit) { 1094 return machineOrUnit, nil 1095 } 1096 for h.unitStatus[machineOrUnit] == "" { 1097 if err := h.updateUnitStatus(); err != nil { 1098 return "", errors.Annotate(err, "cannot resolve machine") 1099 } 1100 } 1101 return h.unitStatus[machineOrUnit], nil 1102 } 1103 1104 func (h *bundleHandler) topLevelMachine(id string) string { 1105 if !names.IsContainerMachine(id) { 1106 return id 1107 } 1108 tag := names.NewMachineTag(id) 1109 return tag.Parent().Id() 1110 } 1111 1112 // resolveRelation returns the relation name resolving the included application 1113 // placeholder. 1114 func resolveRelation(e string, results map[string]string) string { 1115 parts := strings.SplitN(e, ":", 2) 1116 application := resolve(parts[0], results) 1117 if len(parts) == 1 { 1118 return application 1119 } 1120 return fmt.Sprintf("%s:%s", application, parts[1]) 1121 } 1122 1123 // resolve returns the real entity name for the bundle entity (for instance a 1124 // application or a machine) with the given placeholder id. 1125 // A placeholder id is a string like "$deploy-42" or "$addCharm-2", indicating 1126 // the results of a previously applied change. It always starts with a dollar 1127 // sign, followed by the identifier of the referred change. A change id is a 1128 // string indicating the action type ("deploy", "addRelation" etc.), followed 1129 // by a unique incremental number. 1130 // 1131 // Now that the bundlechanges library understands the existing model, if the 1132 // entity already existed in the model, the placeholder value is the actual 1133 // entity from the model, and in these situations the placeholder value doesn't 1134 // start with the '$'. 1135 func resolve(placeholder string, results map[string]string) string { 1136 logger.Debugf("resolve %q from %s", placeholder, pretty.Sprint(results)) 1137 if !strings.HasPrefix(placeholder, "$") { 1138 return placeholder 1139 } 1140 id := placeholder[1:] 1141 return results[id] 1142 } 1143 1144 func processBundleIncludes(baseDir string, data *charm.BundleData) error { 1145 for app, appData := range data.Applications { 1146 // A bundle isn't valid if there are no applications, and applications must 1147 // specify a charm at least, so we know appData must be non-nil. 1148 for key, value := range appData.Options { 1149 result, processed, err := processValue(baseDir, value) 1150 if err != nil { 1151 return errors.Annotatef(err, "processing options value %s for application %s", key, app) 1152 } 1153 if processed { 1154 appData.Options[key] = result 1155 } 1156 } 1157 for key, value := range appData.Annotations { 1158 result, processed, err := processValue(baseDir, value) 1159 if err != nil { 1160 return errors.Annotatef(err, "processing annotation value %s for application %s", key, app) 1161 } 1162 if processed { 1163 appData.Annotations[key] = result.(string) 1164 } 1165 } 1166 } 1167 1168 for machine, machineData := range data.Machines { 1169 if machineData == nil { 1170 continue 1171 } 1172 for key, value := range machineData.Annotations { 1173 result, processed, err := processValue(baseDir, value) 1174 if err != nil { 1175 return errors.Annotatef(err, "processing annotation value %s for machine %s", key, machine) 1176 } 1177 if processed { 1178 machineData.Annotations[key] = result.(string) 1179 } 1180 } 1181 } 1182 return nil 1183 } 1184 1185 func processValue(baseDir string, v interface{}) (interface{}, bool, error) { 1186 1187 const ( 1188 includeFile = "include-file://" 1189 includeBase64 = "include-base64://" 1190 ) 1191 1192 value, ok := v.(string) 1193 if !ok { 1194 // Not a string, just return it unchanged. 1195 return v, false, nil 1196 } 1197 1198 encode := false 1199 readFile := false 1200 filename := "" 1201 1202 if strings.HasPrefix(value, includeFile) { 1203 readFile = true 1204 filename = value[len(includeFile):] 1205 } else if strings.HasPrefix(value, includeBase64) { 1206 encode = true 1207 readFile = true 1208 filename = value[len(includeBase64):] 1209 } 1210 1211 if !readFile { 1212 // Unchanged, just return it. 1213 return v, false, nil 1214 } 1215 1216 if !filepath.IsAbs(filename) { 1217 filename = filepath.Clean(filepath.Join(baseDir, filename)) 1218 } 1219 1220 bytes, err := ioutil.ReadFile(filename) 1221 if err != nil { 1222 return nil, false, errors.Annotate(err, "unable to read file") 1223 } 1224 1225 var result string 1226 if encode { 1227 result = base64.StdEncoding.EncodeToString(bytes) 1228 } else { 1229 result = string(bytes) 1230 } 1231 1232 return result, true, nil 1233 } 1234 1235 type bundleOverlayValueExists struct { 1236 Applications map[string]map[string]interface{} `yaml:"applications"` 1237 } 1238 1239 func processBundleOverlay(data *charm.BundleData, bundleOverlayFiles ...string) error { 1240 for _, filename := range bundleOverlayFiles { 1241 bundleOverlayFile, err := utils.NormalizePath(filename) 1242 if err != nil { 1243 return errors.Annotate(err, "unable to normalise bundle overlay file") 1244 } 1245 // Make sure the filename is absolute. 1246 if !filepath.IsAbs(bundleOverlayFile) { 1247 // TODO(babbageclunk): pass in ctx.Dir rather than using os.Getwd. 1248 cwd, err := os.Getwd() 1249 if err != nil { 1250 return errors.Trace(err) 1251 } 1252 bundleOverlayFile = filepath.Clean(filepath.Join(cwd, bundleOverlayFile)) 1253 } 1254 if err := processSingleBundleOverlay(data, bundleOverlayFile); err != nil { 1255 return errors.Trace(err) 1256 } 1257 } 1258 return nil 1259 } 1260 1261 func processSingleBundleOverlay(data *charm.BundleData, bundleOverlayFile string) error { 1262 config, err := charmrepo.ReadBundleFile(bundleOverlayFile) 1263 if err != nil { 1264 return errors.Annotatef(err, "unable to read bundle overlay file %q", bundleOverlayFile) 1265 } 1266 1267 // From here we walk through the new bundleData, and override values 1268 // in the current bundle. 1269 1270 // If a new application is added, the content is added. 1271 // If an application is defined but with no content, that means 1272 // remove the application from the parent bundle. 1273 // - if this is happening, all related relations are also removed. 1274 // If the application exists in both, values here override values there. 1275 // If machines are defined, they override the entire machines section. 1276 1277 content, err := ioutil.ReadFile(bundleOverlayFile) 1278 if err != nil { 1279 return errors.Annotate(err, "unable to open bundle overlay file") 1280 } 1281 baseDir := filepath.Dir(bundleOverlayFile) 1282 1283 // If this works, then this deserialisation should certainly succeed. 1284 // Since we are only looking to overwrite the values in the underlying bundle 1285 // for config values that are set, we need to know if they were actually set, 1286 // and not just zero. The configCheck structure is a map that allows us to check 1287 // if the fields were actually in the underlying YAML. 1288 var configCheck bundleOverlayValueExists 1289 if err := yaml.Unmarshal(content, &configCheck); err != nil { 1290 return errors.Annotate(err, "unable to deserialize config structure") 1291 } 1292 // Here there is a possibility that the config file uses top level 1293 // applications, whereas the bundleOverlayValueExists only defines the newer 1294 // applications. If that is the case, we error out and tell the user. 1295 if len(configCheck.Applications) == 0 && len(config.Applications) > 0 { 1296 return errors.Errorf("bundle overlay file %q used deprecated 'services' key, this is not valid for bundle overlay files", bundleOverlayFile) 1297 } 1298 1299 // We want to confirm that all the applications mentioned in the config 1300 // actually exist in the bundle data. 1301 for appName, bc := range config.Applications { 1302 app, found := data.Applications[appName] 1303 // If bc is nil, that means to remove it from data. 1304 if bc == nil { 1305 delete(data.Applications, appName) 1306 data.Relations = removeRelations(data.Relations, appName) 1307 continue 1308 } 1309 if !found { 1310 // Add it in. 1311 data.Applications[appName] = bc 1312 continue 1313 } 1314 1315 fieldCheck := configCheck.Applications[appName] 1316 1317 if _, set := fieldCheck["charm"]; set { 1318 app.Charm = bc.Charm 1319 } 1320 if _, set := fieldCheck["series"]; set { 1321 app.Series = bc.Series 1322 } 1323 if _, set := fieldCheck["resources"]; set { 1324 if app.Resources == nil { 1325 app.Resources = make(map[string]interface{}) 1326 } 1327 for key, value := range bc.Resources { 1328 app.Resources[key] = value 1329 } 1330 } 1331 if _, set := fieldCheck["num_units"]; set { 1332 app.NumUnits = bc.NumUnits 1333 } 1334 if _, set := fieldCheck["to"]; set { 1335 app.To = bc.To 1336 } 1337 if _, set := fieldCheck["expose"]; set { 1338 app.Expose = bc.Expose 1339 } 1340 if _, set := fieldCheck["options"]; set { 1341 if app.Options == nil { 1342 app.Options = make(map[string]interface{}) 1343 } 1344 for key, value := range bc.Options { 1345 result, _, err := processValue(baseDir, value) 1346 if err != nil { 1347 return errors.Annotatef(err, "processing config options value %s for application %s", key, appName) 1348 } 1349 app.Options[key] = result 1350 } 1351 } 1352 if _, set := fieldCheck["annotations"]; set { 1353 if app.Annotations == nil { 1354 app.Annotations = make(map[string]string) 1355 } 1356 for key, value := range bc.Annotations { 1357 result, _, err := processValue(baseDir, value) 1358 if err != nil { 1359 return errors.Annotatef(err, "processing config annotations value %s for application %s", key, appName) 1360 } 1361 app.Annotations[key] = result.(string) 1362 } 1363 } 1364 if _, set := fieldCheck["constraints"]; set { 1365 app.Constraints = bc.Constraints 1366 } 1367 if _, set := fieldCheck["storage"]; set { 1368 if app.Storage == nil { 1369 app.Storage = make(map[string]string) 1370 } 1371 for key, value := range bc.Storage { 1372 app.Storage[key] = value 1373 } 1374 } 1375 if _, set := fieldCheck["devices"]; set { 1376 if app.Devices == nil { 1377 app.Devices = make(map[string]string) 1378 } 1379 for key, value := range bc.Devices { 1380 app.Devices[key] = value 1381 } 1382 } 1383 if _, set := fieldCheck["bindings"]; set { 1384 if app.EndpointBindings == nil { 1385 app.EndpointBindings = make(map[string]string) 1386 } 1387 for key, value := range bc.EndpointBindings { 1388 app.EndpointBindings[key] = value 1389 } 1390 } 1391 } 1392 1393 // If series is set in the config, it overrides the bundle. 1394 if config.Series != "" { 1395 data.Series = config.Series 1396 } 1397 1398 // Next process relations. 1399 for _, relation := range config.Relations { 1400 data.Relations = append(data.Relations, relation) 1401 } 1402 1403 // Finally, if the bundle overlay overrode the machines definition use 1404 // that. 1405 if config.Machines != nil { 1406 data.Machines = config.Machines 1407 } 1408 1409 return nil 1410 } 1411 1412 // removeRelations removes any relation defined in data that references 1413 // the application appName. 1414 func removeRelations(data [][]string, appName string) [][]string { 1415 var result [][]string 1416 for _, relation := range data { 1417 // Keep the dud relation in the set, it will be caught by the bundle 1418 // verify code. 1419 if len(relation) == 2 { 1420 left, right := relation[0], relation[1] 1421 if left == appName || strings.HasPrefix(left, appName+":") || 1422 right == appName || strings.HasPrefix(right, appName+":") { 1423 continue 1424 } 1425 } 1426 result = append(result, relation) 1427 } 1428 return result 1429 } 1430 1431 // ModelExtractor provides everything we need to build a 1432 // bundlechanges.Model from a model API connection. 1433 type ModelExtractor interface { 1434 GetAnnotations(tags []string) ([]params.AnnotationsGetResult, error) 1435 GetConstraints(applications ...string) ([]constraints.Value, error) 1436 GetConfig(generation model.GenerationVersion, applications ...string) ([]map[string]interface{}, error) 1437 Sequences() (map[string]int, error) 1438 } 1439 1440 func buildModelRepresentation( 1441 status *params.FullStatus, 1442 apiRoot ModelExtractor, 1443 useExistingMachines bool, 1444 bundleMachines map[string]string, 1445 ) (*bundlechanges.Model, error) { 1446 var ( 1447 annotationTags []string 1448 appNames []string 1449 principalApps []string 1450 ) 1451 machineMap := make(map[string]string) 1452 machines := make(map[string]*bundlechanges.Machine) 1453 for id, machineStatus := range status.Machines { 1454 machines[id] = &bundlechanges.Machine{ 1455 ID: id, 1456 Series: machineStatus.Series, 1457 } 1458 tag := names.NewMachineTag(id) 1459 annotationTags = append(annotationTags, tag.String()) 1460 if useExistingMachines && tag.ContainerType() == "" { 1461 machineMap[id] = id 1462 } 1463 } 1464 // Now iterate over the bundleMachines that the user specified. 1465 for bundleMachine, modelMachine := range bundleMachines { 1466 machineMap[bundleMachine] = modelMachine 1467 } 1468 applications := make(map[string]*bundlechanges.Application) 1469 for name, appStatus := range status.Applications { 1470 app := &bundlechanges.Application{ 1471 Name: name, 1472 Charm: appStatus.Charm, 1473 Scale: appStatus.Scale, 1474 Exposed: appStatus.Exposed, 1475 Series: appStatus.Series, 1476 Placement: appStatus.Placement, 1477 SubordinateTo: appStatus.SubordinateTo, 1478 } 1479 for unitName, unit := range appStatus.Units { 1480 app.Units = append(app.Units, bundlechanges.Unit{ 1481 Name: unitName, 1482 Machine: unit.Machine, 1483 }) 1484 } 1485 applications[name] = app 1486 annotationTags = append(annotationTags, names.NewApplicationTag(name).String()) 1487 appNames = append(appNames, name) 1488 if len(appStatus.Units) > 0 { 1489 // While this isn't entirely accurate, because an application 1490 // without any units is still a principal, it is less bad than 1491 // just using 'SubordinateTo' as a subordinate charm that isn't 1492 // related to anything has that empty too. 1493 principalApps = append(principalApps, name) 1494 } 1495 } 1496 mod := &bundlechanges.Model{ 1497 Applications: applications, 1498 Machines: machines, 1499 MachineMap: machineMap, 1500 } 1501 for _, relation := range status.Relations { 1502 // All relations have two endpoints except peers. 1503 if len(relation.Endpoints) != 2 { 1504 continue 1505 } 1506 mod.Relations = append(mod.Relations, bundlechanges.Relation{ 1507 App1: relation.Endpoints[0].ApplicationName, 1508 Endpoint1: relation.Endpoints[0].Name, 1509 App2: relation.Endpoints[1].ApplicationName, 1510 Endpoint2: relation.Endpoints[1].Name, 1511 }) 1512 } 1513 // Get all the annotations. 1514 annotations, err := apiRoot.GetAnnotations(annotationTags) 1515 if err != nil { 1516 return nil, errors.Trace(err) 1517 } 1518 for _, result := range annotations { 1519 if result.Error.Error != nil { 1520 return nil, errors.Trace(result.Error.Error) 1521 } 1522 tag, err := names.ParseTag(result.EntityTag) 1523 if err != nil { 1524 return nil, errors.Trace(err) // This should never happen. 1525 } 1526 switch kind := tag.Kind(); kind { 1527 case names.ApplicationTagKind: 1528 mod.Applications[tag.Id()].Annotations = result.Annotations 1529 case names.MachineTagKind: 1530 mod.Machines[tag.Id()].Annotations = result.Annotations 1531 default: 1532 return nil, errors.Errorf("unexpected tag kind for annotations: %q", kind) 1533 } 1534 } 1535 // Add in the model sequences. 1536 sequences, err := apiRoot.Sequences() 1537 if err == nil { 1538 mod.Sequence = sequences 1539 } else if !errors.IsNotSupported(err) { 1540 return nil, errors.Annotate(err, "getting model sequences") 1541 } 1542 1543 // When dealing with bundles the current model generation is always used. 1544 configValues, err := apiRoot.GetConfig(model.GenerationCurrent, appNames...) 1545 if err != nil { 1546 return nil, errors.Annotate(err, "getting application options") 1547 } 1548 for i, cfg := range configValues { 1549 options := make(map[string]interface{}) 1550 // The config map has values that looks like this: 1551 // map[string]interface {}{ 1552 // "value": "", 1553 // "source": "user", // or "unset" or "default" 1554 // "description": "Where to gather metrics from.\nExamples:\n host1.maas:9090\n host1.maas:9090, host2.maas:9090\n", 1555 // "type": "string", 1556 // }, 1557 // We want the value iff default is false. 1558 for key, valueMap := range cfg { 1559 value, err := applicationConfigValue(key, valueMap) 1560 if err != nil { 1561 return nil, errors.Annotatef(err, "bad application config for %q", appNames[i]) 1562 } 1563 if value != nil { 1564 options[key] = value 1565 } 1566 } 1567 mod.Applications[appNames[i]].Options = options 1568 } 1569 // Lastly get all the application constraints. 1570 constraintValues, err := apiRoot.GetConstraints(principalApps...) 1571 if err != nil { 1572 return nil, errors.Annotate(err, "getting application constraints") 1573 } 1574 for i, value := range constraintValues { 1575 mod.Applications[principalApps[i]].Constraints = value.String() 1576 } 1577 1578 mod.ConstraintsEqual = func(a, b string) bool { 1579 // Since the constraints have already been validated, we don't 1580 // even bother checking the error response here. 1581 ac, _ := constraints.Parse(a) 1582 bc, _ := constraints.Parse(b) 1583 return reflect.DeepEqual(ac, bc) 1584 } 1585 1586 return mod, nil 1587 } 1588 1589 // applicationConfigValue returns the value if it is not a default value. 1590 // If the value is a default value, nil is returned. 1591 // If there was issue determining the type or value, an error is returned. 1592 func applicationConfigValue(key string, valueMap interface{}) (interface{}, error) { 1593 vm, ok := valueMap.(map[string]interface{}) 1594 if !ok { 1595 return nil, errors.Errorf("unexpected application config value type %T for key %q", valueMap, key) 1596 } 1597 source, found := vm["source"] 1598 if !found { 1599 return nil, errors.Errorf("missing application config value 'source' for key %q", key) 1600 } 1601 if source != "user" { 1602 return nil, nil 1603 } 1604 value, found := vm["value"] 1605 if !found { 1606 return nil, errors.Errorf("missing application config value 'value'") 1607 } 1608 return value, nil 1609 }