github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/environs/bootstrap/bootstrap.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package bootstrap 5 6 import ( 7 stdcontext "context" 8 "fmt" 9 "os" 10 "path/filepath" 11 12 "github.com/juju/charm/v12" 13 "github.com/juju/collections/set" 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "github.com/juju/names/v5" 17 "github.com/juju/utils/v3" 18 "github.com/juju/utils/v3/ssh" 19 "github.com/juju/version/v2" 20 21 "github.com/juju/juju/api" 22 "github.com/juju/juju/cloud" 23 "github.com/juju/juju/cloudconfig/instancecfg" 24 "github.com/juju/juju/cloudconfig/podcfg" 25 "github.com/juju/juju/controller" 26 "github.com/juju/juju/core/arch" 27 corebase "github.com/juju/juju/core/base" 28 "github.com/juju/juju/core/constraints" 29 corecontext "github.com/juju/juju/core/context" 30 "github.com/juju/juju/environs" 31 "github.com/juju/juju/environs/config" 32 "github.com/juju/juju/environs/context" 33 "github.com/juju/juju/environs/imagemetadata" 34 "github.com/juju/juju/environs/simplestreams" 35 "github.com/juju/juju/environs/storage" 36 "github.com/juju/juju/environs/sync" 37 "github.com/juju/juju/environs/tools" 38 "github.com/juju/juju/pki" 39 corestorage "github.com/juju/juju/storage" 40 coretools "github.com/juju/juju/tools" 41 jujuversion "github.com/juju/juju/version" 42 ) 43 44 const ( 45 // SimplestreamsFetcherContextKey defines a way to change the simplestreams 46 // fetcher within a context. 47 SimplestreamsFetcherContextKey corecontext.ContextKey = "simplestreams-fetcher" 48 ) 49 50 const noToolsMessage = `Juju cannot bootstrap because no agent binaries are available for your model. 51 You may want to use the 'agent-metadata-url' configuration setting to specify the binaries' location. 52 ` 53 54 var ( 55 logger = loggo.GetLogger("juju.environs.bootstrap") 56 57 errCancelled = errors.New("cancelled") 58 ) 59 60 // BootstrapParams holds the parameters for bootstrapping an environment. 61 type BootstrapParams struct { 62 // ModelConstraints are merged with the bootstrap constraints 63 // to choose the initial instance, and will be stored in the 64 // initial models' states. 65 ModelConstraints constraints.Value 66 67 // BootstrapConstraints are used to choose the initial instance. 68 // BootstrapConstraints does not affect the model constraints. 69 BootstrapConstraints constraints.Value 70 71 // ControllerName is the controller name. 72 ControllerName string 73 74 // BootstrapImage, if specified, is the image ID to use for the 75 // initial bootstrap machine. 76 BootstrapImage string 77 78 // Cloud contains the properties of the cloud that Juju will be 79 // bootstrapped in. 80 Cloud cloud.Cloud 81 82 // CloudRegion is the name of the cloud region that Juju will be bootstrapped in. 83 CloudRegion string 84 85 // CloudCredentialName is the name of the cloud credential that Juju will be 86 // bootstrapped with. This may be empty, for clouds that do not require 87 // credentials. 88 CloudCredentialName string 89 90 // CloudCredential contains the cloud credential that Juju will be 91 // bootstrapped with. This may be nil, for clouds that do not require 92 // credentialis. 93 CloudCredential *cloud.Credential 94 95 // ControllerConfig is the set of config attributes relevant 96 // to a controller. 97 ControllerConfig controller.Config 98 99 // ControllerInheritedConfig is the set of config attributes to be shared 100 // across all models in the same controller. 101 ControllerInheritedConfig map[string]interface{} 102 103 // RegionInheritedConfig holds region specific configuration attributes to 104 // be shared across all models in the same controller on a particular 105 // cloud. 106 RegionInheritedConfig cloud.RegionConfig 107 108 // InitialModelConfig is the set of config attributes to be overlaid 109 // on the controller config to construct the initial hosted model 110 // config. 111 InitialModelConfig map[string]interface{} 112 113 // Placement, if non-empty, holds an environment-specific placement 114 // directive used to choose the initial instance. 115 Placement string 116 117 // BuildAgent reports whether we should build and upload the local agent 118 // binary and override the environment's specified agent-version. 119 // It is an error to specify BuildAgent with a nil BuildAgentTarball. 120 BuildAgent bool 121 122 // BuildAgentTarball, if non-nil, is a function that may be used to 123 // build tools to upload. If this is nil, tools uploading will never 124 // take place. 125 BuildAgentTarball sync.BuildAgentTarballFunc 126 127 // MetadataDir is an optional path to a local directory containing 128 // tools and/or image metadata. 129 MetadataDir string 130 131 // AgentVersion, if set, determines the exact tools version that 132 // will be used to start the Juju agents. 133 AgentVersion *version.Number 134 135 // AdminSecret contains the administrator password. 136 AdminSecret string 137 138 // CAPrivateKey is the controller's CA certificate private key. 139 CAPrivateKey string 140 141 // ControllerServiceType is the service type of a k8s controller. 142 ControllerServiceType string 143 144 // ControllerExternalName is the external name of a k8s controller. 145 ControllerExternalName string 146 147 // ControllerExternalIPs is the list of external ips for a k8s controller. 148 ControllerExternalIPs []string 149 150 // DialOpts contains the bootstrap dial options. 151 DialOpts environs.BootstrapDialOpts 152 153 // JujuDbSnapPath is the path to a local .snap file that will be used 154 // to run the juju-db service. 155 JujuDbSnapPath string 156 157 // JujuDbSnapAssertionsPath is the path to a local .assertfile that 158 // will be used to test the contents of the .snap at JujuDbSnap. 159 JujuDbSnapAssertionsPath string 160 161 // StoragePools is one or more named storage pools to create 162 // in the controller model. 163 StoragePools map[string]corestorage.Attrs 164 165 // Force is used to allow a bootstrap to be run on unsupported series. 166 Force bool 167 168 // ControllerCharmPath is a local controller charm archive. 169 ControllerCharmPath string 170 171 // ControllerCharmChannel is used when fetching the charmhub controller charm. 172 ControllerCharmChannel charm.Channel 173 174 // ExtraAgentValuesForTesting are testing only values written to the agent config file. 175 ExtraAgentValuesForTesting map[string]string 176 177 // BootstrapBase, if specified, is the base to use for the 178 // initial bootstrap machine (deprecated use BootstrapBase). 179 BootstrapBase corebase.Base 180 181 // SupportedBootstrapBase is a supported set of bases to use for 182 // validating against the bootstrap base. 183 SupportedBootstrapBases []corebase.Base 184 } 185 186 // Validate validates the bootstrap parameters. 187 func (p BootstrapParams) Validate() error { 188 if p.AdminSecret == "" { 189 return errors.New("admin-secret is empty") 190 } 191 if p.ControllerConfig.ControllerUUID() == "" { 192 return errors.New("controller configuration has no controller UUID") 193 } 194 if _, hasCACert := p.ControllerConfig.CACert(); !hasCACert { 195 return errors.New("controller configuration has no ca-cert") 196 } 197 if p.CAPrivateKey == "" { 198 return errors.New("empty ca-private-key") 199 } 200 if p.SupportedBootstrapBases == nil || len(p.SupportedBootstrapBases) == 0 { 201 return errors.NotValidf("supported bootstrap bases") 202 } 203 204 // TODO(axw) validate other things. 205 return nil 206 } 207 208 // withDefaultControllerConstraints returns the given constraints, 209 // updated to choose a default instance type appropriate for a 210 // controller machine. We use this only if the user does not specify 211 // any constraints that would otherwise control the instance type 212 // selection. 213 func withDefaultControllerConstraints(cons constraints.Value) constraints.Value { 214 if !cons.HasInstanceType() && !cons.HasCpuCores() && !cons.HasCpuPower() && !cons.HasMem() { 215 // A default of 3.5GiB will result in machines with up to 4GiB of memory, eg 216 // - 3.75GiB on AWS, Google 217 // - 3.5GiB on Azure 218 var mem uint64 = 3.5 * 1024 219 cons.Mem = &mem 220 } 221 // If we're bootstrapping a controller on a lxd virtual machine, we want to 222 // ensure that it has at least 2 cores. Less than 2 cores can cause the 223 // controller to become unresponsive when installing. 224 if !cons.HasCpuCores() && cons.HasVirtType() && *cons.VirtType == "virtual-machine" { 225 var cores = uint64(2) 226 cons.CpuCores = &cores 227 } 228 return cons 229 } 230 231 // withDefaultCAASControllerConstraints returns the given constraints, 232 // updated to choose a default instance type appropriate for a 233 // controller machine. We use this only if the user does not specify 234 // any constraints that would otherwise control the instance type 235 // selection. 236 func withDefaultCAASControllerConstraints(cons constraints.Value) constraints.Value { 237 if !cons.HasInstanceType() && !cons.HasCpuCores() && !cons.HasCpuPower() && !cons.HasMem() { 238 // TODO(caas): Set memory constraints for mongod and controller containers independently. 239 var mem uint64 = 1.5 * 1024 240 cons.Mem = &mem 241 } 242 return cons 243 } 244 245 func bootstrapCAAS( 246 ctx environs.BootstrapContext, 247 environ environs.BootstrapEnviron, 248 callCtx context.ProviderCallContext, 249 args BootstrapParams, 250 bootstrapParams environs.BootstrapParams, 251 ) error { 252 if args.BuildAgent { 253 return errors.NotSupportedf("--build-agent when bootstrapping a k8s controller") 254 } 255 if args.BootstrapImage != "" { 256 return errors.NotSupportedf("--bootstrap-image when bootstrapping a k8s controller") 257 } 258 if !args.BootstrapBase.Empty() { 259 return errors.NotSupportedf("--bootstrap-series or --bootstrap-base when bootstrapping a k8s controller") 260 } 261 262 constraintsValidator, err := environ.ConstraintsValidator(callCtx) 263 if err != nil { 264 return err 265 } 266 bootstrapConstraints, err := constraintsValidator.Merge( 267 args.ModelConstraints, args.BootstrapConstraints, 268 ) 269 if err != nil { 270 return errors.Trace(err) 271 } 272 bootstrapConstraints = withDefaultCAASControllerConstraints(bootstrapConstraints) 273 bootstrapParams.BootstrapConstraints = bootstrapConstraints 274 275 result, err := environ.Bootstrap(ctx, callCtx, bootstrapParams) 276 if err != nil { 277 return errors.Trace(err) 278 } 279 280 podConfig, err := podcfg.NewBootstrapControllerPodConfig( 281 args.ControllerConfig, 282 args.ControllerName, 283 result.Base.OS, 284 bootstrapConstraints, 285 ) 286 if err != nil { 287 return errors.Trace(err) 288 } 289 290 jujuVersion := jujuversion.Current 291 if args.AgentVersion != nil { 292 jujuVersion = *args.AgentVersion 293 } 294 // set agent version before finalizing bootstrap config 295 if err := setBootstrapAgentVersion(environ, jujuVersion); err != nil { 296 return errors.Trace(err) 297 } 298 podConfig.JujuVersion = jujuVersion 299 if err := finalizePodBootstrapConfig(ctx, podConfig, args, environ.Config()); err != nil { 300 return errors.Annotate(err, "finalizing bootstrap instance config") 301 } 302 if err := result.CaasBootstrapFinalizer(ctx, podConfig, args.DialOpts); err != nil { 303 return errors.Trace(err) 304 } 305 return nil 306 } 307 308 func bootstrapIAAS( 309 ctx environs.BootstrapContext, 310 environ environs.BootstrapEnviron, 311 callCtx context.ProviderCallContext, 312 args BootstrapParams, 313 bootstrapParams environs.BootstrapParams, 314 ) error { 315 cfg := environ.Config() 316 if authKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()); len(authKeys) == 0 { 317 // Apparently this can never happen, so it's not tested. But, one day, 318 // Config will act differently (it's pretty crazy that, AFAICT, the 319 // authorized-keys are optional config settings... but it's impossible 320 // to actually *create* a config without them)... and when it does, 321 // we'll be here to catch this problem early. 322 return errors.Errorf("model configuration has no authorized-keys") 323 } 324 325 _, supportsNetworking := environs.SupportsNetworking(environ) 326 logger.Debugf("model %q supports application/machine networks: %v", cfg.Name(), supportsNetworking) 327 disableNetworkManagement, _ := cfg.DisableNetworkManagement() 328 logger.Debugf("network management by juju enabled: %v", !disableNetworkManagement) 329 330 var ss *simplestreams.Simplestreams 331 if value := ctx.Context().Value(SimplestreamsFetcherContextKey); value == nil { 332 ss = simplestreams.NewSimpleStreams(simplestreams.DefaultDataSourceFactory()) 333 } else if s, ok := value.(*simplestreams.Simplestreams); ok { 334 ss = s 335 } else { 336 return errors.Errorf("expected a valid simple streams type") 337 } 338 339 // Set default tools metadata source, add image metadata source, 340 // then verify constraints. Providers may rely on image metadata 341 // for constraint validation. 342 var customImageMetadata []*imagemetadata.ImageMetadata 343 if args.MetadataDir != "" { 344 var err error 345 customImageMetadata, err = setPrivateMetadataSources(ss, args.MetadataDir) 346 if err != nil { 347 return errors.Trace(err) 348 } 349 } 350 351 // If the provider allows advance discovery of the series and hw 352 // characteristics of the instance we are about to bootstrap, use this 353 // information to backfill in any missing series and/or arch contstraints. 354 if detector, supported := environ.(environs.HardwareCharacteristicsDetector); supported { 355 detectedBase, err := detector.DetectBase() 356 if err != nil { 357 return errors.Trace(err) 358 } 359 detectedHW, err := detector.DetectHardware() 360 if err != nil { 361 return errors.Trace(err) 362 } 363 364 if args.BootstrapBase.Empty() && !detectedBase.Empty() { 365 args.BootstrapBase = detectedBase 366 logger.Debugf("auto-selecting bootstrap series %q", args.BootstrapBase.String()) 367 } 368 if args.BootstrapConstraints.Arch == nil && 369 args.ModelConstraints.Arch == nil && 370 detectedHW != nil && 371 detectedHW.Arch != nil { 372 arch := *detectedHW.Arch 373 args.BootstrapConstraints.Arch = &arch 374 if detector.UpdateModelConstraints() { 375 args.ModelConstraints.Arch = &arch 376 } 377 logger.Debugf("auto-selecting bootstrap arch %q", arch) 378 } 379 } 380 381 requestedBootstrapBase, err := corebase.ValidateBase( 382 args.SupportedBootstrapBases, 383 args.BootstrapBase, 384 config.PreferredBase(cfg), 385 ) 386 if !args.Force && err != nil { 387 // If the base isn't valid (i.e. non-ubuntu) then don't prompt users to use 388 // the --force flag. 389 if requestedBootstrapBase.OS != corebase.UbuntuOS { 390 return errors.NotValidf("non-ubuntu bootstrap base %q", requestedBootstrapBase.String()) 391 } 392 return errors.Annotatef(err, "use --force to override") 393 } 394 bootstrapBase := requestedBootstrapBase 395 396 var bootstrapArchForImageSearch string 397 if args.BootstrapConstraints.Arch != nil { 398 bootstrapArchForImageSearch = *args.BootstrapConstraints.Arch 399 } else if args.ModelConstraints.Arch != nil { 400 bootstrapArchForImageSearch = *args.ModelConstraints.Arch 401 } else { 402 bootstrapArchForImageSearch = arch.HostArch() 403 } 404 405 ctx.Verbosef("Loading image metadata") 406 imageMetadata, err := bootstrapImageMetadata(environ, 407 ss, 408 &bootstrapBase, 409 bootstrapArchForImageSearch, 410 args.BootstrapImage, 411 &customImageMetadata, 412 ) 413 if err != nil { 414 return errors.Trace(err) 415 } 416 417 // We want to determine a list of valid architectures for which to pick tools and images. 418 // This includes architectures from custom and other available image metadata. 419 architectures := set.NewStrings() 420 if len(customImageMetadata) > 0 { 421 for _, customMetadata := range customImageMetadata { 422 architectures.Add(customMetadata.Arch) 423 } 424 } 425 if len(imageMetadata) > 0 { 426 for _, iMetadata := range imageMetadata { 427 architectures.Add(iMetadata.Arch) 428 } 429 } 430 bootstrapParams.ImageMetadata = imageMetadata 431 432 constraintsValidator, err := environ.ConstraintsValidator(callCtx) 433 if err != nil { 434 return err 435 } 436 constraintsValidator.UpdateVocabulary(constraints.Arch, architectures.SortedValues()) 437 438 bootstrapConstraints, err := constraintsValidator.Merge( 439 args.ModelConstraints, args.BootstrapConstraints, 440 ) 441 if err != nil { 442 return errors.Trace(err) 443 } 444 // The follow is used to determine if we should apply the default 445 // constraints when we bootstrap. Generally speaking this should always be 446 // applied, but there are exceptions to the rule e.g. local LXD 447 if checker, ok := environ.(environs.DefaultConstraintsChecker); !ok || checker.ShouldApplyControllerConstraints(bootstrapConstraints) { 448 bootstrapConstraints = withDefaultControllerConstraints(bootstrapConstraints) 449 } 450 bootstrapParams.BootstrapConstraints = bootstrapConstraints 451 452 var bootstrapArch string 453 if bootstrapConstraints.Arch != nil { 454 bootstrapArch = *bootstrapConstraints.Arch 455 } else { 456 // If no arch is specified as a constraint and we couldn't 457 // auto-discover the arch from the provider, we'll fall back 458 // to bootstrapping on the same arch as the CLI client. 459 bootstrapArch = localToolsArch() 460 } 461 462 agentVersion := jujuversion.Current 463 var availableTools coretools.List 464 if !args.BuildAgent { 465 latestPatchTxt := "" 466 versionTxt := fmt.Sprintf("%v", args.AgentVersion) 467 if args.AgentVersion == nil { 468 latestPatchTxt = "latest patch of " 469 versionTxt = fmt.Sprintf("%v.%v", agentVersion.Major, agentVersion.Minor) 470 } 471 ctx.Infof("Looking for %vpackaged Juju agent version %s for %s", latestPatchTxt, versionTxt, bootstrapArch) 472 473 availableTools, err = findPackagedTools(environ, ss, args.AgentVersion, &bootstrapArch, &bootstrapBase) 474 if err != nil && !errors.IsNotFound(err) { 475 return err 476 } 477 if len(availableTools) != 0 { 478 if args.AgentVersion == nil { 479 // If agent version was not specified in the arguments, 480 // we always want the latest/newest available. 481 agentVersion, availableTools = availableTools.Newest() 482 } 483 for _, tool := range availableTools { 484 ctx.Infof("Located Juju agent version %s at %s", tool.Version, tool.URL) 485 } 486 } 487 } 488 // If there are no prepackaged tools and a specific version has not been 489 // requested, look for or build a local binary. 490 var builtTools *sync.BuiltAgent 491 if len(availableTools) == 0 && (args.AgentVersion == nil || isCompatibleVersion(*args.AgentVersion, jujuversion.Current)) { 492 if args.BuildAgentTarball == nil { 493 return errors.New("cannot build agent binary to upload") 494 } 495 if err = validateUploadAllowed(environ, &bootstrapArch, &bootstrapBase, constraintsValidator); err != nil { 496 return err 497 } 498 if args.BuildAgent { 499 ctx.Infof("Building local Juju agent binary version %s for %s", args.AgentVersion, bootstrapArch) 500 } else { 501 ctx.Infof("No packaged binary found, preparing local Juju agent binary") 502 } 503 var forceVersion version.Number 504 availableTools, forceVersion, err = locallyBuildableTools() 505 if err != nil { 506 return errors.Annotate(err, "cannot package bootstrap agent binary") 507 } 508 builtTools, err = args.BuildAgentTarball( 509 args.BuildAgent, cfg.AgentStream(), 510 func(version.Number) version.Number { return forceVersion }, 511 ) 512 if err != nil { 513 return errors.Annotate(err, "cannot package bootstrap agent binary") 514 } 515 defer os.RemoveAll(builtTools.Dir) 516 // Combine the built agent information with the list of 517 // available tools. 518 for i, tool := range availableTools { 519 if tool.URL != "" { 520 continue 521 } 522 filename := filepath.Join(builtTools.Dir, builtTools.StorageName) 523 tool.URL = fmt.Sprintf("file://%s", filename) 524 tool.Size = builtTools.Size 525 tool.SHA256 = builtTools.Sha256Hash 526 527 // Use the version from the built tools but with the 528 // corrected series and arch - this ensures the build 529 // number is right if we found a valid official build. 530 version := builtTools.Version 531 version.Release = tool.Version.Release 532 version.Arch = tool.Version.Arch 533 // But if not an official build, use the forced version. 534 if !builtTools.Official { 535 version.Number = forceVersion 536 } 537 tool.Version = version 538 availableTools[i] = tool 539 } 540 } 541 if len(availableTools) == 0 { 542 return errors.New(noToolsMessage) 543 } 544 bootstrapParams.AvailableTools = availableTools 545 546 // TODO (anastasiamac 2018-02-02) By this stage, we will have a list 547 // of available tools (agent binaries) but they should all be the same 548 // version. Need to do check here, otherwise the provider.Bootstrap call 549 // may fail. This also means that compatibility check, currently done 550 // after provider.Bootstrap call in getBootstrapToolsVersion, 551 // should be done here. 552 553 // If we're uploading, we must override agent-version; 554 // if we're not uploading, we want to ensure we have an 555 // agent-version set anyway, to appease FinishInstanceConfig. 556 // In the latter case, setBootstrapTools will later set 557 // agent-version to the correct thing. 558 if args.AgentVersion != nil { 559 agentVersion = *args.AgentVersion 560 } 561 if cfg, err = cfg.Apply(map[string]interface{}{ 562 "agent-version": agentVersion.String(), 563 }); err != nil { 564 return errors.Trace(err) 565 } 566 if err = environ.SetConfig(cfg); err != nil { 567 return errors.Trace(err) 568 } 569 570 if bootstrapParams.BootstrapConstraints.HasInstanceRole() { 571 instanceRoleEnviron, ok := environ.(environs.InstanceRole) 572 if !ok || !instanceRoleEnviron.SupportsInstanceRoles(callCtx) { 573 return errors.NewNotSupported(nil, "instance role constraint for provider") 574 } 575 576 bootstrapParams, err = finaliseInstanceRole(callCtx, instanceRoleEnviron, bootstrapParams) 577 if err != nil { 578 return errors.Annotate(err, "finalising instance role for provider") 579 } 580 } 581 582 ctx.Verbosef("Starting new instance for initial controller") 583 584 result, err := environ.Bootstrap(ctx, callCtx, bootstrapParams) 585 if err != nil { 586 return errors.Trace(err) 587 } 588 589 publicKey, err := userPublicSigningKey() 590 if err != nil { 591 return errors.Trace(err) 592 } 593 instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( 594 args.ControllerConfig, 595 bootstrapParams.BootstrapConstraints, 596 args.ModelConstraints, 597 result.Base, 598 publicKey, 599 args.ExtraAgentValuesForTesting, 600 ) 601 if err != nil { 602 return errors.Trace(err) 603 } 604 605 matchingTools, err := bootstrapParams.AvailableTools.Match(coretools.Filter{ 606 Arch: result.Arch, 607 OSType: result.Base.OS, 608 }) 609 if err != nil { 610 return errors.Annotatef(err, "expected tools for %q", result.Base.OS) 611 } 612 selectedToolsList, err := getBootstrapToolsVersion(matchingTools) 613 if err != nil { 614 return errors.Trace(err) 615 } 616 // We set agent-version to the newest version, so the agent will immediately upgrade itself. 617 // Note that this only is relevant if a specific agent version has not been requested, since 618 // in that case the specific version will be the only version available. 619 newestToolVersion, _ := matchingTools.Newest() 620 // set agent version before finalizing bootstrap config 621 if err := setBootstrapAgentVersion(environ, newestToolVersion); err != nil { 622 return errors.Trace(err) 623 } 624 625 ctx.Infof("Installing Juju agent on bootstrap instance") 626 if err := instanceConfig.SetTools(selectedToolsList); err != nil { 627 return errors.Trace(err) 628 } 629 630 if err := instanceConfig.SetSnapSource(args.JujuDbSnapPath, args.JujuDbSnapAssertionsPath); err != nil { 631 return errors.Trace(err) 632 } 633 634 if err := instanceConfig.SetControllerCharm(args.ControllerCharmPath); err != nil { 635 return errors.Trace(err) 636 } 637 instanceConfig.Bootstrap.ControllerCharmChannel = args.ControllerCharmChannel 638 639 var environVersion int 640 if e, ok := environ.(environs.Environ); ok { 641 environVersion = e.Provider().Version() 642 } 643 644 if finalizer, ok := environ.(environs.BootstrapCredentialsFinaliser); ok { 645 cred, err := finalizer.FinaliseBootstrapCredential( 646 ctx, 647 bootstrapParams, 648 args.CloudCredential) 649 650 if err != nil { 651 return errors.Annotate(err, "finalizing bootstrap credential") 652 } 653 654 args.CloudCredential = cred 655 } 656 657 // Make sure we have the most recent environ config as the specified 658 // tools version has been updated there. 659 if err := finalizeInstanceBootstrapConfig( 660 ctx, instanceConfig, args, environ.Config(), environVersion, customImageMetadata, 661 ); err != nil { 662 return errors.Annotate(err, "finalizing bootstrap instance config") 663 } 664 if err := result.CloudBootstrapFinalizer(ctx, instanceConfig, args.DialOpts); err != nil { 665 return errors.Trace(err) 666 } 667 return nil 668 } 669 670 func finaliseInstanceRole( 671 ctx context.ProviderCallContext, 672 ir environs.InstanceRole, 673 args environs.BootstrapParams, 674 ) (environs.BootstrapParams, error) { 675 if *args.BootstrapConstraints.InstanceRole != 676 environs.InstanceProfileAutoCreate { 677 return args, nil 678 } 679 irName, err := ir.CreateAutoInstanceRole(ctx, args) 680 args.BootstrapConstraints.InstanceRole = &irName 681 return args, err 682 } 683 684 // Bootstrap bootstraps the given environment. The supplied constraints are 685 // used to provision the instance, and are also set within the bootstrapped 686 // environment. 687 func Bootstrap( 688 ctx environs.BootstrapContext, 689 environ environs.BootstrapEnviron, 690 callCtx context.ProviderCallContext, 691 args BootstrapParams, 692 ) error { 693 if err := args.Validate(); err != nil { 694 return errors.Annotate(err, "validating bootstrap parameters") 695 } 696 697 // TODO(stickupkid): Once environs doesn't have a dependency on series, we 698 // can remove this conversion. 699 var bootstrapSeries string 700 if !args.BootstrapBase.Empty() { 701 var err error 702 bootstrapSeries, err = corebase.GetSeriesFromBase(args.BootstrapBase) 703 if err != nil { 704 return errors.NotValidf("base %q", args.BootstrapBase) 705 } 706 } 707 supportedBootstrapSeries := set.NewStrings() 708 for _, base := range args.SupportedBootstrapBases { 709 s, err := corebase.GetSeriesFromBase(base) 710 if err != nil { 711 return errors.Trace(err) 712 } 713 supportedBootstrapSeries.Add(s) 714 } 715 716 bootstrapParams := environs.BootstrapParams{ 717 CloudName: args.Cloud.Name, 718 CloudRegion: args.CloudRegion, 719 ControllerConfig: args.ControllerConfig, 720 ModelConstraints: args.ModelConstraints, 721 StoragePools: args.StoragePools, 722 BootstrapSeries: bootstrapSeries, 723 SupportedBootstrapSeries: supportedBootstrapSeries, 724 Placement: args.Placement, 725 Force: args.Force, 726 ExtraAgentValuesForTesting: args.ExtraAgentValuesForTesting, 727 } 728 doBootstrap := bootstrapIAAS 729 if cloud.CloudIsCAAS(args.Cloud) { 730 doBootstrap = bootstrapCAAS 731 } 732 733 if err := doBootstrap(ctx, environ, callCtx, args, bootstrapParams); err != nil { 734 return errors.Trace(err) 735 } 736 if IsContextDone(ctx.Context()) { 737 ctx.Infof("Bootstrap cancelled, you may need to manually remove the bootstrap instance") 738 return Cancelled() 739 } 740 741 ctx.Infof("Bootstrap agent now started") 742 return nil 743 } 744 745 func finalizeInstanceBootstrapConfig( 746 ctx environs.BootstrapContext, 747 icfg *instancecfg.InstanceConfig, 748 args BootstrapParams, 749 cfg *config.Config, 750 environVersion int, 751 customImageMetadata []*imagemetadata.ImageMetadata, 752 ) error { 753 if icfg.APIInfo != nil { 754 return errors.New("machine configuration already has api info") 755 } 756 controllerCfg := icfg.ControllerConfig 757 caCert, hasCACert := controllerCfg.CACert() 758 if !hasCACert { 759 return errors.New("controller configuration has no ca-cert") 760 } 761 icfg.APIInfo = &api.Info{ 762 Password: args.AdminSecret, 763 CACert: caCert, 764 ModelTag: names.NewModelTag(cfg.UUID()), 765 } 766 767 authority, err := pki.NewDefaultAuthorityPemCAKey( 768 []byte(caCert), []byte(args.CAPrivateKey)) 769 if err != nil { 770 return errors.Annotate(err, "loading juju certificate authority") 771 } 772 773 leaf, err := authority.LeafRequestForGroup(pki.DefaultLeafGroup). 774 AddDNSNames(controller.DefaultDNSNames...). 775 Commit() 776 777 if err != nil { 778 return errors.Annotate(err, "make juju default controller cert") 779 } 780 781 cert, key, err := leaf.ToPemParts() 782 if err != nil { 783 return errors.Annotate(err, "encoding default controller cert to pem") 784 } 785 786 icfg.Bootstrap.StateServingInfo = controller.StateServingInfo{ 787 StatePort: controllerCfg.StatePort(), 788 APIPort: controllerCfg.APIPort(), 789 Cert: string(cert), 790 PrivateKey: string(key), 791 CAPrivateKey: args.CAPrivateKey, 792 } 793 icfg.Bootstrap.ControllerModelConfig = cfg 794 icfg.Bootstrap.ControllerModelEnvironVersion = environVersion 795 icfg.Bootstrap.CustomImageMetadata = customImageMetadata 796 icfg.Bootstrap.ControllerCloud = args.Cloud 797 icfg.Bootstrap.ControllerCloudRegion = args.CloudRegion 798 icfg.Bootstrap.ControllerCloudCredential = args.CloudCredential 799 icfg.Bootstrap.ControllerCloudCredentialName = args.CloudCredentialName 800 icfg.Bootstrap.ControllerConfig = args.ControllerConfig 801 icfg.Bootstrap.ControllerInheritedConfig = args.ControllerInheritedConfig 802 icfg.Bootstrap.RegionInheritedConfig = args.Cloud.RegionConfig 803 icfg.Bootstrap.InitialModelConfig = args.InitialModelConfig 804 icfg.Bootstrap.StoragePools = args.StoragePools 805 icfg.Bootstrap.Timeout = args.DialOpts.Timeout 806 icfg.Bootstrap.JujuDbSnapPath = args.JujuDbSnapPath 807 icfg.Bootstrap.JujuDbSnapAssertionsPath = args.JujuDbSnapAssertionsPath 808 icfg.Bootstrap.ControllerCharm = args.ControllerCharmPath 809 icfg.Bootstrap.ControllerCharmChannel = args.ControllerCharmChannel 810 return nil 811 } 812 813 func finalizePodBootstrapConfig( 814 ctx environs.BootstrapContext, 815 pcfg *podcfg.ControllerPodConfig, 816 args BootstrapParams, 817 cfg *config.Config, 818 ) error { 819 if pcfg.APIInfo != nil { 820 return errors.New("machine configuration already has api info") 821 } 822 823 controllerCfg := pcfg.Controller 824 caCert, hasCACert := controllerCfg.CACert() 825 if !hasCACert { 826 return errors.New("controller configuration has no ca-cert") 827 } 828 pcfg.APIInfo = &api.Info{ 829 Password: args.AdminSecret, 830 CACert: caCert, 831 ModelTag: names.NewModelTag(cfg.UUID()), 832 } 833 834 authority, err := pki.NewDefaultAuthorityPemCAKey( 835 []byte(caCert), []byte(args.CAPrivateKey)) 836 if err != nil { 837 return errors.Annotate(err, "loading juju certificate authority") 838 } 839 840 // We generate a controller certificate with a set of well known static dns 841 // names. IP addresses are left for other workers to make subsequent 842 // certificates. 843 leaf, err := authority.LeafRequestForGroup(pki.DefaultLeafGroup). 844 AddDNSNames(controller.DefaultDNSNames...). 845 Commit() 846 847 if err != nil { 848 return errors.Annotate(err, "make juju default controller cert") 849 } 850 851 cert, key, err := leaf.ToPemParts() 852 if err != nil { 853 return errors.Annotate(err, "encoding default controller cert to pem") 854 } 855 856 pcfg.Bootstrap.StateServingInfo = controller.StateServingInfo{ 857 StatePort: controllerCfg.StatePort(), 858 APIPort: controllerCfg.APIPort(), 859 Cert: string(cert), 860 PrivateKey: string(key), 861 CAPrivateKey: args.CAPrivateKey, 862 } 863 if _, ok := cfg.AgentVersion(); !ok { 864 return errors.New("controller model configuration has no agent-version") 865 } 866 867 pcfg.AgentEnvironment = make(map[string]string) 868 for k, v := range args.ExtraAgentValuesForTesting { 869 pcfg.AgentEnvironment[k] = v 870 } 871 872 pcfg.Bootstrap.ControllerModelConfig = cfg 873 pcfg.Bootstrap.ControllerCloud = args.Cloud 874 pcfg.Bootstrap.ControllerCloudRegion = args.CloudRegion 875 pcfg.Bootstrap.ControllerCloudCredential = args.CloudCredential 876 pcfg.Bootstrap.ControllerCloudCredentialName = args.CloudCredentialName 877 pcfg.Bootstrap.ControllerConfig = args.ControllerConfig 878 pcfg.Bootstrap.ControllerInheritedConfig = args.ControllerInheritedConfig 879 pcfg.Bootstrap.InitialModelConfig = args.InitialModelConfig 880 pcfg.Bootstrap.StoragePools = args.StoragePools 881 pcfg.Bootstrap.Timeout = args.DialOpts.Timeout 882 pcfg.Bootstrap.ControllerServiceType = args.ControllerServiceType 883 pcfg.Bootstrap.ControllerExternalName = args.ControllerExternalName 884 pcfg.Bootstrap.ControllerExternalIPs = append([]string(nil), args.ControllerExternalIPs...) 885 pcfg.Bootstrap.ControllerCharmPath = args.ControllerCharmPath 886 pcfg.Bootstrap.ControllerCharmChannel = args.ControllerCharmChannel 887 return nil 888 } 889 890 func userPublicSigningKey() (string, error) { 891 signingKeyFile := os.Getenv("JUJU_STREAMS_PUBLICKEY_FILE") 892 signingKey := "" 893 if signingKeyFile != "" { 894 path, err := utils.NormalizePath(signingKeyFile) 895 if err != nil { 896 return "", errors.Annotatef(err, "cannot expand key file path: %s", signingKeyFile) 897 } 898 b, err := os.ReadFile(path) 899 if err != nil { 900 return "", errors.Annotatef(err, "invalid public key file: %s", path) 901 } 902 signingKey = string(b) 903 } 904 return signingKey, nil 905 } 906 907 // bootstrapImageMetadata returns the image metadata to use for bootstrapping 908 // the given environment. If the environment provider does not make use of 909 // simplestreams, no metadata will be returned. 910 // 911 // If a bootstrap image ID is specified, image metadata will be synthesised 912 // using that image ID, and the architecture and series specified by the 913 // initiator. In addition, the custom image metadata that is saved into the 914 // state database will have the synthesised image metadata added to it. 915 func bootstrapImageMetadata( 916 environ environs.BootstrapEnviron, 917 fetcher imagemetadata.SimplestreamsFetcher, 918 bootstrapBase *corebase.Base, 919 bootstrapArch string, 920 bootstrapImageId string, 921 customImageMetadata *[]*imagemetadata.ImageMetadata, 922 ) ([]*imagemetadata.ImageMetadata, error) { 923 924 hasRegion, ok := environ.(simplestreams.HasRegion) 925 if !ok { 926 if bootstrapImageId != "" { 927 // We only support specifying image IDs for providers 928 // that use simplestreams for now. 929 return nil, errors.NotSupportedf( 930 "specifying bootstrap image for %q provider", 931 environ.Config().Type(), 932 ) 933 } 934 // No region, no metadata. 935 return nil, nil 936 } 937 region, err := hasRegion.Region() 938 if err != nil { 939 return nil, errors.Trace(err) 940 } 941 942 if bootstrapImageId != "" { 943 if bootstrapBase == nil { 944 return nil, errors.NotValidf("no base specified with bootstrap image") 945 } 946 // The returned metadata does not have information about the 947 // storage or virtualisation type. Any provider that wants to 948 // filter on those properties should allow for empty values. 949 meta := &imagemetadata.ImageMetadata{ 950 Id: bootstrapImageId, 951 Arch: bootstrapArch, 952 Version: bootstrapBase.Channel.Track, 953 RegionName: region.Region, 954 Endpoint: region.Endpoint, 955 Stream: environ.Config().ImageStream(), 956 } 957 *customImageMetadata = append(*customImageMetadata, meta) 958 return []*imagemetadata.ImageMetadata{meta}, nil 959 } 960 961 // For providers that support making use of simplestreams 962 // image metadata, search public image metadata. We need 963 // to pass this onto Bootstrap for selecting images. 964 sources, err := environs.ImageMetadataSources(environ, fetcher) 965 if err != nil { 966 return nil, errors.Trace(err) 967 } 968 // This constraint will search image metadata for all supported architectures and series. 969 imageConstraint, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 970 CloudSpec: region, 971 Stream: environ.Config().ImageStream(), 972 }) 973 if err != nil { 974 return nil, errors.Trace(err) 975 } 976 logger.Debugf("constraints for image metadata lookup %v", imageConstraint) 977 978 // Get image metadata from all data sources. 979 // Since order of data source matters, order of image metadata matters too. Append is important here. 980 var publicImageMetadata []*imagemetadata.ImageMetadata 981 for _, source := range sources { 982 sourceMetadata, _, err := imagemetadata.Fetch(fetcher, []simplestreams.DataSource{source}, imageConstraint) 983 if errors.Is(err, errors.NotFound) || errors.Is(err, errors.Unauthorized) { 984 logger.Debugf("ignoring image metadata in %s: %v", source.Description(), err) 985 // Just keep looking... 986 continue 987 } else if err != nil { 988 // When we get an actual protocol/unexpected error, we need to stop. 989 return nil, errors.Annotatef(err, "failed looking for image metadata in %s", source.Description()) 990 } 991 logger.Debugf("found %d image metadata in %s", len(sourceMetadata), source.Description()) 992 publicImageMetadata = append(publicImageMetadata, sourceMetadata...) 993 } 994 995 logger.Debugf("found %d image metadata from all image data sources", len(publicImageMetadata)) 996 return publicImageMetadata, nil 997 } 998 999 // getBootstrapToolsVersion returns the newest tools from the given tools list. 1000 func getBootstrapToolsVersion(possibleTools coretools.List) (coretools.List, error) { 1001 if len(possibleTools) == 0 { 1002 return nil, errors.New("no bootstrap agent binaries available") 1003 } 1004 var newVersion version.Number 1005 newVersion, toolsList := possibleTools.Newest() 1006 logger.Infof("newest version: %s", newVersion) 1007 bootstrapVersion := newVersion 1008 // We should only ever bootstrap the exact same version as the client, 1009 // or we risk bootstrap incompatibility. 1010 if !isCompatibleVersion(newVersion, jujuversion.Current) { 1011 compatibleVersion, compatibleTools := findCompatibleTools(possibleTools, jujuversion.Current) 1012 if len(compatibleTools) == 0 { 1013 logger.Infof( 1014 "failed to find %s agent binaries, will attempt to use %s", 1015 jujuversion.Current, newVersion, 1016 ) 1017 } else { 1018 bootstrapVersion, toolsList = compatibleVersion, compatibleTools 1019 } 1020 } 1021 logger.Infof("picked bootstrap agent binary version: %s", bootstrapVersion) 1022 return toolsList, nil 1023 } 1024 1025 // setBootstrapAgentVersion updates the agent-version configuration attribute. 1026 func setBootstrapAgentVersion(environ environs.Configer, toolsVersion version.Number) error { 1027 cfg := environ.Config() 1028 if agentVersion, _ := cfg.AgentVersion(); agentVersion != toolsVersion { 1029 cfg, err := cfg.Apply(map[string]interface{}{ 1030 "agent-version": toolsVersion.String(), 1031 }) 1032 if err == nil { 1033 err = environ.SetConfig(cfg) 1034 } 1035 if err != nil { 1036 return errors.Errorf("failed to update model configuration: %v", err) 1037 } 1038 } 1039 return nil 1040 } 1041 1042 // findCompatibleTools finds tools in the list that have the same major, minor 1043 // and patch level as jujuversion.Current. 1044 // 1045 // Build number is not important to match; uploaded tools will have 1046 // incremented build number, and we want to match them. 1047 func findCompatibleTools(possibleTools coretools.List, version version.Number) (version.Number, coretools.List) { 1048 var compatibleTools coretools.List 1049 for _, tools := range possibleTools { 1050 if isCompatibleVersion(tools.Version.Number, version) { 1051 compatibleTools = append(compatibleTools, tools) 1052 } 1053 } 1054 return compatibleTools.Newest() 1055 } 1056 1057 func isCompatibleVersion(v1, v2 version.Number) bool { 1058 x := v1.ToPatch() 1059 y := v2.ToPatch() 1060 return x.Compare(y) == 0 1061 } 1062 1063 // setPrivateMetadataSources verifies the specified metadataDir exists, 1064 // uses it to set the default agent metadata source for agent binaries, 1065 // and adds an image metadata source after verifying the contents. If the 1066 // directory ends in tools, only the default tools metadata source will be 1067 // set. Same for images. 1068 func setPrivateMetadataSources(fetcher imagemetadata.SimplestreamsFetcher, metadataDir string) ([]*imagemetadata.ImageMetadata, error) { 1069 if _, err := os.Stat(metadataDir); err != nil { 1070 if !os.IsNotExist(err) { 1071 return nil, errors.Annotate(err, "cannot access simplestreams metadata directory") 1072 } 1073 return nil, errors.NotFoundf("simplestreams metadata source: %s", metadataDir) 1074 } 1075 1076 agentBinaryMetadataDir := metadataDir 1077 ending := filepath.Base(agentBinaryMetadataDir) 1078 if ending != storage.BaseToolsPath { 1079 agentBinaryMetadataDir = filepath.Join(metadataDir, storage.BaseToolsPath) 1080 } 1081 if _, err := os.Stat(agentBinaryMetadataDir); err != nil { 1082 if !os.IsNotExist(err) { 1083 return nil, errors.Annotate(err, "cannot access agent metadata") 1084 } 1085 logger.Debugf("no agent directory found, using default agent metadata source: %s", tools.DefaultBaseURL) 1086 } else { 1087 if ending == storage.BaseToolsPath { 1088 // As the specified metadataDir ended in 'tools' 1089 // assume that is the only metadata to find and return. 1090 tools.DefaultBaseURL = filepath.Dir(metadataDir) 1091 logger.Debugf("setting default agent metadata source: %s", tools.DefaultBaseURL) 1092 return nil, nil 1093 } else { 1094 tools.DefaultBaseURL = metadataDir 1095 logger.Debugf("setting default agent metadata source: %s", tools.DefaultBaseURL) 1096 } 1097 } 1098 1099 imageMetadataDir := metadataDir 1100 ending = filepath.Base(imageMetadataDir) 1101 if ending != storage.BaseImagesPath { 1102 imageMetadataDir = filepath.Join(metadataDir, storage.BaseImagesPath) 1103 } 1104 if _, err := os.Stat(imageMetadataDir); err != nil { 1105 if !os.IsNotExist(err) { 1106 return nil, errors.Annotate(err, "cannot access image metadata") 1107 } 1108 return nil, nil 1109 } else { 1110 logger.Debugf("setting default image metadata source: %s", imageMetadataDir) 1111 } 1112 1113 baseURL := fmt.Sprintf("file://%s", filepath.ToSlash(imageMetadataDir)) 1114 publicKey, err := simplestreams.UserPublicSigningKey() 1115 if err != nil { 1116 return nil, errors.Trace(err) 1117 } 1118 // TODO: (hml) 2020-01-08 1119 // Why ignore the the model-config "ssl-hostname-verification" value in 1120 // the config here? Its default value is true. 1121 dataSourceConfig := simplestreams.Config{ 1122 Description: "bootstrap metadata", 1123 BaseURL: baseURL, 1124 PublicSigningKey: publicKey, 1125 HostnameVerification: false, 1126 Priority: simplestreams.CUSTOM_CLOUD_DATA, 1127 } 1128 if err := dataSourceConfig.Validate(); err != nil { 1129 return nil, errors.Annotate(err, "simplestreams config validation failed") 1130 } 1131 dataSource := fetcher.NewDataSource(dataSourceConfig) 1132 1133 // Read the image metadata, as we'll want to upload it to the environment. 1134 imageConstraint, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{}) 1135 if err != nil { 1136 return nil, errors.Trace(err) 1137 } 1138 existingMetadata, _, err := imagemetadata.Fetch(fetcher, []simplestreams.DataSource{dataSource}, imageConstraint) 1139 if err != nil && !errors.IsNotFound(err) { 1140 return nil, errors.Annotatef(err, "cannot read image metadata in %s", dataSource.Description()) 1141 } 1142 1143 // Add an image metadata datasource for constraint validation, etc. 1144 environs.RegisterUserImageDataSourceFunc("bootstrap metadata", func(environs.Environ) (simplestreams.DataSource, error) { 1145 return dataSource, nil 1146 }) 1147 logger.Infof("custom image metadata added to search path") 1148 return existingMetadata, nil 1149 } 1150 1151 // Cancelled returns an error that satisfies IsCancelled. 1152 func Cancelled() error { 1153 return errCancelled 1154 } 1155 1156 // IsContextDone returns true if the context is done. 1157 func IsContextDone(ctx stdcontext.Context) bool { 1158 return ctx.Err() != nil 1159 }