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