github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/cloudconfig/instancecfg/instancecfg.go (about) 1 // Copyright 2012, 2013, 2015, 2016 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 "encoding/json" 9 "fmt" 10 "net" 11 "os" 12 "path" 13 "reflect" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/juju/charm/v12" 19 "github.com/juju/errors" 20 "github.com/juju/loggo" 21 "github.com/juju/names/v5" 22 "github.com/juju/proxy" 23 "github.com/juju/utils/v3/shell" 24 "github.com/juju/version/v2" 25 "gopkg.in/yaml.v2" 26 27 "github.com/juju/juju/agent" 28 agenttools "github.com/juju/juju/agent/tools" 29 "github.com/juju/juju/api" 30 "github.com/juju/juju/cloud" 31 "github.com/juju/juju/controller" 32 corebase "github.com/juju/juju/core/base" 33 "github.com/juju/juju/core/constraints" 34 "github.com/juju/juju/core/instance" 35 "github.com/juju/juju/core/model" 36 "github.com/juju/juju/core/paths" 37 "github.com/juju/juju/environs/config" 38 "github.com/juju/juju/environs/imagemetadata" 39 "github.com/juju/juju/environs/tags" 40 "github.com/juju/juju/service" 41 "github.com/juju/juju/service/common" 42 "github.com/juju/juju/storage" 43 coretools "github.com/juju/juju/tools" 44 ) 45 46 var logger = loggo.GetLogger("juju.cloudconfig.instancecfg") 47 48 // InstanceConfig represents initialization information for a new juju instance. 49 type InstanceConfig struct { 50 // Tags is a set of tags to set on the instance, if supported. This 51 // should be populated using the InstanceTags method in this package. 52 Tags map[string]string 53 54 // Bootstrap contains bootstrap-specific configuration. If this is set, 55 // Controller must also be set. 56 Bootstrap *BootstrapConfig 57 58 // Controller contains configuration for the controller 59 // used to manage this new instance. 60 ControllerConfig controller.Config 61 62 // The public key used to sign Juju simplestreams image metadata. 63 PublicImageSigningKey string 64 65 // APIInfo holds the means for the new instance to communicate with the 66 // juju state API. Unless the new instance is running a controller (Controller is 67 // set), there must be at least one controller address supplied. 68 // The entity name must match that of the instance being started, 69 // or be empty when starting a controller. 70 APIInfo *api.Info 71 72 // ControllerTag identifies the controller. 73 ControllerTag names.ControllerTag 74 75 // MachineNonce is set at provisioning/bootstrap time and used to 76 // ensure the agent is running on the correct instance. 77 MachineNonce string 78 79 // tools is the list of juju tools used to install the Juju agent 80 // on the new instance. Each of the entries in the list must have 81 // identical versions and hashes, but may have different URLs. 82 tools coretools.List 83 84 // TransientDataDir holds the directory that juju can use to write 85 // transient files that get purged after a system reboot. 86 TransientDataDir string 87 88 // DataDir holds the directory that juju state will be put in the new 89 // instance. 90 DataDir string 91 92 // LogDir holds the directory that juju logs will be written to. 93 LogDir string 94 95 // MetricsSpoolDir represents the spool directory path, where all 96 // metrics are stored. 97 MetricsSpoolDir string 98 99 // Jobs holds what machine jobs to run. 100 Jobs []model.MachineJob 101 102 // CloudInitOutputLog specifies the path to the output log for cloud-init. 103 // The directory containing the log file must already exist. 104 CloudInitOutputLog string 105 106 // CloudInitUserData defines key/value pairs from the model-config 107 // specified by the user. 108 CloudInitUserData map[string]interface{} 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 // DisableSSLHostnameVerification can be set to true to tell cloud-init 134 // that it shouldn't verify SSL certificates 135 DisableSSLHostnameVerification bool 136 137 // Base represents the instance base. 138 Base corebase.Base 139 140 // MachineAgentServiceName is the init service name for the Juju machine agent. 141 MachineAgentServiceName string 142 143 // LegacyProxySettings define normal http, https and ftp proxies. 144 // These values are written to the /etc for the user profile and systemd settings. 145 LegacyProxySettings proxy.Settings 146 147 // JujuProxySettings define normal http, https and ftp proxies for accessing 148 // the outside network. These values are not written to disk. 149 JujuProxySettings proxy.Settings 150 151 // AptProxySettings define the http, https and ftp proxy settings to use 152 // for apt, which may or may not be the same as the normal ProxySettings. 153 AptProxySettings proxy.Settings 154 155 // AptMirror defines an APT mirror location, which, if specified, will 156 // override the default APT sources. 157 AptMirror string 158 159 // SnapProxySettings define the http, https and ftp proxy settings to 160 // use for snap, which may or may not be the same as the normal 161 // ProxySettings. 162 SnapProxySettings proxy.Settings 163 164 // SnapStoreAssertions contains a list of assertions that must be 165 // passed to snapd together with a store proxy ID parameter before it 166 // can connect to a snap store proxy. 167 SnapStoreAssertions string 168 169 // SnapStoreProxyID references a store entry in the snap store 170 // assertion list that must be passed to snapd before it can connect to 171 // a snap store proxy. 172 SnapStoreProxyID string 173 174 // SnapStoreProxyURL specifies the address of the snap store proxy. If 175 // specified instead of the assertions/storeID settings above, juju can 176 // directly contact the proxy to retrieve the assertions and store ID. 177 SnapStoreProxyURL string 178 179 // The type of Simple Stream to download and deploy on this instance. 180 ImageStream string 181 182 // EnableOSRefreshUpdate specifies whether Juju will refresh its 183 // respective OS's updates list. 184 EnableOSRefreshUpdate bool 185 186 // EnableOSUpgrade defines Juju's behavior when provisioning 187 // instances. If enabled, the OS will perform any upgrades 188 // available as part of its provisioning. 189 EnableOSUpgrade bool 190 191 // NetBondReconfigureDelay defines the duration in seconds that the 192 // networking bridgescript should pause between ifdown, then 193 // ifup when bridging bonded interfaces. See bugs #1594855 and 194 // #1269921. 195 NetBondReconfigureDelay int 196 197 // Profiles is a slice of (lxd) profile names to be used by a container 198 Profiles []string 199 } 200 201 // BootstrapConfig represents bootstrap-specific initialization information 202 // for a new juju instance. This is only relevant for the bootstrap machine. 203 type BootstrapConfig struct { 204 StateInitializationParams 205 206 // ControllerCharm is a local controller charm to be used. 207 ControllerCharm string 208 209 // Timeout is the amount of time to wait for bootstrap to complete. 210 Timeout time.Duration 211 212 // InitialSSHHostKeys contains the initial SSH host keys to configure 213 // on the bootstrap machine, indexed by algorithm. These will only be 214 // valid for the initial SSH connection. The first thing we do upon 215 // making the initial SSH connection is to replace each of these host 216 // keys, to avoid the host keys being extracted from the metadata 217 // service by a bad actor post-bootstrap. 218 // 219 // Any existing host keys on the machine with algorithms not specified 220 // in the map will be left alone. This is important so that we do not 221 // trample on the host keys of manually provisioned machines. 222 InitialSSHHostKeys SSHHostKeys 223 224 // StateServingInfo holds the information for serving the state. 225 // This is only specified for bootstrap; controllers started 226 // subsequently will acquire their serving info from another 227 // server. 228 StateServingInfo controller.StateServingInfo 229 230 // JujuDbSnapPath is the path to a .snap file that will be used as the juju-db 231 // service. 232 JujuDbSnapPath string 233 234 // JujuDbSnapAssertions is a path to a .assert file that will be used 235 // to verify the .snap at JujuDbSnapPath 236 JujuDbSnapAssertionsPath string 237 238 // ControllerServiceType is the service type of a k8s controller. 239 ControllerServiceType string 240 241 // IgnoreProxy tells the boostrap provider to no deploy any controller 242 // proxying resources. Currently only used in k8s 243 IgnoreProxy bool 244 245 // ControllerExternalName is the external name of a k8s controller. 246 ControllerExternalName string 247 248 // ControllerExternalIPs is the list of external ips for a k8s controller. 249 ControllerExternalIPs []string 250 } 251 252 // SSHHostKeys contains the SSH host keys to configure for a bootstrap host. 253 type SSHHostKeys []SSHKeyPair 254 255 // SSHKeyPair is an SSH host key pair. 256 type SSHKeyPair struct { 257 // Private contains the private key, PEM-encoded. 258 Private string 259 260 // Public contains the public key in authorized_keys format. 261 Public string 262 263 // PublicKeyAlgorithm contains the public key algorithm as defined by golang.org/x/crypto/ssh KeyAlgo* 264 PublicKeyAlgorithm string 265 } 266 267 // StateInitializationParams contains parameters for initializing the 268 // state database. 269 // 270 // This structure will be passed to the bootstrap agent. To do so, the 271 // Marshal and Unmarshal methods must be used. 272 type StateInitializationParams struct { 273 // ControllerModelConfig holds the initial controller model configuration. 274 ControllerModelConfig *config.Config 275 276 // ControllerModelEnvironVersion holds the initial controller model 277 // environ version. 278 ControllerModelEnvironVersion int 279 280 // ControllerCloud contains the properties of the cloud that Juju will 281 // be bootstrapped in. 282 ControllerCloud cloud.Cloud 283 284 // ControllerCloudRegion is the name of the cloud region that Juju will be 285 // bootstrapped in. 286 ControllerCloudRegion string 287 288 // ControllerCloudCredentialName is the name of the cloud credential that 289 // Juju will be bootstrapped with. 290 ControllerCloudCredentialName string 291 292 // ControllerCloudCredential contains the cloud credential that Juju will 293 // be bootstrapped with. 294 ControllerCloudCredential *cloud.Credential 295 296 // ControllerConfig is the set of config attributes relevant 297 // to a controller. 298 ControllerConfig controller.Config 299 300 // ControllerCharmPath points to a controller charm on Charmhub. 301 ControllerCharmPath string 302 303 // ControllerCharmChannel is used when deploying the controller charm. 304 ControllerCharmChannel charm.Channel 305 306 // ControllerInheritedConfig is a set of config attributes to be shared by all 307 // models managed by this controller. 308 ControllerInheritedConfig map[string]interface{} 309 310 // RegionInheritedConfig holds region specific configuration attributes to 311 // be shared across all models in the same controller on a particular 312 // cloud. 313 RegionInheritedConfig cloud.RegionConfig 314 315 // InitialModelConfig is a set of config attributes to be overlaid 316 // on the controller model config (Config, above) to construct the 317 // initial hosted model config. 318 InitialModelConfig map[string]interface{} 319 320 // BootstrapMachineInstanceId is the instance ID of the bootstrap 321 // machine instance being initialized. 322 BootstrapMachineInstanceId instance.Id 323 324 // BootstrapMachineDisplayName is the human readable name for 325 // the bootstrap machine instance being initialized. 326 BootstrapMachineDisplayName string 327 328 // BootstrapMachineConstraints holds the constraints for the bootstrap 329 // machine. 330 BootstrapMachineConstraints constraints.Value 331 332 // BootstrapMachineHardwareCharacteristics contains the hardware 333 // characteristics of the bootstrap machine instance being initialized. 334 BootstrapMachineHardwareCharacteristics *instance.HardwareCharacteristics 335 336 // ModelConstraints holds the initial model constraints. 337 ModelConstraints constraints.Value 338 339 // CustomImageMetadata is optional custom simplestreams image metadata 340 // to store in environment storage at bootstrap time. This is ignored 341 // in non-bootstrap instances. 342 CustomImageMetadata []*imagemetadata.ImageMetadata 343 344 // StoragePools is one or more named storage pools to create 345 // in the controller model. 346 StoragePools map[string]storage.Attrs 347 } 348 349 type stateInitializationParamsInternal struct { 350 ControllerConfig map[string]interface{} `yaml:"controller-config"` 351 ControllerModelConfig map[string]interface{} `yaml:"controller-model-config"` 352 ControllerModelEnvironVersion int `yaml:"controller-model-version"` 353 ControllerInheritedConfig map[string]interface{} `yaml:"controller-config-defaults,omitempty"` 354 RegionInheritedConfig cloud.RegionConfig `yaml:"region-inherited-config,omitempty"` 355 InitialModelConfig map[string]interface{} `yaml:"initial-model-config,omitempty"` 356 StoragePools map[string]storage.Attrs `yaml:"storage-pools,omitempty"` 357 BootstrapMachineInstanceId instance.Id `yaml:"bootstrap-machine-instance-id,omitempty"` 358 BootstrapMachineConstraints constraints.Value `yaml:"bootstrap-machine-constraints"` 359 BootstrapMachineHardwareCharacteristics *instance.HardwareCharacteristics `yaml:"bootstrap-machine-hardware,omitempty"` 360 BootstrapMachineDisplayName string `yaml:"bootstrap-machine-display-name,omitempty"` 361 ModelConstraints constraints.Value `yaml:"model-constraints"` 362 CustomImageMetadataJSON string `yaml:"custom-image-metadata,omitempty"` 363 ControllerCloud string `yaml:"controller-cloud"` 364 ControllerCloudRegion string `yaml:"controller-cloud-region"` 365 ControllerCloudCredentialName string `yaml:"controller-cloud-credential-name,omitempty"` 366 ControllerCloudCredential *cloud.Credential `yaml:"controller-cloud-credential,omitempty"` 367 ControllerCharmPath string `yaml:"controller-charm-path,omitempty"` 368 ControllerCharmChannel charm.Channel `yaml:"controller-charm-channel,omitempty"` 369 } 370 371 // Marshal marshals StateInitializationParams to an opaque byte array. 372 func (p *StateInitializationParams) Marshal() ([]byte, error) { 373 customImageMetadataJSON, err := json.Marshal(p.CustomImageMetadata) 374 if err != nil { 375 return nil, errors.Annotate(err, "marshalling custom image metadata") 376 } 377 controllerCloud, err := cloud.MarshalCloud(p.ControllerCloud) 378 if err != nil { 379 return nil, errors.Annotate(err, "marshalling cloud definition") 380 } 381 internal := stateInitializationParamsInternal{ 382 ControllerConfig: p.ControllerConfig, 383 ControllerModelConfig: p.ControllerModelConfig.AllAttrs(), 384 ControllerModelEnvironVersion: p.ControllerModelEnvironVersion, 385 ControllerInheritedConfig: p.ControllerInheritedConfig, 386 RegionInheritedConfig: p.RegionInheritedConfig, 387 InitialModelConfig: p.InitialModelConfig, 388 StoragePools: p.StoragePools, 389 BootstrapMachineInstanceId: p.BootstrapMachineInstanceId, 390 BootstrapMachineConstraints: p.BootstrapMachineConstraints, 391 BootstrapMachineHardwareCharacteristics: p.BootstrapMachineHardwareCharacteristics, 392 BootstrapMachineDisplayName: p.BootstrapMachineDisplayName, 393 ModelConstraints: p.ModelConstraints, 394 CustomImageMetadataJSON: string(customImageMetadataJSON), 395 ControllerCloud: string(controllerCloud), 396 ControllerCloudRegion: p.ControllerCloudRegion, 397 ControllerCloudCredentialName: p.ControllerCloudCredentialName, 398 ControllerCloudCredential: p.ControllerCloudCredential, 399 ControllerCharmPath: p.ControllerCharmPath, 400 ControllerCharmChannel: p.ControllerCharmChannel, 401 } 402 return yaml.Marshal(&internal) 403 } 404 405 // Unmarshal unmarshals StateInitializationParams from a byte array that 406 // was generated with StateInitializationParams.Marshal. 407 func (p *StateInitializationParams) Unmarshal(data []byte) error { 408 var internal stateInitializationParamsInternal 409 if err := yaml.Unmarshal(data, &internal); err != nil { 410 return errors.Annotate(err, "unmarshalling state initialization params") 411 } 412 var imageMetadata []*imagemetadata.ImageMetadata 413 if err := json.Unmarshal([]byte(internal.CustomImageMetadataJSON), &imageMetadata); err != nil { 414 return errors.Trace(err) 415 } 416 cfg, err := config.New(config.NoDefaults, internal.ControllerModelConfig) 417 if err != nil { 418 return errors.Trace(err) 419 } 420 controllerCloud, err := cloud.UnmarshalCloud([]byte(internal.ControllerCloud)) 421 if err != nil { 422 return errors.Trace(err) 423 } 424 *p = StateInitializationParams{ 425 ControllerConfig: internal.ControllerConfig, 426 ControllerModelConfig: cfg, 427 ControllerModelEnvironVersion: internal.ControllerModelEnvironVersion, 428 ControllerInheritedConfig: internal.ControllerInheritedConfig, 429 RegionInheritedConfig: internal.RegionInheritedConfig, 430 InitialModelConfig: internal.InitialModelConfig, 431 StoragePools: internal.StoragePools, 432 BootstrapMachineInstanceId: internal.BootstrapMachineInstanceId, 433 BootstrapMachineConstraints: internal.BootstrapMachineConstraints, 434 BootstrapMachineHardwareCharacteristics: internal.BootstrapMachineHardwareCharacteristics, 435 BootstrapMachineDisplayName: internal.BootstrapMachineDisplayName, 436 ModelConstraints: internal.ModelConstraints, 437 CustomImageMetadata: imageMetadata, 438 ControllerCloud: controllerCloud, 439 ControllerCloudRegion: internal.ControllerCloudRegion, 440 ControllerCloudCredentialName: internal.ControllerCloudCredentialName, 441 ControllerCloudCredential: internal.ControllerCloudCredential, 442 ControllerCharmPath: internal.ControllerCharmPath, 443 ControllerCharmChannel: internal.ControllerCharmChannel, 444 } 445 return nil 446 } 447 448 func (cfg *InstanceConfig) agentInfo() service.AgentInfo { 449 return service.NewMachineAgentInfo( 450 cfg.MachineId, 451 cfg.DataDir, 452 cfg.LogDir, 453 ) 454 } 455 456 func (cfg *InstanceConfig) IsController() bool { 457 return model.AnyJobNeedsState(cfg.Jobs...) 458 } 459 460 func (cfg *InstanceConfig) ToolsDir(renderer shell.Renderer) string { 461 return cfg.agentInfo().ToolsDir(renderer) 462 } 463 464 func (cfg *InstanceConfig) InitService(renderer shell.Renderer) (service.Service, error) { 465 conf := service.AgentConf(cfg.agentInfo(), renderer) 466 467 name := cfg.MachineAgentServiceName 468 svc, err := newService(name, conf) 469 return svc, errors.Trace(err) 470 } 471 472 var newService = func(name string, conf common.Conf) (service.Service, error) { 473 return service.NewService(name, conf) 474 } 475 476 func (cfg *InstanceConfig) AgentConfig( 477 tag names.Tag, 478 toolsVersion version.Number, 479 ) (agent.ConfigSetter, error) { 480 configParams := agent.AgentConfigParams{ 481 Paths: agent.Paths{ 482 DataDir: cfg.DataDir, 483 TransientDataDir: cfg.TransientDataDir, 484 LogDir: cfg.LogDir, 485 MetricsSpoolDir: cfg.MetricsSpoolDir, 486 }, 487 Jobs: cfg.Jobs, 488 Tag: tag, 489 UpgradedToVersion: toolsVersion, 490 Password: cfg.APIInfo.Password, 491 Nonce: cfg.MachineNonce, 492 APIAddresses: cfg.APIHostAddrs(), 493 CACert: cfg.APIInfo.CACert, 494 Values: cfg.AgentEnvironment, 495 Controller: cfg.ControllerTag, 496 Model: cfg.APIInfo.ModelTag, 497 } 498 if cfg.ControllerConfig != nil { 499 configParams.AgentLogfileMaxBackups = cfg.ControllerConfig.AgentLogfileMaxBackups() 500 configParams.AgentLogfileMaxSizeMB = cfg.ControllerConfig.AgentLogfileMaxSizeMB() 501 configParams.QueryTracingEnabled = cfg.ControllerConfig.QueryTracingEnabled() 502 configParams.QueryTracingThreshold = cfg.ControllerConfig.QueryTracingThreshold() 503 } 504 if cfg.Bootstrap == nil { 505 return agent.NewAgentConfig(configParams) 506 } 507 return agent.NewStateMachineConfig(configParams, cfg.Bootstrap.StateServingInfo) 508 } 509 510 // JujuTools returns the directory where Juju tools are stored. 511 func (cfg *InstanceConfig) JujuTools() string { 512 return agenttools.SharedToolsDir(cfg.DataDir, cfg.AgentVersion()) 513 } 514 515 // SnapDir returns the directory where snaps should be uploaded to. 516 func (cfg *InstanceConfig) SnapDir() string { 517 return path.Join(cfg.DataDir, "snap") 518 } 519 520 // CharmDir returns the directory where system charms should be uploaded to. 521 func (cfg *InstanceConfig) CharmDir() string { 522 return path.Join(cfg.DataDir, "charms") 523 } 524 525 func (cfg *InstanceConfig) APIHostAddrs() []string { 526 var hosts []string 527 if cfg.Bootstrap != nil { 528 hosts = append(hosts, net.JoinHostPort( 529 "localhost", strconv.Itoa(cfg.Bootstrap.StateServingInfo.APIPort)), 530 ) 531 } 532 if cfg.APIInfo != nil { 533 hosts = append(hosts, cfg.APIInfo.Addrs...) 534 } 535 return hosts 536 } 537 538 func (cfg *InstanceConfig) APIHosts() []string { 539 var hosts []string 540 if cfg.Bootstrap != nil { 541 hosts = append(hosts, "localhost") 542 } 543 if cfg.APIInfo != nil { 544 for _, addr := range cfg.APIInfo.Addrs { 545 host, _, err := net.SplitHostPort(addr) 546 if err != nil { 547 logger.Errorf("Can't split API address %q to host:port - %q", host, err) 548 continue 549 } 550 hosts = append(hosts, host) 551 } 552 } 553 return hosts 554 } 555 556 // AgentVersion returns the version of the Juju agent that will be configured 557 // on the instance. The zero value will be returned if there are no tools set. 558 func (cfg *InstanceConfig) AgentVersion() version.Binary { 559 if len(cfg.tools) == 0 { 560 return version.Binary{} 561 } 562 return cfg.tools[0].Version 563 } 564 565 // ToolsList returns the list of tools in the order in which they will 566 // be tried. 567 func (cfg *InstanceConfig) ToolsList() coretools.List { 568 if cfg.tools == nil { 569 return nil 570 } 571 return copyToolsList(cfg.tools) 572 } 573 574 // SetTools sets the tools that should be tried when provisioning this 575 // instance. There must be at least one. Other than the URL, each item 576 // must be the same. 577 // 578 // TODO(axw) 2016-04-19 lp:1572116 579 // SetTools should verify that the tools have URLs, since they will 580 // be needed for downloading on the instance. We can't do that until 581 // all usage-sites are updated to pass through non-empty URLs. 582 func (cfg *InstanceConfig) SetTools(toolsList coretools.List) error { 583 if len(toolsList) == 0 { 584 return errors.New("need at least 1 agent binary") 585 } 586 var tools *coretools.Tools 587 for _, listed := range toolsList { 588 if listed == nil { 589 return errors.New("nil entry in agent binaries list") 590 } 591 info := *listed 592 info.URL = "" 593 if tools == nil { 594 tools = &info 595 continue 596 } 597 if !reflect.DeepEqual(info, *tools) { 598 return errors.Errorf("agent binary info mismatch (%v, %v)", *tools, info) 599 } 600 } 601 cfg.tools = copyToolsList(toolsList) 602 return nil 603 } 604 605 func copyToolsList(in coretools.List) coretools.List { 606 out := make(coretools.List, len(in)) 607 for i, tools := range in { 608 copied := *tools 609 out[i] = &copied 610 } 611 return out 612 } 613 614 // SetSnapSource annotates the instance configuration 615 // with the location of a local .snap to upload during 616 // the instance's provisioning. 617 func (cfg *InstanceConfig) SetSnapSource(snapPath string, snapAssertionsPath string) error { 618 if snapPath == "" { 619 return nil 620 } 621 622 _, err := os.Stat(snapPath) 623 if err != nil { 624 return errors.Annotatef(err, "unable set local snap (at %s)", snapPath) 625 } 626 627 _, err = os.Stat(snapAssertionsPath) 628 if err != nil { 629 return errors.Annotatef(err, "unable set local snap .assert (at %s)", snapAssertionsPath) 630 } 631 632 cfg.Bootstrap.JujuDbSnapPath = snapPath 633 cfg.Bootstrap.JujuDbSnapAssertionsPath = snapAssertionsPath 634 635 return nil 636 } 637 638 // SetControllerCharm annotates the instance configuration 639 // with the location of a local controller charm to upload during 640 // the instance's provisioning. 641 func (cfg *InstanceConfig) SetControllerCharm(controllerCharmPath string) error { 642 if controllerCharmPath == "" { 643 return nil 644 } 645 646 _, err := os.Stat(controllerCharmPath) 647 if err != nil { 648 return errors.Annotatef(err, "unable set local controller charm (at %s)", controllerCharmPath) 649 } 650 651 cfg.Bootstrap.ControllerCharm = controllerCharmPath 652 653 return nil 654 } 655 656 // VerifyConfig verifies that the InstanceConfig is valid. 657 func (cfg *InstanceConfig) VerifyConfig() (err error) { 658 defer errors.DeferredAnnotatef(&err, "invalid machine configuration") 659 if !names.IsValidMachine(cfg.MachineId) { 660 return errors.New("invalid machine id") 661 } 662 if cfg.DataDir == "" { 663 return errors.New("missing var directory") 664 } 665 if cfg.LogDir == "" { 666 return errors.New("missing log directory") 667 } 668 if cfg.MetricsSpoolDir == "" { 669 return errors.New("missing metrics spool directory") 670 } 671 if len(cfg.Jobs) == 0 { 672 return errors.New("missing machine jobs") 673 } 674 if cfg.CloudInitOutputLog == "" { 675 return errors.New("missing cloud-init output log path") 676 } 677 if cfg.tools == nil { 678 // SetTools() has never been called successfully. 679 return errors.New("missing agent binaries") 680 } 681 // We don't need to check cfg.toolsURLs since SetTools() does. 682 if cfg.APIInfo == nil { 683 return errors.New("missing API info") 684 } 685 if cfg.APIInfo.ModelTag.Id() == "" { 686 return errors.New("missing model tag") 687 } 688 if len(cfg.APIInfo.CACert) == 0 { 689 return errors.New("missing API CA certificate") 690 } 691 if cfg.MachineAgentServiceName == "" { 692 return errors.New("missing machine agent service name") 693 } 694 if cfg.MachineNonce == "" { 695 return errors.New("missing machine nonce") 696 } 697 if cfg.Bootstrap != nil { 698 if err := cfg.verifyBootstrapConfig(); err != nil { 699 return errors.Trace(err) 700 } 701 } else { 702 if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) { 703 return errors.New("API entity tag must match started machine") 704 } 705 if len(cfg.APIInfo.Addrs) == 0 { 706 return errors.New("missing API hosts") 707 } 708 } 709 return nil 710 } 711 712 func (cfg *InstanceConfig) verifyBootstrapConfig() (err error) { 713 defer errors.DeferredAnnotatef(&err, "invalid bootstrap configuration") 714 if cfg.ControllerConfig == nil { 715 return errors.New("bootstrap config supplied without controller config") 716 } 717 if err := cfg.Bootstrap.VerifyConfig(); err != nil { 718 return errors.Trace(err) 719 } 720 if cfg.APIInfo.Tag != nil { 721 return errors.New("entity tag must be nil when bootstrapping") 722 } 723 return nil 724 } 725 726 // VerifyConfig verifies that the BootstrapConfig is valid. 727 func (cfg *BootstrapConfig) VerifyConfig() (err error) { 728 if cfg.ControllerModelConfig == nil { 729 return errors.New("missing model configuration") 730 } 731 if len(cfg.StateServingInfo.Cert) == 0 { 732 return errors.New("missing controller certificate") 733 } 734 if len(cfg.StateServingInfo.PrivateKey) == 0 { 735 return errors.New("missing controller private key") 736 } 737 if len(cfg.StateServingInfo.CAPrivateKey) == 0 { 738 return errors.New("missing ca cert private key") 739 } 740 if cfg.StateServingInfo.StatePort == 0 { 741 return errors.New("missing state port") 742 } 743 if cfg.StateServingInfo.APIPort == 0 { 744 return errors.New("missing API port") 745 } 746 if cfg.BootstrapMachineInstanceId == "" { 747 return errors.New("missing bootstrap machine instance ID") 748 } 749 return nil 750 } 751 752 // DefaultBridgeName is the network bridge device name used for LXC and KVM 753 // containers 754 const DefaultBridgeName = "br-eth0" 755 756 // NewInstanceConfig sets up a basic machine configuration, for a 757 // non-bootstrap node. You'll still need to supply more information, 758 // but this takes care of the fixed entries and the ones that are 759 // always needed. 760 func NewInstanceConfig( 761 controllerTag names.ControllerTag, 762 machineID, 763 machineNonce, 764 imageStream string, 765 base corebase.Base, 766 apiInfo *api.Info, 767 ) (*InstanceConfig, error) { 768 osType := paths.OSType(base.OS) 769 logDir := paths.LogDir(osType) 770 icfg := &InstanceConfig{ 771 // Fixed entries. 772 DataDir: paths.DataDir(osType), 773 LogDir: path.Join(logDir, "juju"), 774 MetricsSpoolDir: paths.MetricsSpoolDir(osType), 775 Jobs: []model.MachineJob{model.JobHostUnits}, 776 CloudInitOutputLog: path.Join(logDir, "cloud-init-output.log"), 777 TransientDataDir: paths.TransientDataDir(osType), 778 MachineAgentServiceName: "jujud-" + names.NewMachineTag(machineID).String(), 779 Base: base, 780 Tags: map[string]string{}, 781 782 // Parameter entries. 783 ControllerTag: controllerTag, 784 MachineId: machineID, 785 MachineNonce: machineNonce, 786 APIInfo: apiInfo, 787 ImageStream: imageStream, 788 } 789 return icfg, nil 790 } 791 792 // NewBootstrapInstanceConfig sets up a basic machine configuration for a 793 // bootstrap node. You'll still need to supply more information, but this 794 // takes care of the fixed entries and the ones that are always needed. 795 func NewBootstrapInstanceConfig( 796 config controller.Config, 797 cons, modelCons constraints.Value, 798 base corebase.Base, publicImageSigningKey string, 799 agentEnvironment map[string]string, 800 ) (*InstanceConfig, error) { 801 // For a bootstrap instance, the caller must provide the state.Info 802 // and the api.Info. The machine id must *always* be "0". 803 icfg, err := NewInstanceConfig(names.NewControllerTag(config.ControllerUUID()), agent.BootstrapControllerId, agent.BootstrapNonce, "", base, nil) 804 if err != nil { 805 return nil, err 806 } 807 icfg.PublicImageSigningKey = publicImageSigningKey 808 icfg.ControllerConfig = make(map[string]interface{}) 809 for k, v := range config { 810 icfg.ControllerConfig[k] = v 811 } 812 icfg.Bootstrap = &BootstrapConfig{ 813 StateInitializationParams: StateInitializationParams{ 814 BootstrapMachineConstraints: cons, 815 ModelConstraints: modelCons, 816 }, 817 } 818 for k, v := range agentEnvironment { 819 if icfg.AgentEnvironment == nil { 820 icfg.AgentEnvironment = make(map[string]string) 821 } 822 icfg.AgentEnvironment[k] = v 823 } 824 icfg.Jobs = []model.MachineJob{ 825 model.JobManageModel, 826 model.JobHostUnits, 827 } 828 return icfg, nil 829 } 830 831 // ProxyConfiguration encapsulates all proxy-related settings that can be used 832 // to populate an InstanceConfig. 833 type ProxyConfiguration struct { 834 // Legacy proxy settings. 835 Legacy proxy.Settings 836 837 // Juju-specific proxy settings. 838 Juju proxy.Settings 839 840 // Apt-specific proxy settings. 841 Apt proxy.Settings 842 843 // Snap-specific proxy settings. 844 Snap proxy.Settings 845 846 // Apt mirror. 847 AptMirror string 848 849 // SnapStoreAssertions contains a list of assertions that must be 850 // passed to snapd together with a store proxy ID parameter before it 851 // can connect to a snap store proxy. 852 SnapStoreAssertions string 853 854 // SnapStoreProxyID references a store entry in the snap store 855 // assertion list that must be passed to snapd before it can connect to 856 // a snap store proxy. 857 SnapStoreProxyID string 858 859 // SnapStoreProxyURL specifies the address of the snap store proxy. If 860 // specified instead of the assertions/storeID settings above, juju can 861 // directly contact the proxy to retrieve the assertions and store ID. 862 SnapStoreProxyURL string 863 } 864 865 // proxyConfigurationFromEnv populates a ProxyConfiguration object from an 866 // environment Config value. 867 func proxyConfigurationFromEnv(cfg *config.Config) ProxyConfiguration { 868 return ProxyConfiguration{ 869 Legacy: cfg.LegacyProxySettings(), 870 Juju: cfg.JujuProxySettings(), 871 Apt: cfg.AptProxySettings(), 872 AptMirror: cfg.AptMirror(), 873 Snap: cfg.SnapProxySettings(), 874 SnapStoreAssertions: cfg.SnapStoreAssertions(), 875 SnapStoreProxyID: cfg.SnapStoreProxy(), 876 SnapStoreProxyURL: cfg.SnapStoreProxyURL(), 877 } 878 } 879 880 // PopulateInstanceConfig is called both from the FinishInstanceConfig below, 881 // which does have access to the environment config, and from the container 882 // provisioners, which don't have access to the environment config. Everything 883 // that is needed to provision a container needs to be returned to the 884 // provisioner in the ContainerConfig structure. Those values are then used to 885 // call this function. 886 func PopulateInstanceConfig(icfg *InstanceConfig, 887 providerType, authorizedKeys string, 888 sslHostnameVerification bool, 889 proxyCfg ProxyConfiguration, 890 enableOSRefreshUpdates bool, 891 enableOSUpgrade bool, 892 cloudInitUserData map[string]interface{}, 893 profiles []string, 894 ) error { 895 icfg.AuthorizedKeys = authorizedKeys 896 if icfg.AgentEnvironment == nil { 897 icfg.AgentEnvironment = make(map[string]string) 898 } 899 icfg.AgentEnvironment[agent.ProviderType] = providerType 900 icfg.AgentEnvironment[agent.ContainerType] = string(icfg.MachineContainerType) 901 icfg.DisableSSLHostnameVerification = !sslHostnameVerification 902 icfg.LegacyProxySettings = proxyCfg.Legacy 903 icfg.LegacyProxySettings.AutoNoProxy = strings.Join(icfg.APIHosts(), ",") 904 icfg.JujuProxySettings = proxyCfg.Juju 905 // No AutoNoProxy needed as juju no proxy values are CIDR aware. 906 icfg.AptProxySettings = proxyCfg.Apt 907 icfg.AptMirror = proxyCfg.AptMirror 908 icfg.SnapProxySettings = proxyCfg.Snap 909 icfg.SnapStoreAssertions = proxyCfg.SnapStoreAssertions 910 icfg.SnapStoreProxyID = proxyCfg.SnapStoreProxyID 911 icfg.SnapStoreProxyURL = proxyCfg.SnapStoreProxyURL 912 icfg.EnableOSRefreshUpdate = enableOSRefreshUpdates 913 icfg.EnableOSUpgrade = enableOSUpgrade 914 icfg.CloudInitUserData = cloudInitUserData 915 icfg.Profiles = profiles 916 return nil 917 } 918 919 // FinishInstanceConfig sets fields on a InstanceConfig that can be determined by 920 // inspecting a plain config.Config and the machine constraints at the last 921 // moment before creating the user-data. It assumes that the supplied Config comes 922 // from an environment that has passed through all the validation checks in the 923 // Bootstrap func, and that has set an agent-version (via finding the tools to, 924 // use for bootstrap, or otherwise). 925 // TODO(fwereade) This function is not meant to be "good" in any serious way: 926 // it is better that this functionality be collected in one place here than 927 // that it be spread out across 3 or 4 providers, but this is its only 928 // redeeming feature. 929 func FinishInstanceConfig(icfg *InstanceConfig, cfg *config.Config) (err error) { 930 defer errors.DeferredAnnotatef(&err, "cannot complete machine configuration") 931 if err := PopulateInstanceConfig( 932 icfg, 933 cfg.Type(), 934 cfg.AuthorizedKeys(), 935 cfg.SSLHostnameVerification(), 936 proxyConfigurationFromEnv(cfg), 937 cfg.EnableOSRefreshUpdate(), 938 cfg.EnableOSUpgrade(), 939 cfg.CloudInitUserData(), 940 nil, 941 ); err != nil { 942 return errors.Trace(err) 943 } 944 if icfg.IsController() { 945 // Add NUMACTL preference. Needed to work for both bootstrap and high availability 946 // Only makes sense for controller, 947 logger.Debugf("Setting numa ctl preference to %v", icfg.ControllerConfig.NUMACtlPreference()) 948 // Unfortunately, AgentEnvironment can only take strings as values 949 icfg.AgentEnvironment[agent.NUMACtlPreference] = fmt.Sprintf("%v", icfg.ControllerConfig.NUMACtlPreference()) 950 } 951 return nil 952 } 953 954 // InstanceTags returns the minimum set of tags that should be set on a 955 // machine instance, if the provider supports them. 956 func InstanceTags(modelUUID, controllerUUID string, tagger tags.ResourceTagger, isController bool) map[string]string { 957 instanceTags := tags.ResourceTags( 958 names.NewModelTag(modelUUID), 959 names.NewControllerTag(controllerUUID), 960 tagger, 961 ) 962 if isController { 963 instanceTags[tags.JujuIsController] = "true" 964 } 965 return instanceTags 966 }