github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/commands/bootstrap.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package commands 5 6 import ( 7 "bufio" 8 "fmt" 9 "os" 10 "os/user" 11 "sort" 12 "strings" 13 14 "github.com/juju/cmd" 15 "github.com/juju/errors" 16 "github.com/juju/gnuflag" 17 "github.com/juju/schema" 18 "github.com/juju/utils" 19 "github.com/juju/utils/featureflag" 20 "github.com/juju/version" 21 "gopkg.in/juju/charm.v6-unstable" 22 23 jujucloud "github.com/juju/juju/cloud" 24 "github.com/juju/juju/cmd/juju/common" 25 "github.com/juju/juju/cmd/modelcmd" 26 "github.com/juju/juju/constraints" 27 "github.com/juju/juju/controller" 28 "github.com/juju/juju/environs" 29 "github.com/juju/juju/environs/bootstrap" 30 "github.com/juju/juju/environs/config" 31 "github.com/juju/juju/environs/sync" 32 "github.com/juju/juju/feature" 33 "github.com/juju/juju/instance" 34 "github.com/juju/juju/juju/osenv" 35 "github.com/juju/juju/jujuclient" 36 jujuversion "github.com/juju/juju/version" 37 ) 38 39 // provisionalProviders is the names of providers that are hidden behind 40 // feature flags. 41 var provisionalProviders = map[string]string{ 42 "vsphere": feature.VSphereProvider, 43 } 44 45 var usageBootstrapSummary = ` 46 Initializes a cloud environment.`[1:] 47 48 var usageBootstrapDetails = ` 49 Used without arguments, bootstrap will step you through the process of 50 initializing a Juju cloud environment. Initialization consists of creating 51 a 'controller' model and provisioning a machine to act as controller. 52 53 We recommend you call your controller ‘username-region’ e.g. ‘fred-us-east-1’ 54 See --clouds for a list of clouds and credentials. 55 See --regions <cloud> for a list of available regions for a given cloud. 56 57 Credentials are set beforehand and are distinct from any other 58 configuration (see `[1:] + "`juju add-credential`" + `). 59 The 'controller' model typically does not run workloads. It should remain 60 pristine to run and manage Juju's own infrastructure for the corresponding 61 cloud. Additional (hosted) models should be created with ` + "`juju create-\nmodel`" + ` for workload purposes. 62 Note that a 'default' model is also created and becomes the current model 63 of the environment once the command completes. It can be discarded if 64 other models are created. 65 66 If '--bootstrap-constraints' is used, its values will also apply to any 67 future controllers provisioned for high availability (HA). 68 69 If '--constraints' is used, its values will be set as the default 70 constraints for all future workload machines in the model, exactly as if 71 the constraints were set with ` + "`juju set-model-constraints`" + `. 72 73 It is possible to override constraints and the automatic machine selection 74 algorithm by assigning a "placement directive" via the '--to' option. This 75 dictates what machine to use for the controller. This would typically be 76 used with the MAAS provider ('--to <host>.maas'). 77 78 You can change the default timeout and retry delays used during the 79 bootstrap by changing the following settings in your configuration 80 (all values represent number of seconds): 81 # How long to wait for a connection to the controller 82 bootstrap-timeout: 600 # default: 10 minutes 83 # How long to wait between connection attempts to a controller 84 address. 85 bootstrap-retry-delay: 5 # default: 5 seconds 86 # How often to refresh controller addresses from the API server. 87 bootstrap-addresses-delay: 10 # default: 10 seconds 88 89 Private clouds may need to specify their own custom image metadata and 90 tools/agent. Use '--metadata-source' whose value is a local directory. 91 The value of '--agent-version' will become the default tools version to 92 use in all models for this controller. The full binary version is accepted 93 (e.g.: 2.0.1-xenial-amd64) but only the numeric version (e.g.: 2.0.1) is 94 used. Otherwise, by default, the version used is that of the client. 95 96 Examples: 97 juju bootstrap 98 juju bootstrap --clouds 99 juju bootstrap --regions aws 100 juju bootstrap joe-us-east1 google 101 juju bootstrap --config=~/config-rs.yaml joe-syd rackspace 102 juju bootstrap --config agent-version=1.25.3 joe-us-east-1 aws 103 juju bootstrap --config bootstrap-timeout=1200 joe-eastus azure 104 105 See also: 106 add-credentials 107 add-model 108 set-constraints` 109 110 // defaultHostedModelName is the name of the hosted model created in each 111 // controller for deploying workloads to, in addition to the "controller" model. 112 const defaultHostedModelName = "default" 113 114 func newBootstrapCommand() cmd.Command { 115 return modelcmd.Wrap( 116 &bootstrapCommand{}, 117 modelcmd.WrapSkipModelFlags, modelcmd.WrapSkipDefaultModel, 118 ) 119 } 120 121 // bootstrapCommand is responsible for launching the first machine in a juju 122 // environment, and setting up everything necessary to continue working. 123 type bootstrapCommand struct { 124 modelcmd.ModelCommandBase 125 126 Constraints constraints.Value 127 ConstraintsStr string 128 BootstrapConstraints constraints.Value 129 BootstrapConstraintsStr string 130 BootstrapSeries string 131 BootstrapImage string 132 BuildAgent bool 133 MetadataSource string 134 Placement string 135 KeepBrokenEnvironment bool 136 AutoUpgrade bool 137 AgentVersionParam string 138 AgentVersion *version.Number 139 ForceAPIPort bool 140 config common.ConfigFlag 141 142 showClouds bool 143 showRegionsForCloud string 144 controllerName string 145 hostedModelName string 146 CredentialName string 147 Cloud string 148 Region string 149 noGUI bool 150 interactive bool 151 } 152 153 func (c *bootstrapCommand) Info() *cmd.Info { 154 return &cmd.Info{ 155 Name: "bootstrap", 156 Args: "<controller name> <cloud name>[/region]", 157 Purpose: usageBootstrapSummary, 158 Doc: usageBootstrapDetails, 159 } 160 } 161 162 func (c *bootstrapCommand) SetFlags(f *gnuflag.FlagSet) { 163 c.ModelCommandBase.SetFlags(f) 164 f.StringVar(&c.ConstraintsStr, "constraints", "", "Set model constraints") 165 f.StringVar(&c.BootstrapConstraintsStr, "bootstrap-constraints", "", "Specify bootstrap machine constraints") 166 f.StringVar(&c.BootstrapSeries, "bootstrap-series", "", "Specify the series of the bootstrap machine") 167 if featureflag.Enabled(feature.ImageMetadata) { 168 f.StringVar(&c.BootstrapImage, "bootstrap-image", "", "Specify the image of the bootstrap machine") 169 } 170 f.BoolVar(&c.BuildAgent, "build-agent", false, "Build local version of agent binary before bootstrapping") 171 f.StringVar(&c.MetadataSource, "metadata-source", "", "Local path to use as tools and/or metadata source") 172 f.StringVar(&c.Placement, "to", "", "Placement directive indicating an instance to bootstrap") 173 f.BoolVar(&c.KeepBrokenEnvironment, "keep-broken", false, "Do not destroy the model if bootstrap fails") 174 f.BoolVar(&c.AutoUpgrade, "auto-upgrade", false, "Upgrade to the latest patch release tools on first bootstrap") 175 f.BoolVar(&c.ForceAPIPort, "force-api-port", false, "Allow use of non-standard HTTPS port when official DNS name specified") 176 f.StringVar(&c.AgentVersionParam, "agent-version", "", "Version of tools to use for Juju agents") 177 f.StringVar(&c.CredentialName, "credential", "", "Credentials to use when bootstrapping") 178 f.Var(&c.config, "config", "Specify a controller configuration file, or one or more configuration\n options\n (--config config.yaml [--config key=value ...])") 179 f.StringVar(&c.hostedModelName, "d", defaultHostedModelName, "Name of the default hosted model for the controller") 180 f.StringVar(&c.hostedModelName, "default-model", defaultHostedModelName, "Name of the default hosted model for the controller") 181 f.BoolVar(&c.noGUI, "no-gui", false, "Do not install the Juju GUI in the controller when bootstrapping") 182 f.BoolVar(&c.showClouds, "clouds", false, "Print the available clouds which can be used to bootstrap a Juju environment") 183 f.StringVar(&c.showRegionsForCloud, "regions", "", "Print the available regions for the specified cloud") 184 } 185 186 func (c *bootstrapCommand) Init(args []string) (err error) { 187 if c.showClouds && c.showRegionsForCloud != "" { 188 return errors.New("--clouds and --regions can't be used together") 189 } 190 if c.showClouds { 191 return cmd.CheckEmpty(args) 192 } 193 if c.showRegionsForCloud != "" { 194 return cmd.CheckEmpty(args) 195 } 196 if c.AgentVersionParam != "" && c.BuildAgent { 197 return errors.New("--agent-version and --build-agent can't be used together") 198 } 199 if c.BootstrapSeries != "" && !charm.IsValidSeries(c.BootstrapSeries) { 200 return errors.NotValidf("series %q", c.BootstrapSeries) 201 } 202 203 // Parse the placement directive. Bootstrap currently only 204 // supports provider-specific placement directives. 205 if c.Placement != "" { 206 _, err = instance.ParsePlacement(c.Placement) 207 if err != instance.ErrPlacementScopeMissing { 208 // We only support unscoped placement directives for bootstrap. 209 return errors.Errorf("unsupported bootstrap placement directive %q", c.Placement) 210 } 211 } 212 if !c.AutoUpgrade { 213 // With no auto upgrade chosen, we default to the version matching the bootstrap client. 214 vers := jujuversion.Current 215 c.AgentVersion = &vers 216 } 217 if c.AgentVersionParam != "" { 218 if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil { 219 c.AgentVersion = &vers.Number 220 } else if vers, err := version.Parse(c.AgentVersionParam); err == nil { 221 c.AgentVersion = &vers 222 } else { 223 return err 224 } 225 } 226 if c.AgentVersion != nil && (c.AgentVersion.Major != jujuversion.Current.Major || c.AgentVersion.Minor != jujuversion.Current.Minor) { 227 return errors.New("requested agent version major.minor mismatch") 228 } 229 230 switch len(args) { 231 case 0: 232 // no args or flags, go interactive. 233 c.interactive = true 234 return nil 235 case 1: 236 // The user must specify both positional arguments: the controller name, 237 // and the cloud name (optionally with region specified). 238 return errors.New("controller name and cloud name are required") 239 } 240 c.controllerName = args[0] 241 c.Cloud = args[1] 242 if i := strings.IndexRune(c.Cloud, '/'); i > 0 { 243 c.Cloud, c.Region = c.Cloud[:i], c.Cloud[i+1:] 244 } 245 return cmd.CheckEmpty(args[2:]) 246 } 247 248 // BootstrapInterface provides bootstrap functionality that Run calls to support cleaner testing. 249 type BootstrapInterface interface { 250 Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args bootstrap.BootstrapParams) error 251 CloudRegionDetector(environs.EnvironProvider) (environs.CloudRegionDetector, bool) 252 } 253 254 type bootstrapFuncs struct{} 255 256 func (b bootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error { 257 return bootstrap.Bootstrap(ctx, env, args) 258 } 259 260 func (b bootstrapFuncs) CloudRegionDetector(provider environs.EnvironProvider) (environs.CloudRegionDetector, bool) { 261 detector, ok := provider.(environs.CloudRegionDetector) 262 return detector, ok 263 } 264 265 var getBootstrapFuncs = func() BootstrapInterface { 266 return &bootstrapFuncs{} 267 } 268 269 var ( 270 bootstrapPrepare = bootstrap.Prepare 271 environsDestroy = environs.Destroy 272 waitForAgentInitialisation = common.WaitForAgentInitialisation 273 ) 274 275 var ambiguousDetectedCredentialError = errors.New(` 276 more than one credential detected 277 run juju autoload-credentials and specify a credential using the --credential argument`[1:], 278 ) 279 280 var ambiguousCredentialError = errors.New(` 281 more than one credential is available 282 specify a credential using the --credential argument`[1:], 283 ) 284 285 func (c *bootstrapCommand) parseConstraints(ctx *cmd.Context) (err error) { 286 allAliases := map[string]string{} 287 defer common.WarnConstraintAliases(ctx, allAliases) 288 if c.ConstraintsStr != "" { 289 cons, aliases, err := constraints.ParseWithAliases(c.ConstraintsStr) 290 for k, v := range aliases { 291 allAliases[k] = v 292 } 293 if err != nil { 294 return err 295 } 296 c.Constraints = cons 297 } 298 if c.BootstrapConstraintsStr != "" { 299 cons, aliases, err := constraints.ParseWithAliases(c.BootstrapConstraintsStr) 300 for k, v := range aliases { 301 allAliases[k] = v 302 } 303 if err != nil { 304 return err 305 } 306 c.BootstrapConstraints = cons 307 } 308 return nil 309 } 310 311 // Run connects to the environment specified on the command line and bootstraps 312 // a juju in that environment if none already exists. If there is as yet no environments.yaml file, 313 // the user is informed how to create one. 314 func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { 315 if err := c.parseConstraints(ctx); err != nil { 316 return err 317 } 318 if c.BootstrapImage != "" { 319 if c.BootstrapSeries == "" { 320 return errors.Errorf("--bootstrap-image must be used with --bootstrap-series") 321 } 322 cons, err := constraints.Merge(c.Constraints, c.BootstrapConstraints) 323 if err != nil { 324 return errors.Trace(err) 325 } 326 if !cons.HasArch() { 327 return errors.Errorf("--bootstrap-image must be used with --bootstrap-constraints, specifying architecture") 328 } 329 } 330 if c.interactive { 331 if err := c.runInteractive(ctx); err != nil { 332 return errors.Trace(err) 333 } 334 // now run normal bootstrap using info gained above. 335 } 336 if c.showClouds { 337 return printClouds(ctx, c.ClientStore()) 338 } 339 if c.showRegionsForCloud != "" { 340 return printCloudRegions(ctx, c.showRegionsForCloud) 341 } 342 343 bootstrapFuncs := getBootstrapFuncs() 344 345 // Get the cloud definition identified by c.Cloud. If c.Cloud does not 346 // identify a cloud in clouds.yaml, but is the name of a provider, and 347 // that provider implements environs.CloudRegionDetector, we'll 348 // synthesise a Cloud structure with the detected regions and no auth- 349 // types. 350 cloud, err := jujucloud.CloudByName(c.Cloud) 351 if errors.IsNotFound(err) { 352 ctx.Verbosef("cloud %q not found, trying as a provider name", c.Cloud) 353 provider, err := environs.Provider(c.Cloud) 354 if errors.IsNotFound(err) { 355 return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) 356 } else if err != nil { 357 return errors.Trace(err) 358 } 359 detector, ok := bootstrapFuncs.CloudRegionDetector(provider) 360 if !ok { 361 ctx.Verbosef( 362 "provider %q does not support detecting regions", 363 c.Cloud, 364 ) 365 return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) 366 } 367 var cloudEndpoint string 368 regions, err := detector.DetectRegions() 369 if errors.IsNotFound(err) { 370 // It's not an error to have no regions. If the 371 // provider does not support regions, then we 372 // reinterpret the supplied region name as the 373 // cloud's endpoint. This enables the user to 374 // supply, for example, maas/<IP> or manual/<IP>. 375 if c.Region != "" { 376 ctx.Verbosef("interpreting %q as the cloud endpoint", c.Region) 377 cloudEndpoint = c.Region 378 c.Region = "" 379 } 380 } else if err != nil { 381 return errors.Annotatef(err, 382 "detecting regions for %q cloud provider", 383 c.Cloud, 384 ) 385 } 386 schemas := provider.CredentialSchemas() 387 authTypes := make([]jujucloud.AuthType, 0, len(schemas)) 388 for authType := range schemas { 389 authTypes = append(authTypes, authType) 390 } 391 // Since we are iterating over a map, lets sort the authTypes so 392 // they are always in a consistent order. 393 sort.Sort(jujucloud.AuthTypes(authTypes)) 394 cloud = &jujucloud.Cloud{ 395 Type: c.Cloud, 396 AuthTypes: authTypes, 397 Endpoint: cloudEndpoint, 398 Regions: regions, 399 } 400 } else if err != nil { 401 return errors.Trace(err) 402 } 403 if err := checkProviderType(cloud.Type); errors.IsNotFound(err) { 404 // This error will get handled later. 405 } else if err != nil { 406 return errors.Trace(err) 407 } 408 409 provider, err := environs.Provider(cloud.Type) 410 if err != nil { 411 return errors.Trace(err) 412 } 413 // Custom clouds may not have explicitly declared support for any auth- 414 // types, in which case we'll assume that they support everything that 415 // the provider supports. 416 if len(cloud.AuthTypes) == 0 { 417 for authType := range provider.CredentialSchemas() { 418 cloud.AuthTypes = append(cloud.AuthTypes, authType) 419 } 420 } 421 422 // Get the credentials and region name. 423 store := c.ClientStore() 424 var detectedCredentialName string 425 credential, credentialName, regionName, err := modelcmd.GetCredentials( 426 ctx, store, modelcmd.GetCredentialsParams{ 427 Cloud: *cloud, 428 CloudName: c.Cloud, 429 CloudRegion: c.Region, 430 CredentialName: c.CredentialName, 431 }, 432 ) 433 if errors.Cause(err) == modelcmd.ErrMultipleCredentials { 434 return ambiguousCredentialError 435 } 436 if errors.IsNotFound(err) && c.CredentialName == "" { 437 // No credential was explicitly specified, and no credential 438 // was found in credentials.yaml; have the provider detect 439 // credentials from the environment. 440 ctx.Verbosef("no credentials found, checking environment") 441 detected, err := modelcmd.DetectCredential(c.Cloud, cloud.Type) 442 if errors.Cause(err) == modelcmd.ErrMultipleCredentials { 443 return ambiguousDetectedCredentialError 444 } else if err != nil { 445 return errors.Trace(err) 446 } 447 // We have one credential so extract it from the map. 448 var oneCredential jujucloud.Credential 449 for detectedCredentialName, oneCredential = range detected.AuthCredentials { 450 } 451 credential = &oneCredential 452 regionName = c.Region 453 if regionName == "" { 454 regionName = detected.DefaultRegion 455 } 456 logger.Debugf( 457 "authenticating with region %q and credential %q (%v)", 458 regionName, detectedCredentialName, credential.Label, 459 ) 460 logger.Tracef("credential: %v", credential) 461 } else if err != nil { 462 return errors.Trace(err) 463 } 464 465 region, err := getRegion(cloud, c.Cloud, regionName) 466 if err != nil { 467 fmt.Fprintf(ctx.GetStderr(), 468 "%s\n\nSpecify an alternative region, or try %q.", 469 err, "juju update-clouds", 470 ) 471 return cmd.ErrSilent 472 } 473 474 controllerModelUUID, err := utils.NewUUID() 475 if err != nil { 476 return errors.Trace(err) 477 } 478 hostedModelUUID, err := utils.NewUUID() 479 if err != nil { 480 return errors.Trace(err) 481 } 482 controllerUUID, err := utils.NewUUID() 483 if err != nil { 484 return errors.Trace(err) 485 } 486 487 // Create a model config, and split out any controller 488 // and bootstrap config attributes. 489 modelConfigAttrs := map[string]interface{}{ 490 "type": cloud.Type, 491 "name": bootstrap.ControllerModelName, 492 config.UUIDKey: controllerModelUUID.String(), 493 } 494 userConfigAttrs, err := c.config.ReadAttrs(ctx) 495 if err != nil { 496 return errors.Trace(err) 497 } 498 499 // The provider may define some custom attributes specific 500 // to the provider. These will be added to the model config. 501 providerAttrs := make(map[string]interface{}) 502 if ps, ok := provider.(config.ConfigSchemaSource); ok { 503 for attr := range ps.ConfigSchema() { 504 if v, ok := userConfigAttrs[attr]; ok { 505 providerAttrs[attr] = v 506 } 507 } 508 fields := schema.FieldMap(ps.ConfigSchema(), ps.ConfigDefaults()) 509 if coercedAttrs, err := fields.Coerce(providerAttrs, nil); err != nil { 510 return errors.Annotatef(err, "invalid attribute value(s) for %v cloud", cloud.Type) 511 } else { 512 providerAttrs = coercedAttrs.(map[string]interface{}) 513 } 514 } 515 logger.Debugf("provider attrs: %v", providerAttrs) 516 for k, v := range userConfigAttrs { 517 modelConfigAttrs[k] = v 518 } 519 // Provider specific attributes are either already specified in model 520 // config (but may have been coerced), or were not present. Either way, 521 // copy them in. 522 for k, v := range providerAttrs { 523 modelConfigAttrs[k] = v 524 } 525 bootstrapConfigAttrs := make(map[string]interface{}) 526 controllerConfigAttrs := make(map[string]interface{}) 527 // Based on the attribute names in clouds.yaml, create 528 // a map of shared config for all models on this cloud. 529 inheritedControllerAttrs := make(map[string]interface{}) 530 for k, v := range cloud.Config { 531 switch { 532 case bootstrap.IsBootstrapAttribute(k): 533 bootstrapConfigAttrs[k] = v 534 continue 535 case controller.ControllerOnlyAttribute(k): 536 controllerConfigAttrs[k] = v 537 continue 538 } 539 inheritedControllerAttrs[k] = v 540 } 541 for k, v := range modelConfigAttrs { 542 switch { 543 case bootstrap.IsBootstrapAttribute(k): 544 bootstrapConfigAttrs[k] = v 545 delete(modelConfigAttrs, k) 546 case controller.ControllerOnlyAttribute(k): 547 controllerConfigAttrs[k] = v 548 delete(modelConfigAttrs, k) 549 } 550 } 551 bootstrapConfig, err := bootstrap.NewConfig(bootstrapConfigAttrs) 552 if err != nil { 553 return errors.Annotate(err, "constructing bootstrap config") 554 } 555 controllerConfig, err := controller.NewConfig( 556 controllerUUID.String(), bootstrapConfig.CACert, controllerConfigAttrs, 557 ) 558 if err != nil { 559 return errors.Annotate(err, "constructing controller config") 560 } 561 if controllerConfig.AutocertDNSName() != "" && controllerConfig.APIPort() != 443 && !c.ForceAPIPort { 562 return errors.Errorf(`autocert-dns-name is set but it's not usually possible to obtain official certificates without api-port=443 config; use --force-api-port to override this if you plan on using a port forwarder`) 563 } 564 565 if err := common.FinalizeAuthorizedKeys(ctx, modelConfigAttrs); err != nil { 566 return errors.Annotate(err, "finalizing authorized-keys") 567 } 568 logger.Debugf("preparing controller with config: %v", modelConfigAttrs) 569 570 // Read existing current controller so we can clean up on error. 571 var oldCurrentController string 572 oldCurrentController, err = store.CurrentController() 573 if errors.IsNotFound(err) { 574 oldCurrentController = "" 575 } else if err != nil { 576 return errors.Annotate(err, "error reading current controller") 577 } 578 579 defer func() { 580 if resultErr == nil || errors.IsAlreadyExists(resultErr) { 581 return 582 } 583 if oldCurrentController != "" { 584 if err := store.SetCurrentController(oldCurrentController); err != nil { 585 logger.Errorf( 586 "cannot reset current controller to %q: %v", 587 oldCurrentController, err, 588 ) 589 } 590 } 591 if err := store.RemoveController(c.controllerName); err != nil { 592 logger.Errorf( 593 "cannot destroy newly created controller %q details: %v", 594 c.controllerName, err, 595 ) 596 } 597 }() 598 599 bootstrapModelConfig := make(map[string]interface{}) 600 for k, v := range inheritedControllerAttrs { 601 bootstrapModelConfig[k] = v 602 } 603 for k, v := range modelConfigAttrs { 604 bootstrapModelConfig[k] = v 605 } 606 // Add in any default attribute values if not already 607 // specified, making the recorded bootstrap config 608 // immutable to changes in Juju. 609 for k, v := range config.ConfigDefaults() { 610 if _, ok := bootstrapModelConfig[k]; !ok { 611 bootstrapModelConfig[k] = v 612 } 613 } 614 615 environ, err := bootstrapPrepare( 616 modelcmd.BootstrapContext(ctx), store, 617 bootstrap.PrepareParams{ 618 ModelConfig: bootstrapModelConfig, 619 ControllerConfig: controllerConfig, 620 ControllerName: c.controllerName, 621 Cloud: environs.CloudSpec{ 622 Type: cloud.Type, 623 Name: c.Cloud, 624 Region: region.Name, 625 Endpoint: region.Endpoint, 626 IdentityEndpoint: region.IdentityEndpoint, 627 StorageEndpoint: region.StorageEndpoint, 628 Credential: credential, 629 }, 630 CredentialName: credentialName, 631 AdminSecret: bootstrapConfig.AdminSecret, 632 }, 633 ) 634 if err != nil { 635 return errors.Trace(err) 636 } 637 638 // Set the current model to the initial hosted model. 639 if err := store.UpdateModel(c.controllerName, c.hostedModelName, jujuclient.ModelDetails{ 640 hostedModelUUID.String(), 641 }); err != nil { 642 return errors.Trace(err) 643 } 644 if err := store.SetCurrentModel(c.controllerName, c.hostedModelName); err != nil { 645 return errors.Trace(err) 646 } 647 648 // Set the current controller so "juju status" can be run while 649 // bootstrapping is underway. 650 if err := store.SetCurrentController(c.controllerName); err != nil { 651 return errors.Trace(err) 652 } 653 654 cloudRegion := c.Cloud 655 if region.Name != "" { 656 cloudRegion = fmt.Sprintf("%s/%s", cloudRegion, region.Name) 657 } 658 ctx.Infof( 659 "Creating Juju controller %q on %s", 660 c.controllerName, cloudRegion, 661 ) 662 663 // If we error out for any reason, clean up the environment. 664 defer func() { 665 if resultErr != nil { 666 if c.KeepBrokenEnvironment { 667 ctx.Infof(` 668 bootstrap failed but --keep-broken was specified so resources are not being destroyed. 669 When you have finished diagnosing the problem, remember to clean up the failed controller. 670 See `[1:] + "`juju kill-controller`" + `.`) 671 } else { 672 handleBootstrapError(ctx, resultErr, func() error { 673 return environsDestroy( 674 c.controllerName, environ, store, 675 ) 676 }) 677 } 678 } 679 }() 680 681 // Block interruption during bootstrap. Providers may also 682 // register for interrupt notification so they can exit early. 683 interrupted := make(chan os.Signal, 1) 684 defer close(interrupted) 685 ctx.InterruptNotify(interrupted) 686 defer ctx.StopInterruptNotify(interrupted) 687 go func() { 688 for _ = range interrupted { 689 ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") 690 } 691 }() 692 693 // If --metadata-source is specified, override the default tools metadata source so 694 // SyncTools can use it, and also upload any image metadata. 695 var metadataDir string 696 if c.MetadataSource != "" { 697 metadataDir = ctx.AbsPath(c.MetadataSource) 698 } 699 700 // Merge environ and bootstrap-specific constraints. 701 constraintsValidator, err := environ.ConstraintsValidator() 702 if err != nil { 703 return errors.Trace(err) 704 } 705 bootstrapConstraints, err := constraintsValidator.Merge( 706 c.Constraints, c.BootstrapConstraints, 707 ) 708 if err != nil { 709 return errors.Trace(err) 710 } 711 logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints) 712 713 hostedModelConfig := map[string]interface{}{ 714 "name": c.hostedModelName, 715 config.UUIDKey: hostedModelUUID.String(), 716 } 717 for k, v := range inheritedControllerAttrs { 718 hostedModelConfig[k] = v 719 } 720 721 // We copy across any user supplied attributes to the hosted model config. 722 // But only if the attributes have not been removed from the controller 723 // model config as part of preparing the controller model. 724 controllerModelConfigAttrs := environ.Config().AllAttrs() 725 for k, v := range userConfigAttrs { 726 if _, ok := controllerModelConfigAttrs[k]; ok { 727 hostedModelConfig[k] = v 728 } 729 } 730 // Ensure that certain config attributes are not included in the hosted 731 // model config. These attributes may be modified during bootstrap; by 732 // removing them from this map, we ensure the modified values are 733 // inherited. 734 delete(hostedModelConfig, config.AuthorizedKeysKey) 735 delete(hostedModelConfig, config.AgentVersionKey) 736 737 // Check whether the Juju GUI must be installed in the controller. 738 // Leaving this value empty means no GUI will be installed. 739 var guiDataSourceBaseURL string 740 if !c.noGUI { 741 guiDataSourceBaseURL = common.GUIDataSourceBaseURL() 742 } 743 744 if credentialName == "" { 745 // credentialName will be empty if the credential was detected. 746 // We must supply a name for the credential in the database, 747 // so choose one. 748 credentialName = detectedCredentialName 749 } 750 751 err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{ 752 ModelConstraints: c.Constraints, 753 BootstrapConstraints: bootstrapConstraints, 754 BootstrapSeries: c.BootstrapSeries, 755 BootstrapImage: c.BootstrapImage, 756 Placement: c.Placement, 757 BuildAgent: c.BuildAgent, 758 BuildAgentTarball: sync.BuildAgentTarball, 759 AgentVersion: c.AgentVersion, 760 MetadataDir: metadataDir, 761 Cloud: *cloud, 762 CloudName: c.Cloud, 763 CloudRegion: region.Name, 764 CloudCredential: credential, 765 CloudCredentialName: credentialName, 766 ControllerConfig: controllerConfig, 767 ControllerInheritedConfig: inheritedControllerAttrs, 768 RegionInheritedConfig: cloud.RegionConfig, 769 HostedModelConfig: hostedModelConfig, 770 GUIDataSourceBaseURL: guiDataSourceBaseURL, 771 AdminSecret: bootstrapConfig.AdminSecret, 772 CAPrivateKey: bootstrapConfig.CAPrivateKey, 773 DialOpts: environs.BootstrapDialOpts{ 774 Timeout: bootstrapConfig.BootstrapTimeout, 775 RetryDelay: bootstrapConfig.BootstrapRetryDelay, 776 AddressesDelay: bootstrapConfig.BootstrapAddressesDelay, 777 }, 778 }) 779 if err != nil { 780 return errors.Annotate(err, "failed to bootstrap model") 781 } 782 783 if err := c.SetModelName(modelcmd.JoinModelName(c.controllerName, c.hostedModelName)); err != nil { 784 return errors.Trace(err) 785 } 786 787 agentVersion := jujuversion.Current 788 if c.AgentVersion != nil { 789 agentVersion = *c.AgentVersion 790 } 791 err = common.SetBootstrapEndpointAddress(c.ClientStore(), c.controllerName, agentVersion, controllerConfig.APIPort(), environ) 792 if err != nil { 793 return errors.Annotate(err, "saving bootstrap endpoint address") 794 } 795 796 // To avoid race conditions when running scripted bootstraps, wait 797 // for the controller's machine agent to be ready to accept commands 798 // before exiting this bootstrap command. 799 return waitForAgentInitialisation(ctx, &c.ModelCommandBase, c.controllerName, c.hostedModelName) 800 } 801 802 // runInteractive queries the user about bootstrap config interactively at the 803 // command prompt. 804 func (c *bootstrapCommand) runInteractive(ctx *cmd.Context) error { 805 scanner := bufio.NewScanner(ctx.Stdin) 806 clouds, err := assembleClouds() 807 if err != nil { 808 return errors.Trace(err) 809 } 810 c.Cloud, err = queryCloud(clouds, jujucloud.DefaultLXD, scanner, ctx.Stdout) 811 if err != nil { 812 return errors.Trace(err) 813 } 814 cloud, err := jujucloud.CloudByName(c.Cloud) 815 if err != nil { 816 return errors.Trace(err) 817 } 818 819 switch len(cloud.Regions) { 820 case 0: 821 // No region to choose, nothing to do. 822 case 1: 823 // If there's just one, don't prompt, just use it. 824 c.Region = cloud.Regions[0].Name 825 default: 826 c.Region, err = queryRegion(c.Cloud, cloud.Regions, scanner, ctx.Stdout) 827 if err != nil { 828 return errors.Trace(err) 829 } 830 } 831 832 var username string 833 if u, err := user.Current(); err == nil { 834 username = u.Username 835 } 836 defName := defaultControllerName(username, c.Cloud, c.Region, cloud) 837 838 c.controllerName, err = queryName(defName, scanner, ctx.Stdout) 839 if err != nil { 840 return errors.Trace(err) 841 } 842 return nil 843 } 844 845 // getRegion returns the cloud.Region to use, based on the specified 846 // region name. If no region name is specified, and there is at least 847 // one region, we use the first region in the list. 848 func getRegion(cloud *jujucloud.Cloud, cloudName, regionName string) (jujucloud.Region, error) { 849 if regionName != "" { 850 region, err := jujucloud.RegionByName(cloud.Regions, regionName) 851 if err != nil { 852 return jujucloud.Region{}, errors.Trace(err) 853 } 854 return *region, nil 855 } 856 if len(cloud.Regions) > 0 { 857 // No region was specified, use the first region in the list. 858 return cloud.Regions[0], nil 859 } 860 return jujucloud.Region{ 861 "", // no region name 862 cloud.Endpoint, 863 cloud.IdentityEndpoint, 864 cloud.StorageEndpoint, 865 }, nil 866 } 867 868 // checkProviderType ensures the provider type is okay. 869 func checkProviderType(envType string) error { 870 featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey) 871 flag, ok := provisionalProviders[envType] 872 if ok && !featureflag.Enabled(flag) { 873 msg := `the %q provider is provisional in this version of Juju. To use it anyway, set JUJU_DEV_FEATURE_FLAGS="%s" in your shell model` 874 return errors.Errorf(msg, envType, flag) 875 } 876 return nil 877 } 878 879 // handleBootstrapError is called to clean up if bootstrap fails. 880 func handleBootstrapError(ctx *cmd.Context, err error, cleanup func() error) { 881 ch := make(chan os.Signal, 1) 882 ctx.InterruptNotify(ch) 883 defer ctx.StopInterruptNotify(ch) 884 defer close(ch) 885 go func() { 886 for _ = range ch { 887 fmt.Fprintln(ctx.GetStderr(), "Cleaning up failed bootstrap") 888 } 889 }() 890 if err := cleanup(); err != nil { 891 logger.Errorf("error cleaning up: %v", err) 892 } 893 }