github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 "os" 9 "strings" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/schema" 14 "github.com/juju/utils" 15 "github.com/juju/utils/proxy" 16 "github.com/juju/utils/series" 17 "github.com/juju/version" 18 "gopkg.in/juju/charmrepo.v2-unstable" 19 "gopkg.in/juju/environschema.v1" 20 "gopkg.in/juju/names.v2" 21 22 "github.com/juju/juju/controller" 23 "github.com/juju/juju/environs/tags" 24 "github.com/juju/juju/juju/osenv" 25 "github.com/juju/juju/logfwd/syslog" 26 ) 27 28 var logger = loggo.GetLogger("juju.environs.config") 29 30 const ( 31 // FwInstance requests the use of an individual firewall per instance. 32 FwInstance = "instance" 33 34 // FwGlobal requests the use of a single firewall group for all machines. 35 // When ports are opened for one machine, all machines will have the same 36 // port opened. 37 FwGlobal = "global" 38 39 // FwNone requests that no firewalling should be performed inside 40 // the environment. No firewaller worker will be started. It's 41 // useful for clouds without support for either global or per 42 // instance security groups. 43 FwNone = "none" 44 ) 45 46 // TODO(katco-): Please grow this over time. 47 // Centralized place to store values of config keys. This transitions 48 // mistakes in referencing key-values to a compile-time error. 49 const ( 50 // 51 // Settings Attributes 52 // 53 54 // NameKey is the key for the model's name. 55 NameKey = "name" 56 57 // TypeKey is the key for the model's cloud type. 58 TypeKey = "type" 59 60 // AgentVersionKey is the key for the model's Juju agent version. 61 AgentVersionKey = "agent-version" 62 63 // UUIDKey is the key for the model UUID attribute. 64 UUIDKey = "uuid" 65 66 // AuthorizedKeysKey is the key for the authorized-keys attribute. 67 AuthorizedKeysKey = "authorized-keys" 68 69 // ProvisionerHarvestModeKey stores the key for this setting. 70 ProvisionerHarvestModeKey = "provisioner-harvest-mode" 71 72 // AgentStreamKey stores the key for this setting. 73 AgentStreamKey = "agent-stream" 74 75 // AgentMetadataURLKey stores the key for this setting. 76 AgentMetadataURLKey = "agent-metadata-url" 77 78 // HTTPProxyKey stores the key for this setting. 79 HTTPProxyKey = "http-proxy" 80 81 // HTTPSProxyKey stores the key for this setting. 82 HTTPSProxyKey = "https-proxy" 83 84 // FTPProxyKey stores the key for this setting. 85 FTPProxyKey = "ftp-proxy" 86 87 // AptHTTPProxyKey stores the key for this setting. 88 AptHTTPProxyKey = "apt-http-proxy" 89 90 // AptHTTPSProxyKey stores the key for this setting. 91 AptHTTPSProxyKey = "apt-https-proxy" 92 93 // AptFTPProxyKey stores the key for this setting. 94 AptFTPProxyKey = "apt-ftp-proxy" 95 96 // NoProxyKey stores the key for this setting. 97 NoProxyKey = "no-proxy" 98 99 // The default block storage source. 100 StorageDefaultBlockSourceKey = "storage-default-block-source" 101 102 // ResourceTagsKey is an optional list or space-separated string 103 // of k=v pairs, defining the tags for ResourceTags. 104 ResourceTagsKey = "resource-tags" 105 106 // LogForwardEnabled determines whether the log forward functionality is enabled. 107 LogForwardEnabled = "logforward-enabled" 108 109 // LogFwdSyslogHost sets the hostname:port of the syslog server. 110 LogFwdSyslogHost = "syslog-host" 111 112 // LogFwdSyslogCACert sets the certificate of the CA that signed the syslog 113 // server certificate. 114 LogFwdSyslogCACert = "syslog-ca-cert" 115 116 // LogFwdSyslogClientCert sets the client certificate for syslog 117 // forwarding. 118 LogFwdSyslogClientCert = "syslog-client-cert" 119 120 // LogFwdSyslogClientKey sets the client key for syslog 121 // forwarding. 122 LogFwdSyslogClientKey = "syslog-client-key" 123 124 // AutomaticallyRetryHooks determines whether the uniter will 125 // automatically retry a hook that has failed 126 AutomaticallyRetryHooks = "automatically-retry-hooks" 127 128 // TransmitVendorMetricsKey is the key for whether the controller sends 129 // metrics collected in this model for anonymized aggregate analytics. 130 TransmitVendorMetricsKey = "transmit-vendor-metrics" 131 132 // 133 // Deprecated Settings Attributes 134 // 135 136 // IgnoreMachineAddresses, when true, will cause the 137 // machine worker not to discover any machine addresses 138 // on start up. 139 IgnoreMachineAddresses = "ignore-machine-addresses" 140 ) 141 142 // ParseHarvestMode parses description of harvesting method and 143 // returns the representation. 144 func ParseHarvestMode(description string) (HarvestMode, error) { 145 description = strings.ToLower(description) 146 for method, descr := range harvestingMethodToFlag { 147 if description == descr { 148 return method, nil 149 } 150 } 151 return 0, fmt.Errorf("unknown harvesting method: %s", description) 152 } 153 154 // HarvestMode is a bit field which is used to store the harvesting 155 // behavior for Juju. 156 type HarvestMode uint32 157 158 const ( 159 // HarvestNone signifies that Juju should not harvest any 160 // machines. 161 HarvestNone HarvestMode = 1 << iota 162 // HarvestUnknown signifies that Juju should only harvest machines 163 // which exist, but we don't know about. 164 HarvestUnknown 165 // HarvestDestroyed signifies that Juju should only harvest 166 // machines which have been explicitly released by the user 167 // through a destroy of a service/model/unit. 168 HarvestDestroyed 169 // HarvestAll signifies that Juju should harvest both unknown and 170 // destroyed instances. ♫ Don't fear the reaper. ♫ 171 HarvestAll HarvestMode = HarvestUnknown | HarvestDestroyed 172 ) 173 174 // A mapping from method to description. Going this way will be the 175 // more common operation, so we want this type of lookup to be O(1). 176 var harvestingMethodToFlag = map[HarvestMode]string{ 177 HarvestAll: "all", 178 HarvestNone: "none", 179 HarvestUnknown: "unknown", 180 HarvestDestroyed: "destroyed", 181 } 182 183 // String returns the description of the harvesting mode. 184 func (method HarvestMode) String() string { 185 if description, ok := harvestingMethodToFlag[method]; ok { 186 return description 187 } 188 panic("Unknown harvesting method.") 189 } 190 191 // None returns whether or not the None harvesting flag is set. 192 func (method HarvestMode) HarvestNone() bool { 193 return method&HarvestNone != 0 194 } 195 196 // Destroyed returns whether or not the Destroyed harvesting flag is set. 197 func (method HarvestMode) HarvestDestroyed() bool { 198 return method&HarvestDestroyed != 0 199 } 200 201 // Unknown returns whether or not the Unknown harvesting flag is set. 202 func (method HarvestMode) HarvestUnknown() bool { 203 return method&HarvestUnknown != 0 204 } 205 206 type HasDefaultSeries interface { 207 DefaultSeries() (string, bool) 208 } 209 210 // PreferredSeries returns the preferred series to use when a charm does not 211 // explicitly specify a series. 212 func PreferredSeries(cfg HasDefaultSeries) string { 213 if series, ok := cfg.DefaultSeries(); ok { 214 return series 215 } 216 return series.LatestLts() 217 } 218 219 // Config holds an immutable environment configuration. 220 type Config struct { 221 // defined holds the attributes that are defined for Config. 222 // unknown holds the other attributes that are passed in (aka UnknownAttrs). 223 // the union of these two are AllAttrs 224 defined, unknown map[string]interface{} 225 } 226 227 // Defaulting is a value that specifies whether a configuration 228 // creator should use defaults from the environment. 229 type Defaulting bool 230 231 const ( 232 UseDefaults Defaulting = true 233 NoDefaults Defaulting = false 234 ) 235 236 // TODO(rog) update the doc comment below - it's getting messy 237 // and it assumes too much prior knowledge. 238 239 // New returns a new configuration. Fields that are common to all 240 // environment providers are verified. If useDefaults is UseDefaults, 241 // default values will be taken from the environment. 242 // 243 // "ca-cert-path" and "ca-private-key-path" are translated into the 244 // "ca-cert" and "ca-private-key" values. If not specified, CA details 245 // will be read from: 246 // 247 // ~/.local/share/juju/<name>-cert.pem 248 // ~/.local/share/juju/<name>-private-key.pem 249 // 250 // if $XDG_DATA_HOME is defined it will be used instead of ~/.local/share 251 func New(withDefaults Defaulting, attrs map[string]interface{}) (*Config, error) { 252 checker := noDefaultsChecker 253 if withDefaults { 254 checker = withDefaultsChecker 255 } 256 defined, err := checker.Coerce(attrs, nil) 257 if err != nil { 258 return nil, err 259 } 260 c := &Config{ 261 defined: defined.(map[string]interface{}), 262 unknown: make(map[string]interface{}), 263 } 264 if err := c.ensureUnitLogging(); err != nil { 265 return nil, err 266 } 267 // no old config to compare against 268 if err := Validate(c, nil); err != nil { 269 return nil, err 270 } 271 // Copy unknown attributes onto the type-specific map. 272 for k, v := range attrs { 273 if _, ok := fields[k]; !ok { 274 c.unknown[k] = v 275 } 276 } 277 return c, nil 278 } 279 280 var defaultConfigValues = map[string]interface{}{ 281 // Network. 282 "firewall-mode": FwInstance, 283 "disable-network-management": false, 284 IgnoreMachineAddresses: false, 285 "ssl-hostname-verification": true, 286 "proxy-ssh": false, 287 288 "default-series": series.LatestLts(), 289 ProvisionerHarvestModeKey: HarvestDestroyed.String(), 290 ResourceTagsKey: "", 291 "logging-config": "", 292 AutomaticallyRetryHooks: true, 293 "enable-os-refresh-update": true, 294 "enable-os-upgrade": true, 295 "development": false, 296 "test-mode": false, 297 TransmitVendorMetricsKey: true, 298 299 // Image and agent streams and URLs. 300 "image-stream": "released", 301 "image-metadata-url": "", 302 AgentStreamKey: "released", 303 AgentMetadataURLKey: "", 304 305 // Log forward settings. 306 LogForwardEnabled: false, 307 308 // Proxy settings. 309 HTTPProxyKey: "", 310 HTTPSProxyKey: "", 311 FTPProxyKey: "", 312 NoProxyKey: "", 313 AptHTTPProxyKey: "", 314 AptHTTPSProxyKey: "", 315 AptFTPProxyKey: "", 316 "apt-mirror": "", 317 } 318 319 // ConfigDefaults returns the config default values 320 // to be used for any new model where there is no 321 // value yet defined. 322 func ConfigDefaults() map[string]interface{} { 323 return defaultConfigValues 324 } 325 326 func (c *Config) ensureUnitLogging() error { 327 loggingConfig := c.asString("logging-config") 328 // If the logging config hasn't been set, then look for the os environment 329 // variable, and failing that, get the config from loggo itself. 330 if loggingConfig == "" { 331 if environmentValue := os.Getenv(osenv.JujuLoggingConfigEnvKey); environmentValue != "" { 332 loggingConfig = environmentValue 333 } else { 334 loggingConfig = loggo.LoggerInfo() 335 } 336 } 337 levels, err := loggo.ParseConfigString(loggingConfig) 338 if err != nil { 339 return err 340 } 341 // If there is is no specified level for "unit", then set one. 342 if _, ok := levels["unit"]; !ok { 343 loggingConfig = loggingConfig + ";unit=DEBUG" 344 } 345 c.defined["logging-config"] = loggingConfig 346 return nil 347 } 348 349 // ProcessDeprecatedAttributes gathers any deprecated attributes in attrs and adds or replaces 350 // them with new name value pairs for the replacement attrs. 351 // Ths ensures that older versions of Juju which require that deprecated 352 // attribute values still be used will work as expected. 353 func ProcessDeprecatedAttributes(attrs map[string]interface{}) map[string]interface{} { 354 processedAttrs := make(map[string]interface{}, len(attrs)) 355 for k, v := range attrs { 356 processedAttrs[k] = v 357 } 358 // No deprecated attributes at the moment. 359 return processedAttrs 360 } 361 362 // CoerceForStorage transforms attributes prior to being saved in a persistent store. 363 func CoerceForStorage(attrs map[string]interface{}) map[string]interface{} { 364 coercedAttrs := make(map[string]interface{}, len(attrs)) 365 for attrName, attrValue := range attrs { 366 if attrName == ResourceTagsKey { 367 // Resource Tags are specified by the user as a string but transformed 368 // to a map when config is parsed. We want to store as a string. 369 var tagsSlice []string 370 if tags, ok := attrValue.(map[string]string); ok { 371 for resKey, resValue := range tags { 372 tagsSlice = append(tagsSlice, fmt.Sprintf("%v=%v", resKey, resValue)) 373 } 374 attrValue = strings.Join(tagsSlice, " ") 375 } 376 } 377 coercedAttrs[attrName] = attrValue 378 } 379 return coercedAttrs 380 } 381 382 // InvalidConfigValue is an error type for a config value that failed validation. 383 type InvalidConfigValueError struct { 384 // Key is the config key used to access the value. 385 Key string 386 // Value is the value that failed validation. 387 Value string 388 // Reason indicates why the value failed validation. 389 Reason error 390 } 391 392 // Error returns the error string. 393 func (e *InvalidConfigValueError) Error() string { 394 msg := fmt.Sprintf("invalid config value for %s: %q", e.Key, e.Value) 395 if e.Reason != nil { 396 msg = msg + ": " + e.Reason.Error() 397 } 398 return msg 399 } 400 401 // Validate ensures that config is a valid configuration. If old is not nil, 402 // it holds the previous environment configuration for consideration when 403 // validating changes. 404 func Validate(cfg, old *Config) error { 405 // Check that all other fields that have been specified are non-empty, 406 // unless they're allowed to be empty for backward compatibility, 407 for attr, val := range cfg.defined { 408 if !isEmpty(val) { 409 continue 410 } 411 if !allowEmpty(attr) { 412 return fmt.Errorf("empty %s in model configuration", attr) 413 } 414 } 415 416 modelName := cfg.asString(NameKey) 417 if modelName == "" { 418 return errors.New("empty name in model configuration") 419 } 420 if !names.IsValidModelName(modelName) { 421 return fmt.Errorf("%q is not a valid name: model names may only contain lowercase letters, digits and hyphens", modelName) 422 } 423 424 // Check that the agent version parses ok if set explicitly; otherwise leave 425 // it alone. 426 if v, ok := cfg.defined[AgentVersionKey].(string); ok { 427 if _, err := version.Parse(v); err != nil { 428 return fmt.Errorf("invalid agent version in model configuration: %q", v) 429 } 430 } 431 432 // If the logging config is set, make sure it is valid. 433 if v, ok := cfg.defined["logging-config"].(string); ok { 434 if _, err := loggo.ParseConfigString(v); err != nil { 435 return err 436 } 437 } 438 439 if lfCfg, ok := cfg.LogFwdSyslog(); ok { 440 if err := lfCfg.Validate(); err != nil { 441 return errors.Annotate(err, "invalid syslog forwarding config") 442 } 443 } 444 445 if uuid := cfg.UUID(); !utils.IsValidUUIDString(uuid) { 446 return errors.Errorf("uuid: expected UUID, got string(%q)", uuid) 447 } 448 449 // Ensure the resource tags have the expected k=v format. 450 if _, err := cfg.resourceTags(); err != nil { 451 return errors.Annotate(err, "validating resource tags") 452 } 453 454 // Check the immutable config values. These can't change 455 if old != nil { 456 for _, attr := range immutableAttributes { 457 oldv, ok := old.defined[attr] 458 if !ok { 459 continue 460 } 461 if newv := cfg.defined[attr]; newv != oldv { 462 return fmt.Errorf("cannot change %s from %#v to %#v", attr, oldv, newv) 463 } 464 } 465 if _, oldFound := old.AgentVersion(); oldFound { 466 if _, newFound := cfg.AgentVersion(); !newFound { 467 return errors.New("cannot clear agent-version") 468 } 469 } 470 } 471 472 cfg.defined = ProcessDeprecatedAttributes(cfg.defined) 473 return nil 474 } 475 476 func isEmpty(val interface{}) bool { 477 switch val := val.(type) { 478 case nil: 479 return true 480 case bool: 481 return false 482 case int: 483 // TODO(rog) fix this to return false when 484 // we can lose backward compatibility. 485 // https://bugs.launchpad.net/juju-core/+bug/1224492 486 return val == 0 487 case string: 488 return val == "" 489 case []interface{}: 490 return len(val) == 0 491 case map[string]string: 492 return len(val) == 0 493 } 494 panic(fmt.Errorf("unexpected type %T in configuration", val)) 495 } 496 497 // asString is a private helper method to keep the ugly string casting 498 // in once place. It returns the given named attribute as a string, 499 // returning "" if it isn't found. 500 func (c *Config) asString(name string) string { 501 value, _ := c.defined[name].(string) 502 return value 503 } 504 505 // mustString returns the named attribute as an string, panicking if 506 // it is not found or is empty. 507 func (c *Config) mustString(name string) string { 508 value, _ := c.defined[name].(string) 509 if value == "" { 510 panic(fmt.Errorf("empty value for %q found in configuration (type %T, val %v)", name, c.defined[name], c.defined[name])) 511 } 512 return value 513 } 514 515 // mustInt returns the named attribute as an integer, panicking if 516 // it is not found or is zero. Zero values should have been 517 // diagnosed at Validate time. 518 func (c *Config) mustInt(name string) int { 519 value, _ := c.defined[name].(int) 520 if value == 0 { 521 panic(fmt.Errorf("empty value for %q found in configuration", name)) 522 } 523 return value 524 } 525 526 // Type returns the model's cloud provider type. 527 func (c *Config) Type() string { 528 return c.mustString(TypeKey) 529 } 530 531 // Name returns the model name. 532 func (c *Config) Name() string { 533 return c.mustString(NameKey) 534 } 535 536 // UUID returns the uuid for the model. 537 func (c *Config) UUID() string { 538 return c.mustString(UUIDKey) 539 } 540 541 // DefaultSeries returns the configured default Ubuntu series for the environment, 542 // and whether the default series was explicitly configured on the environment. 543 func (c *Config) DefaultSeries() (string, bool) { 544 s, ok := c.defined["default-series"] 545 if !ok { 546 return "", false 547 } 548 switch s := s.(type) { 549 case string: 550 return s, s != "" 551 default: 552 logger.Errorf("invalid default-series: %q", s) 553 return "", false 554 } 555 } 556 557 // AuthorizedKeys returns the content for ssh's authorized_keys file. 558 func (c *Config) AuthorizedKeys() string { 559 value, _ := c.defined[AuthorizedKeysKey].(string) 560 return value 561 } 562 563 // ProxySSH returns a flag indicating whether SSH commands 564 // should be proxied through the API server. 565 func (c *Config) ProxySSH() bool { 566 value, _ := c.defined["proxy-ssh"].(bool) 567 return value 568 } 569 570 // ProxySettings returns all four proxy settings; http, https, ftp, and no 571 // proxy. 572 func (c *Config) ProxySettings() proxy.Settings { 573 return proxy.Settings{ 574 Http: c.HTTPProxy(), 575 Https: c.HTTPSProxy(), 576 Ftp: c.FTPProxy(), 577 NoProxy: c.NoProxy(), 578 } 579 } 580 581 // HTTPProxy returns the http proxy for the environment. 582 func (c *Config) HTTPProxy() string { 583 return c.asString(HTTPProxyKey) 584 } 585 586 // HTTPSProxy returns the https proxy for the environment. 587 func (c *Config) HTTPSProxy() string { 588 return c.asString(HTTPSProxyKey) 589 } 590 591 // FTPProxy returns the ftp proxy for the environment. 592 func (c *Config) FTPProxy() string { 593 return c.asString(FTPProxyKey) 594 } 595 596 // NoProxy returns the 'no proxy' for the environment. 597 func (c *Config) NoProxy() string { 598 return c.asString(NoProxyKey) 599 } 600 601 func (c *Config) getWithFallback(key, fallback string) string { 602 value := c.asString(key) 603 if value == "" { 604 value = c.asString(fallback) 605 } 606 return value 607 } 608 609 // addSchemeIfMissing adds a scheme to a URL if it is missing 610 func addSchemeIfMissing(defaultScheme string, url string) string { 611 if url != "" && !strings.Contains(url, "://") { 612 url = defaultScheme + "://" + url 613 } 614 return url 615 } 616 617 // AptProxySettings returns all three proxy settings; http, https and ftp. 618 func (c *Config) AptProxySettings() proxy.Settings { 619 return proxy.Settings{ 620 Http: c.AptHTTPProxy(), 621 Https: c.AptHTTPSProxy(), 622 Ftp: c.AptFTPProxy(), 623 } 624 } 625 626 // AptHTTPProxy returns the apt http proxy for the environment. 627 // Falls back to the default http-proxy if not specified. 628 func (c *Config) AptHTTPProxy() string { 629 return addSchemeIfMissing("http", c.getWithFallback(AptHTTPProxyKey, HTTPProxyKey)) 630 } 631 632 // AptHTTPSProxy returns the apt https proxy for the environment. 633 // Falls back to the default https-proxy if not specified. 634 func (c *Config) AptHTTPSProxy() string { 635 return addSchemeIfMissing("https", c.getWithFallback(AptHTTPSProxyKey, HTTPSProxyKey)) 636 } 637 638 // AptFTPProxy returns the apt ftp proxy for the environment. 639 // Falls back to the default ftp-proxy if not specified. 640 func (c *Config) AptFTPProxy() string { 641 return addSchemeIfMissing("ftp", c.getWithFallback(AptFTPProxyKey, FTPProxyKey)) 642 } 643 644 // AptMirror sets the apt mirror for the environment. 645 func (c *Config) AptMirror() string { 646 return c.asString("apt-mirror") 647 } 648 649 // LogFwdSyslog returns the syslog forwarding config. 650 func (c *Config) LogFwdSyslog() (*syslog.RawConfig, bool) { 651 partial := false 652 var lfCfg syslog.RawConfig 653 654 if s, ok := c.defined[LogForwardEnabled]; ok { 655 partial = true 656 lfCfg.Enabled = s.(bool) 657 } 658 659 if s, ok := c.defined[LogFwdSyslogHost]; ok && s != "" { 660 partial = true 661 lfCfg.Host = s.(string) 662 } 663 664 if s, ok := c.defined[LogFwdSyslogCACert]; ok && s != "" { 665 partial = true 666 lfCfg.CACert = s.(string) 667 } 668 669 if s, ok := c.defined[LogFwdSyslogClientCert]; ok && s != "" { 670 partial = true 671 lfCfg.ClientCert = s.(string) 672 } 673 674 if s, ok := c.defined[LogFwdSyslogClientKey]; ok && s != "" { 675 partial = true 676 lfCfg.ClientKey = s.(string) 677 } 678 679 if !partial { 680 return nil, false 681 } 682 return &lfCfg, true 683 } 684 685 // FirewallMode returns whether the firewall should 686 // manage ports per machine, globally, or not at all. 687 // (FwInstance, FwGlobal, or FwNone). 688 func (c *Config) FirewallMode() string { 689 return c.mustString("firewall-mode") 690 } 691 692 // AgentVersion returns the proposed version number for the agent tools, 693 // and whether it has been set. Once an environment is bootstrapped, this 694 // must always be valid. 695 func (c *Config) AgentVersion() (version.Number, bool) { 696 if v, ok := c.defined[AgentVersionKey].(string); ok { 697 n, err := version.Parse(v) 698 if err != nil { 699 panic(err) // We should have checked it earlier. 700 } 701 return n, true 702 } 703 return version.Zero, false 704 } 705 706 // AgentMetadataURL returns the URL that locates the agent tarballs and metadata, 707 // and whether it has been set. 708 func (c *Config) AgentMetadataURL() (string, bool) { 709 if url, ok := c.defined[AgentMetadataURLKey]; ok && url != "" { 710 return url.(string), true 711 } 712 return "", false 713 } 714 715 // ImageMetadataURL returns the URL at which the metadata used to locate image ids is located, 716 // and wether it has been set. 717 func (c *Config) ImageMetadataURL() (string, bool) { 718 if url, ok := c.defined["image-metadata-url"]; ok && url != "" { 719 return url.(string), true 720 } 721 return "", false 722 } 723 724 // Development returns whether the environment is in development mode. 725 func (c *Config) Development() bool { 726 value, _ := c.defined["development"].(bool) 727 return value 728 } 729 730 // EnableOSRefreshUpdate returns whether or not newly provisioned 731 // instances should run their respective OS's update capability. 732 func (c *Config) EnableOSRefreshUpdate() bool { 733 if val, ok := c.defined["enable-os-refresh-update"].(bool); !ok { 734 return true 735 } else { 736 return val 737 } 738 } 739 740 // EnableOSUpgrade returns whether or not newly provisioned instances 741 // should run their respective OS's upgrade capability. 742 func (c *Config) EnableOSUpgrade() bool { 743 if val, ok := c.defined["enable-os-upgrade"].(bool); !ok { 744 return true 745 } else { 746 return val 747 } 748 } 749 750 // SSLHostnameVerification returns weather the environment has requested 751 // SSL hostname verification to be enabled. 752 func (c *Config) SSLHostnameVerification() bool { 753 return c.defined["ssl-hostname-verification"].(bool) 754 } 755 756 // LoggingConfig returns the configuration string for the loggers. 757 func (c *Config) LoggingConfig() string { 758 return c.asString("logging-config") 759 } 760 761 // AutomaticallyRetryHooks returns whether we should automatically retry hooks. 762 // By default this should be true. 763 func (c *Config) AutomaticallyRetryHooks() bool { 764 if val, ok := c.defined["automatically-retry-hooks"].(bool); !ok { 765 return true 766 } else { 767 return val 768 } 769 } 770 771 // TransmitVendorMetrics returns whether the controller sends charm-collected metrics 772 // in this model for anonymized aggregate analytics. By default this should be true. 773 func (c *Config) TransmitVendorMetrics() bool { 774 if val, ok := c.defined[TransmitVendorMetricsKey].(bool); !ok { 775 return true 776 } else { 777 return val 778 } 779 } 780 781 // ProvisionerHarvestMode reports the harvesting methodology the 782 // provisioner should take. 783 func (c *Config) ProvisionerHarvestMode() HarvestMode { 784 if v, ok := c.defined[ProvisionerHarvestModeKey].(string); ok { 785 if method, err := ParseHarvestMode(v); err != nil { 786 // This setting should have already been validated. Don't 787 // burden the caller with handling any errors. 788 panic(err) 789 } else { 790 return method 791 } 792 } else { 793 return HarvestDestroyed 794 } 795 } 796 797 // ImageStream returns the simplestreams stream 798 // used to identify which image ids to search 799 // when starting an instance. 800 func (c *Config) ImageStream() string { 801 v, _ := c.defined["image-stream"].(string) 802 if v != "" { 803 return v 804 } 805 return "released" 806 } 807 808 // AgentStream returns the simplestreams stream 809 // used to identify which tools to use when 810 // when bootstrapping or upgrading an environment. 811 func (c *Config) AgentStream() string { 812 v, _ := c.defined[AgentStreamKey].(string) 813 if v != "" { 814 return v 815 } 816 return "released" 817 } 818 819 // TestMode indicates if the environment is intended for testing. 820 // In this case, accessing the charm store does not affect statistical 821 // data of the store. 822 func (c *Config) TestMode() bool { 823 val, _ := c.defined["test-mode"].(bool) 824 return val 825 } 826 827 // DisableNetworkManagement reports whether Juju is allowed to 828 // configure and manage networking inside the environment. 829 func (c *Config) DisableNetworkManagement() (bool, bool) { 830 v, ok := c.defined["disable-network-management"].(bool) 831 return v, ok 832 } 833 834 // IgnoreMachineAddresses reports whether Juju will discover 835 // and store machine addresses on startup. 836 func (c *Config) IgnoreMachineAddresses() (bool, bool) { 837 v, ok := c.defined[IgnoreMachineAddresses].(bool) 838 return v, ok 839 } 840 841 // StorageDefaultBlockSource returns the default block storage 842 // source for the environment. 843 func (c *Config) StorageDefaultBlockSource() (string, bool) { 844 bs := c.asString(StorageDefaultBlockSourceKey) 845 return bs, bs != "" 846 } 847 848 // ResourceTags returns a set of tags to set on environment resources 849 // that Juju creates and manages, if the provider supports them. These 850 // tags have no special meaning to Juju, but may be used for existing 851 // chargeback accounting schemes or other identification purposes. 852 func (c *Config) ResourceTags() (map[string]string, bool) { 853 tags, err := c.resourceTags() 854 if err != nil { 855 panic(err) // should be prevented by Validate 856 } 857 return tags, tags != nil 858 } 859 860 func (c *Config) resourceTags() (map[string]string, error) { 861 v, ok := c.defined[ResourceTagsKey].(map[string]string) 862 if !ok { 863 return nil, nil 864 } 865 for k := range v { 866 if strings.HasPrefix(k, tags.JujuTagPrefix) { 867 return nil, errors.Errorf("tag %q uses reserved prefix %q", k, tags.JujuTagPrefix) 868 } 869 } 870 return v, nil 871 } 872 873 // UnknownAttrs returns a copy of the raw configuration attributes 874 // that are supposedly specific to the environment type. They could 875 // also be wrong attributes, though. Only the specific environment 876 // implementation can tell. 877 func (c *Config) UnknownAttrs() map[string]interface{} { 878 newAttrs := make(map[string]interface{}) 879 for k, v := range c.unknown { 880 newAttrs[k] = v 881 } 882 return newAttrs 883 } 884 885 // AllAttrs returns a copy of the raw configuration attributes. 886 func (c *Config) AllAttrs() map[string]interface{} { 887 allAttrs := c.UnknownAttrs() 888 for k, v := range c.defined { 889 allAttrs[k] = v 890 } 891 return allAttrs 892 } 893 894 // Remove returns a new configuration that has the attributes of c minus attrs. 895 func (c *Config) Remove(attrs []string) (*Config, error) { 896 defined := c.AllAttrs() 897 for _, k := range attrs { 898 delete(defined, k) 899 } 900 return New(NoDefaults, defined) 901 } 902 903 // Apply returns a new configuration that has the attributes of c plus attrs. 904 func (c *Config) Apply(attrs map[string]interface{}) (*Config, error) { 905 defined := c.AllAttrs() 906 for k, v := range attrs { 907 defined[k] = v 908 } 909 return New(NoDefaults, defined) 910 } 911 912 // fields holds the validation schema fields derived from configSchema. 913 var fields = func() schema.Fields { 914 combinedSchema, err := Schema(nil) 915 if err != nil { 916 panic(err) 917 } 918 fs, _, err := combinedSchema.ValidationSchema() 919 if err != nil { 920 panic(err) 921 } 922 return fs 923 }() 924 925 // alwaysOptional holds configuration defaults for attributes that may 926 // be unspecified even after a configuration has been created with all 927 // defaults filled out. 928 // 929 // This table is not definitive: it specifies those attributes which are 930 // optional when the config goes through its initial schema coercion, 931 // but some fields listed as optional here are actually mandatory 932 // with NoDefaults and are checked at the later Validate stage. 933 var alwaysOptional = schema.Defaults{ 934 AgentVersionKey: schema.Omit, 935 AuthorizedKeysKey: schema.Omit, 936 937 LogForwardEnabled: schema.Omit, 938 LogFwdSyslogHost: schema.Omit, 939 LogFwdSyslogCACert: schema.Omit, 940 LogFwdSyslogClientCert: schema.Omit, 941 LogFwdSyslogClientKey: schema.Omit, 942 943 // Storage related config. 944 // Environ providers will specify their own defaults. 945 StorageDefaultBlockSourceKey: schema.Omit, 946 947 "firewall-mode": schema.Omit, 948 "logging-config": schema.Omit, 949 ProvisionerHarvestModeKey: schema.Omit, 950 HTTPProxyKey: schema.Omit, 951 HTTPSProxyKey: schema.Omit, 952 FTPProxyKey: schema.Omit, 953 NoProxyKey: schema.Omit, 954 AptHTTPProxyKey: schema.Omit, 955 AptHTTPSProxyKey: schema.Omit, 956 AptFTPProxyKey: schema.Omit, 957 "apt-mirror": schema.Omit, 958 AgentStreamKey: schema.Omit, 959 ResourceTagsKey: schema.Omit, 960 "cloudimg-base-url": schema.Omit, 961 "enable-os-refresh-update": schema.Omit, 962 "enable-os-upgrade": schema.Omit, 963 "image-stream": schema.Omit, 964 "image-metadata-url": schema.Omit, 965 AgentMetadataURLKey: schema.Omit, 966 "default-series": schema.Omit, 967 "development": schema.Omit, 968 "ssl-hostname-verification": schema.Omit, 969 "proxy-ssh": schema.Omit, 970 "disable-network-management": schema.Omit, 971 IgnoreMachineAddresses: schema.Omit, 972 AutomaticallyRetryHooks: schema.Omit, 973 "test-mode": schema.Omit, 974 TransmitVendorMetricsKey: schema.Omit, 975 } 976 977 func allowEmpty(attr string) bool { 978 return alwaysOptional[attr] == "" || alwaysOptional[attr] == schema.Omit 979 } 980 981 var defaultsWhenParsing = allDefaults() 982 983 // allDefaults returns a schema.Defaults that contains 984 // defaults to be used when creating a new config with 985 // UseDefaults. 986 func allDefaults() schema.Defaults { 987 d := schema.Defaults{} 988 configDefaults := ConfigDefaults() 989 for attr, val := range configDefaults { 990 d[attr] = val 991 } 992 for attr, val := range alwaysOptional { 993 if _, ok := d[attr]; !ok { 994 d[attr] = val 995 } 996 } 997 return d 998 } 999 1000 // immutableAttributes holds those attributes 1001 // which are not allowed to change in the lifetime 1002 // of an environment. 1003 var immutableAttributes = []string{ 1004 NameKey, 1005 TypeKey, 1006 UUIDKey, 1007 "firewall-mode", 1008 } 1009 1010 var ( 1011 withDefaultsChecker = schema.FieldMap(fields, defaultsWhenParsing) 1012 noDefaultsChecker = schema.FieldMap(fields, alwaysOptional) 1013 ) 1014 1015 // ValidateUnknownAttrs checks the unknown attributes of the config against 1016 // the supplied fields and defaults, and returns an error if any fails to 1017 // validate. Unknown fields are warned about, but preserved, on the basis 1018 // that they are reasonably likely to have been written by or for a version 1019 // of juju that does recognise the fields, but that their presence is still 1020 // anomalous to some degree and should be flagged (and that there is thereby 1021 // a mechanism for observing fields that really are typos etc). 1022 func (cfg *Config) ValidateUnknownAttrs(fields schema.Fields, defaults schema.Defaults) (map[string]interface{}, error) { 1023 attrs := cfg.UnknownAttrs() 1024 checker := schema.FieldMap(fields, defaults) 1025 coerced, err := checker.Coerce(attrs, nil) 1026 if err != nil { 1027 // TODO(ericsnow) Drop this? 1028 logger.Debugf("coercion failed attributes: %#v, checker: %#v, %v", attrs, checker, err) 1029 return nil, err 1030 } 1031 result := coerced.(map[string]interface{}) 1032 for name, value := range attrs { 1033 if fields[name] == nil { 1034 if val, isString := value.(string); isString && val != "" { 1035 // only warn about attributes with non-empty string values 1036 logger.Warningf("unknown config field %q", name) 1037 } 1038 result[name] = value 1039 } 1040 } 1041 return result, nil 1042 } 1043 1044 // SpecializeCharmRepo customizes a repository for a given configuration. 1045 // It returns a charm repository with test mode enabled if applicable. 1046 func SpecializeCharmRepo(repo charmrepo.Interface, cfg *Config) charmrepo.Interface { 1047 type specializer interface { 1048 WithTestMode() charmrepo.Interface 1049 } 1050 if store, ok := repo.(specializer); ok { 1051 if cfg.TestMode() { 1052 return store.WithTestMode() 1053 } 1054 } 1055 return repo 1056 } 1057 1058 func addIfNotEmpty(settings map[string]interface{}, key, value string) { 1059 if value != "" { 1060 settings[key] = value 1061 } 1062 } 1063 1064 // ProxyConfigMap returns a map suitable to be applied to a Config to update 1065 // proxy settings. 1066 func ProxyConfigMap(proxySettings proxy.Settings) map[string]interface{} { 1067 settings := make(map[string]interface{}) 1068 addIfNotEmpty(settings, HTTPProxyKey, proxySettings.Http) 1069 addIfNotEmpty(settings, HTTPSProxyKey, proxySettings.Https) 1070 addIfNotEmpty(settings, FTPProxyKey, proxySettings.Ftp) 1071 addIfNotEmpty(settings, NoProxyKey, proxySettings.NoProxy) 1072 return settings 1073 } 1074 1075 // AptProxyConfigMap returns a map suitable to be applied to a Config to update 1076 // proxy settings. 1077 func AptProxyConfigMap(proxySettings proxy.Settings) map[string]interface{} { 1078 settings := make(map[string]interface{}) 1079 addIfNotEmpty(settings, AptHTTPProxyKey, proxySettings.Http) 1080 addIfNotEmpty(settings, AptHTTPSProxyKey, proxySettings.Https) 1081 addIfNotEmpty(settings, AptFTPProxyKey, proxySettings.Ftp) 1082 return settings 1083 } 1084 1085 // Schema returns a configuration schema that includes both 1086 // the given extra fields and all the fields defined in this package. 1087 // It returns an error if extra defines any fields defined in this 1088 // package. 1089 func Schema(extra environschema.Fields) (environschema.Fields, error) { 1090 fields := make(environschema.Fields) 1091 for name, field := range configSchema { 1092 if controller.ControllerOnlyAttribute(name) { 1093 return nil, errors.Errorf("config field %q clashes with controller config", name) 1094 } 1095 fields[name] = field 1096 } 1097 for name, field := range extra { 1098 if controller.ControllerOnlyAttribute(name) { 1099 return nil, errors.Errorf("config field %q clashes with controller config", name) 1100 } 1101 if _, ok := fields[name]; ok { 1102 return nil, errors.Errorf("config field %q clashes with global config", name) 1103 } 1104 fields[name] = field 1105 } 1106 return fields, nil 1107 } 1108 1109 // configSchema holds information on all the fields defined by 1110 // the config package. 1111 // TODO(rog) make this available to external packages. 1112 var configSchema = environschema.Fields{ 1113 AgentMetadataURLKey: { 1114 Description: "URL of private stream", 1115 Type: environschema.Tstring, 1116 Group: environschema.EnvironGroup, 1117 }, 1118 AgentStreamKey: { 1119 Description: `Version of Juju to use for deploy/upgrades.`, 1120 Type: environschema.Tstring, 1121 Group: environschema.EnvironGroup, 1122 }, 1123 AgentVersionKey: { 1124 Description: "The desired Juju agent version to use", 1125 Type: environschema.Tstring, 1126 Group: environschema.JujuGroup, 1127 Immutable: true, 1128 }, 1129 AptFTPProxyKey: { 1130 // TODO document acceptable format 1131 Description: "The APT FTP proxy for the model", 1132 Type: environschema.Tstring, 1133 Group: environschema.EnvironGroup, 1134 }, 1135 AptHTTPProxyKey: { 1136 // TODO document acceptable format 1137 Description: "The APT HTTP proxy for the model", 1138 Type: environschema.Tstring, 1139 Group: environschema.EnvironGroup, 1140 }, 1141 AptHTTPSProxyKey: { 1142 // TODO document acceptable format 1143 Description: "The APT HTTPS proxy for the model", 1144 Type: environschema.Tstring, 1145 Group: environschema.EnvironGroup, 1146 }, 1147 "apt-mirror": { 1148 // TODO document acceptable format 1149 Description: "The APT mirror for the model", 1150 Type: environschema.Tstring, 1151 Group: environschema.EnvironGroup, 1152 }, 1153 AuthorizedKeysKey: { 1154 Description: "Any authorized SSH public keys for the model, as found in a ~/.ssh/authorized_keys file", 1155 Type: environschema.Tstring, 1156 Group: environschema.EnvironGroup, 1157 }, 1158 "default-series": { 1159 Description: "The default series of Ubuntu to use for deploying charms", 1160 Type: environschema.Tstring, 1161 Group: environschema.EnvironGroup, 1162 }, 1163 "development": { 1164 Description: "Whether the model is in development mode", 1165 Type: environschema.Tbool, 1166 Group: environschema.EnvironGroup, 1167 }, 1168 "disable-network-management": { 1169 Description: "Whether the provider should control networks (on MAAS models, set to true for MAAS to control networks", 1170 Type: environschema.Tbool, 1171 Group: environschema.EnvironGroup, 1172 }, 1173 IgnoreMachineAddresses: { 1174 Description: "Whether the machine worker should discover machine addresses on startup", 1175 Type: environschema.Tbool, 1176 Group: environschema.EnvironGroup, 1177 }, 1178 "enable-os-refresh-update": { 1179 Description: `Whether newly provisioned instances should run their respective OS's update capability.`, 1180 Type: environschema.Tbool, 1181 Group: environschema.EnvironGroup, 1182 }, 1183 "enable-os-upgrade": { 1184 Description: `Whether newly provisioned instances should run their respective OS's upgrade capability.`, 1185 Type: environschema.Tbool, 1186 Group: environschema.EnvironGroup, 1187 }, 1188 "firewall-mode": { 1189 Description: `The mode to use for network firewalling. 1190 1191 'instance' requests the use of an individual firewall per instance. 1192 1193 'global' uses a single firewall for all instances (access 1194 for a network port is enabled to one instance if any instance requires 1195 that port). 1196 1197 'none' requests that no firewalling should be performed 1198 inside the model. It's useful for clouds without support for either 1199 global or per instance security groups.`, 1200 Type: environschema.Tstring, 1201 Values: []interface{}{FwInstance, FwGlobal, FwNone}, 1202 Immutable: true, 1203 Group: environschema.EnvironGroup, 1204 }, 1205 FTPProxyKey: { 1206 Description: "The FTP proxy value to configure on instances, in the FTP_PROXY environment variable", 1207 Type: environschema.Tstring, 1208 Group: environschema.EnvironGroup, 1209 }, 1210 HTTPProxyKey: { 1211 Description: "The HTTP proxy value to configure on instances, in the HTTP_PROXY environment variable", 1212 Type: environschema.Tstring, 1213 Group: environschema.EnvironGroup, 1214 }, 1215 HTTPSProxyKey: { 1216 Description: "The HTTPS proxy value to configure on instances, in the HTTPS_PROXY environment variable", 1217 Type: environschema.Tstring, 1218 Group: environschema.EnvironGroup, 1219 }, 1220 "image-metadata-url": { 1221 Description: "The URL at which the metadata used to locate OS image ids is located", 1222 Type: environschema.Tstring, 1223 Group: environschema.EnvironGroup, 1224 }, 1225 "image-stream": { 1226 Description: `The simplestreams stream used to identify which image ids to search when starting an instance.`, 1227 Type: environschema.Tstring, 1228 Group: environschema.EnvironGroup, 1229 }, 1230 "logging-config": { 1231 Description: `The configuration string to use when configuring Juju agent logging (see http://godoc.org/github.com/juju/loggo#ParseConfigurationString for details)`, 1232 Type: environschema.Tstring, 1233 Group: environschema.EnvironGroup, 1234 }, 1235 NameKey: { 1236 Description: "The name of the current model", 1237 Type: environschema.Tstring, 1238 Mandatory: true, 1239 Immutable: true, 1240 Group: environschema.EnvironGroup, 1241 }, 1242 NoProxyKey: { 1243 Description: "List of domain addresses not to be proxied (comma-separated)", 1244 Type: environschema.Tstring, 1245 Group: environschema.EnvironGroup, 1246 }, 1247 ProvisionerHarvestModeKey: { 1248 // default: destroyed, but also depends on current setting of ProvisionerSafeModeKey 1249 Description: "What to do with unknown machines. See https://jujucharms.com/docs/stable/config-general#juju-lifecycle-and-harvesting (default destroyed)", 1250 Type: environschema.Tstring, 1251 Values: []interface{}{"all", "none", "unknown", "destroyed"}, 1252 Group: environschema.EnvironGroup, 1253 }, 1254 "proxy-ssh": { 1255 // default: true 1256 Description: `Whether SSH commands should be proxied through the API server`, 1257 Type: environschema.Tbool, 1258 Group: environschema.EnvironGroup, 1259 }, 1260 ResourceTagsKey: { 1261 Description: "resource tags", 1262 Type: environschema.Tattrs, 1263 Group: environschema.EnvironGroup, 1264 }, 1265 LogForwardEnabled: { 1266 Description: `Whether syslog forwarding is enabled.`, 1267 Type: environschema.Tbool, 1268 Group: environschema.EnvironGroup, 1269 }, 1270 LogFwdSyslogHost: { 1271 Description: `The hostname:port of the syslog server.`, 1272 Type: environschema.Tstring, 1273 Group: environschema.EnvironGroup, 1274 }, 1275 LogFwdSyslogCACert: { 1276 Description: `The certificate of the CA that signed the syslog server certificate, in PEM format.`, 1277 Type: environschema.Tstring, 1278 Group: environschema.EnvironGroup, 1279 }, 1280 LogFwdSyslogClientCert: { 1281 Description: `The syslog client certificate in PEM format.`, 1282 Type: environschema.Tstring, 1283 Group: environschema.EnvironGroup, 1284 }, 1285 LogFwdSyslogClientKey: { 1286 Description: `The syslog client key in PEM format.`, 1287 Type: environschema.Tstring, 1288 Group: environschema.EnvironGroup, 1289 }, 1290 "ssl-hostname-verification": { 1291 Description: "Whether SSL hostname verification is enabled (default true)", 1292 Type: environschema.Tbool, 1293 Group: environschema.EnvironGroup, 1294 }, 1295 StorageDefaultBlockSourceKey: { 1296 Description: "The default block storage source for the model", 1297 Type: environschema.Tstring, 1298 Group: environschema.EnvironGroup, 1299 }, 1300 "test-mode": { 1301 Description: `Whether the model is intended for testing. 1302 If true, accessing the charm store does not affect statistical 1303 data of the store. (default false)`, 1304 Type: environschema.Tbool, 1305 Group: environschema.EnvironGroup, 1306 }, 1307 TypeKey: { 1308 Description: "Type of model, e.g. local, ec2", 1309 Type: environschema.Tstring, 1310 Mandatory: true, 1311 Immutable: true, 1312 Group: environschema.EnvironGroup, 1313 }, 1314 UUIDKey: { 1315 Description: "The UUID of the model", 1316 Type: environschema.Tstring, 1317 Group: environschema.JujuGroup, 1318 Immutable: true, 1319 }, 1320 AutomaticallyRetryHooks: { 1321 Description: "Determines whether the uniter should automatically retry failed hooks", 1322 Type: environschema.Tbool, 1323 Group: environschema.EnvironGroup, 1324 }, 1325 TransmitVendorMetricsKey: { 1326 Description: "Determines whether metrics declared by charms deployed into this model are sent for anonymized aggregate analytics", 1327 Type: environschema.Tbool, 1328 Group: environschema.EnvironGroup, 1329 }, 1330 }