github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "sort" 11 "strings" 12 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 "github.com/juju/gnuflag" 16 "github.com/juju/schema" 17 "github.com/juju/utils" 18 "github.com/juju/utils/featureflag" 19 "github.com/juju/version" 20 "gopkg.in/juju/charm.v6" 21 "gopkg.in/juju/names.v2" 22 23 jujucloud "github.com/juju/juju/cloud" 24 jujucmd "github.com/juju/juju/cmd" 25 "github.com/juju/juju/cmd/juju/common" 26 "github.com/juju/juju/cmd/modelcmd" 27 "github.com/juju/juju/controller" 28 "github.com/juju/juju/core/constraints" 29 "github.com/juju/juju/core/instance" 30 "github.com/juju/juju/core/model" 31 "github.com/juju/juju/environs" 32 "github.com/juju/juju/environs/bootstrap" 33 "github.com/juju/juju/environs/config" 34 "github.com/juju/juju/environs/context" 35 "github.com/juju/juju/environs/sync" 36 "github.com/juju/juju/feature" 37 "github.com/juju/juju/juju" 38 "github.com/juju/juju/juju/osenv" 39 "github.com/juju/juju/jujuclient" 40 "github.com/juju/juju/network" 41 "github.com/juju/juju/provider/lxd/lxdnames" 42 jujuversion "github.com/juju/juju/version" 43 ) 44 45 // provisionalProviders is the names of providers that are hidden behind 46 // feature flags. 47 var provisionalProviders = map[string]string{} 48 49 var usageBootstrapSummary = ` 50 Initializes a cloud environment.`[1:] 51 52 var usageBootstrapDetails = ` 53 Used without arguments, bootstrap will step you through the process of 54 initializing a Juju cloud environment. Initialization consists of creating 55 a 'controller' model and provisioning a machine to act as controller. 56 57 We recommend you call your controller ‘username-region’ e.g. ‘fred-us-east-1’ 58 See --clouds for a list of clouds and credentials. 59 See --regions <cloud> for a list of available regions for a given cloud. 60 61 Credentials are set beforehand and are distinct from any other 62 configuration (see `[1:] + "`juju add-credential`" + `). 63 The 'controller' model typically does not run workloads. It should remain 64 pristine to run and manage Juju's own infrastructure for the corresponding 65 cloud. Additional (hosted) models should be created with ` + "`juju create-\nmodel`" + ` for workload purposes. 66 Note that a 'default' model is also created and becomes the current model 67 of the environment once the command completes. It can be discarded if 68 other models are created. 69 70 If '--bootstrap-constraints' is used, its values will also apply to any 71 future controllers provisioned for high availability (HA). 72 73 If '--constraints' is used, its values will be set as the default 74 constraints for all future workload machines in the model, exactly as if 75 the constraints were set with ` + "`juju set-model-constraints`" + `. 76 77 It is possible to override constraints and the automatic machine selection 78 algorithm by assigning a "placement directive" via the '--to' option. This 79 dictates what machine to use for the controller. This would typically be 80 used with the MAAS provider ('--to <host>.maas'). 81 82 Available keys for use with --config can be found here: 83 https://jujucharms.com/stable/controllers-config 84 https://jujucharms.com/stable/models-config 85 86 You can change the default timeout and retry delays used during the 87 bootstrap by changing the following settings in your configuration 88 (all values represent number of seconds): 89 # How long to wait for a connection to the controller 90 bootstrap-timeout: 600 # default: 10 minutes 91 # How long to wait between connection attempts to a controller 92 address. 93 bootstrap-retry-delay: 5 # default: 5 seconds 94 # How often to refresh controller addresses from the API server. 95 bootstrap-addresses-delay: 10 # default: 10 seconds 96 97 Private clouds may need to specify their own custom image metadata and 98 tools/agent. Use '--metadata-source' whose value is a local directory. 99 100 By default, the Juju version of the agent binary that is downloaded and 101 installed on all models for the new controller will be the same as that 102 of the Juju client used to perform the bootstrap. 103 However, a user can specify a different agent version via '--agent-version' 104 option to bootstrap command. Juju will use this version for models' agents 105 as long as the client's version is from the same Juju release series. 106 In other words, a 2.2.1 client can bootstrap any 2.2.x agents but cannot 107 bootstrap any 2.0.x or 2.1.x agents. 108 The agent version can be specified a simple numeric version, e.g. 2.2.4. 109 110 For example, at the time when 2.3.0, 2.3.1 and 2.3.2 are released and your 111 agent stream is 'released' (default), then a 2.3.1 client can bootstrap: 112 * 2.3.0 controller by running '... bootstrap --agent-version=2.3.0 ...'; 113 * 2.3.1 controller by running '... bootstrap ...'; 114 * 2.3.2 controller by running 'bootstrap --auto-upgrade'. 115 However, if this client has a copy of codebase, then a local copy of Juju 116 will be built and bootstrapped - 2.3.1.1. 117 118 Examples: 119 juju bootstrap 120 juju bootstrap --clouds 121 juju bootstrap --regions aws 122 juju bootstrap aws 123 juju bootstrap aws/us-east-1 124 juju bootstrap google joe-us-east1 125 juju bootstrap --config=~/config-rs.yaml rackspace joe-syd 126 juju bootstrap --agent-version=2.2.4 aws joe-us-east-1 127 juju bootstrap --config bootstrap-timeout=1200 azure joe-eastus 128 129 See also: 130 add-credentials 131 add-model 132 controller-config 133 model-config 134 set-constraints 135 show-cloud` 136 137 // defaultHostedModelName is the name of the hosted model created in each 138 // controller for deploying workloads to, in addition to the "controller" model. 139 const defaultHostedModelName = "default" 140 141 func newBootstrapCommand() cmd.Command { 142 command := &bootstrapCommand{} 143 command.CanClearCurrentModel = true 144 return modelcmd.Wrap(command, 145 modelcmd.WrapSkipModelFlags, 146 modelcmd.WrapSkipDefaultModel, 147 ) 148 } 149 150 // bootstrapCommand is responsible for launching the first machine in a juju 151 // environment, and setting up everything necessary to continue working. 152 type bootstrapCommand struct { 153 modelcmd.ModelCommandBase 154 155 Constraints constraints.Value 156 ConstraintsStr string 157 BootstrapConstraints constraints.Value 158 BootstrapConstraintsStr string 159 BootstrapSeries string 160 BootstrapImage string 161 BuildAgent bool 162 MetadataSource string 163 Placement string 164 KeepBrokenEnvironment bool 165 AutoUpgrade bool 166 AgentVersionParam string 167 AgentVersion *version.Number 168 config common.ConfigFlag 169 modelDefaults common.ConfigFlag 170 171 showClouds bool 172 showRegionsForCloud string 173 controllerName string 174 hostedModelName string 175 CredentialName string 176 Cloud string 177 Region string 178 noGUI bool 179 noSwitch bool 180 interactive bool 181 } 182 183 func (c *bootstrapCommand) Info() *cmd.Info { 184 return jujucmd.Info(&cmd.Info{ 185 Name: "bootstrap", 186 Args: "[<cloud name>[/region] [<controller name>]]", 187 Purpose: usageBootstrapSummary, 188 Doc: usageBootstrapDetails, 189 }) 190 } 191 192 func (c *bootstrapCommand) SetFlags(f *gnuflag.FlagSet) { 193 c.ModelCommandBase.SetFlags(f) 194 f.StringVar(&c.ConstraintsStr, "constraints", "", "Set model constraints") 195 f.StringVar(&c.BootstrapConstraintsStr, "bootstrap-constraints", "", "Specify bootstrap machine constraints") 196 f.StringVar(&c.BootstrapSeries, "bootstrap-series", "", "Specify the series of the bootstrap machine") 197 if featureflag.Enabled(feature.ImageMetadata) { 198 f.StringVar(&c.BootstrapImage, "bootstrap-image", "", "Specify the image of the bootstrap machine") 199 } 200 f.BoolVar(&c.BuildAgent, "build-agent", false, "Build local version of agent binary before bootstrapping") 201 f.StringVar(&c.MetadataSource, "metadata-source", "", "Local path to use as agent and/or image metadata source") 202 f.StringVar(&c.Placement, "to", "", "Placement directive indicating an instance to bootstrap") 203 f.BoolVar(&c.KeepBrokenEnvironment, "keep-broken", false, "Do not destroy the model if bootstrap fails") 204 f.BoolVar(&c.AutoUpgrade, "auto-upgrade", false, "After bootstrap, upgrade to the latest patch release") 205 f.StringVar(&c.AgentVersionParam, "agent-version", "", "Version of agent binaries to use for Juju agents") 206 f.StringVar(&c.CredentialName, "credential", "", "Credentials to use when bootstrapping") 207 f.Var(&c.config, "config", "Specify a controller configuration file, or one or more configuration\n options\n (--config config.yaml [--config key=value ...])") 208 f.Var(&c.modelDefaults, "model-default", "Specify a configuration file, or one or more configuration\n options to be set for all models, unless otherwise specified\n (--model-default config.yaml [--model-default key=value ...])") 209 f.StringVar(&c.hostedModelName, "d", defaultHostedModelName, "Name of the default hosted model for the controller") 210 f.StringVar(&c.hostedModelName, "default-model", defaultHostedModelName, "Name of the default hosted model for the controller") 211 f.BoolVar(&c.showClouds, "clouds", false, "Print the available clouds which can be used to bootstrap a Juju environment") 212 f.StringVar(&c.showRegionsForCloud, "regions", "", "Print the available regions for the specified cloud") 213 f.BoolVar(&c.noGUI, "no-gui", false, "Do not install the Juju GUI in the controller when bootstrapping") 214 f.BoolVar(&c.noSwitch, "no-switch", false, "Do not switch to the newly created controller") 215 } 216 217 func (c *bootstrapCommand) Init(args []string) (err error) { 218 if c.showClouds && c.showRegionsForCloud != "" { 219 return errors.New("--clouds and --regions can't be used together") 220 } 221 if c.showClouds { 222 return cmd.CheckEmpty(args) 223 } 224 if c.showRegionsForCloud != "" { 225 return cmd.CheckEmpty(args) 226 } 227 if c.AgentVersionParam != "" && c.BuildAgent { 228 return errors.New("--agent-version and --build-agent can't be used together") 229 } 230 if c.BootstrapSeries != "" && !charm.IsValidSeries(c.BootstrapSeries) { 231 return errors.NotValidf("series %q", c.BootstrapSeries) 232 } 233 234 /* controller is the name of controller created for internal juju management */ 235 if c.hostedModelName == "controller" { 236 return errors.New(" 'controller' name is already assigned to juju internal management model") 237 } 238 239 // Parse the placement directive. Bootstrap currently only 240 // supports provider-specific placement directives. 241 if c.Placement != "" { 242 _, err = instance.ParsePlacement(c.Placement) 243 if err != instance.ErrPlacementScopeMissing { 244 // We only support unscoped placement directives for bootstrap. 245 return errors.Errorf("unsupported bootstrap placement directive %q", c.Placement) 246 } 247 } 248 if !c.AutoUpgrade { 249 // With no auto upgrade chosen, we default to the version matching the bootstrap client. 250 vers := jujuversion.Current 251 c.AgentVersion = &vers 252 } 253 if c.AgentVersionParam != "" { 254 if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil { 255 c.AgentVersion = &vers.Number 256 } else if vers, err := version.Parse(c.AgentVersionParam); err == nil { 257 c.AgentVersion = &vers 258 } else { 259 return err 260 } 261 } 262 if c.AgentVersion != nil && (c.AgentVersion.Major != jujuversion.Current.Major || c.AgentVersion.Minor != jujuversion.Current.Minor) { 263 return errors.Errorf("this client can only bootstrap %v.%v agents", jujuversion.Current.Major, jujuversion.Current.Minor) 264 } 265 266 switch len(args) { 267 case 0: 268 // no args or flags, go interactive. 269 c.interactive = true 270 return nil 271 } 272 c.Cloud = args[0] 273 if i := strings.IndexRune(c.Cloud, '/'); i > 0 { 274 c.Cloud, c.Region = c.Cloud[:i], c.Cloud[i+1:] 275 } 276 if ok := names.IsValidCloud(c.Cloud); !ok { 277 return errors.NotValidf("cloud name %q", c.Cloud) 278 } 279 if len(args) > 1 { 280 c.controllerName = args[1] 281 return cmd.CheckEmpty(args[2:]) 282 } 283 return nil 284 } 285 286 // BootstrapInterface provides bootstrap functionality that Run calls to support cleaner testing. 287 type BootstrapInterface interface { 288 // Bootstrap bootstraps a controller. 289 Bootstrap(ctx environs.BootstrapContext, environ environs.BootstrapEnviron, callCtx context.ProviderCallContext, args bootstrap.BootstrapParams) error 290 291 // CloudDetector returns a CloudDetector for the given provider, 292 // if the provider supports it. 293 CloudDetector(environs.EnvironProvider) (environs.CloudDetector, bool) 294 295 // CloudRegionDetector returns a CloudRegionDetector for the given provider, 296 // if the provider supports it. 297 CloudRegionDetector(environs.EnvironProvider) (environs.CloudRegionDetector, bool) 298 299 // CloudFinalizer returns a CloudFinalizer for the given provider, 300 // if the provider supports it. 301 CloudFinalizer(environs.EnvironProvider) (environs.CloudFinalizer, bool) 302 } 303 304 type bootstrapFuncs struct{} 305 306 func (b bootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.BootstrapEnviron, callCtx context.ProviderCallContext, args bootstrap.BootstrapParams) error { 307 return bootstrap.Bootstrap(ctx, env, callCtx, args) 308 } 309 310 func (b bootstrapFuncs) CloudDetector(provider environs.EnvironProvider) (environs.CloudDetector, bool) { 311 detector, ok := provider.(environs.CloudDetector) 312 return detector, ok 313 } 314 315 func (b bootstrapFuncs) CloudRegionDetector(provider environs.EnvironProvider) (environs.CloudRegionDetector, bool) { 316 detector, ok := provider.(environs.CloudRegionDetector) 317 return detector, ok 318 } 319 320 func (b bootstrapFuncs) CloudFinalizer(provider environs.EnvironProvider) (environs.CloudFinalizer, bool) { 321 finalizer, ok := provider.(environs.CloudFinalizer) 322 return finalizer, ok 323 } 324 325 var getBootstrapFuncs = func() BootstrapInterface { 326 return &bootstrapFuncs{} 327 } 328 329 var ( 330 bootstrapPrepareController = bootstrap.PrepareController 331 environsDestroy = environs.Destroy 332 waitForAgentInitialisation = common.WaitForAgentInitialisation 333 ) 334 335 var ambiguousDetectedCredentialError = errors.New(` 336 more than one credential detected 337 run juju autoload-credentials and specify a credential using the --credential argument`[1:], 338 ) 339 340 var ambiguousCredentialError = errors.New(` 341 more than one credential is available 342 specify a credential using the --credential argument`[1:], 343 ) 344 345 func (c *bootstrapCommand) parseConstraints(ctx *cmd.Context) (err error) { 346 allAliases := map[string]string{} 347 defer common.WarnConstraintAliases(ctx, allAliases) 348 if c.ConstraintsStr != "" { 349 cons, aliases, err := constraints.ParseWithAliases(c.ConstraintsStr) 350 for k, v := range aliases { 351 allAliases[k] = v 352 } 353 if err != nil { 354 return err 355 } 356 c.Constraints = cons 357 } 358 if c.BootstrapConstraintsStr != "" { 359 cons, aliases, err := constraints.ParseWithAliases(c.BootstrapConstraintsStr) 360 for k, v := range aliases { 361 allAliases[k] = v 362 } 363 if err != nil { 364 return err 365 } 366 c.BootstrapConstraints = cons 367 } 368 return nil 369 } 370 371 // Run connects to the environment specified on the command line and bootstraps 372 // a juju in that environment if none already exists. If there is as yet no environments.yaml file, 373 // the user is informed how to create one. 374 func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { 375 defer func() { 376 resultErr = handleChooseCloudRegionError(ctx, resultErr) 377 }() 378 379 if err := c.parseConstraints(ctx); err != nil { 380 return err 381 } 382 383 // Start by checking for usage errors, requests for information 384 finished, err := c.handleCommandLineErrorsAndInfoRequests(ctx) 385 if err != nil { 386 return errors.Trace(err) 387 } 388 if finished { 389 return nil 390 } 391 392 // Run interactive bootstrap if needed/asked for 393 if c.interactive { 394 if err := c.runInteractive(ctx); err != nil { 395 return errors.Trace(err) 396 } 397 // now run normal bootstrap using info gained above. 398 } 399 400 cloud, provider, err := c.cloud(ctx) 401 if err != nil { 402 return errors.Trace(err) 403 } 404 405 isCAASController := jujucloud.CloudIsCAAS(cloud) 406 407 if isCAASController && !featureflag.Enabled(feature.DeveloperMode) { 408 return errors.NotSupportedf("bootstrap to kubernetes cluster") 409 } 410 411 // Custom clouds may not have explicitly declared support for any auth- 412 // types, in which case we'll assume that they support everything that 413 // the provider supports. 414 if len(cloud.AuthTypes) == 0 { 415 for authType := range provider.CredentialSchemas() { 416 cloud.AuthTypes = append(cloud.AuthTypes, authType) 417 } 418 } 419 420 credentials, regionName, err := c.credentialsAndRegionName(ctx, provider, cloud) 421 if err != nil { 422 if errors.IsNotFound(err) { 423 err = errors.NewNotFound(nil, fmt.Sprintf("%v\nSee `juju add-credential %s --help` for instructions", err, cloud.Name)) 424 } 425 426 if err == cmd.ErrSilent { 427 return err 428 } 429 return errors.Trace(err) 430 } 431 432 cloudCallCtx := context.NewCloudCallContext() 433 // At this stage, the credential we intend to use is not yet stored 434 // server-side. So, if the credential is not accepted by the provider, 435 // we cannot mark it as invalid, just log it as an informative message. 436 cloudCallCtx.InvalidateCredentialFunc = func(reason string) error { 437 ctx.Infof("Cloud credential %q is not accepted by cloud provider: %v", credentials.name, reason) 438 return nil 439 } 440 441 region, err := common.ChooseCloudRegion(cloud, regionName) 442 if err != nil { 443 return errors.Trace(err) 444 } 445 if c.controllerName == "" { 446 c.controllerName = defaultControllerName(cloud.Name, region.Name) 447 } 448 449 // set a Region so it's config can be found below. 450 if c.Region == "" { 451 c.Region = region.Name 452 } 453 454 config, err := c.bootstrapConfigs(ctx, cloud, provider) 455 if err != nil { 456 return errors.Trace(err) 457 } 458 459 // Read existing current controller so we can clean up on error. 460 var oldCurrentController string 461 store := c.ClientStore() 462 oldCurrentController, err = store.CurrentController() 463 if errors.IsNotFound(err) { 464 oldCurrentController = "" 465 } else if err != nil { 466 return errors.Annotate(err, "error reading current controller") 467 } 468 469 defer func() { 470 if resultErr == nil || errors.IsAlreadyExists(resultErr) { 471 return 472 } 473 if oldCurrentController != "" { 474 if err := store.SetCurrentController(oldCurrentController); err != nil { 475 logger.Errorf( 476 "cannot reset current controller to %q: %v", 477 oldCurrentController, err, 478 ) 479 } 480 } 481 if err := store.RemoveController(c.controllerName); err != nil { 482 logger.Errorf( 483 "cannot destroy newly created controller %q details: %v", 484 c.controllerName, err, 485 ) 486 } 487 }() 488 489 bootstrapCtx := modelcmd.BootstrapContext(ctx) 490 bootstrapPrepareParams := bootstrap.PrepareParams{ 491 ModelConfig: config.bootstrapModel, 492 ControllerConfig: config.controller, 493 ControllerName: c.controllerName, 494 Cloud: environs.CloudSpec{ 495 Type: cloud.Type, 496 Name: cloud.Name, 497 Region: region.Name, 498 Endpoint: region.Endpoint, 499 IdentityEndpoint: region.IdentityEndpoint, 500 StorageEndpoint: region.StorageEndpoint, 501 Credential: credentials.credential, 502 CACertificates: cloud.CACertificates, 503 }, 504 CredentialName: credentials.name, 505 AdminSecret: config.bootstrap.AdminSecret, 506 } 507 bootstrapParams := bootstrap.BootstrapParams{ 508 BootstrapSeries: c.BootstrapSeries, 509 BootstrapImage: c.BootstrapImage, 510 Placement: c.Placement, 511 BuildAgent: c.BuildAgent, 512 BuildAgentTarball: sync.BuildAgentTarball, 513 AgentVersion: c.AgentVersion, 514 Cloud: cloud, 515 CloudRegion: region.Name, 516 ControllerConfig: config.controller, 517 ControllerInheritedConfig: config.inheritedControllerAttrs, 518 RegionInheritedConfig: cloud.RegionConfig, 519 AdminSecret: config.bootstrap.AdminSecret, 520 CAPrivateKey: config.bootstrap.CAPrivateKey, 521 DialOpts: environs.BootstrapDialOpts{ 522 Timeout: config.bootstrap.BootstrapTimeout, 523 RetryDelay: config.bootstrap.BootstrapRetryDelay, 524 AddressesDelay: config.bootstrap.BootstrapAddressesDelay, 525 }, 526 } 527 528 environ, err := bootstrapPrepareController( 529 isCAASController, bootstrapCtx, store, bootstrapPrepareParams, 530 ) 531 if err != nil { 532 return errors.Trace(err) 533 } 534 535 if isCAASController { 536 if !c.noSwitch { 537 if err := store.SetCurrentController(c.controllerName); err != nil { 538 return errors.Trace(err) 539 } 540 } 541 } else { 542 543 // only IAAS has hosted model. 544 hostedModelUUID, err := utils.NewUUID() 545 if err != nil { 546 return errors.Trace(err) 547 } 548 549 // Set the current model to the initial hosted model. 550 modelDetails := jujuclient.ModelDetails{ 551 ModelUUID: hostedModelUUID.String(), 552 ModelType: model.IAAS, 553 } 554 if featureflag.Enabled(feature.Generations) { 555 modelDetails.ModelGeneration = model.GenerationCurrent 556 } 557 if err := store.UpdateModel( 558 c.controllerName, 559 c.hostedModelName, 560 modelDetails, 561 ); err != nil { 562 return errors.Trace(err) 563 } 564 565 if !c.noSwitch { 566 if err := store.SetCurrentModel(c.controllerName, c.hostedModelName); err != nil { 567 return errors.Trace(err) 568 } 569 if err := store.SetCurrentController(c.controllerName); err != nil { 570 return errors.Trace(err) 571 } 572 } 573 574 bootstrapParams.HostedModelConfig = c.hostedModelConfig( 575 hostedModelUUID, config.inheritedControllerAttrs, config.userConfigAttrs, environ, 576 ) 577 } 578 579 cloudRegion := c.Cloud 580 if region.Name != "" { 581 cloudRegion = fmt.Sprintf("%s/%s", cloudRegion, region.Name) 582 } 583 ctx.Infof( 584 "Creating Juju controller %q on %s", 585 c.controllerName, cloudRegion, 586 ) 587 588 // If we error out for any reason, clean up the environment. 589 defer func() { 590 if resultErr != nil { 591 if c.KeepBrokenEnvironment { 592 ctx.Infof(` 593 bootstrap failed but --keep-broken was specified. 594 This means that cloud resources are left behind, but not registered to 595 your local client, as the controller was not successfully created. 596 However, you should be able to ssh into the machine using the user "ubuntu" and 597 their IP address for diagnosis and investigation. 598 When you are ready to clean up the failed controller, use your cloud console or 599 equivalent CLI tools to terminate the instances and remove remaining resources. 600 601 See `[1:] + "`juju kill-controller`" + `.`) 602 } else { 603 logger.Errorf("%v", resultErr) 604 logger.Debugf("(error details: %v)", errors.Details(resultErr)) 605 // Set resultErr to cmd.ErrSilent to prevent 606 // logging the error twice. 607 resultErr = cmd.ErrSilent 608 handleBootstrapError(ctx, func() error { 609 return environsDestroy( 610 c.controllerName, environ, cloudCallCtx, store, 611 ) 612 }) 613 } 614 } 615 }() 616 617 // Block interruption during bootstrap. Providers may also 618 // register for interrupt notification so they can exit early. 619 interrupted := make(chan os.Signal, 1) 620 defer close(interrupted) 621 ctx.InterruptNotify(interrupted) 622 defer ctx.StopInterruptNotify(interrupted) 623 go func() { 624 for range interrupted { 625 ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") 626 } 627 }() 628 629 // If --metadata-source is specified, override the default tools metadata source so 630 // SyncTools can use it, and also upload any image metadata. 631 if c.MetadataSource != "" { 632 bootstrapParams.MetadataDir = ctx.AbsPath(c.MetadataSource) 633 } 634 635 constraintsValidator, err := environ.ConstraintsValidator(cloudCallCtx) 636 if err != nil { 637 return errors.Trace(err) 638 } 639 640 // Merge in any space constraints that should be implied from controller 641 // space config. 642 // Do it before calling merge, because the constraints will be validated 643 // there. 644 constraints := c.Constraints 645 constraints.Spaces = config.controller.AsSpaceConstraints(constraints.Spaces) 646 647 // Merge environ and bootstrap-specific constraints. 648 bootstrapParams.BootstrapConstraints, err = constraintsValidator.Merge(constraints, c.BootstrapConstraints) 649 if err != nil { 650 return errors.Trace(err) 651 } 652 logger.Infof("combined bootstrap constraints: %v", bootstrapParams.BootstrapConstraints) 653 654 bootstrapParams.ModelConstraints = c.Constraints 655 656 // Check whether the Juju GUI must be installed in the controller. 657 // Leaving this value empty means no GUI will be installed. 658 if !c.noGUI { 659 bootstrapParams.GUIDataSourceBaseURL = common.GUIDataSourceBaseURL() 660 } 661 662 if credentials.name == "" { 663 // credentialName will be empty if the credential was detected. 664 // We must supply a name for the credential in the database, 665 // so choose one. 666 credentials.name = credentials.detectedName 667 } 668 bootstrapParams.CloudCredential = credentials.credential 669 bootstrapParams.CloudCredentialName = credentials.name 670 671 bootstrapFuncs := getBootstrapFuncs() 672 if err = bootstrapFuncs.Bootstrap( 673 modelcmd.BootstrapContext(ctx), 674 environ, 675 cloudCallCtx, 676 bootstrapParams, 677 ); err != nil { 678 return errors.Annotate(err, "failed to bootstrap model") 679 } 680 681 if isCAASController { 682 // TODO(caas): wait and fetch controller public endpoint then update juju home 683 return nil 684 } 685 686 if err = c.SetModelName(modelcmd.JoinModelName(c.controllerName, c.hostedModelName), false); err != nil { 687 return errors.Trace(err) 688 } 689 690 agentVersion := jujuversion.Current 691 if c.AgentVersion != nil { 692 agentVersion = *c.AgentVersion 693 } 694 var addrs []network.Address 695 if env, ok := environ.(environs.InstanceBroker); ok { 696 addrs, err = common.BootstrapEndpointAddresses(env, cloudCallCtx) 697 if err != nil { 698 return errors.Trace(err) 699 } 700 } else { 701 // TODO(caas): this should never happen. but we need enhance here with the above TODO solved together 702 return errors.NewNotValid(nil, "unexpected error happened, IAAS mode should have environs.Environ implemented.") 703 } 704 if err := juju.UpdateControllerDetailsFromLogin( 705 c.ClientStore(), 706 c.controllerName, 707 juju.UpdateControllerParams{ 708 AgentVersion: agentVersion.String(), 709 CurrentHostPorts: [][]network.HostPort{network.AddressesWithPort(addrs, config.controller.APIPort())}, 710 PublicDNSName: newStringIfNonEmpty(config.controller.AutocertDNSName()), 711 MachineCount: newInt(1), 712 ControllerMachineCount: newInt(1), 713 }); err != nil { 714 return errors.Annotate(err, "saving bootstrap endpoint address") 715 } 716 717 // To avoid race conditions when running scripted bootstraps, wait 718 // for the controller's machine agent to be ready to accept commands 719 // before exiting this bootstrap command. 720 return waitForAgentInitialisation(ctx, &c.ModelCommandBase, c.controllerName, c.hostedModelName) 721 } 722 723 func (c *bootstrapCommand) handleCommandLineErrorsAndInfoRequests(ctx *cmd.Context) (bool, error) { 724 if c.BootstrapImage != "" { 725 if c.BootstrapSeries == "" { 726 return true, errors.Errorf("--bootstrap-image must be used with --bootstrap-series") 727 } 728 cons, err := constraints.Merge(c.Constraints, c.BootstrapConstraints) 729 if err != nil { 730 return true, errors.Trace(err) 731 } 732 if !cons.HasArch() { 733 return true, errors.Errorf("--bootstrap-image must be used with --bootstrap-constraints, specifying architecture") 734 } 735 } 736 if c.showClouds { 737 return true, printClouds(ctx, c.ClientStore()) 738 } 739 if c.showRegionsForCloud != "" { 740 return true, printCloudRegions(ctx, c.showRegionsForCloud) 741 } 742 743 return false, nil 744 } 745 746 func (c *bootstrapCommand) cloud(ctx *cmd.Context) (jujucloud.Cloud, environs.EnvironProvider, error) { 747 bootstrapFuncs := getBootstrapFuncs() 748 fail := func(err error) (jujucloud.Cloud, environs.EnvironProvider, error) { 749 return jujucloud.Cloud{}, nil, err 750 } 751 752 // Get the cloud definition identified by c.Cloud. If c.Cloud does not 753 // identify a cloud in clouds.yaml, then we check if any of the 754 // providers can detect a cloud with the given name. Otherwise, if the 755 // cloud name identifies a provider *type* (e.g. "openstack"), then we 756 // check if that provider can detect cloud regions, and synthesise a 757 // cloud with those regions. 758 var provider environs.EnvironProvider 759 var cloud jujucloud.Cloud 760 cloudptr, err := jujucloud.CloudByName(c.Cloud) 761 if errors.IsNotFound(err) { 762 cloud, provider, err = c.detectCloud(ctx, bootstrapFuncs) 763 if err != nil { 764 return fail(errors.Trace(err)) 765 } 766 } else if err != nil { 767 return fail(errors.Trace(err)) 768 } else { 769 cloud = *cloudptr 770 if err := checkProviderType(cloud.Type); err != nil { 771 return fail(errors.Trace(err)) 772 } 773 provider, err = environs.Provider(cloud.Type) 774 if err != nil { 775 return fail(errors.Trace(err)) 776 } 777 } 778 779 if finalizer, ok := bootstrapFuncs.CloudFinalizer(provider); ok { 780 cloud, err = finalizer.FinalizeCloud(ctx, cloud) 781 if err != nil { 782 return fail(errors.Trace(err)) 783 } 784 } 785 786 return cloud, provider, nil 787 } 788 789 func (c *bootstrapCommand) detectCloud( 790 ctx *cmd.Context, 791 bootstrapFuncs BootstrapInterface, 792 ) (jujucloud.Cloud, environs.EnvironProvider, error) { 793 fail := func(err error) (jujucloud.Cloud, environs.EnvironProvider, error) { 794 return jujucloud.Cloud{}, nil, err 795 } 796 797 // Check if any of the registered providers can give us a cloud with 798 // the specified name. The first one wins. 799 for _, providerType := range environs.RegisteredProviders() { 800 provider, err := environs.Provider(providerType) 801 if err != nil { 802 return fail(errors.Trace(err)) 803 } 804 cloudDetector, ok := bootstrapFuncs.CloudDetector(provider) 805 if !ok { 806 continue 807 } 808 cloud, err := cloudDetector.DetectCloud(c.Cloud) 809 if errors.IsNotFound(err) { 810 continue 811 } else if err != nil { 812 return fail(errors.Trace(err)) 813 } 814 return cloud, provider, nil 815 } 816 817 ctx.Verbosef("cloud %q not found, trying as a provider name", c.Cloud) 818 provider, err := environs.Provider(c.Cloud) 819 if errors.IsNotFound(err) { 820 return fail(errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds"))) 821 } else if err != nil { 822 return fail(errors.Trace(err)) 823 } 824 regionDetector, ok := bootstrapFuncs.CloudRegionDetector(provider) 825 if !ok { 826 ctx.Verbosef( 827 "provider %q does not support detecting regions", 828 c.Cloud, 829 ) 830 return fail(errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds"))) 831 } 832 833 var cloudEndpoint string 834 regions, err := regionDetector.DetectRegions() 835 if errors.IsNotFound(err) { 836 // It's not an error to have no regions. If the 837 // provider does not support regions, then we 838 // reinterpret the supplied region name as the 839 // cloud's endpoint. This enables the user to 840 // supply, for example, maas/<IP> or manual/<IP>. 841 if c.Region != "" { 842 ctx.Verbosef("interpreting %q as the cloud endpoint", c.Region) 843 cloudEndpoint = c.Region 844 c.Region = "" 845 } 846 } else if err != nil { 847 return fail(errors.Annotatef(err, 848 "detecting regions for %q cloud provider", 849 c.Cloud, 850 )) 851 } 852 schemas := provider.CredentialSchemas() 853 authTypes := make([]jujucloud.AuthType, 0, len(schemas)) 854 for authType := range schemas { 855 authTypes = append(authTypes, authType) 856 } 857 858 // Since we are iterating over a map, lets sort the authTypes so 859 // they are always in a consistent order. 860 sort.Sort(jujucloud.AuthTypes(authTypes)) 861 return jujucloud.Cloud{ 862 Name: c.Cloud, 863 Type: c.Cloud, 864 AuthTypes: authTypes, 865 Endpoint: cloudEndpoint, 866 Regions: regions, 867 }, provider, nil 868 } 869 870 type bootstrapCredentials struct { 871 credential *jujucloud.Credential 872 name string 873 detectedName string 874 } 875 876 // Get the credentials and region name. 877 func (c *bootstrapCommand) credentialsAndRegionName( 878 ctx *cmd.Context, 879 provider environs.EnvironProvider, 880 cloud jujucloud.Cloud, 881 ) ( 882 creds bootstrapCredentials, 883 regionName string, 884 err error, 885 ) { 886 887 store := c.ClientStore() 888 889 // When looking for credentials, we should attempt to see if there are any 890 // credentials that should be registered, before we get or detect them 891 err = common.RegisterCredentials(ctx, store, provider, modelcmd.RegisterCredentialsParams{ 892 Cloud: cloud, 893 }) 894 if err != nil { 895 logger.Errorf("registering credentials errored %s", err) 896 } 897 898 var detected bool 899 creds.credential, creds.name, regionName, detected, err = common.GetOrDetectCredential( 900 ctx, store, provider, modelcmd.GetCredentialsParams{ 901 Cloud: cloud, 902 CloudRegion: c.Region, 903 CredentialName: c.CredentialName, 904 }, 905 ) 906 switch errors.Cause(err) { 907 case nil: 908 case modelcmd.ErrMultipleCredentials: 909 return bootstrapCredentials{}, "", ambiguousCredentialError 910 case common.ErrMultipleDetectedCredentials: 911 return bootstrapCredentials{}, "", ambiguousDetectedCredentialError 912 default: 913 return bootstrapCredentials{}, "", errors.Trace(err) 914 } 915 logger.Debugf( 916 "authenticating with region %q and credential %q (%v)", 917 regionName, creds.name, creds.credential.Label, 918 ) 919 if detected { 920 creds.detectedName = creds.name 921 creds.name = "" 922 } 923 logger.Tracef("credential: %v", creds.credential) 924 return creds, regionName, nil 925 } 926 927 type bootstrapConfigs struct { 928 bootstrapModel map[string]interface{} 929 controller controller.Config 930 bootstrap bootstrap.Config 931 inheritedControllerAttrs map[string]interface{} 932 userConfigAttrs map[string]interface{} 933 } 934 935 func (c *bootstrapCommand) bootstrapConfigs( 936 ctx *cmd.Context, 937 cloud jujucloud.Cloud, 938 provider environs.EnvironProvider, 939 ) ( 940 bootstrapConfigs, 941 error, 942 ) { 943 944 controllerModelUUID, err := utils.NewUUID() 945 if err != nil { 946 return bootstrapConfigs{}, errors.Trace(err) 947 } 948 controllerUUID, err := utils.NewUUID() 949 if err != nil { 950 return bootstrapConfigs{}, errors.Trace(err) 951 } 952 953 // Create a model config, and split out any controller 954 // and bootstrap config attributes. 955 combinedConfig := map[string]interface{}{ 956 "type": cloud.Type, 957 "name": bootstrap.ControllerModelName, 958 config.UUIDKey: controllerModelUUID.String(), 959 } 960 961 userConfigAttrs, err := c.config.ReadAttrs(ctx) 962 if err != nil { 963 return bootstrapConfigs{}, errors.Trace(err) 964 } 965 modelDefaultConfigAttrs, err := c.modelDefaults.ReadAttrs(ctx) 966 if err != nil { 967 return bootstrapConfigs{}, errors.Trace(err) 968 } 969 // The provider may define some custom attributes specific 970 // to the provider. These will be added to the model config. 971 providerAttrs := make(map[string]interface{}) 972 if ps, ok := provider.(config.ConfigSchemaSource); ok { 973 for attr := range ps.ConfigSchema() { 974 // Start with the model defaults, and if also specified 975 // in the user config attrs, they override the model default. 976 if v, ok := modelDefaultConfigAttrs[attr]; ok { 977 providerAttrs[attr] = v 978 } 979 if v, ok := userConfigAttrs[attr]; ok { 980 providerAttrs[attr] = v 981 } 982 } 983 fields := schema.FieldMap(ps.ConfigSchema(), ps.ConfigDefaults()) 984 if coercedAttrs, err := fields.Coerce(providerAttrs, nil); err != nil { 985 return bootstrapConfigs{}, 986 errors.Annotatef(err, "invalid attribute value(s) for %v cloud", cloud.Type) 987 } else { 988 providerAttrs = coercedAttrs.(map[string]interface{}) 989 } 990 } 991 992 bootstrapConfigAttrs := make(map[string]interface{}) 993 controllerConfigAttrs := make(map[string]interface{}) 994 // Based on the attribute names in clouds.yaml, create 995 // a map of shared config for all models on this cloud. 996 inheritedControllerAttrs := make(map[string]interface{}) 997 for k, v := range cloud.Config { 998 switch { 999 case bootstrap.IsBootstrapAttribute(k): 1000 bootstrapConfigAttrs[k] = v 1001 continue 1002 case controller.ControllerOnlyAttribute(k): 1003 controllerConfigAttrs[k] = v 1004 continue 1005 } 1006 inheritedControllerAttrs[k] = v 1007 } 1008 // Region config values, for the region to be bootstrapped, from clouds.yaml 1009 // override what is in the cloud config. 1010 for k, v := range cloud.RegionConfig[c.Region] { 1011 switch { 1012 case bootstrap.IsBootstrapAttribute(k): 1013 bootstrapConfigAttrs[k] = v 1014 continue 1015 case controller.ControllerOnlyAttribute(k): 1016 controllerConfigAttrs[k] = v 1017 continue 1018 } 1019 inheritedControllerAttrs[k] = v 1020 } 1021 // Model defaults are added to the inherited controller attributes. 1022 // Any command line set model defaults override what is in the cloud config. 1023 for k, v := range modelDefaultConfigAttrs { 1024 switch { 1025 case bootstrap.IsBootstrapAttribute(k): 1026 return bootstrapConfigs{}, 1027 errors.Errorf("%q is a bootstrap only attribute, and cannot be set as a model-default", k) 1028 case controller.ControllerOnlyAttribute(k): 1029 return bootstrapConfigs{}, 1030 errors.Errorf("%q is a controller attribute, and cannot be set as a model-default", k) 1031 } 1032 inheritedControllerAttrs[k] = v 1033 } 1034 1035 // Start with the model defaults, then add in user config attributes. 1036 for k, v := range modelDefaultConfigAttrs { 1037 combinedConfig[k] = v 1038 } 1039 1040 // Provider specific attributes are either already specified in model 1041 // config (but may have been coerced), or were not present. Either way, 1042 // copy them in. 1043 logger.Debugf("provider attrs: %v", providerAttrs) 1044 for k, v := range providerAttrs { 1045 combinedConfig[k] = v 1046 } 1047 1048 for k, v := range inheritedControllerAttrs { 1049 combinedConfig[k] = v 1050 } 1051 1052 for k, v := range userConfigAttrs { 1053 combinedConfig[k] = v 1054 } 1055 1056 // Add in any default attribute values if not already 1057 // specified, making the recorded bootstrap config 1058 // immutable to changes in Juju. 1059 for k, v := range config.ConfigDefaults() { 1060 if _, ok := combinedConfig[k]; !ok { 1061 combinedConfig[k] = v 1062 } 1063 } 1064 1065 bootstrapModelConfig := make(map[string]interface{}) 1066 for k, v := range combinedConfig { 1067 switch { 1068 case bootstrap.IsBootstrapAttribute(k): 1069 bootstrapConfigAttrs[k] = v 1070 case controller.ControllerOnlyAttribute(k): 1071 controllerConfigAttrs[k] = v 1072 default: 1073 bootstrapModelConfig[k] = v 1074 } 1075 } 1076 1077 bootstrapConfig, err := bootstrap.NewConfig(bootstrapConfigAttrs) 1078 if err != nil { 1079 return bootstrapConfigs{}, errors.Annotate(err, "constructing bootstrap config") 1080 } 1081 controllerConfig, err := controller.NewConfig( 1082 controllerUUID.String(), bootstrapConfig.CACert, controllerConfigAttrs, 1083 ) 1084 if err != nil { 1085 return bootstrapConfigs{}, errors.Annotate(err, "constructing controller config") 1086 } 1087 if controllerConfig.AutocertDNSName() != "" { 1088 if _, ok := controllerConfigAttrs[controller.APIPort]; !ok { 1089 // The configuration did not explicitly mention the API port, 1090 // so default to 443 because it is not usually possible to 1091 // obtain autocert certificates without listening on port 443. 1092 controllerConfig[controller.APIPort] = 443 1093 } 1094 } 1095 1096 if err := common.FinalizeAuthorizedKeys(ctx, bootstrapModelConfig); err != nil { 1097 return bootstrapConfigs{}, errors.Annotate(err, "finalizing authorized-keys") 1098 } 1099 logger.Debugf("preparing controller with config: %v", bootstrapModelConfig) 1100 1101 configs := bootstrapConfigs{ 1102 bootstrapModel: bootstrapModelConfig, 1103 controller: controllerConfig, 1104 bootstrap: bootstrapConfig, 1105 inheritedControllerAttrs: inheritedControllerAttrs, 1106 userConfigAttrs: userConfigAttrs, 1107 } 1108 return configs, nil 1109 } 1110 1111 func (c *bootstrapCommand) hostedModelConfig( 1112 hostedModelUUID utils.UUID, 1113 inheritedControllerAttrs, 1114 userConfigAttrs map[string]interface{}, 1115 environ environs.ConfigGetter, 1116 ) map[string]interface{} { 1117 1118 hostedModelConfig := map[string]interface{}{ 1119 "name": c.hostedModelName, 1120 config.UUIDKey: hostedModelUUID.String(), 1121 } 1122 for k, v := range inheritedControllerAttrs { 1123 hostedModelConfig[k] = v 1124 } 1125 1126 // We copy across any user supplied attributes to the hosted model config. 1127 // But only if the attributes have not been removed from the controller 1128 // model config as part of preparing the controller model. 1129 controllerModelConfigAttrs := environ.Config().AllAttrs() 1130 for k, v := range userConfigAttrs { 1131 if _, ok := controllerModelConfigAttrs[k]; ok { 1132 hostedModelConfig[k] = v 1133 } 1134 } 1135 // Ensure that certain config attributes are not included in the hosted 1136 // model config. These attributes may be modified during bootstrap; by 1137 // removing them from this map, we ensure the modified values are 1138 // inherited. 1139 delete(hostedModelConfig, config.AuthorizedKeysKey) 1140 delete(hostedModelConfig, config.AgentVersionKey) 1141 1142 return hostedModelConfig 1143 } 1144 1145 // runInteractive queries the user about bootstrap config interactively at the 1146 // command prompt. 1147 func (c *bootstrapCommand) runInteractive(ctx *cmd.Context) error { 1148 scanner := bufio.NewScanner(ctx.Stdin) 1149 clouds, err := assembleClouds() 1150 if err != nil { 1151 return errors.Trace(err) 1152 } 1153 c.Cloud, err = queryCloud(clouds, lxdnames.DefaultCloud, scanner, ctx.Stdout) 1154 if err != nil { 1155 return errors.Trace(err) 1156 } 1157 cloud, err := common.CloudByName(c.Cloud) 1158 if err != nil { 1159 return errors.Trace(err) 1160 } 1161 1162 switch len(cloud.Regions) { 1163 case 0: 1164 // No region to choose, nothing to do. 1165 case 1: 1166 // If there's just one, don't prompt, just use it. 1167 c.Region = cloud.Regions[0].Name 1168 default: 1169 c.Region, err = queryRegion(c.Cloud, cloud.Regions, scanner, ctx.Stdout) 1170 if err != nil { 1171 return errors.Trace(err) 1172 } 1173 } 1174 1175 defName := defaultControllerName(c.Cloud, c.Region) 1176 1177 c.controllerName, err = queryName(defName, scanner, ctx.Stdout) 1178 if err != nil { 1179 return errors.Trace(err) 1180 } 1181 return nil 1182 } 1183 1184 // checkProviderType ensures the provider type is okay. 1185 func checkProviderType(envType string) error { 1186 featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey) 1187 flag, ok := provisionalProviders[envType] 1188 if ok && !featureflag.Enabled(flag) { 1189 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` 1190 return errors.Errorf(msg, envType, flag) 1191 } 1192 return nil 1193 } 1194 1195 // handleBootstrapError is called to clean up if bootstrap fails. 1196 func handleBootstrapError(ctx *cmd.Context, cleanup func() error) { 1197 ch := make(chan os.Signal, 1) 1198 ctx.InterruptNotify(ch) 1199 defer ctx.StopInterruptNotify(ch) 1200 defer close(ch) 1201 go func() { 1202 for range ch { 1203 fmt.Fprintln(ctx.GetStderr(), "Cleaning up failed bootstrap") 1204 } 1205 }() 1206 logger.Debugf("cleaning up after failed bootstrap") 1207 if err := cleanup(); err != nil { 1208 logger.Errorf("error cleaning up: %v", err) 1209 } 1210 } 1211 1212 func handleChooseCloudRegionError(ctx *cmd.Context, err error) error { 1213 if !common.IsChooseCloudRegionError(err) { 1214 return err 1215 } 1216 fmt.Fprintf(ctx.GetStderr(), 1217 "%s\n\nSpecify an alternative region, or try %q.\n", 1218 err, "juju update-clouds", 1219 ) 1220 return cmd.ErrSilent 1221 } 1222 1223 func newInt(i int) *int { 1224 return &i 1225 } 1226 1227 func newStringIfNonEmpty(s string) *string { 1228 if s == "" { 1229 return nil 1230 } 1231 return &s 1232 }