github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cloudconfig/instancecfg/instancecfg.go (about) 1 // Copyright 2012, 2013, 2015 Canonical Ltd. 2 // Copyright 2015 Cloudbase Solutions SRL 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 package instancecfg 6 7 import ( 8 "fmt" 9 "net" 10 "path" 11 "reflect" 12 "strconv" 13 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "github.com/juju/names" 17 "github.com/juju/utils/proxy" 18 "github.com/juju/utils/shell" 19 "github.com/juju/version" 20 21 "github.com/juju/juju/agent" 22 agenttools "github.com/juju/juju/agent/tools" 23 "github.com/juju/juju/api" 24 "github.com/juju/juju/apiserver/params" 25 "github.com/juju/juju/constraints" 26 "github.com/juju/juju/environs/config" 27 "github.com/juju/juju/environs/imagemetadata" 28 "github.com/juju/juju/environs/tags" 29 "github.com/juju/juju/instance" 30 "github.com/juju/juju/juju/paths" 31 "github.com/juju/juju/mongo" 32 "github.com/juju/juju/service" 33 "github.com/juju/juju/service/common" 34 "github.com/juju/juju/state/multiwatcher" 35 coretools "github.com/juju/juju/tools" 36 ) 37 38 var logger = loggo.GetLogger("juju.cloudconfig.instancecfg") 39 40 // InstanceConfig represents initialization information for a new juju instance. 41 type InstanceConfig struct { 42 // Tags is a set of tags to set on the instance, if supported. This 43 // should be populated using the InstanceTags method in this package. 44 Tags map[string]string 45 46 // Bootstrap specifies whether the new instance is the bootstrap 47 // instance. When this is true, StateServingInfo should be set 48 // and filled out. 49 Bootstrap bool 50 51 // StateServingInfo holds the information for serving the state. 52 // This must only be set if the Bootstrap field is true 53 // (controllers started subsequently will acquire their serving info 54 // from another server) 55 StateServingInfo *params.StateServingInfo 56 57 // MongoInfo holds the means for the new instance to communicate with the 58 // juju state database. Unless the new instance is running a controller 59 // (Controller is set), there must be at least one controller address supplied. 60 // The entity name must match that of the instance being started, 61 // or be empty when starting a controller. 62 MongoInfo *mongo.MongoInfo 63 64 // APIInfo holds the means for the new instance to communicate with the 65 // juju state API. Unless the new instance is running a controller (Controller is 66 // set), there must be at least one controller address supplied. 67 // The entity name must match that of the instance being started, 68 // or be empty when starting a controller. 69 APIInfo *api.Info 70 71 // InstanceId is the instance ID of the instance being initialised. 72 // This is required when bootstrapping, and ignored otherwise. 73 InstanceId instance.Id 74 75 // HardwareCharacteristics contains the harrdware characteristics of 76 // the instance being initialised. This optional, and is only used by 77 // the bootstrap agent during state initialisation. 78 HardwareCharacteristics *instance.HardwareCharacteristics 79 80 // MachineNonce is set at provisioning/bootstrap time and used to 81 // ensure the agent is running on the correct instance. 82 MachineNonce string 83 84 // tools is the list of juju tools used to install the Juju agent 85 // on the new instance. Each of the entries in the list must have 86 // identical versions and hashes, but may have different URLs. 87 tools coretools.List 88 89 // GUI is the Juju GUI archive to be installed in the new instance. 90 GUI *coretools.GUIArchive 91 92 // DataDir holds the directory that juju state will be put in the new 93 // instance. 94 DataDir string 95 96 // LogDir holds the directory that juju logs will be written to. 97 LogDir string 98 99 // MetricsSpoolDir represents the spool directory path, where all 100 // metrics are stored. 101 MetricsSpoolDir string 102 103 // Jobs holds what machine jobs to run. 104 Jobs []multiwatcher.MachineJob 105 106 // CloudInitOutputLog specifies the path to the output log for cloud-init. 107 // The directory containing the log file must already exist. 108 CloudInitOutputLog string 109 110 // MachineId identifies the new machine. 111 MachineId string 112 113 // MachineContainerType specifies the type of container that the instance 114 // is. If the instance is not a container, then the type is "". 115 MachineContainerType instance.ContainerType 116 117 // MachineContainerHostname specifies the hostname to be used with the 118 // cloud config for the instance. If this is not set, hostname uses the default. 119 MachineContainerHostname string 120 121 // AuthorizedKeys specifies the keys that are allowed to 122 // connect to the instance (see cloudinit.SSHAddAuthorizedKeys) 123 // If no keys are supplied, there can be no ssh access to the node. 124 // On a bootstrap instance, that is fatal. On other 125 // instances it will mean that the ssh, scp and debug-hooks 126 // commands cannot work. 127 AuthorizedKeys string 128 129 // AgentEnvironment defines additional configuration variables to set in 130 // the instance agent config. 131 AgentEnvironment map[string]string 132 133 // WARNING: this is only set if the instance being configured is 134 // a controller node. 135 // 136 // Config holds the initial environment configuration. 137 Config *config.Config 138 139 // HostedModelConfig is a set of config attributes to be overlaid 140 // on the controller model config (Config, above) to construct the 141 // initial hosted model config. 142 HostedModelConfig map[string]interface{} 143 144 // Constraints holds the machine constraints. 145 Constraints constraints.Value 146 147 // ModelConstraints holds the initial model constraints. 148 ModelConstraints constraints.Value 149 150 // DisableSSLHostnameVerification can be set to true to tell cloud-init 151 // that it shouldn't verify SSL certificates 152 DisableSSLHostnameVerification bool 153 154 // Series represents the instance series. 155 Series string 156 157 // MachineAgentServiceName is the init service name for the Juju machine agent. 158 MachineAgentServiceName string 159 160 // ProxySettings define normal http, https and ftp proxies. 161 ProxySettings proxy.Settings 162 163 // AptProxySettings define the http, https and ftp proxy settings to use 164 // for apt, which may or may not be the same as the normal ProxySettings. 165 AptProxySettings proxy.Settings 166 167 // AptMirror defines an APT mirror location, which, if specified, will 168 // override the default APT sources. 169 AptMirror string 170 171 // PreferIPv6 mirrors the value of prefer-ipv6 environment setting 172 // and when set IPv6 addresses for connecting to the API/state 173 // servers will be preferred over IPv4 ones. 174 PreferIPv6 bool 175 176 // The type of Simple Stream to download and deploy on this instance. 177 ImageStream string 178 179 // The public key used to sign Juju simplestreams image metadata. 180 PublicImageSigningKey string 181 182 // CustomImageMetadata is optional custom simplestreams image metadata 183 // to store in environment storage at bootstrap time. This is ignored 184 // in non-bootstrap instances. 185 CustomImageMetadata []*imagemetadata.ImageMetadata 186 187 // EnableOSRefreshUpdate specifies whether Juju will refresh its 188 // respective OS's updates list. 189 EnableOSRefreshUpdate bool 190 191 // EnableOSUpgrade defines Juju's behavior when provisioning 192 // instances. If enabled, the OS will perform any upgrades 193 // available as part of its provisioning. 194 EnableOSUpgrade bool 195 } 196 197 func (cfg *InstanceConfig) agentInfo() service.AgentInfo { 198 return service.NewMachineAgentInfo( 199 cfg.MachineId, 200 cfg.DataDir, 201 cfg.LogDir, 202 ) 203 } 204 205 func (cfg *InstanceConfig) ToolsDir(renderer shell.Renderer) string { 206 return cfg.agentInfo().ToolsDir(renderer) 207 } 208 209 func (cfg *InstanceConfig) InitService(renderer shell.Renderer) (service.Service, error) { 210 conf := service.AgentConf(cfg.agentInfo(), renderer) 211 212 name := cfg.MachineAgentServiceName 213 svc, err := newService(name, conf, cfg.Series) 214 return svc, errors.Trace(err) 215 } 216 217 var newService = func(name string, conf common.Conf, series string) (service.Service, error) { 218 return service.NewService(name, conf, series) 219 } 220 221 func (cfg *InstanceConfig) AgentConfig( 222 tag names.Tag, 223 toolsVersion version.Number, 224 ) (agent.ConfigSetter, error) { 225 // TODO for HAState: the stateHostAddrs and apiHostAddrs here assume that 226 // if the instance is a controller then to use localhost. This may be 227 // sufficient, but needs thought in the new world order. 228 var password string 229 if cfg.MongoInfo == nil { 230 password = cfg.APIInfo.Password 231 } else { 232 password = cfg.MongoInfo.Password 233 } 234 configParams := agent.AgentConfigParams{ 235 Paths: agent.Paths{ 236 DataDir: cfg.DataDir, 237 LogDir: cfg.LogDir, 238 MetricsSpoolDir: cfg.MetricsSpoolDir, 239 }, 240 Jobs: cfg.Jobs, 241 Tag: tag, 242 UpgradedToVersion: toolsVersion, 243 Password: password, 244 Nonce: cfg.MachineNonce, 245 StateAddresses: cfg.stateHostAddrs(), 246 APIAddresses: cfg.ApiHostAddrs(), 247 CACert: cfg.MongoInfo.CACert, 248 Values: cfg.AgentEnvironment, 249 PreferIPv6: cfg.PreferIPv6, 250 Model: cfg.APIInfo.ModelTag, 251 } 252 if !cfg.Bootstrap { 253 return agent.NewAgentConfig(configParams) 254 } 255 return agent.NewStateMachineConfig(configParams, *cfg.StateServingInfo) 256 } 257 258 // JujuTools returns the directory where Juju tools are stored. 259 func (cfg *InstanceConfig) JujuTools() string { 260 return agenttools.SharedToolsDir(cfg.DataDir, cfg.AgentVersion()) 261 } 262 263 // GUITools returns the directory where the Juju GUI release is stored. 264 func (cfg *InstanceConfig) GUITools() string { 265 return agenttools.SharedGUIDir(cfg.DataDir) 266 } 267 268 func (cfg *InstanceConfig) stateHostAddrs() []string { 269 var hosts []string 270 if cfg.Bootstrap { 271 if cfg.PreferIPv6 { 272 hosts = append(hosts, net.JoinHostPort("::1", strconv.Itoa(cfg.StateServingInfo.StatePort))) 273 } else { 274 hosts = append(hosts, net.JoinHostPort("localhost", strconv.Itoa(cfg.StateServingInfo.StatePort))) 275 } 276 } 277 if cfg.MongoInfo != nil { 278 hosts = append(hosts, cfg.MongoInfo.Addrs...) 279 } 280 return hosts 281 } 282 283 func (cfg *InstanceConfig) ApiHostAddrs() []string { 284 var hosts []string 285 if cfg.Bootstrap { 286 if cfg.PreferIPv6 { 287 hosts = append(hosts, net.JoinHostPort("::1", strconv.Itoa(cfg.StateServingInfo.APIPort))) 288 } else { 289 hosts = append(hosts, net.JoinHostPort("localhost", strconv.Itoa(cfg.StateServingInfo.APIPort))) 290 } 291 } 292 if cfg.APIInfo != nil { 293 hosts = append(hosts, cfg.APIInfo.Addrs...) 294 } 295 return hosts 296 } 297 298 // AgentVersion returns the version of the Juju agent that will be configured 299 // on the instance. The zero value will be returned if there are no tools set. 300 func (cfg *InstanceConfig) AgentVersion() version.Binary { 301 if len(cfg.tools) == 0 { 302 return version.Binary{} 303 } 304 return cfg.tools[0].Version 305 } 306 307 // ToolsList returns the list of tools in the order in which they will 308 // be tried. 309 func (cfg *InstanceConfig) ToolsList() coretools.List { 310 if cfg.tools == nil { 311 return nil 312 } 313 return copyToolsList(cfg.tools) 314 } 315 316 // SetTools sets the tools that should be tried when provisioning this 317 // instance. There must be at least one. Other than the URL, each item 318 // must be the same. 319 // 320 // TODO(axw) 2016-04-19 lp:1572116 321 // SetTools should verify that the tools have URLs, since they will 322 // be needed for downloading on the instance. We can't do that until 323 // all usage-sites are updated to pass through non-empty URLs. 324 func (cfg *InstanceConfig) SetTools(toolsList coretools.List) error { 325 if len(toolsList) == 0 { 326 return errors.New("need at least 1 tools") 327 } 328 var tools *coretools.Tools 329 for _, listed := range toolsList { 330 if listed == nil { 331 return errors.New("nil entry in tools list") 332 } 333 info := *listed 334 info.URL = "" 335 if tools == nil { 336 tools = &info 337 continue 338 } 339 if !reflect.DeepEqual(info, *tools) { 340 return errors.Errorf("tools info mismatch (%v, %v)", *tools, info) 341 } 342 } 343 cfg.tools = copyToolsList(toolsList) 344 return nil 345 } 346 347 func copyToolsList(in coretools.List) coretools.List { 348 out := make(coretools.List, len(in)) 349 for i, tools := range in { 350 copied := *tools 351 out[i] = &copied 352 } 353 return out 354 } 355 356 type requiresError string 357 358 func (e requiresError) Error() string { 359 return "invalid machine configuration: missing " + string(e) 360 } 361 362 func (cfg *InstanceConfig) VerifyConfig() (err error) { 363 defer errors.DeferredAnnotatef(&err, "invalid machine configuration") 364 if !names.IsValidMachine(cfg.MachineId) { 365 return errors.New("invalid machine id") 366 } 367 if cfg.DataDir == "" { 368 return errors.New("missing var directory") 369 } 370 if cfg.LogDir == "" { 371 return errors.New("missing log directory") 372 } 373 if cfg.MetricsSpoolDir == "" { 374 return errors.New("missing metrics spool directory") 375 } 376 if len(cfg.Jobs) == 0 { 377 return errors.New("missing machine jobs") 378 } 379 if cfg.CloudInitOutputLog == "" { 380 return errors.New("missing cloud-init output log path") 381 } 382 if cfg.tools == nil { 383 // SetTools() has never been called successfully. 384 return errors.New("missing tools") 385 } 386 // We don't need to check cfg.toolsURLs since SetTools() does. 387 if cfg.MongoInfo == nil { 388 return errors.New("missing state info") 389 } 390 if len(cfg.MongoInfo.CACert) == 0 { 391 return errors.New("missing CA certificate") 392 } 393 if cfg.APIInfo == nil { 394 return errors.New("missing API info") 395 } 396 if cfg.APIInfo.ModelTag.Id() == "" { 397 return errors.New("missing model tag") 398 } 399 if len(cfg.APIInfo.CACert) == 0 { 400 return errors.New("missing API CA certificate") 401 } 402 if cfg.MachineAgentServiceName == "" { 403 return errors.New("missing machine agent service name") 404 } 405 if cfg.Bootstrap { 406 if cfg.Config == nil { 407 return errors.New("missing model configuration") 408 } 409 if cfg.MongoInfo.Tag != nil { 410 return errors.New("entity tag must be nil when starting a controller") 411 } 412 if cfg.APIInfo.Tag != nil { 413 return errors.New("entity tag must be nil when starting a controller") 414 } 415 if cfg.StateServingInfo == nil { 416 return errors.New("missing state serving info") 417 } 418 if len(cfg.StateServingInfo.Cert) == 0 { 419 return errors.New("missing controller certificate") 420 } 421 if len(cfg.StateServingInfo.PrivateKey) == 0 { 422 return errors.New("missing controller private key") 423 } 424 if len(cfg.StateServingInfo.CAPrivateKey) == 0 { 425 return errors.New("missing ca cert private key") 426 } 427 if cfg.StateServingInfo.StatePort == 0 { 428 return errors.New("missing state port") 429 } 430 if cfg.StateServingInfo.APIPort == 0 { 431 return errors.New("missing API port") 432 } 433 if cfg.InstanceId == "" { 434 return errors.New("missing instance-id") 435 } 436 if len(cfg.HostedModelConfig) == 0 { 437 return errors.New("missing hosted model config") 438 } 439 } else { 440 if len(cfg.MongoInfo.Addrs) == 0 { 441 return errors.New("missing state hosts") 442 } 443 if cfg.MongoInfo.Tag != names.NewMachineTag(cfg.MachineId) { 444 return errors.New("entity tag must match started machine") 445 } 446 if len(cfg.APIInfo.Addrs) == 0 { 447 return errors.New("missing API hosts") 448 } 449 if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) { 450 return errors.New("entity tag must match started machine") 451 } 452 if cfg.StateServingInfo != nil { 453 return errors.New("state serving info unexpectedly present") 454 } 455 if len(cfg.HostedModelConfig) != 0 { 456 return errors.New("hosted model config unexpectedly present") 457 } 458 } 459 if cfg.MachineNonce == "" { 460 return errors.New("missing machine nonce") 461 } 462 return nil 463 } 464 465 // DefaultBridgePrefix is the prefix for all network bridge device 466 // name used for LXC and KVM containers. 467 const DefaultBridgePrefix = "br-" 468 469 // DefaultBridgeName is the network bridge device name used for LXC and KVM 470 // containers 471 const DefaultBridgeName = DefaultBridgePrefix + "eth0" 472 473 // NewInstanceConfig sets up a basic machine configuration, for a 474 // non-bootstrap node. You'll still need to supply more information, 475 // but this takes care of the fixed entries and the ones that are 476 // always needed. 477 func NewInstanceConfig( 478 machineID, 479 machineNonce, 480 imageStream, 481 series, 482 publicImageSigningKey string, 483 secureServerConnections bool, 484 mongoInfo *mongo.MongoInfo, 485 apiInfo *api.Info, 486 ) (*InstanceConfig, error) { 487 dataDir, err := paths.DataDir(series) 488 if err != nil { 489 return nil, err 490 } 491 logDir, err := paths.LogDir(series) 492 if err != nil { 493 return nil, err 494 } 495 metricsSpoolDir, err := paths.MetricsSpoolDir(series) 496 if err != nil { 497 return nil, err 498 } 499 cloudInitOutputLog := path.Join(logDir, "cloud-init-output.log") 500 icfg := &InstanceConfig{ 501 // Fixed entries. 502 DataDir: dataDir, 503 LogDir: path.Join(logDir, "juju"), 504 MetricsSpoolDir: metricsSpoolDir, 505 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 506 CloudInitOutputLog: cloudInitOutputLog, 507 MachineAgentServiceName: "jujud-" + names.NewMachineTag(machineID).String(), 508 Series: series, 509 Tags: map[string]string{}, 510 511 // Parameter entries. 512 MachineId: machineID, 513 MachineNonce: machineNonce, 514 MongoInfo: mongoInfo, 515 APIInfo: apiInfo, 516 ImageStream: imageStream, 517 PublicImageSigningKey: publicImageSigningKey, 518 AgentEnvironment: map[string]string{ 519 agent.AllowsSecureConnection: strconv.FormatBool(secureServerConnections), 520 }, 521 } 522 return icfg, nil 523 } 524 525 // NewBootstrapInstanceConfig sets up a basic machine configuration for a 526 // bootstrap node. You'll still need to supply more information, but this 527 // takes care of the fixed entries and the ones that are always needed. 528 func NewBootstrapInstanceConfig( 529 cons, modelCons constraints.Value, 530 series, publicImageSigningKey string, 531 ) (*InstanceConfig, error) { 532 // For a bootstrap instance, FinishInstanceConfig will provide the 533 // state.Info and the api.Info. The machine id must *always* be "0". 534 icfg, err := NewInstanceConfig("0", agent.BootstrapNonce, "", series, publicImageSigningKey, true, nil, nil) 535 if err != nil { 536 return nil, err 537 } 538 icfg.Bootstrap = true 539 icfg.Jobs = []multiwatcher.MachineJob{ 540 multiwatcher.JobManageModel, 541 multiwatcher.JobHostUnits, 542 } 543 icfg.Constraints = cons 544 icfg.ModelConstraints = modelCons 545 return icfg, nil 546 } 547 548 // PopulateInstanceConfig is called both from the FinishInstanceConfig below, 549 // which does have access to the environment config, and from the container 550 // provisioners, which don't have access to the environment config. Everything 551 // that is needed to provision a container needs to be returned to the 552 // provisioner in the ContainerConfig structure. Those values are then used to 553 // call this function. 554 func PopulateInstanceConfig(icfg *InstanceConfig, 555 providerType, authorizedKeys string, 556 sslHostnameVerification bool, 557 proxySettings, aptProxySettings proxy.Settings, 558 aptMirror string, 559 preferIPv6 bool, 560 enableOSRefreshUpdates bool, 561 enableOSUpgrade bool, 562 ) error { 563 if authorizedKeys == "" { 564 return fmt.Errorf("model configuration has no authorized-keys") 565 } 566 icfg.AuthorizedKeys = authorizedKeys 567 if icfg.AgentEnvironment == nil { 568 icfg.AgentEnvironment = make(map[string]string) 569 } 570 icfg.AgentEnvironment[agent.ProviderType] = providerType 571 icfg.AgentEnvironment[agent.ContainerType] = string(icfg.MachineContainerType) 572 icfg.DisableSSLHostnameVerification = !sslHostnameVerification 573 icfg.ProxySettings = proxySettings 574 icfg.AptProxySettings = aptProxySettings 575 icfg.AptMirror = aptMirror 576 icfg.PreferIPv6 = preferIPv6 577 icfg.EnableOSRefreshUpdate = enableOSRefreshUpdates 578 icfg.EnableOSUpgrade = enableOSUpgrade 579 return nil 580 } 581 582 // FinishInstanceConfig sets fields on a InstanceConfig that can be determined by 583 // inspecting a plain config.Config and the machine constraints at the last 584 // moment before bootstrapping. It assumes that the supplied Config comes from 585 // an environment that has passed through all the validation checks in the 586 // Bootstrap func, and that has set an agent-version (via finding the tools to, 587 // use for bootstrap, or otherwise). 588 // TODO(fwereade) This function is not meant to be "good" in any serious way: 589 // it is better that this functionality be collected in one place here than 590 // that it be spread out across 3 or 4 providers, but this is its only 591 // redeeming feature. 592 func FinishInstanceConfig(icfg *InstanceConfig, cfg *config.Config) (err error) { 593 defer errors.DeferredAnnotatef(&err, "cannot complete machine configuration") 594 595 if err := PopulateInstanceConfig( 596 icfg, 597 cfg.Type(), 598 cfg.AuthorizedKeys(), 599 cfg.SSLHostnameVerification(), 600 cfg.ProxySettings(), 601 cfg.AptProxySettings(), 602 cfg.AptMirror(), 603 cfg.PreferIPv6(), 604 cfg.EnableOSRefreshUpdate(), 605 cfg.EnableOSUpgrade(), 606 ); err != nil { 607 return errors.Trace(err) 608 } 609 610 if isStateInstanceConfig(icfg) { 611 // Add NUMACTL preference. Needed to work for both bootstrap and high availability 612 // Only makes sense for controller 613 logger.Debugf("Setting numa ctl preference to %v", cfg.NumaCtlPreference()) 614 // Unfortunately, AgentEnvironment can only take strings as values 615 icfg.AgentEnvironment[agent.NumaCtlPreference] = fmt.Sprintf("%v", cfg.NumaCtlPreference()) 616 } 617 // The following settings are only appropriate at bootstrap time. At the 618 // moment, the only controller is the bootstrap node, but this 619 // will probably change. 620 if !icfg.Bootstrap { 621 return nil 622 } 623 if icfg.APIInfo != nil || icfg.MongoInfo != nil { 624 return errors.New("machine configuration already has api/state info") 625 } 626 caCert, hasCACert := cfg.CACert() 627 if !hasCACert { 628 return errors.New("model configuration has no ca-cert") 629 } 630 password := cfg.AdminSecret() 631 if password == "" { 632 return errors.New("model configuration has no admin-secret") 633 } 634 icfg.APIInfo = &api.Info{ 635 Password: password, 636 CACert: caCert, 637 ModelTag: names.NewModelTag(cfg.UUID()), 638 } 639 icfg.MongoInfo = &mongo.MongoInfo{Password: password, Info: mongo.Info{CACert: caCert}} 640 641 // These really are directly relevant to running a controller. 642 // Initially, generate a controller certificate with no host IP 643 // addresses in the SAN field. Once the controller is up and the 644 // NIC addresses become known, the certificate can be regenerated. 645 cert, key, err := cfg.GenerateControllerCertAndKey(nil) 646 if err != nil { 647 return errors.Annotate(err, "cannot generate controller certificate") 648 } 649 caPrivateKey, hasCAPrivateKey := cfg.CAPrivateKey() 650 if !hasCAPrivateKey { 651 return errors.New("model configuration has no ca-private-key") 652 } 653 srvInfo := params.StateServingInfo{ 654 StatePort: cfg.StatePort(), 655 APIPort: cfg.APIPort(), 656 Cert: string(cert), 657 PrivateKey: string(key), 658 CAPrivateKey: caPrivateKey, 659 } 660 icfg.StateServingInfo = &srvInfo 661 if icfg.Config, err = bootstrapConfig(cfg); err != nil { 662 return errors.Trace(err) 663 } 664 665 return nil 666 } 667 668 // InstanceTags returns the minimum set of tags that should be set on a 669 // machine instance, if the provider supports them. 670 func InstanceTags(cfg *config.Config, jobs []multiwatcher.MachineJob) map[string]string { 671 instanceTags := tags.ResourceTags( 672 names.NewModelTag(cfg.UUID()), 673 names.NewModelTag(cfg.ControllerUUID()), 674 cfg, 675 ) 676 if multiwatcher.AnyJobNeedsState(jobs...) { 677 instanceTags[tags.JujuIsController] = "true" 678 } 679 return instanceTags 680 } 681 682 // bootstrapConfig returns a copy of the supplied configuration with the 683 // admin-secret and ca-private-key attributes removed. If the resulting 684 // config is not suitable for bootstrapping an environment, an error is 685 // returned. 686 // This function is copied from environs in here so we can avoid an import loop 687 func bootstrapConfig(cfg *config.Config) (*config.Config, error) { 688 m := cfg.AllAttrs() 689 // We never want to push admin-secret or the root CA private key to the cloud. 690 delete(m, "admin-secret") 691 delete(m, "ca-private-key") 692 cfg, err := config.New(config.NoDefaults, m) 693 if err != nil { 694 return nil, err 695 } 696 if _, ok := cfg.AgentVersion(); !ok { 697 return nil, fmt.Errorf("model configuration has no agent-version") 698 } 699 return cfg, nil 700 } 701 702 // isStateInstanceConfig determines if given machine configuration 703 // is for controller by iterating over machine's jobs. 704 // If JobManageModel is present, this is a controller. 705 func isStateInstanceConfig(icfg *InstanceConfig) bool { 706 for _, aJob := range icfg.Jobs { 707 if aJob == multiwatcher.JobManageModel { 708 return true 709 } 710 } 711 return false 712 }