github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/config/config.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package config 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "net/url" 10 "os" 11 "path/filepath" 12 "strings" 13 "time" 14 15 "github.com/juju/errors" 16 "github.com/juju/loggo" 17 "github.com/juju/schema" 18 "github.com/juju/utils" 19 "github.com/juju/utils/proxy" 20 "github.com/juju/utils/series" 21 "github.com/juju/version" 22 "gopkg.in/juju/charmrepo.v2-unstable" 23 "gopkg.in/juju/environschema.v1" 24 "gopkg.in/macaroon-bakery.v1/bakery" 25 26 "github.com/juju/juju/cert" 27 "github.com/juju/juju/environs/tags" 28 "github.com/juju/juju/juju/osenv" 29 ) 30 31 var logger = loggo.GetLogger("juju.environs.config") 32 33 const ( 34 // FwInstance requests the use of an individual firewall per instance. 35 FwInstance = "instance" 36 37 // FwGlobal requests the use of a single firewall group for all machines. 38 // When ports are opened for one machine, all machines will have the same 39 // port opened. 40 FwGlobal = "global" 41 42 // FwNone requests that no firewalling should be performed inside 43 // the environment. No firewaller worker will be started. It's 44 // useful for clouds without support for either global or per 45 // instance security groups. 46 FwNone = "none" 47 48 // DefaultStatePort is the default port the controller is listening on. 49 DefaultStatePort int = 37017 50 51 // DefaultApiPort is the default port the API server is listening on. 52 DefaultAPIPort int = 17070 53 54 // DefaultBootstrapSSHTimeout is the amount of time to wait 55 // contacting a controller, in seconds. 56 DefaultBootstrapSSHTimeout int = 600 57 58 // DefaultBootstrapSSHRetryDelay is the amount of time between 59 // attempts to connect to an address, in seconds. 60 DefaultBootstrapSSHRetryDelay int = 5 61 62 // DefaultBootstrapSSHAddressesDelay is the amount of time between 63 // refreshing the addresses, in seconds. Not too frequent, as we 64 // refresh addresses from the provider each time. 65 DefaultBootstrapSSHAddressesDelay int = 10 66 67 // DefaultNumaControlPolicy should not be used by default. 68 // Only use numactl if user specifically requests it 69 DefaultNumaControlPolicy = false 70 71 // DefaultPreventDestroyEnvironment should not be used by default. 72 // Only prevent destroy-model from running 73 // if user specifically requests it. Otherwise, let it run. 74 DefaultPreventDestroyEnvironment = false 75 76 // DefaultPreventRemoveObject should not be used by default. 77 // Only prevent remove-object from running 78 // if user specifically requests it. Otherwise, let it run. 79 // Object here is a juju artifact - machine, service, unit or relation. 80 DefaultPreventRemoveObject = false 81 82 // DefaultPreventAllChanges should not be used by default. 83 // Only prevent all-changes from running 84 // if user specifically requests it. Otherwise, let them run. 85 DefaultPreventAllChanges = false 86 87 // DefaultLXCDefaultMTU is the default value for "lxc-default-mtu" 88 // config setting. Only non-zero, positive integer values will 89 // have effect. 90 DefaultLXCDefaultMTU = 0 91 ) 92 93 // TODO(katco-): Please grow this over time. 94 // Centralized place to store values of config keys. This transitions 95 // mistakes in referencing key-values to a compile-time error. 96 const ( 97 // 98 // Settings Attributes 99 // 100 101 // NameKey is the key for the model's name. 102 NameKey = "name" 103 104 // TypeKey is the key for the model's cloud type. 105 TypeKey = "type" 106 107 // AgentVersionKey is the key for the model's Juju agent version. 108 AgentVersionKey = "agent-version" 109 110 // CACertKey is the key for the controller's CA certificate attribute. 111 CACertKey = "ca-cert" 112 113 // UUIDKey is the key for the model UUID attribute. 114 UUIDKey = "uuid" 115 116 // ControllerUUIDKey is the key for the controller UUID attribute. 117 ControllerUUIDKey = "controller-uuid" 118 119 // ProvisionerHarvestModeKey stores the key for this setting. 120 ProvisionerHarvestModeKey = "provisioner-harvest-mode" 121 122 // AgentStreamKey stores the key for this setting. 123 AgentStreamKey = "agent-stream" 124 125 // AgentMetadataURLKey stores the key for this setting. 126 AgentMetadataURLKey = "agent-metadata-url" 127 128 // HttpProxyKey stores the key for this setting. 129 HttpProxyKey = "http-proxy" 130 131 // HttpsProxyKey stores the key for this setting. 132 HttpsProxyKey = "https-proxy" 133 134 // FtpProxyKey stores the key for this setting. 135 FtpProxyKey = "ftp-proxy" 136 137 // AptHttpProxyKey stores the key for this setting. 138 AptHttpProxyKey = "apt-http-proxy" 139 140 // AptHttpsProxyKey stores the key for this setting. 141 AptHttpsProxyKey = "apt-https-proxy" 142 143 // AptFtpProxyKey stores the key for this setting. 144 AptFtpProxyKey = "apt-ftp-proxy" 145 146 // NoProxyKey stores the key for this setting. 147 NoProxyKey = "no-proxy" 148 149 // LxcClone stores the value for this setting. 150 LxcClone = "lxc-clone" 151 152 // NumaControlPolicyKey stores the value for this setting 153 SetNumaControlPolicyKey = "set-numa-control-policy" 154 155 // BlockKeyPrefix is the prefix used for environment variables that block commands 156 // TODO(anastasiamac 2015-02-27) remove it and all related post 1.24 as obsolete 157 BlockKeyPrefix = "block-" 158 159 // PreventDestroyEnvironmentKey stores the value for this setting 160 PreventDestroyEnvironmentKey = BlockKeyPrefix + "destroy-model" 161 162 // PreventRemoveObjectKey stores the value for this setting 163 PreventRemoveObjectKey = BlockKeyPrefix + "remove-object" 164 165 // PreventAllChangesKey stores the value for this setting 166 PreventAllChangesKey = BlockKeyPrefix + "all-changes" 167 168 // The default block storage source. 169 StorageDefaultBlockSourceKey = "storage-default-block-source" 170 171 // ResourceTagsKey is an optional list or space-separated string 172 // of k=v pairs, defining the tags for ResourceTags. 173 ResourceTagsKey = "resource-tags" 174 175 // For LXC containers, is the container allowed to mount block 176 // devices. A theoretical security issue, so must be explicitly 177 // allowed by the user. 178 AllowLXCLoopMounts = "allow-lxc-loop-mounts" 179 180 // LXCDefaultMTU, when set to a positive integer, overrides the 181 // Machine Transmission Unit (MTU) setting of all network 182 // interfaces created for LXC containers. See also bug #1442257. 183 LXCDefaultMTU = "lxc-default-mtu" 184 185 // CloudImageBaseURL allows a user to override the default url that the 186 // 'ubuntu-cloudimg-query' executable uses to find container images. This 187 // is primarily for enabling Juju to work cleanly in a closed network. 188 CloudImageBaseURL = "cloudimg-base-url" 189 190 // IdentityURL sets the url of the identity manager. 191 IdentityURL = "identity-url" 192 193 // IdentityPublicKey sets the public key of the identity manager. 194 IdentityPublicKey = "identity-public-key" 195 196 // AutomaticallyRetryHooks determines whether the uniter will 197 // automatically retry a hook that has failed 198 AutomaticallyRetryHooks = "automatically-retry-hooks" 199 200 // 201 // Deprecated Settings Attributes 202 // 203 204 // Deprecated by provisioner-harvest-mode 205 // ProvisionerSafeModeKey stores the key for this setting. 206 ProvisionerSafeModeKey = "provisioner-safe-mode" 207 208 // Deprecated by agent-stream 209 // ToolsStreamKey stores the key for this setting. 210 ToolsStreamKey = "tools-stream" 211 212 // Deprecated by agent-metadata-url 213 // ToolsMetadataURLKey stores the key for this setting. 214 ToolsMetadataURLKey = "tools-metadata-url" 215 216 // Deprecated by use-clone 217 // LxcUseClone stores the key for this setting. 218 LxcUseClone = "lxc-use-clone" 219 220 // IgnoreMachineAddresses, when true, will cause the 221 // machine worker not to discover any machine addresses 222 // on start up. 223 IgnoreMachineAddresses = "ignore-machine-addresses" 224 ) 225 226 // ParseHarvestMode parses description of harvesting method and 227 // returns the representation. 228 func ParseHarvestMode(description string) (HarvestMode, error) { 229 description = strings.ToLower(description) 230 for method, descr := range harvestingMethodToFlag { 231 if description == descr { 232 return method, nil 233 } 234 } 235 return 0, fmt.Errorf("unknown harvesting method: %s", description) 236 } 237 238 // HarvestMode is a bit field which is used to store the harvesting 239 // behavior for Juju. 240 type HarvestMode uint32 241 242 const ( 243 // HarvestNone signifies that Juju should not harvest any 244 // machines. 245 HarvestNone HarvestMode = 1 << iota 246 // HarvestUnknown signifies that Juju should only harvest machines 247 // which exist, but we don't know about. 248 HarvestUnknown 249 // HarvestDestroyed signifies that Juju should only harvest 250 // machines which have been explicitly released by the user 251 // through a destroy of a service/model/unit. 252 HarvestDestroyed 253 // HarvestAll signifies that Juju should harvest both unknown and 254 // destroyed instances. ♫ Don't fear the reaper. ♫ 255 HarvestAll HarvestMode = HarvestUnknown | HarvestDestroyed 256 ) 257 258 // A mapping from method to description. Going this way will be the 259 // more common operation, so we want this type of lookup to be O(1). 260 var harvestingMethodToFlag = map[HarvestMode]string{ 261 HarvestAll: "all", 262 HarvestNone: "none", 263 HarvestUnknown: "unknown", 264 HarvestDestroyed: "destroyed", 265 } 266 267 // proxyAttrs contains attribute names that could contain loopback URLs, pointing to localhost 268 var ProxyAttributes = []string{ 269 HttpProxyKey, 270 HttpsProxyKey, 271 FtpProxyKey, 272 AptHttpProxyKey, 273 AptHttpsProxyKey, 274 AptFtpProxyKey, 275 } 276 277 // String returns the description of the harvesting mode. 278 func (method HarvestMode) String() string { 279 if description, ok := harvestingMethodToFlag[method]; ok { 280 return description 281 } 282 panic("Unknown harvesting method.") 283 } 284 285 // None returns whether or not the None harvesting flag is set. 286 func (method HarvestMode) HarvestNone() bool { 287 return method&HarvestNone != 0 288 } 289 290 // Destroyed returns whether or not the Destroyed harvesting flag is set. 291 func (method HarvestMode) HarvestDestroyed() bool { 292 return method&HarvestDestroyed != 0 293 } 294 295 // Unknown returns whether or not the Unknown harvesting flag is set. 296 func (method HarvestMode) HarvestUnknown() bool { 297 return method&HarvestUnknown != 0 298 } 299 300 type HasDefaultSeries interface { 301 DefaultSeries() (string, bool) 302 } 303 304 // PreferredSeries returns the preferred series to use when a charm does not 305 // explicitly specify a series. 306 func PreferredSeries(cfg HasDefaultSeries) string { 307 if series, ok := cfg.DefaultSeries(); ok { 308 return series 309 } 310 return series.LatestLts() 311 } 312 313 // Config holds an immutable environment configuration. 314 type Config struct { 315 // defined holds the attributes that are defined for Config. 316 // unknown holds the other attributes that are passed in (aka UnknownAttrs). 317 // the union of these two are AllAttrs 318 defined, unknown map[string]interface{} 319 } 320 321 // Defaulting is a value that specifies whether a configuration 322 // creator should use defaults from the environment. 323 type Defaulting bool 324 325 const ( 326 UseDefaults Defaulting = true 327 NoDefaults Defaulting = false 328 ) 329 330 // TODO(rog) update the doc comment below - it's getting messy 331 // and it assumes too much prior knowledge. 332 333 // New returns a new configuration. Fields that are common to all 334 // environment providers are verified. If useDefaults is UseDefaults, 335 // default values will be taken from the environment. 336 // 337 // Specifically, the "authorized-keys-path" key 338 // is translated into "authorized-keys" by loading the content from 339 // respective file. Similarly, "ca-cert-path" and "ca-private-key-path" 340 // are translated into the "ca-cert" and "ca-private-key" values. If 341 // not specified, authorized SSH keys and CA details will be read from: 342 // 343 // ~/.ssh/id_dsa.pub 344 // ~/.ssh/id_rsa.pub 345 // ~/.ssh/identity.pub 346 // ~/.local/share/juju/<name>-cert.pem 347 // ~/.local/share/juju/<name>-private-key.pem 348 // 349 // if $XDG_DATA_HOME is defined it will be used instead of ~/.local/share 350 // 351 // The required keys (after any files have been read) are "name", 352 // "type" and "authorized-keys", all of type string. Additional keys 353 // recognised are "agent-version" (string) and "development" (bool). 354 func New(withDefaults Defaulting, attrs map[string]interface{}) (*Config, error) { 355 checker := noDefaultsChecker 356 if withDefaults { 357 checker = withDefaultsChecker 358 } 359 defined, err := checker.Coerce(attrs, nil) 360 if err != nil { 361 return nil, err 362 } 363 c := &Config{ 364 defined: defined.(map[string]interface{}), 365 unknown: make(map[string]interface{}), 366 } 367 if withDefaults { 368 if err := c.fillInDefaults(); err != nil { 369 return nil, err 370 } 371 } 372 if err := c.ensureUnitLogging(); err != nil { 373 return nil, err 374 } 375 // no old config to compare against 376 if err := Validate(c, nil); err != nil { 377 return nil, err 378 } 379 // Copy unknown attributes onto the type-specific map. 380 for k, v := range attrs { 381 if _, ok := fields[k]; !ok { 382 c.unknown[k] = v 383 } 384 } 385 return c, nil 386 } 387 388 func (c *Config) ensureUnitLogging() error { 389 loggingConfig := c.asString("logging-config") 390 // If the logging config hasn't been set, then look for the os environment 391 // variable, and failing that, get the config from loggo itself. 392 if loggingConfig == "" { 393 if environmentValue := os.Getenv(osenv.JujuLoggingConfigEnvKey); environmentValue != "" { 394 loggingConfig = environmentValue 395 } else { 396 loggingConfig = loggo.LoggerInfo() 397 } 398 } 399 levels, err := loggo.ParseConfigurationString(loggingConfig) 400 if err != nil { 401 return err 402 } 403 // If there is is no specified level for "unit", then set one. 404 if _, ok := levels["unit"]; !ok { 405 loggingConfig = loggingConfig + ";unit=DEBUG" 406 } 407 c.defined["logging-config"] = loggingConfig 408 return nil 409 } 410 411 func (c *Config) fillInDefaults() error { 412 // For backward compatibility purposes, we treat as unset string 413 // valued attributes that are set to the empty string, and fill 414 // out their defaults accordingly. 415 c.fillInStringDefault("firewall-mode") 416 417 // Load authorized-keys-path into authorized-keys if necessary. 418 path := c.asString("authorized-keys-path") 419 keys := c.asString("authorized-keys") 420 if path != "" || keys == "" { 421 var err error 422 c.defined["authorized-keys"], err = ReadAuthorizedKeys(path) 423 if err != nil { 424 return err 425 } 426 } 427 delete(c.defined, "authorized-keys-path") 428 429 // Don't use c.Name() because the name hasn't 430 // been verified yet. 431 name := c.asString(NameKey) 432 if name == "" { 433 return fmt.Errorf("empty name in model configuration") 434 } 435 err := maybeReadAttrFromFile(c.defined, CACertKey, name+"-cert.pem") 436 if err != nil { 437 return err 438 } 439 err = maybeReadAttrFromFile(c.defined, "ca-private-key", name+"-private-key.pem") 440 if err != nil { 441 return err 442 } 443 return nil 444 } 445 446 func (c *Config) fillInStringDefault(attr string) { 447 if c.asString(attr) == "" { 448 c.defined[attr] = defaults[attr] 449 } 450 } 451 452 // ProcessDeprecatedAttributes gathers any deprecated attributes in attrs and adds or replaces 453 // them with new name value pairs for the replacement attrs. 454 // Ths ensures that older versions of Juju which require that deprecated 455 // attribute values still be used will work as expected. 456 func ProcessDeprecatedAttributes(attrs map[string]interface{}) map[string]interface{} { 457 processedAttrs := make(map[string]interface{}, len(attrs)) 458 for k, v := range attrs { 459 processedAttrs[k] = v 460 } 461 // The tools url has changed so ensure that both old and new values are in the config so that 462 // upgrades work. "agent-metadata-url" is the old attribute name. 463 if oldToolsURL, ok := attrs[ToolsMetadataURLKey]; ok && oldToolsURL.(string) != "" { 464 if newTools, ok := attrs[AgentMetadataURLKey]; !ok || newTools.(string) == "" { 465 // Ensure the new attribute name "agent-metadata-url" is set. 466 processedAttrs[AgentMetadataURLKey] = oldToolsURL 467 } 468 // Even if the user has edited their environment yaml to remove the deprecated tools-metadata-url value, 469 // we still want it in the config for upgrades. 470 processedAttrs[ToolsMetadataURLKey] = processedAttrs[AgentMetadataURLKey] 471 } 472 473 // Copy across lxc-use-clone to lxc-clone. 474 if lxcUseClone, ok := attrs[LxcUseClone]; ok { 475 _, newValSpecified := attrs[LxcClone] 476 // Ensure the new attribute name "lxc-clone" is set. 477 if !newValSpecified { 478 processedAttrs[LxcClone] = lxcUseClone 479 } 480 } 481 482 // Update the provider type from null to manual. 483 if attrs[TypeKey] == "null" { 484 processedAttrs[TypeKey] = "manual" 485 } 486 487 if _, ok := attrs[ProvisionerHarvestModeKey]; !ok { 488 if safeMode, ok := attrs[ProvisionerSafeModeKey].(bool); ok { 489 490 var harvestModeDescr string 491 if safeMode { 492 harvestModeDescr = HarvestDestroyed.String() 493 } else { 494 harvestModeDescr = HarvestAll.String() 495 } 496 497 processedAttrs[ProvisionerHarvestModeKey] = harvestModeDescr 498 499 logger.Infof( 500 `Based on your "%s" setting, configuring "%s" to "%s".`, 501 ProvisionerSafeModeKey, 502 ProvisionerHarvestModeKey, 503 harvestModeDescr, 504 ) 505 } 506 } 507 508 // Update agent-stream from tools-stream if agent-stream was not specified but tools-stream was. 509 if _, ok := attrs[AgentStreamKey]; !ok { 510 if toolsKey, ok := attrs[ToolsStreamKey]; ok { 511 processedAttrs[AgentStreamKey] = toolsKey 512 logger.Infof( 513 `Based on your "%s" setting, configuring "%s" to "%s".`, 514 ToolsStreamKey, 515 AgentStreamKey, 516 toolsKey, 517 ) 518 } 519 } 520 return processedAttrs 521 } 522 523 // InvalidConfigValue is an error type for a config value that failed validation. 524 type InvalidConfigValueError struct { 525 // Key is the config key used to access the value. 526 Key string 527 // Value is the value that failed validation. 528 Value string 529 // Reason indicates why the value failed validation. 530 Reason error 531 } 532 533 // Error returns the error string. 534 func (e *InvalidConfigValueError) Error() string { 535 msg := fmt.Sprintf("invalid config value for %s: %q", e.Key, e.Value) 536 if e.Reason != nil { 537 msg = msg + ": " + e.Reason.Error() 538 } 539 return msg 540 } 541 542 // Validate ensures that config is a valid configuration. If old is not nil, 543 // it holds the previous environment configuration for consideration when 544 // validating changes. 545 func Validate(cfg, old *Config) error { 546 // Check that we don't have any disallowed fields. 547 for _, attr := range allowedWithDefaultsOnly { 548 if _, ok := cfg.defined[attr]; ok { 549 return fmt.Errorf("attribute %q is not allowed in configuration", attr) 550 } 551 } 552 // Check that mandatory fields are specified. 553 for _, attr := range mandatoryWithoutDefaults { 554 if _, ok := cfg.defined[attr]; !ok { 555 return fmt.Errorf("%s missing from model configuration", attr) 556 } 557 } 558 559 // Check that all other fields that have been specified are non-empty, 560 // unless they're allowed to be empty for backward compatibility, 561 for attr, val := range cfg.defined { 562 if !isEmpty(val) { 563 continue 564 } 565 if !allowEmpty(attr) { 566 return fmt.Errorf("empty %s in model configuration", attr) 567 } 568 } 569 570 if strings.ContainsAny(cfg.mustString(NameKey), "/\\") { 571 return fmt.Errorf("model name contains unsafe characters") 572 } 573 574 // Check that the agent version parses ok if set explicitly; otherwise leave 575 // it alone. 576 if v, ok := cfg.defined[AgentVersionKey].(string); ok { 577 if _, err := version.Parse(v); err != nil { 578 return fmt.Errorf("invalid agent version in model configuration: %q", v) 579 } 580 } 581 582 // If the logging config is set, make sure it is valid. 583 if v, ok := cfg.defined["logging-config"].(string); ok { 584 if _, err := loggo.ParseConfigurationString(v); err != nil { 585 return err 586 } 587 } 588 589 if v, ok := cfg.defined[IdentityURL].(string); ok { 590 u, err := url.Parse(v) 591 if err != nil { 592 return fmt.Errorf("invalid identity URL: %v", err) 593 } 594 if u.Scheme != "https" { 595 return fmt.Errorf("URL needs to be https") 596 } 597 598 } 599 600 if v, ok := cfg.defined[IdentityPublicKey].(string); ok { 601 var key bakery.PublicKey 602 if err := key.UnmarshalText([]byte(v)); err != nil { 603 return fmt.Errorf("invalid identity public key: %v", err) 604 } 605 } 606 607 caCert, caCertOK := cfg.CACert() 608 caKey, caKeyOK := cfg.CAPrivateKey() 609 if caCertOK || caKeyOK { 610 if err := verifyKeyPair(caCert, caKey); err != nil { 611 return errors.Annotate(err, "bad CA certificate/key in configuration") 612 } 613 } 614 615 if uuid := cfg.UUID(); !utils.IsValidUUIDString(uuid) { 616 return errors.Errorf("uuid: expected UUID, got string(%q)", uuid) 617 } 618 619 if uuid := cfg.ControllerUUID(); !utils.IsValidUUIDString(uuid) { 620 return errors.Errorf("controller-uuid: expected UUID, got string(%q)", uuid) 621 } 622 623 // Ensure the resource tags have the expected k=v format. 624 if _, err := cfg.resourceTags(); err != nil { 625 return errors.Annotate(err, "validating resource tags") 626 } 627 628 // Check the immutable config values. These can't change 629 if old != nil { 630 for _, attr := range immutableAttributes { 631 if newv, oldv := cfg.defined[attr], old.defined[attr]; newv != oldv { 632 return fmt.Errorf("cannot change %s from %#v to %#v", attr, oldv, newv) 633 } 634 } 635 if _, oldFound := old.AgentVersion(); oldFound { 636 if _, newFound := cfg.AgentVersion(); !newFound { 637 return fmt.Errorf("cannot clear agent-version") 638 } 639 } 640 } 641 642 // Check LXCDefaultMTU is a positive integer, when set. 643 if lxcDefaultMTU, ok := cfg.LXCDefaultMTU(); ok && lxcDefaultMTU < 0 { 644 return errors.Errorf("%s: expected positive integer, got %v", LXCDefaultMTU, lxcDefaultMTU) 645 } 646 647 cfg.defined = ProcessDeprecatedAttributes(cfg.defined) 648 return nil 649 } 650 651 func isEmpty(val interface{}) bool { 652 switch val := val.(type) { 653 case nil: 654 return true 655 case bool: 656 return false 657 case int: 658 // TODO(rog) fix this to return false when 659 // we can lose backward compatibility. 660 // https://bugs.launchpad.net/juju-core/+bug/1224492 661 return val == 0 662 case string: 663 return val == "" 664 case []interface{}: 665 return len(val) == 0 666 case map[string]string: 667 return len(val) == 0 668 } 669 panic(fmt.Errorf("unexpected type %T in configuration", val)) 670 } 671 672 // maybeReadAttrFromFile sets defined[attr] to: 673 // 674 // 1) The content of the file defined[attr+"-path"], if that's set 675 // 2) The value of defined[attr] if it is already set. 676 // 3) The content of defaultPath if it exists and defined[attr] is unset 677 // 4) Preserves the content of defined[attr], otherwise 678 // 679 // The defined[attr+"-path"] key is always deleted. 680 func maybeReadAttrFromFile(defined map[string]interface{}, attr, defaultPath string) error { 681 if !osenv.IsJujuXDGDataHomeSet() { 682 logger.Debugf("JUJU_DATA not set, not attempting to read file %q", defaultPath) 683 return nil 684 } 685 pathAttr := attr + "-path" 686 path, _ := defined[pathAttr].(string) 687 delete(defined, pathAttr) 688 hasPath := path != "" 689 if !hasPath { 690 // No path and attribute is already set; leave it be. 691 if s, _ := defined[attr].(string); s != "" { 692 return nil 693 } 694 path = defaultPath 695 } 696 path, err := utils.NormalizePath(path) 697 if err != nil { 698 return err 699 } 700 if !filepath.IsAbs(path) { 701 path = osenv.JujuXDGDataHomePath(path) 702 } 703 data, err := ioutil.ReadFile(path) 704 if err != nil { 705 if os.IsNotExist(err) && !hasPath { 706 // If the default path isn't found, it's 707 // not an error. 708 return nil 709 } 710 return err 711 } 712 if len(data) == 0 { 713 return fmt.Errorf("file %q is empty", path) 714 } 715 defined[attr] = string(data) 716 return nil 717 } 718 719 // asString is a private helper method to keep the ugly string casting 720 // in once place. It returns the given named attribute as a string, 721 // returning "" if it isn't found. 722 func (c *Config) asString(name string) string { 723 value, _ := c.defined[name].(string) 724 return value 725 } 726 727 // mustString returns the named attribute as an string, panicking if 728 // it is not found or is empty. 729 func (c *Config) mustString(name string) string { 730 value, _ := c.defined[name].(string) 731 if value == "" { 732 panic(fmt.Errorf("empty value for %q found in configuration (type %T, val %v)", name, c.defined[name], c.defined[name])) 733 } 734 return value 735 } 736 737 // mustInt returns the named attribute as an integer, panicking if 738 // it is not found or is zero. Zero values should have been 739 // diagnosed at Validate time. 740 func (c *Config) mustInt(name string) int { 741 value, _ := c.defined[name].(int) 742 if value == 0 { 743 panic(fmt.Errorf("empty value for %q found in configuration", name)) 744 } 745 return value 746 } 747 748 // Type returns the model's cloud provider type. 749 func (c *Config) Type() string { 750 return c.mustString(TypeKey) 751 } 752 753 // Name returns the model name. 754 func (c *Config) Name() string { 755 return c.mustString(NameKey) 756 } 757 758 // UUID returns the uuid for the model. 759 func (c *Config) UUID() string { 760 return c.mustString(UUIDKey) 761 } 762 763 // ControllerUUID returns the uuid for the model's controller. 764 func (c *Config) ControllerUUID() string { 765 return c.mustString(ControllerUUIDKey) 766 } 767 768 // DefaultSeries returns the configured default Ubuntu series for the environment, 769 // and whether the default series was explicitly configured on the environment. 770 func (c *Config) DefaultSeries() (string, bool) { 771 if s, ok := c.defined["default-series"]; ok { 772 if series, ok := s.(string); ok && series != "" { 773 return series, true 774 } else if !ok { 775 logger.Warningf("invalid default-series: %q", s) 776 } 777 } 778 return "", false 779 } 780 781 // StatePort returns the controller port for the environment. 782 func (c *Config) StatePort() int { 783 return c.mustInt("state-port") 784 } 785 786 // APIPort returns the API server port for the environment. 787 func (c *Config) APIPort() int { 788 return c.mustInt("api-port") 789 } 790 791 // NumaCtlPreference returns if numactl is preferred. 792 func (c *Config) NumaCtlPreference() bool { 793 if numa, ok := c.defined[SetNumaControlPolicyKey]; ok { 794 return numa.(bool) 795 } 796 return DefaultNumaControlPolicy 797 } 798 799 // PreventDestroyEnvironment returns if destroy-model 800 // should be blocked from proceeding, thus preventing the operation. 801 func (c *Config) PreventDestroyEnvironment() bool { 802 if attrValue, ok := c.defined[PreventDestroyEnvironmentKey]; ok { 803 return attrValue.(bool) 804 } 805 return DefaultPreventDestroyEnvironment 806 } 807 808 // PreventRemoveObject returns if remove-object 809 // should be blocked from proceeding, thus preventing the operation. 810 // Object in this context is a juju artifact: either a machine, 811 // a service, a unit or a relation. 812 func (c *Config) PreventRemoveObject() bool { 813 if attrValue, ok := c.defined[PreventRemoveObjectKey]; ok { 814 return attrValue.(bool) 815 } 816 return DefaultPreventRemoveObject 817 } 818 819 // PreventAllChanges returns if all-changes 820 // should be blocked from proceeding, thus preventing the operation. 821 // Changes in this context are any alterations to current environment. 822 func (c *Config) PreventAllChanges() bool { 823 if attrValue, ok := c.defined[PreventAllChangesKey]; ok { 824 return attrValue.(bool) 825 } 826 return DefaultPreventAllChanges 827 } 828 829 // AuthorizedKeys returns the content for ssh's authorized_keys file. 830 func (c *Config) AuthorizedKeys() string { 831 return c.mustString("authorized-keys") 832 } 833 834 // ProxySSH returns a flag indicating whether SSH commands 835 // should be proxied through the API server. 836 func (c *Config) ProxySSH() bool { 837 value, _ := c.defined["proxy-ssh"].(bool) 838 return value 839 } 840 841 // ProxySettings returns all four proxy settings; http, https, ftp, and no 842 // proxy. 843 func (c *Config) ProxySettings() proxy.Settings { 844 return proxy.Settings{ 845 Http: c.HttpProxy(), 846 Https: c.HttpsProxy(), 847 Ftp: c.FtpProxy(), 848 NoProxy: c.NoProxy(), 849 } 850 } 851 852 // HttpProxy returns the http proxy for the environment. 853 func (c *Config) HttpProxy() string { 854 return c.asString(HttpProxyKey) 855 } 856 857 // HttpsProxy returns the https proxy for the environment. 858 func (c *Config) HttpsProxy() string { 859 return c.asString(HttpsProxyKey) 860 } 861 862 // FtpProxy returns the ftp proxy for the environment. 863 func (c *Config) FtpProxy() string { 864 return c.asString(FtpProxyKey) 865 } 866 867 // NoProxy returns the 'no proxy' for the environment. 868 func (c *Config) NoProxy() string { 869 return c.asString(NoProxyKey) 870 } 871 872 func (c *Config) getWithFallback(key, fallback string) string { 873 value := c.asString(key) 874 if value == "" { 875 value = c.asString(fallback) 876 } 877 return value 878 } 879 880 // addSchemeIfMissing adds a scheme to a URL if it is missing 881 func addSchemeIfMissing(defaultScheme string, url string) string { 882 if url != "" && !strings.Contains(url, "://") { 883 url = defaultScheme + "://" + url 884 } 885 return url 886 } 887 888 // AptProxySettings returns all three proxy settings; http, https and ftp. 889 func (c *Config) AptProxySettings() proxy.Settings { 890 return proxy.Settings{ 891 Http: c.AptHttpProxy(), 892 Https: c.AptHttpsProxy(), 893 Ftp: c.AptFtpProxy(), 894 } 895 } 896 897 // AptHttpProxy returns the apt http proxy for the environment. 898 // Falls back to the default http-proxy if not specified. 899 func (c *Config) AptHttpProxy() string { 900 return addSchemeIfMissing("http", c.getWithFallback(AptHttpProxyKey, HttpProxyKey)) 901 } 902 903 // AptHttpsProxy returns the apt https proxy for the environment. 904 // Falls back to the default https-proxy if not specified. 905 func (c *Config) AptHttpsProxy() string { 906 return addSchemeIfMissing("https", c.getWithFallback(AptHttpsProxyKey, HttpsProxyKey)) 907 } 908 909 // AptFtpProxy returns the apt ftp proxy for the environment. 910 // Falls back to the default ftp-proxy if not specified. 911 func (c *Config) AptFtpProxy() string { 912 return addSchemeIfMissing("ftp", c.getWithFallback(AptFtpProxyKey, FtpProxyKey)) 913 } 914 915 // AptMirror sets the apt mirror for the environment. 916 func (c *Config) AptMirror() string { 917 return c.asString("apt-mirror") 918 } 919 920 // BootstrapSSHOpts returns the SSH timeout and retry delays used 921 // during bootstrap. 922 func (c *Config) BootstrapSSHOpts() SSHTimeoutOpts { 923 opts := SSHTimeoutOpts{ 924 Timeout: time.Duration(DefaultBootstrapSSHTimeout) * time.Second, 925 RetryDelay: time.Duration(DefaultBootstrapSSHRetryDelay) * time.Second, 926 AddressesDelay: time.Duration(DefaultBootstrapSSHAddressesDelay) * time.Second, 927 } 928 if v, ok := c.defined["bootstrap-timeout"].(int); ok && v != 0 { 929 opts.Timeout = time.Duration(v) * time.Second 930 } 931 if v, ok := c.defined["bootstrap-retry-delay"].(int); ok && v != 0 { 932 opts.RetryDelay = time.Duration(v) * time.Second 933 } 934 if v, ok := c.defined["bootstrap-addresses-delay"].(int); ok && v != 0 { 935 opts.AddressesDelay = time.Duration(v) * time.Second 936 } 937 return opts 938 } 939 940 // CACert returns the certificate of the CA that signed the controller 941 // certificate, in PEM format, and whether the setting is available. 942 func (c *Config) CACert() (string, bool) { 943 if s, ok := c.defined[CACertKey]; ok { 944 return s.(string), true 945 } 946 return "", false 947 } 948 949 // CAPrivateKey returns the private key of the CA that signed the state 950 // server certificate, in PEM format, and whether the setting is available. 951 func (c *Config) CAPrivateKey() (key string, ok bool) { 952 if s, ok := c.defined["ca-private-key"]; ok && s != "" { 953 return s.(string), true 954 } 955 return "", false 956 } 957 958 // AdminSecret returns the administrator password. 959 // It's empty if the password has not been set. 960 func (c *Config) AdminSecret() string { 961 if s, ok := c.defined["admin-secret"]; ok && s != "" { 962 return s.(string) 963 } 964 return "" 965 } 966 967 // FirewallMode returns whether the firewall should 968 // manage ports per machine, globally, or not at all. 969 // (FwInstance, FwGlobal, or FwNone). 970 func (c *Config) FirewallMode() string { 971 return c.mustString("firewall-mode") 972 } 973 974 // AgentVersion returns the proposed version number for the agent tools, 975 // and whether it has been set. Once an environment is bootstrapped, this 976 // must always be valid. 977 func (c *Config) AgentVersion() (version.Number, bool) { 978 if v, ok := c.defined[AgentVersionKey].(string); ok { 979 n, err := version.Parse(v) 980 if err != nil { 981 panic(err) // We should have checked it earlier. 982 } 983 return n, true 984 } 985 return version.Zero, false 986 } 987 988 // AgentMetadataURL returns the URL that locates the agent tarballs and metadata, 989 // and whether it has been set. 990 func (c *Config) AgentMetadataURL() (string, bool) { 991 if url, ok := c.defined[AgentMetadataURLKey]; ok && url != "" { 992 return url.(string), true 993 } 994 return "", false 995 } 996 997 // ImageMetadataURL returns the URL at which the metadata used to locate image ids is located, 998 // and wether it has been set. 999 func (c *Config) ImageMetadataURL() (string, bool) { 1000 if url, ok := c.defined["image-metadata-url"]; ok && url != "" { 1001 return url.(string), true 1002 } 1003 return "", false 1004 } 1005 1006 // Development returns whether the environment is in development mode. 1007 func (c *Config) Development() bool { 1008 return c.defined["development"].(bool) 1009 } 1010 1011 // PreferIPv6 returns whether IPv6 addresses for API endpoints and 1012 // machines will be preferred (when available) over IPv4. 1013 func (c *Config) PreferIPv6() bool { 1014 v, _ := c.defined["prefer-ipv6"].(bool) 1015 return v 1016 } 1017 1018 // EnableOSRefreshUpdate returns whether or not newly provisioned 1019 // instances should run their respective OS's update capability. 1020 func (c *Config) EnableOSRefreshUpdate() bool { 1021 if val, ok := c.defined["enable-os-refresh-update"].(bool); !ok { 1022 return true 1023 } else { 1024 return val 1025 } 1026 } 1027 1028 // EnableOSUpgrade returns whether or not newly provisioned instances 1029 // should run their respective OS's upgrade capability. 1030 func (c *Config) EnableOSUpgrade() bool { 1031 if val, ok := c.defined["enable-os-upgrade"].(bool); !ok { 1032 return true 1033 } else { 1034 return val 1035 } 1036 } 1037 1038 // SSLHostnameVerification returns weather the environment has requested 1039 // SSL hostname verification to be enabled. 1040 func (c *Config) SSLHostnameVerification() bool { 1041 return c.defined["ssl-hostname-verification"].(bool) 1042 } 1043 1044 // LoggingConfig returns the configuration string for the loggers. 1045 func (c *Config) LoggingConfig() string { 1046 return c.asString("logging-config") 1047 } 1048 1049 // AutomaticallyRetryHooks returns whether we should automatically retry hooks. 1050 // By default this should be true. 1051 func (c *Config) AutomaticallyRetryHooks() bool { 1052 if val, ok := c.defined["automatically-retry-hooks"].(bool); !ok { 1053 return true 1054 } else { 1055 return val 1056 } 1057 } 1058 1059 // ProvisionerHarvestMode reports the harvesting methodology the 1060 // provisioner should take. 1061 func (c *Config) ProvisionerHarvestMode() HarvestMode { 1062 if v, ok := c.defined[ProvisionerHarvestModeKey].(string); ok { 1063 if method, err := ParseHarvestMode(v); err != nil { 1064 // This setting should have already been validated. Don't 1065 // burden the caller with handling any errors. 1066 panic(err) 1067 } else { 1068 return method 1069 } 1070 } else { 1071 return HarvestDestroyed 1072 } 1073 } 1074 1075 // ImageStream returns the simplestreams stream 1076 // used to identify which image ids to search 1077 // when starting an instance. 1078 func (c *Config) ImageStream() string { 1079 v, _ := c.defined["image-stream"].(string) 1080 if v != "" { 1081 return v 1082 } 1083 return "released" 1084 } 1085 1086 // AgentStream returns the simplestreams stream 1087 // used to identify which tools to use when 1088 // when bootstrapping or upgrading an environment. 1089 func (c *Config) AgentStream() string { 1090 v, _ := c.defined[AgentStreamKey].(string) 1091 if v != "" { 1092 return v 1093 } 1094 return "released" 1095 } 1096 1097 // TestMode indicates if the environment is intended for testing. 1098 // In this case, accessing the charm store does not affect statistical 1099 // data of the store. 1100 func (c *Config) TestMode() bool { 1101 return c.defined["test-mode"].(bool) 1102 } 1103 1104 // LXCUseClone reports whether the LXC provisioner should create a 1105 // template and use cloning to speed up container provisioning. 1106 func (c *Config) LXCUseClone() (bool, bool) { 1107 v, ok := c.defined[LxcClone].(bool) 1108 return v, ok 1109 } 1110 1111 // LXCUseCloneAUFS reports whether the LXC provisioner should create a 1112 // lxc clone using aufs if available. 1113 func (c *Config) LXCUseCloneAUFS() (bool, bool) { 1114 v, ok := c.defined["lxc-clone-aufs"].(bool) 1115 return v, ok 1116 } 1117 1118 // LXCDefaultMTU reports whether the LXC provisioner should create a 1119 // containers with a specific MTU value for all network intefaces. 1120 func (c *Config) LXCDefaultMTU() (int, bool) { 1121 v, ok := c.defined[LXCDefaultMTU].(int) 1122 if !ok { 1123 return DefaultLXCDefaultMTU, false 1124 } 1125 return v, ok 1126 } 1127 1128 // DisableNetworkManagement reports whether Juju is allowed to 1129 // configure and manage networking inside the environment. 1130 func (c *Config) DisableNetworkManagement() (bool, bool) { 1131 v, ok := c.defined["disable-network-management"].(bool) 1132 return v, ok 1133 } 1134 1135 // IgnoreMachineAddresses reports whether Juju will discover 1136 // and store machine addresses on startup. 1137 func (c *Config) IgnoreMachineAddresses() (bool, bool) { 1138 v, ok := c.defined[IgnoreMachineAddresses].(bool) 1139 return v, ok 1140 } 1141 1142 // StorageDefaultBlockSource returns the default block storage 1143 // source for the environment. 1144 func (c *Config) StorageDefaultBlockSource() (string, bool) { 1145 bs := c.asString(StorageDefaultBlockSourceKey) 1146 return bs, bs != "" 1147 } 1148 1149 // AllowLXCLoopMounts returns whether loop devices are allowed 1150 // to be mounted inside lxc containers. 1151 func (c *Config) AllowLXCLoopMounts() (bool, bool) { 1152 v, ok := c.defined[AllowLXCLoopMounts].(bool) 1153 return v, ok 1154 } 1155 1156 // CloudImageBaseURL returns the specified override url that the 'ubuntu- 1157 // cloudimg-query' executable uses to find container images. The empty string 1158 // means that the default URL is used. 1159 func (c *Config) CloudImageBaseURL() string { 1160 return c.asString(CloudImageBaseURL) 1161 } 1162 1163 // ResourceTags returns a set of tags to set on environment resources 1164 // that Juju creates and manages, if the provider supports them. These 1165 // tags have no special meaning to Juju, but may be used for existing 1166 // chargeback accounting schemes or other identification purposes. 1167 func (c *Config) ResourceTags() (map[string]string, bool) { 1168 tags, err := c.resourceTags() 1169 if err != nil { 1170 panic(err) // should be prevented by Validate 1171 } 1172 return tags, tags != nil 1173 } 1174 1175 func (c *Config) resourceTags() (map[string]string, error) { 1176 v, ok := c.defined[ResourceTagsKey].(map[string]string) 1177 if !ok { 1178 return nil, nil 1179 } 1180 for k := range v { 1181 if strings.HasPrefix(k, tags.JujuTagPrefix) { 1182 return nil, errors.Errorf("tag %q uses reserved prefix %q", k, tags.JujuTagPrefix) 1183 } 1184 } 1185 return v, nil 1186 } 1187 1188 // UnknownAttrs returns a copy of the raw configuration attributes 1189 // that are supposedly specific to the environment type. They could 1190 // also be wrong attributes, though. Only the specific environment 1191 // implementation can tell. 1192 func (c *Config) UnknownAttrs() map[string]interface{} { 1193 newAttrs := make(map[string]interface{}) 1194 for k, v := range c.unknown { 1195 newAttrs[k] = v 1196 } 1197 return newAttrs 1198 } 1199 1200 // AllAttrs returns a copy of the raw configuration attributes. 1201 func (c *Config) AllAttrs() map[string]interface{} { 1202 allAttrs := c.UnknownAttrs() 1203 for k, v := range c.defined { 1204 allAttrs[k] = v 1205 } 1206 return allAttrs 1207 } 1208 1209 // Remove returns a new configuration that has the attributes of c minus attrs. 1210 func (c *Config) Remove(attrs []string) (*Config, error) { 1211 defined := c.AllAttrs() 1212 for _, k := range attrs { 1213 delete(defined, k) 1214 } 1215 return New(NoDefaults, defined) 1216 } 1217 1218 // Apply returns a new configuration that has the attributes of c plus attrs. 1219 func (c *Config) Apply(attrs map[string]interface{}) (*Config, error) { 1220 defined := c.AllAttrs() 1221 for k, v := range attrs { 1222 defined[k] = v 1223 } 1224 return New(NoDefaults, defined) 1225 } 1226 1227 // IdentityURL returns the url of the identity manager. 1228 func (c *Config) IdentityURL() string { 1229 return c.asString(IdentityURL) 1230 } 1231 1232 // IdentityPublicKey returns the public key of the identity manager. 1233 func (c *Config) IdentityPublicKey() *bakery.PublicKey { 1234 key := c.asString(IdentityPublicKey) 1235 if key == "" { 1236 return nil 1237 } 1238 var pubKey bakery.PublicKey 1239 err := pubKey.UnmarshalText([]byte(key)) 1240 if err != nil { 1241 // We check if the key string can be unmarshalled into a PublicKey in the 1242 // Validate function, so we really do not expect this to fail. 1243 panic(err) 1244 } 1245 return &pubKey 1246 } 1247 1248 // fields holds the validation schema fields derived from configSchema. 1249 var fields = func() schema.Fields { 1250 fs, _, err := configSchema.ValidationSchema() 1251 if err != nil { 1252 panic(err) 1253 } 1254 return fs 1255 }() 1256 1257 // alwaysOptional holds configuration defaults for attributes that may 1258 // be unspecified even after a configuration has been created with all 1259 // defaults filled out. 1260 // 1261 // This table is not definitive: it specifies those attributes which are 1262 // optional when the config goes through its initial schema coercion, 1263 // but some fields listed as optional here are actually mandatory 1264 // with NoDefaults and are checked at the later Validate stage. 1265 var alwaysOptional = schema.Defaults{ 1266 AgentVersionKey: schema.Omit, 1267 CACertKey: schema.Omit, 1268 "authorized-keys": schema.Omit, 1269 "authorized-keys-path": schema.Omit, 1270 "ca-cert-path": schema.Omit, 1271 "ca-private-key-path": schema.Omit, 1272 "logging-config": schema.Omit, 1273 ProvisionerHarvestModeKey: schema.Omit, 1274 "bootstrap-timeout": schema.Omit, 1275 "bootstrap-retry-delay": schema.Omit, 1276 "bootstrap-addresses-delay": schema.Omit, 1277 "rsyslog-ca-cert": schema.Omit, 1278 HttpProxyKey: schema.Omit, 1279 HttpsProxyKey: schema.Omit, 1280 FtpProxyKey: schema.Omit, 1281 NoProxyKey: schema.Omit, 1282 AptHttpProxyKey: schema.Omit, 1283 AptHttpsProxyKey: schema.Omit, 1284 AptFtpProxyKey: schema.Omit, 1285 "apt-mirror": schema.Omit, 1286 LxcClone: schema.Omit, 1287 LXCDefaultMTU: schema.Omit, 1288 "disable-network-management": schema.Omit, 1289 IgnoreMachineAddresses: schema.Omit, 1290 AgentStreamKey: schema.Omit, 1291 IdentityURL: schema.Omit, 1292 IdentityPublicKey: schema.Omit, 1293 SetNumaControlPolicyKey: DefaultNumaControlPolicy, 1294 AllowLXCLoopMounts: false, 1295 ResourceTagsKey: schema.Omit, 1296 CloudImageBaseURL: schema.Omit, 1297 1298 // AutomaticallyRetryHooks is assumed to be true if missing 1299 AutomaticallyRetryHooks: schema.Omit, 1300 1301 // Storage related config. 1302 // Environ providers will specify their own defaults. 1303 StorageDefaultBlockSourceKey: schema.Omit, 1304 1305 // Deprecated fields, retain for backwards compatibility. 1306 ToolsMetadataURLKey: "", 1307 LxcUseClone: schema.Omit, 1308 ProvisionerSafeModeKey: schema.Omit, 1309 ToolsStreamKey: schema.Omit, 1310 PreventDestroyEnvironmentKey: schema.Omit, 1311 PreventRemoveObjectKey: schema.Omit, 1312 PreventAllChangesKey: schema.Omit, 1313 1314 // For backward compatibility reasons, the following 1315 // attributes default to empty strings rather than being 1316 // omitted. 1317 // TODO(rog) remove this support when we can 1318 // remove upgrade compatibility with versions prior to 1.14. 1319 "admin-secret": "", // TODO(rog) omit 1320 "ca-private-key": "", // TODO(rog) omit 1321 "image-metadata-url": "", // TODO(rog) omit 1322 AgentMetadataURLKey: "", // TODO(rog) omit 1323 1324 "default-series": "", 1325 1326 // For backward compatibility only - default ports were 1327 // not filled out in previous versions of the configuration. 1328 "state-port": DefaultStatePort, 1329 "api-port": DefaultAPIPort, 1330 // Previously image-stream could be set to an empty value 1331 "image-stream": "", 1332 "test-mode": false, 1333 "proxy-ssh": false, 1334 "lxc-clone-aufs": false, 1335 "prefer-ipv6": false, 1336 "enable-os-refresh-update": schema.Omit, 1337 "enable-os-upgrade": schema.Omit, 1338 } 1339 1340 func allowEmpty(attr string) bool { 1341 return alwaysOptional[attr] == "" 1342 } 1343 1344 var defaults = allDefaults() 1345 1346 // allDefaults returns a schema.Defaults that contains 1347 // defaults to be used when creating a new config with 1348 // UseDefaults. 1349 func allDefaults() schema.Defaults { 1350 d := schema.Defaults{ 1351 "firewall-mode": FwInstance, 1352 "development": false, 1353 "ssl-hostname-verification": true, 1354 "state-port": DefaultStatePort, 1355 "api-port": DefaultAPIPort, 1356 "bootstrap-timeout": DefaultBootstrapSSHTimeout, 1357 "bootstrap-retry-delay": DefaultBootstrapSSHRetryDelay, 1358 "bootstrap-addresses-delay": DefaultBootstrapSSHAddressesDelay, 1359 "proxy-ssh": false, 1360 "prefer-ipv6": false, 1361 "disable-network-management": false, 1362 IgnoreMachineAddresses: false, 1363 SetNumaControlPolicyKey: DefaultNumaControlPolicy, 1364 AutomaticallyRetryHooks: true, 1365 } 1366 for attr, val := range alwaysOptional { 1367 if _, ok := d[attr]; !ok { 1368 d[attr] = val 1369 } 1370 } 1371 return d 1372 } 1373 1374 // allowedWithDefaultsOnly holds those attributes 1375 // that are only allowed in a configuration that is 1376 // being created with UseDefaults. 1377 var allowedWithDefaultsOnly = []string{ 1378 "ca-cert-path", 1379 "ca-private-key-path", 1380 "authorized-keys-path", 1381 } 1382 1383 // mandatoryWithoutDefaults holds those attributes 1384 // that are mandatory if the configuration is created 1385 // with no defaults but optional otherwise. 1386 var mandatoryWithoutDefaults = []string{ 1387 "authorized-keys", 1388 } 1389 1390 // immutableAttributes holds those attributes 1391 // which are not allowed to change in the lifetime 1392 // of an environment. 1393 var immutableAttributes = []string{ 1394 NameKey, 1395 TypeKey, 1396 UUIDKey, 1397 ControllerUUIDKey, 1398 "firewall-mode", 1399 "state-port", 1400 "api-port", 1401 "bootstrap-timeout", 1402 "bootstrap-retry-delay", 1403 "bootstrap-addresses-delay", 1404 LxcClone, 1405 LXCDefaultMTU, 1406 "lxc-clone-aufs", 1407 "prefer-ipv6", 1408 IdentityURL, 1409 IdentityPublicKey, 1410 } 1411 1412 var ( 1413 withDefaultsChecker = schema.FieldMap(fields, defaults) 1414 noDefaultsChecker = schema.FieldMap(fields, alwaysOptional) 1415 ) 1416 1417 // ValidateUnknownAttrs checks the unknown attributes of the config against 1418 // the supplied fields and defaults, and returns an error if any fails to 1419 // validate. Unknown fields are warned about, but preserved, on the basis 1420 // that they are reasonably likely to have been written by or for a version 1421 // of juju that does recognise the fields, but that their presence is still 1422 // anomalous to some degree and should be flagged (and that there is thereby 1423 // a mechanism for observing fields that really are typos etc). 1424 func (cfg *Config) ValidateUnknownAttrs(fields schema.Fields, defaults schema.Defaults) (map[string]interface{}, error) { 1425 attrs := cfg.UnknownAttrs() 1426 checker := schema.FieldMap(fields, defaults) 1427 coerced, err := checker.Coerce(attrs, nil) 1428 if err != nil { 1429 // TODO(ericsnow) Drop this? 1430 logger.Debugf("coercion failed attributes: %#v, checker: %#v, %v", attrs, checker, err) 1431 return nil, err 1432 } 1433 result := coerced.(map[string]interface{}) 1434 for name, value := range attrs { 1435 if fields[name] == nil { 1436 if val, isString := value.(string); isString && val != "" { 1437 // only warn about attributes with non-empty string values 1438 logger.Warningf("unknown config field %q", name) 1439 } 1440 result[name] = value 1441 } 1442 } 1443 return result, nil 1444 } 1445 1446 // GenerateControllerCertAndKey makes sure that the config has a CACert and 1447 // CAPrivateKey, generates and returns new certificate and key. 1448 func (cfg *Config) GenerateControllerCertAndKey(hostAddresses []string) (string, string, error) { 1449 caCert, hasCACert := cfg.CACert() 1450 if !hasCACert { 1451 return "", "", fmt.Errorf("model configuration has no ca-cert") 1452 } 1453 caKey, hasCAKey := cfg.CAPrivateKey() 1454 if !hasCAKey { 1455 return "", "", fmt.Errorf("model configuration has no ca-private-key") 1456 } 1457 return cert.NewDefaultServer(caCert, caKey, hostAddresses) 1458 } 1459 1460 // SpecializeCharmRepo customizes a repository for a given configuration. 1461 // It returns a charm repository with test mode enabled if applicable. 1462 func SpecializeCharmRepo(repo charmrepo.Interface, cfg *Config) charmrepo.Interface { 1463 type specializer interface { 1464 WithTestMode() charmrepo.Interface 1465 } 1466 if store, ok := repo.(specializer); ok { 1467 if cfg.TestMode() { 1468 return store.WithTestMode() 1469 } 1470 } 1471 return repo 1472 } 1473 1474 // SSHTimeoutOpts lists the amount of time we will wait for various 1475 // parts of the SSH connection to complete. This is similar to 1476 // DialOpts, see http://pad.lv/1258889 about possibly deduplicating 1477 // them. 1478 type SSHTimeoutOpts struct { 1479 // Timeout is the amount of time to wait contacting a state 1480 // server. 1481 Timeout time.Duration 1482 1483 // RetryDelay is the amount of time between attempts to connect to 1484 // an address. 1485 RetryDelay time.Duration 1486 1487 // AddressesDelay is the amount of time between refreshing the 1488 // addresses. 1489 AddressesDelay time.Duration 1490 } 1491 1492 func addIfNotEmpty(settings map[string]interface{}, key, value string) { 1493 if value != "" { 1494 settings[key] = value 1495 } 1496 } 1497 1498 // ProxyConfigMap returns a map suitable to be applied to a Config to update 1499 // proxy settings. 1500 func ProxyConfigMap(proxySettings proxy.Settings) map[string]interface{} { 1501 settings := make(map[string]interface{}) 1502 addIfNotEmpty(settings, HttpProxyKey, proxySettings.Http) 1503 addIfNotEmpty(settings, HttpsProxyKey, proxySettings.Https) 1504 addIfNotEmpty(settings, FtpProxyKey, proxySettings.Ftp) 1505 addIfNotEmpty(settings, NoProxyKey, proxySettings.NoProxy) 1506 return settings 1507 } 1508 1509 // AptProxyConfigMap returns a map suitable to be applied to a Config to update 1510 // proxy settings. 1511 func AptProxyConfigMap(proxySettings proxy.Settings) map[string]interface{} { 1512 settings := make(map[string]interface{}) 1513 addIfNotEmpty(settings, AptHttpProxyKey, proxySettings.Http) 1514 addIfNotEmpty(settings, AptHttpsProxyKey, proxySettings.Https) 1515 addIfNotEmpty(settings, AptFtpProxyKey, proxySettings.Ftp) 1516 return settings 1517 } 1518 1519 // Schema returns a configuration schema that includes both 1520 // the given extra fields and all the fields defined in this package. 1521 // It returns an error if extra defines any fields defined in this 1522 // package. 1523 func Schema(extra environschema.Fields) (environschema.Fields, error) { 1524 fields := make(environschema.Fields) 1525 for name, field := range configSchema { 1526 fields[name] = field 1527 } 1528 for name, field := range extra { 1529 if _, ok := fields[name]; ok { 1530 return nil, errors.Errorf("config field %q clashes with global config", name) 1531 } 1532 fields[name] = field 1533 } 1534 return fields, nil 1535 } 1536 1537 // configSchema holds information on all the fields defined by 1538 // the config package. 1539 // TODO(rog) make this available to external packages. 1540 var configSchema = environschema.Fields{ 1541 "admin-secret": { 1542 Description: "The password for the administrator user", 1543 Type: environschema.Tstring, 1544 Secret: true, 1545 Example: "<random secret>", 1546 Group: environschema.EnvironGroup, 1547 }, 1548 AgentMetadataURLKey: { 1549 Description: "URL of private stream", 1550 Type: environschema.Tstring, 1551 Group: environschema.EnvironGroup, 1552 }, 1553 AgentStreamKey: { 1554 Description: `Version of Juju to use for deploy/upgrades.`, 1555 Type: environschema.Tstring, 1556 Group: environschema.EnvironGroup, 1557 }, 1558 AgentVersionKey: { 1559 Description: "The desired Juju agent version to use", 1560 Type: environschema.Tstring, 1561 Group: environschema.JujuGroup, 1562 Immutable: true, 1563 }, 1564 AllowLXCLoopMounts: { 1565 Description: `whether loop devices are allowed to be mounted inside lxc containers.`, 1566 Type: environschema.Tbool, 1567 Group: environschema.EnvironGroup, 1568 }, 1569 "api-port": { 1570 Description: "The TCP port for the API servers to listen on", 1571 Type: environschema.Tint, 1572 Group: environschema.EnvironGroup, 1573 Immutable: true, 1574 }, 1575 AptFtpProxyKey: { 1576 // TODO document acceptable format 1577 Description: "The APT FTP proxy for the model", 1578 Type: environschema.Tstring, 1579 Group: environschema.EnvironGroup, 1580 }, 1581 AptHttpProxyKey: { 1582 // TODO document acceptable format 1583 Description: "The APT HTTP proxy for the model", 1584 Type: environschema.Tstring, 1585 Group: environschema.EnvironGroup, 1586 }, 1587 AptHttpsProxyKey: { 1588 // TODO document acceptable format 1589 Description: "The APT HTTPS proxy for the model", 1590 Type: environschema.Tstring, 1591 Group: environschema.EnvironGroup, 1592 }, 1593 "apt-mirror": { 1594 // TODO document acceptable format 1595 Description: "The APT mirror for the model", 1596 Type: environschema.Tstring, 1597 Group: environschema.EnvironGroup, 1598 }, 1599 "authorized-keys": { 1600 // TODO what to do about authorized-keys-path ? 1601 Description: "Any authorized SSH public keys for the model, as found in a ~/.ssh/authorized_keys file", 1602 Type: environschema.Tstring, 1603 Group: environschema.EnvironGroup, 1604 }, 1605 "authorized-keys-path": { 1606 Description: "Path to file containing SSH authorized keys", 1607 Type: environschema.Tstring, 1608 }, 1609 PreventAllChangesKey: { 1610 Description: `Whether all changes to the model will be prevented`, 1611 Type: environschema.Tbool, 1612 Group: environschema.EnvironGroup, 1613 }, 1614 PreventDestroyEnvironmentKey: { 1615 Description: `Whether the model will be prevented from destruction`, 1616 Type: environschema.Tbool, 1617 Group: environschema.EnvironGroup, 1618 }, 1619 PreventRemoveObjectKey: { 1620 Description: `Whether remove operations (machine, service, unit or relation) will be prevented`, 1621 Type: environschema.Tbool, 1622 Group: environschema.EnvironGroup, 1623 }, 1624 "bootstrap-addresses-delay": { 1625 Description: "The amount of time between refreshing the addresses in seconds. Not too frequent as we refresh addresses from the provider each time.", 1626 Type: environschema.Tint, 1627 Immutable: true, 1628 Group: environschema.EnvironGroup, 1629 }, 1630 "bootstrap-retry-delay": { 1631 Description: "Time between attempts to connect to an address in seconds.", 1632 Type: environschema.Tint, 1633 Immutable: true, 1634 Group: environschema.EnvironGroup, 1635 }, 1636 "bootstrap-timeout": { 1637 Description: "The amount of time to wait contacting a controller in seconds", 1638 Type: environschema.Tint, 1639 Immutable: true, 1640 Group: environschema.EnvironGroup, 1641 }, 1642 CACertKey: { 1643 Description: `The certificate of the CA that signed the controller certificate, in PEM format`, 1644 Type: environschema.Tstring, 1645 Group: environschema.EnvironGroup, 1646 }, 1647 "ca-cert-path": { 1648 Description: "Path to file containing CA certificate", 1649 Type: environschema.Tstring, 1650 }, 1651 "ca-private-key": { 1652 Description: `The private key of the CA that signed the controller certificate, in PEM format`, 1653 Type: environschema.Tstring, 1654 Group: environschema.EnvironGroup, 1655 }, 1656 "ca-private-key-path": { 1657 Description: "Path to file containing CA private key", 1658 Type: environschema.Tstring, 1659 }, 1660 CloudImageBaseURL: { 1661 Description: "A URL to use instead of the default 'https://cloud-images.ubuntu.com/query' that the 'ubuntu-cloudimg-query' executable uses to find container images. This is primarily for enabling Juju to work cleanly in a closed network.", 1662 Type: environschema.Tstring, 1663 Group: environschema.EnvironGroup, 1664 }, 1665 "default-series": { 1666 Description: "The default series of Ubuntu to use for deploying charms", 1667 Type: environschema.Tstring, 1668 Group: environschema.EnvironGroup, 1669 }, 1670 "development": { 1671 Description: "Whether the model is in development mode", 1672 Type: environschema.Tbool, 1673 Group: environschema.EnvironGroup, 1674 }, 1675 "disable-network-management": { 1676 Description: "Whether the provider should control networks (on MAAS models, set to true for MAAS to control networks", 1677 Type: environschema.Tbool, 1678 Group: environschema.EnvironGroup, 1679 }, 1680 IgnoreMachineAddresses: { 1681 Description: "Whether the machine worker should discover machine addresses on startup", 1682 Type: environschema.Tbool, 1683 Group: environschema.EnvironGroup, 1684 }, 1685 "enable-os-refresh-update": { 1686 Description: `Whether newly provisioned instances should run their respective OS's update capability.`, 1687 Type: environschema.Tbool, 1688 Group: environschema.EnvironGroup, 1689 }, 1690 "enable-os-upgrade": { 1691 Description: `Whether newly provisioned instances should run their respective OS's upgrade capability.`, 1692 Type: environschema.Tbool, 1693 Group: environschema.EnvironGroup, 1694 }, 1695 "firewall-mode": { 1696 Description: `The mode to use for network firewalling. 1697 1698 'instance' requests the use of an individual firewall per instance. 1699 1700 'global' uses a single firewall for all instances (access 1701 for a network port is enabled to one instance if any instance requires 1702 that port). 1703 1704 'none' requests that no firewalling should be performed 1705 inside the model. It's useful for clouds without support for either 1706 global or per instance security groups.`, 1707 Type: environschema.Tstring, 1708 // Note that we need the empty value because it can 1709 // be found in legacy environments. 1710 Values: []interface{}{FwInstance, FwGlobal, FwNone, ""}, 1711 Immutable: true, 1712 Group: environschema.EnvironGroup, 1713 }, 1714 FtpProxyKey: { 1715 Description: "The FTP proxy value to configure on instances, in the FTP_PROXY environment variable", 1716 Type: environschema.Tstring, 1717 Group: environschema.EnvironGroup, 1718 }, 1719 HttpProxyKey: { 1720 Description: "The HTTP proxy value to configure on instances, in the HTTP_PROXY environment variable", 1721 Type: environschema.Tstring, 1722 Group: environschema.EnvironGroup, 1723 }, 1724 HttpsProxyKey: { 1725 Description: "The HTTPS proxy value to configure on instances, in the HTTPS_PROXY environment variable", 1726 Type: environschema.Tstring, 1727 Group: environschema.EnvironGroup, 1728 }, 1729 "image-metadata-url": { 1730 Description: "The URL at which the metadata used to locate OS image ids is located", 1731 Type: environschema.Tstring, 1732 Group: environschema.EnvironGroup, 1733 }, 1734 "image-stream": { 1735 Description: `The simplestreams stream used to identify which image ids to search when starting an instance.`, 1736 Type: environschema.Tstring, 1737 Group: environschema.EnvironGroup, 1738 }, 1739 "logging-config": { 1740 Description: `The configuration string to use when configuring Juju agent logging (see http://godoc.org/github.com/juju/loggo#ParseConfigurationString for details)`, 1741 Type: environschema.Tstring, 1742 Group: environschema.EnvironGroup, 1743 }, 1744 LxcClone: { 1745 Description: "Whether to use lxc-clone to create new LXC containers", 1746 Type: environschema.Tbool, 1747 Immutable: true, 1748 Group: environschema.EnvironGroup, 1749 }, 1750 "lxc-clone-aufs": { 1751 Description: `Whether the LXC provisioner should creat an LXC clone using AUFS if available`, 1752 Type: environschema.Tbool, 1753 Immutable: true, 1754 Group: environschema.EnvironGroup, 1755 }, 1756 LXCDefaultMTU: { 1757 // default: the default MTU setting for the container 1758 Description: `The MTU setting to use for network interfaces in LXC containers`, 1759 Type: environschema.Tint, 1760 Immutable: true, 1761 Group: environschema.EnvironGroup, 1762 }, 1763 LxcUseClone: { 1764 Description: `Whether the LXC provisioner should create a template and use cloning to speed up container provisioning. (deprecated by lxc-clone)`, 1765 Type: environschema.Tbool, 1766 }, 1767 NameKey: { 1768 Description: "The name of the current model", 1769 Type: environschema.Tstring, 1770 Mandatory: true, 1771 Immutable: true, 1772 Group: environschema.EnvironGroup, 1773 }, 1774 NoProxyKey: { 1775 Description: "List of domain addresses not to be proxied (comma-separated)", 1776 Type: environschema.Tstring, 1777 Group: environschema.EnvironGroup, 1778 }, 1779 "prefer-ipv6": { 1780 Description: `Whether to prefer IPv6 over IPv4 addresses for API endpoints and machines`, 1781 Type: environschema.Tbool, 1782 Immutable: true, 1783 Group: environschema.EnvironGroup, 1784 }, 1785 ProvisionerHarvestModeKey: { 1786 // default: destroyed, but also depends on current setting of ProvisionerSafeModeKey 1787 Description: "What to do with unknown machines. See https://jujucharms.com/docs/stable/config-general#juju-lifecycle-and-harvesting (default destroyed)", 1788 Type: environschema.Tstring, 1789 Values: []interface{}{"all", "none", "unknown", "destroyed"}, 1790 Group: environschema.EnvironGroup, 1791 }, 1792 ProvisionerSafeModeKey: { 1793 Description: `Whether to run the provisioner in "destroyed" harvest mode (deprecated, superceded by provisioner-harvest-mode)`, 1794 Type: environschema.Tbool, 1795 Group: environschema.EnvironGroup, 1796 }, 1797 "proxy-ssh": { 1798 // default: true 1799 Description: `Whether SSH commands should be proxied through the API server`, 1800 Type: environschema.Tbool, 1801 Group: environschema.EnvironGroup, 1802 }, 1803 ResourceTagsKey: { 1804 Description: "resource tags", 1805 Type: environschema.Tattrs, 1806 Group: environschema.EnvironGroup, 1807 }, 1808 "rsyslog-ca-cert": { 1809 Description: `The certificate of the CA that signed the rsyslog certificate, in PEM format.`, 1810 Type: environschema.Tstring, 1811 Group: environschema.EnvironGroup, 1812 }, 1813 SetNumaControlPolicyKey: { 1814 Description: "Tune Juju controller to work with NUMA if present (default false)", 1815 Type: environschema.Tbool, 1816 Group: environschema.EnvironGroup, 1817 }, 1818 "ssl-hostname-verification": { 1819 Description: "Whether SSL hostname verification is enabled (default true)", 1820 Type: environschema.Tbool, 1821 Group: environschema.EnvironGroup, 1822 }, 1823 StorageDefaultBlockSourceKey: { 1824 Description: "The default block storage source for the model", 1825 Type: environschema.Tstring, 1826 Group: environschema.EnvironGroup, 1827 }, 1828 "state-port": { 1829 Description: "Port for the API server to listen on.", 1830 Type: environschema.Tint, 1831 Immutable: true, 1832 Group: environschema.EnvironGroup, 1833 }, 1834 "test-mode": { 1835 Description: `Whether the model is intended for testing. 1836 If true, accessing the charm store does not affect statistical 1837 data of the store. (default false)`, 1838 Type: environschema.Tbool, 1839 Group: environschema.EnvironGroup, 1840 }, 1841 ToolsMetadataURLKey: { 1842 Description: `deprecated, superceded by agent-metadata-url`, 1843 Type: environschema.Tstring, 1844 Group: environschema.EnvironGroup, 1845 }, 1846 ToolsStreamKey: { 1847 Description: `deprecated, superceded by agent-stream`, 1848 Type: environschema.Tstring, 1849 Group: environschema.EnvironGroup, 1850 }, 1851 TypeKey: { 1852 Description: "Type of model, e.g. local, ec2", 1853 Type: environschema.Tstring, 1854 Mandatory: true, 1855 Immutable: true, 1856 Group: environschema.EnvironGroup, 1857 }, 1858 UUIDKey: { 1859 Description: "The UUID of the model", 1860 Type: environschema.Tstring, 1861 Group: environschema.JujuGroup, 1862 Immutable: true, 1863 }, 1864 ControllerUUIDKey: { 1865 Description: "The UUID of the model's controller", 1866 Type: environschema.Tstring, 1867 Group: environschema.JujuGroup, 1868 Immutable: true, 1869 }, 1870 IdentityURL: { 1871 Description: "IdentityURL specifies the URL of the identity manager", 1872 Type: environschema.Tstring, 1873 Group: environschema.JujuGroup, 1874 Immutable: true, 1875 }, 1876 IdentityPublicKey: { 1877 Description: "Public key of the identity manager. If this is omitted, the public key will be fetched from the IdentityURL.", 1878 Type: environschema.Tstring, 1879 Group: environschema.JujuGroup, 1880 Immutable: true, 1881 }, 1882 AutomaticallyRetryHooks: { 1883 Description: "Determines whether the uniter should automatically retry failed hooks", 1884 Type: environschema.Tbool, 1885 Group: environschema.EnvironGroup, 1886 }, 1887 }