github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "net" 9 "net/url" 10 "os" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/juju/collections/set" 16 "github.com/juju/errors" 17 "github.com/juju/featureflag" 18 "github.com/juju/loggo" 19 "github.com/juju/names/v5" 20 "github.com/juju/proxy" 21 "github.com/juju/schema" 22 "github.com/juju/utils/v3" 23 "github.com/juju/version/v2" 24 "gopkg.in/juju/environschema.v1" 25 "gopkg.in/yaml.v2" 26 27 "github.com/juju/juju/charmhub" 28 "github.com/juju/juju/controller" 29 corebase "github.com/juju/juju/core/base" 30 corelogger "github.com/juju/juju/core/logger" 31 "github.com/juju/juju/core/network" 32 "github.com/juju/juju/environs/tags" 33 "github.com/juju/juju/feature" 34 "github.com/juju/juju/juju/osenv" 35 "github.com/juju/juju/logfwd/syslog" 36 jujuversion "github.com/juju/juju/version" 37 ) 38 39 var logger = loggo.GetLogger("juju.environs.config") 40 41 const ( 42 // FwInstance requests the use of an individual firewall per instance. 43 FwInstance = "instance" 44 45 // FwGlobal requests the use of a single firewall group for all machines. 46 // When ports are opened for one machine, all machines will have the same 47 // port opened. 48 FwGlobal = "global" 49 50 // FwNone requests that no firewalling should be performed inside 51 // the environment. No firewaller worker will be started. It's 52 // useful for clouds without support for either global or per 53 // instance security groups. 54 FwNone = "none" 55 ) 56 57 // TODO(katco-): Please grow this over time. 58 // Centralized place to store values of config keys. This transitions 59 // mistakes in referencing key-values to a compile-time error. 60 const ( 61 // 62 // Settings Attributes 63 // 64 65 // NameKey is the key for the model's name. 66 NameKey = "name" 67 68 // TypeKey is the key for the model's cloud type. 69 TypeKey = "type" 70 71 // AgentVersionKey is the key for the model's Juju agent version. 72 AgentVersionKey = "agent-version" 73 74 // UUIDKey is the key for the model UUID attribute. 75 UUIDKey = "uuid" 76 77 // AuthorizedKeysKey is the key for the authorized-keys attribute. 78 AuthorizedKeysKey = "authorized-keys" 79 80 // ProvisionerHarvestModeKey stores the key for this setting. 81 ProvisionerHarvestModeKey = "provisioner-harvest-mode" 82 83 // NumProvisionWorkersKey is the key for number of model provisioner 84 // workers. 85 NumProvisionWorkersKey = "num-provision-workers" 86 87 // NumContainerProvisionWorkersKey is the key for the number of 88 // container provisioner workers per machine setting. 89 NumContainerProvisionWorkersKey = "num-container-provision-workers" 90 91 // ImageStreamKey is the key used to specify the stream 92 // for OS images. 93 ImageStreamKey = "image-stream" 94 95 // ImageMetadataURLKey is the key used to specify the location 96 // of OS image metadata. 97 ImageMetadataURLKey = "image-metadata-url" 98 99 // ImageMetadataDefaultsDisabledKey is the key used to disable image 100 // metadata default sources. 101 ImageMetadataDefaultsDisabledKey = "image-metadata-defaults-disabled" 102 103 // AgentStreamKey stores the key for this setting. 104 AgentStreamKey = "agent-stream" 105 106 // AgentMetadataURLKey stores the key for this setting. 107 AgentMetadataURLKey = "agent-metadata-url" 108 109 // ContainerImageStreamKey is the key used to specify the stream 110 // for container OS images. 111 ContainerImageStreamKey = "container-image-stream" 112 113 // ContainerImageMetadataURLKey is the key used to specify the location 114 // of OS image metadata for containers. 115 ContainerImageMetadataURLKey = "container-image-metadata-url" 116 117 // ContainerImageMetadataDefaultsDisabledKey is the key used to disable image 118 // metadata default sources for containers. 119 ContainerImageMetadataDefaultsDisabledKey = "container-image-metadata-defaults-disabled" 120 121 // Proxy behaviour has become something of an annoying thing to define 122 // well. These following four proxy variables are being kept to continue 123 // with the existing behaviour for those deployments that specify them. 124 // With these proxy values set, a file is written to every machine 125 // in /etc/profile.d so the ubuntu user gets the environment variables 126 // set when SSHing in. The OS environment also is set in the juju agents 127 // and charm hook environments. 128 129 // HTTPProxyKey stores the key for this setting. 130 HTTPProxyKey = "http-proxy" 131 132 // HTTPSProxyKey stores the key for this setting. 133 HTTPSProxyKey = "https-proxy" 134 135 // FTPProxyKey stores the key for this setting. 136 FTPProxyKey = "ftp-proxy" 137 138 // NoProxyKey stores the key for this setting. 139 NoProxyKey = "no-proxy" 140 141 // The new proxy keys are passed into hook contexts with the prefix 142 // JUJU_CHARM_ then HTTP_PROXY, HTTPS_PROXY, FTP_PROXY, and NO_PROXY. 143 // This allows the charm to set a proxy when it thinks it needs one. 144 // These values are not set in the general environment. 145 146 // JujuHTTPProxyKey stores the key for this setting. 147 JujuHTTPProxyKey = "juju-http-proxy" 148 149 // JujuHTTPSProxyKey stores the key for this setting. 150 JujuHTTPSProxyKey = "juju-https-proxy" 151 152 // JujuFTPProxyKey stores the key for this setting. 153 JujuFTPProxyKey = "juju-ftp-proxy" 154 155 // JujuNoProxyKey stores the key for this setting. 156 JujuNoProxyKey = "juju-no-proxy" 157 158 // The APT proxy values specified here work with both the 159 // legacy and juju proxy settings. If no value is specified, 160 // the value is determined by the either the legacy or juju value 161 // if one is specified. 162 163 // AptHTTPProxyKey stores the key for this setting. 164 AptHTTPProxyKey = "apt-http-proxy" 165 166 // AptHTTPSProxyKey stores the key for this setting. 167 AptHTTPSProxyKey = "apt-https-proxy" 168 169 // AptFTPProxyKey stores the key for this setting. 170 AptFTPProxyKey = "apt-ftp-proxy" 171 172 // AptNoProxyKey stores the key for this setting. 173 AptNoProxyKey = "apt-no-proxy" 174 175 // AptMirrorKey is used to set the apt mirror. 176 AptMirrorKey = "apt-mirror" 177 178 // SnapHTTPProxyKey is used to set the snap core setting proxy.http for deployed machines. 179 SnapHTTPProxyKey = "snap-http-proxy" 180 // SnapHTTPSProxyKey is used to set the snap core setting proxy.https for deployed machines. 181 SnapHTTPSProxyKey = "snap-https-proxy" 182 // SnapStoreProxyKey is used to set the snap core setting proxy.store for deployed machines. 183 SnapStoreProxyKey = "snap-store-proxy" 184 // SnapStoreAssertionsKey is used to configure the deployed machines to acknowledge the 185 // store proxy assertions. 186 SnapStoreAssertionsKey = "snap-store-assertions" 187 // SnapStoreProxyURLKey is used to specify the URL to a snap store proxy. 188 SnapStoreProxyURLKey = "snap-store-proxy-url" 189 190 // NetBondReconfigureDelayKey is the key to pass when bridging 191 // the network for containers. 192 NetBondReconfigureDelayKey = "net-bond-reconfigure-delay" 193 194 // ContainerNetworkingMethod is the key for setting up 195 // networking method for containers. 196 ContainerNetworkingMethod = "container-networking-method" 197 198 // StorageDefaultBlockSourceKey is the key for the default block storage source. 199 StorageDefaultBlockSourceKey = "storage-default-block-source" 200 201 // StorageDefaultFilesystemSourceKey is the key for the default filesystem storage source. 202 StorageDefaultFilesystemSourceKey = "storage-default-filesystem-source" 203 204 // ResourceTagsKey is an optional list or space-separated string 205 // of k=v pairs, defining the tags for ResourceTags. 206 ResourceTagsKey = "resource-tags" 207 208 // LogForwardEnabled determines whether the log forward functionality is enabled. 209 LogForwardEnabled = "logforward-enabled" 210 211 // LogFwdSyslogHost sets the hostname:port of the syslog server. 212 LogFwdSyslogHost = "syslog-host" 213 214 // LogFwdSyslogCACert sets the certificate of the CA that signed the syslog 215 // server certificate. 216 LogFwdSyslogCACert = "syslog-ca-cert" 217 218 // LogFwdSyslogClientCert sets the client certificate for syslog 219 // forwarding. 220 LogFwdSyslogClientCert = "syslog-client-cert" 221 222 // LogFwdSyslogClientKey sets the client key for syslog 223 // forwarding. 224 LogFwdSyslogClientKey = "syslog-client-key" 225 226 // AutomaticallyRetryHooks determines whether the uniter will 227 // automatically retry a hook that has failed 228 AutomaticallyRetryHooks = "automatically-retry-hooks" 229 230 // TransmitVendorMetricsKey is the key for whether the controller sends 231 // metrics collected in this model for anonymized aggregate analytics. 232 TransmitVendorMetricsKey = "transmit-vendor-metrics" 233 234 // ExtraInfoKey is the key for arbitrary user specified string data that 235 // is stored against the model. 236 ExtraInfoKey = "extra-info" 237 238 // MaxStatusHistoryAge is the maximum age of status history values 239 // to keep when pruning, eg "72h" 240 MaxStatusHistoryAge = "max-status-history-age" 241 242 // MaxStatusHistorySize is the maximum size the status history 243 // collection can grow to before it is pruned, eg "5M" 244 MaxStatusHistorySize = "max-status-history-size" 245 246 // MaxActionResultsAge is the maximum age of actions to keep when pruning, eg 247 // "72h" 248 MaxActionResultsAge = "max-action-results-age" 249 250 // MaxActionResultsSize is the maximum size the actions collection can 251 // grow to before it is pruned, eg "5M" 252 MaxActionResultsSize = "max-action-results-size" 253 254 // UpdateStatusHookInterval is how often to run the update-status hook. 255 UpdateStatusHookInterval = "update-status-hook-interval" 256 257 // EgressSubnets are the source addresses from which traffic from this model 258 // originates if the model is deployed such that NAT or similar is in use. 259 EgressSubnets = "egress-subnets" 260 261 // FanConfig defines the configuration for FAN network running in the model. 262 FanConfig = "fan-config" 263 264 // CloudInitUserDataKey is the key to specify cloud-init yaml the user 265 // wants to add into the cloud-config data produced by Juju when 266 // provisioning machines. 267 CloudInitUserDataKey = "cloudinit-userdata" 268 269 // BackupDirKey specifies the backup working directory. 270 BackupDirKey = "backup-dir" 271 272 // ContainerInheritPropertiesKey is the key to specify a list of properties 273 // to be copied from a machine to a container during provisioning. The 274 // list will be comma separated. 275 ContainerInheritPropertiesKey = "container-inherit-properties" 276 277 // DefaultSpace specifies which space should be used for the default 278 // endpoint bindings. 279 DefaultSpace = "default-space" 280 281 // LXDSnapChannel selects the channel to use when installing LXD from a snap. 282 LXDSnapChannel = "lxd-snap-channel" 283 284 // CharmHubURLKey is the key for the url to use for CharmHub API calls 285 CharmHubURLKey = "charmhub-url" 286 287 // ModeKey is the key for defining the mode that a given model should be 288 // using. 289 // It is expected that when in a different mode, Juju will perform in a 290 // different state. 291 ModeKey = "mode" 292 293 // SSHAllowKey is a comma separated list of CIDRs from which machines in 294 // this model will accept connections to the SSH service 295 SSHAllowKey = "ssh-allow" 296 297 // SAASIngressAllowKey is a comma separated list of CIDRs 298 // specifying what ingress can be applied to offers in this model 299 SAASIngressAllowKey = "saas-ingress-allow" 300 301 // 302 // Deprecated Settings Attributes 303 // 304 305 // IgnoreMachineAddresses when true, will cause the 306 // machine worker not to discover any machine addresses 307 // on start up. 308 IgnoreMachineAddresses = "ignore-machine-addresses" 309 310 // TestModeKey is the key for identifying the model should be run in test 311 // mode. 312 TestModeKey = "test-mode" 313 314 // DisableTelemetryKey is a key for determining whether telemetry on juju 315 // models will be done. 316 DisableTelemetryKey = "disable-telemetry" 317 318 // LoggingOutputKey is a key for determining the destination of output for 319 // logging. 320 LoggingOutputKey = "logging-output" 321 322 // DefaultSeriesKey is a key for determining the series a model should 323 // explicitly use for charms unless otherwise provided. 324 DefaultSeriesKey = "default-series" 325 326 // DefaultBaseKey is a key for determining the base a model should 327 // explicitly use for charms unless otherwise provided. 328 DefaultBaseKey = "default-base" 329 330 // SecretBackendKey is used to specify the secret backend. 331 SecretBackendKey = "secret-backend" 332 ) 333 334 // ParseHarvestMode parses description of harvesting method and 335 // returns the representation. 336 func ParseHarvestMode(description string) (HarvestMode, error) { 337 description = strings.ToLower(description) 338 for method, descr := range harvestingMethodToFlag { 339 if description == descr { 340 return method, nil 341 } 342 } 343 return 0, fmt.Errorf("unknown harvesting method: %s", description) 344 } 345 346 // HarvestMode is a bit field which is used to store the harvesting 347 // behavior for Juju. 348 type HarvestMode uint32 349 350 const ( 351 // HarvestNone signifies that Juju should not harvest any 352 // machines. 353 HarvestNone HarvestMode = 1 << iota 354 // HarvestUnknown signifies that Juju should only harvest machines 355 // which exist, but we don't know about. 356 HarvestUnknown 357 // HarvestDestroyed signifies that Juju should only harvest 358 // machines which have been explicitly released by the user 359 // through a destroy of an application/model/unit. 360 HarvestDestroyed 361 // HarvestAll signifies that Juju should harvest both unknown and 362 // destroyed instances. ♫ Don't fear the reaper. ♫ 363 HarvestAll = HarvestUnknown | HarvestDestroyed 364 ) 365 366 // A mapping from method to description. Going this way will be the 367 // more common operation, so we want this type of lookup to be O(1). 368 var harvestingMethodToFlag = map[HarvestMode]string{ 369 HarvestAll: "all", 370 HarvestNone: "none", 371 HarvestUnknown: "unknown", 372 HarvestDestroyed: "destroyed", 373 } 374 375 // String returns the description of the harvesting mode. 376 func (method HarvestMode) String() string { 377 if description, ok := harvestingMethodToFlag[method]; ok { 378 return description 379 } 380 panic("Unknown harvesting method.") 381 } 382 383 // HarvestNone returns whether or not the None harvesting flag is set. 384 func (method HarvestMode) HarvestNone() bool { 385 return method&HarvestNone != 0 386 } 387 388 // HarvestDestroyed returns whether or not the Destroyed harvesting flag is set. 389 func (method HarvestMode) HarvestDestroyed() bool { 390 return method&HarvestDestroyed != 0 391 } 392 393 // HarvestUnknown returns whether or not the Unknown harvesting flag is set. 394 func (method HarvestMode) HarvestUnknown() bool { 395 return method&HarvestUnknown != 0 396 } 397 398 // GetDefaultSupportedLTSBase returns the DefaultSupportedLTSBase. 399 // This is exposed for one reason and one reason only; testing! 400 // The fact that PreferredBase doesn't take an argument for a default base 401 // as a fallback. We then have to expose this so we can exercise the branching 402 // code for other scenarios makes me sad. 403 var GetDefaultSupportedLTSBase = jujuversion.DefaultSupportedLTSBase 404 405 // HasDefaultBase defines a interface if a type has a default base or not. 406 type HasDefaultBase interface { 407 DefaultBase() (string, bool) 408 } 409 410 // PreferredBase returns the preferred base to use when a charm does not 411 // explicitly specify a base. 412 func PreferredBase(cfg HasDefaultBase) corebase.Base { 413 base, ok := cfg.DefaultBase() 414 if ok { 415 // We can safely ignore the error here as we know that we have 416 // validated the base when we set it. 417 return corebase.MustParseBaseFromString(base) 418 } 419 return GetDefaultSupportedLTSBase() 420 } 421 422 // Config holds an immutable environment configuration. 423 type Config struct { 424 // defined holds the attributes that are defined for Config. 425 // unknown holds the other attributes that are passed in (aka UnknownAttrs). 426 // the union of these two are AllAttrs 427 defined, unknown map[string]interface{} 428 } 429 430 // Defaulting is a value that specifies whether a configuration 431 // creator should use defaults from the environment. 432 type Defaulting bool 433 434 const ( 435 // UseDefaults defines a constant for indicating of the default should be 436 // used for the configuration. 437 UseDefaults Defaulting = true 438 // NoDefaults defines a constant for indicating that no defaults should be 439 // used for the configuration. 440 NoDefaults Defaulting = false 441 ) 442 443 // TODO(rog) update the doc comment below - it's getting messy 444 // and it assumes too much prior knowledge. 445 446 // New returns a new configuration. Fields that are common to all 447 // environment providers are verified. If useDefaults is UseDefaults, 448 // default values will be taken from the environment. 449 // 450 // "ca-cert-path" and "ca-private-key-path" are translated into the 451 // "ca-cert" and "ca-private-key" values. If not specified, CA details 452 // will be read from: 453 // 454 // ~/.local/share/juju/<name>-cert.pem 455 // ~/.local/share/juju/<name>-private-key.pem 456 // 457 // if $XDG_DATA_HOME is defined it will be used instead of ~/.local/share 458 // 459 // The attrs map can not be nil, otherwise a panic is raised. 460 func New(withDefaults Defaulting, attrs map[string]interface{}) (*Config, error) { 461 initSchema.Do(func() { 462 allFields = fields() 463 defaultsWhenParsing = allDefaults() 464 withDefaultsChecker = schema.FieldMap(allFields, defaultsWhenParsing) 465 noDefaultsChecker = schema.FieldMap(allFields, alwaysOptional) 466 }) 467 checker := noDefaultsChecker 468 if withDefaults { 469 checker = withDefaultsChecker 470 } else { 471 // Config may be from an older Juju. 472 // Handle the case where we are parsing a fully formed 473 // set of config attributes (NoDefaults) and a value is strictly 474 // not optional, but may have previously been either set to empty 475 // or is missing. 476 // In this case, we use the default. 477 for k := range defaultsWhenParsing { 478 v, ok := attrs[k] 479 if ok && v != "" { 480 continue 481 } 482 _, explicitlyOptional := alwaysOptional[k] 483 if !explicitlyOptional { 484 attrs[k] = defaultsWhenParsing[k] 485 } 486 } 487 } 488 489 defined, err := checker.Coerce(attrs, nil) 490 if err != nil { 491 return nil, errors.Trace(err) 492 } 493 494 c := &Config{ 495 defined: defined.(map[string]interface{}), 496 unknown: make(map[string]interface{}), 497 } 498 if err := c.setLoggingFromEnviron(); err != nil { 499 return nil, errors.Trace(err) 500 } 501 502 // no old config to compare against 503 if err := Validate(c, nil); err != nil { 504 return nil, errors.Trace(err) 505 } 506 // Copy unknown attributes onto the type-specific map. 507 for k, v := range attrs { 508 if _, ok := allFields[k]; !ok { 509 c.unknown[k] = v 510 } 511 } 512 return c, nil 513 } 514 515 const ( 516 // DefaultStatusHistoryAge is the default value for MaxStatusHistoryAge. 517 DefaultStatusHistoryAge = "336h" // 2 weeks 518 519 // DefaultStatusHistorySize is the default value for MaxStatusHistorySize. 520 DefaultStatusHistorySize = "5G" 521 522 // DefaultUpdateStatusHookInterval is the default value for 523 // UpdateStatusHookInterval 524 DefaultUpdateStatusHookInterval = "5m" 525 526 // DefaultActionResultsAge is the default for the age of the results for an 527 // action. 528 DefaultActionResultsAge = "336h" // 2 weeks 529 530 // DefaultActionResultsSize is the default size of the action results. 531 DefaultActionResultsSize = "5G" 532 533 // DefaultLxdSnapChannel is the default lxd snap channel to install on host vms. 534 DefaultLxdSnapChannel = "5.0/stable" 535 536 // DefaultSecretBackend is the default secret backend to use. 537 DefaultSecretBackend = "auto" 538 ) 539 540 var defaultConfigValues = map[string]interface{}{ 541 // Network. 542 "firewall-mode": FwInstance, 543 "disable-network-management": false, 544 IgnoreMachineAddresses: false, 545 "ssl-hostname-verification": true, 546 "proxy-ssh": false, 547 DefaultSpace: "", 548 // Why is net-bond-reconfigure-delay set to 17 seconds? 549 // 550 // The value represents the amount of time in seconds to sleep 551 // between ifdown and ifup when bridging bonded interfaces; 552 // this is a platform bug and all of this can go away when bug 553 // #1657579 (and #1594855 and #1269921) are fixed. 554 // 555 // For a long time the bridge script hardcoded a value of 3s 556 // but some setups now require an even longer period. The last 557 // reported issue was fixed with a 10s timeout, however, we're 558 // increasing that because this issue (and solution) is not 559 // very discoverable and we would like bridging to work 560 // out-of-the-box. 561 // 562 // This value can be further tweaked via: 563 // 564 // $ juju model-config net-bond-reconfigure-delay=30 565 NetBondReconfigureDelayKey: 17, 566 ContainerNetworkingMethod: "", 567 568 DefaultBaseKey: "", 569 570 ProvisionerHarvestModeKey: HarvestDestroyed.String(), 571 NumProvisionWorkersKey: 16, 572 NumContainerProvisionWorkersKey: 4, 573 ResourceTagsKey: "", 574 "logging-config": "", 575 LoggingOutputKey: "", 576 AutomaticallyRetryHooks: true, 577 "enable-os-refresh-update": true, 578 "enable-os-upgrade": true, 579 "development": false, 580 TestModeKey: false, 581 ModeKey: RequiresPromptsMode, 582 DisableTelemetryKey: false, 583 TransmitVendorMetricsKey: true, 584 UpdateStatusHookInterval: DefaultUpdateStatusHookInterval, 585 EgressSubnets: "", 586 FanConfig: "", 587 CloudInitUserDataKey: "", 588 ContainerInheritPropertiesKey: "", 589 BackupDirKey: "", 590 LXDSnapChannel: DefaultLxdSnapChannel, 591 592 CharmHubURLKey: charmhub.DefaultServerURL, 593 594 // Image and agent streams and URLs. 595 ImageStreamKey: "released", 596 ImageMetadataURLKey: "", 597 ImageMetadataDefaultsDisabledKey: false, 598 AgentStreamKey: "released", 599 AgentMetadataURLKey: "", 600 ContainerImageStreamKey: "released", 601 ContainerImageMetadataURLKey: "", 602 ContainerImageMetadataDefaultsDisabledKey: false, 603 604 // Log forward settings. 605 LogForwardEnabled: false, 606 607 // Proxy settings. 608 HTTPProxyKey: "", 609 HTTPSProxyKey: "", 610 FTPProxyKey: "", 611 NoProxyKey: "127.0.0.1,localhost,::1", 612 JujuHTTPProxyKey: "", 613 JujuHTTPSProxyKey: "", 614 JujuFTPProxyKey: "", 615 JujuNoProxyKey: "127.0.0.1,localhost,::1", 616 617 AptHTTPProxyKey: "", 618 AptHTTPSProxyKey: "", 619 AptFTPProxyKey: "", 620 AptNoProxyKey: "", 621 AptMirrorKey: "", 622 623 SnapHTTPProxyKey: "", 624 SnapHTTPSProxyKey: "", 625 SnapStoreProxyKey: "", 626 SnapStoreAssertionsKey: "", 627 SnapStoreProxyURLKey: "", 628 629 // Status history settings 630 MaxStatusHistoryAge: DefaultStatusHistoryAge, 631 MaxStatusHistorySize: DefaultStatusHistorySize, 632 MaxActionResultsAge: DefaultActionResultsAge, 633 MaxActionResultsSize: DefaultActionResultsSize, 634 635 // Secret settings. 636 SecretBackendKey: DefaultSecretBackend, 637 638 // Model firewall settings 639 SSHAllowKey: "0.0.0.0/0,::/0", 640 SAASIngressAllowKey: "0.0.0.0/0,::/0", 641 } 642 643 // defaultLoggingConfig is the default value for logging-config if it is otherwise not set. 644 // We don't use the defaultConfigValues mechanism because one way to set the logging config is 645 // via the JUJU_LOGGING_CONFIG environment variable, which needs to be taken into account before 646 // we set the default. 647 const defaultLoggingConfig = "<root>=INFO" 648 649 // ConfigDefaults returns the config default values 650 // to be used for any new model where there is no 651 // value yet defined. 652 func ConfigDefaults() map[string]interface{} { 653 defaults := make(map[string]interface{}) 654 for name, value := range defaultConfigValues { 655 if developerConfigValue(name) { 656 continue 657 } 658 defaults[name] = value 659 } 660 return defaults 661 } 662 663 func (c *Config) setLoggingFromEnviron() error { 664 loggingConfig := c.asString("logging-config") 665 // If the logging config hasn't been set, then look for the os environment 666 // variable, and failing that, get the config from loggo itself. 667 if loggingConfig == "" { 668 if environmentValue := os.Getenv(osenv.JujuLoggingConfigEnvKey); environmentValue != "" { 669 c.defined["logging-config"] = environmentValue 670 } else { 671 c.defined["logging-config"] = defaultLoggingConfig 672 } 673 } 674 return nil 675 } 676 677 // ProcessDeprecatedAttributes gathers any deprecated attributes in attrs and adds or replaces 678 // them with new name value pairs for the replacement attrs. 679 // Ths ensures that older versions of Juju which require that deprecated 680 // attribute values still be used will work as expected. 681 func ProcessDeprecatedAttributes(attrs map[string]interface{}) map[string]interface{} { 682 processedAttrs := make(map[string]interface{}, len(attrs)) 683 for k, v := range attrs { 684 processedAttrs[k] = v 685 } 686 // No deprecated attributes at the moment. 687 return processedAttrs 688 } 689 690 // CoerceForStorage transforms attributes prior to being saved in a persistent store. 691 func CoerceForStorage(attrs map[string]interface{}) map[string]interface{} { 692 coercedAttrs := make(map[string]interface{}, len(attrs)) 693 for attrName, attrValue := range attrs { 694 if attrName == ResourceTagsKey { 695 // Resource Tags are specified by the user as a string but transformed 696 // to a map when config is parsed. We want to store as a string. 697 var tagsSlice []string 698 if tags, ok := attrValue.(map[string]string); ok { 699 for resKey, resValue := range tags { 700 tagsSlice = append(tagsSlice, fmt.Sprintf("%v=%v", resKey, resValue)) 701 } 702 attrValue = strings.Join(tagsSlice, " ") 703 } 704 } 705 coercedAttrs[attrName] = attrValue 706 } 707 return coercedAttrs 708 } 709 710 // Validate ensures that config is a valid configuration. If old is not nil, 711 // it holds the previous environment configuration for consideration when 712 // validating changes. 713 func Validate(cfg, old *Config) error { 714 // Check that all other fields that have been specified are non-empty, 715 // unless they're allowed to be empty for backward compatibility, 716 for attr, val := range cfg.defined { 717 if !isEmpty(val) { 718 continue 719 } 720 if !allowEmpty(attr) { 721 return fmt.Errorf("empty %s in model configuration", attr) 722 } 723 } 724 725 modelName := cfg.asString(NameKey) 726 if modelName == "" { 727 return errors.New("empty name in model configuration") 728 } 729 if !names.IsValidModelName(modelName) { 730 return fmt.Errorf("%q is not a valid name: model names may only contain lowercase letters, digits and hyphens", modelName) 731 } 732 733 // Check that the agent version parses ok if set explicitly; otherwise leave 734 // it alone. 735 if v, ok := cfg.defined[AgentVersionKey].(string); ok { 736 if _, err := version.Parse(v); err != nil { 737 return fmt.Errorf("invalid agent version in model configuration: %q", v) 738 } 739 } 740 741 // If the logging config is set, make sure it is valid. 742 if v, ok := cfg.defined["logging-config"].(string); ok { 743 if _, err := loggo.ParseConfigString(v); err != nil { 744 return err 745 } 746 } 747 748 if lfCfg, ok := cfg.LogFwdSyslog(); ok { 749 if err := lfCfg.Validate(); err != nil { 750 return errors.Annotate(err, "invalid syslog forwarding config") 751 } 752 } 753 754 if uuid := cfg.UUID(); !utils.IsValidUUIDString(uuid) { 755 return errors.Errorf("uuid: expected UUID, got string(%q)", uuid) 756 } 757 758 // Ensure the resource tags have the expected k=v format. 759 if _, err := cfg.resourceTags(); err != nil { 760 return errors.Annotate(err, "validating resource tags") 761 } 762 763 if v, ok := cfg.defined[MaxStatusHistoryAge].(string); ok { 764 if _, err := time.ParseDuration(v); err != nil { 765 return errors.Annotate(err, "invalid max status history age in model configuration") 766 } 767 } 768 769 if v, ok := cfg.defined[MaxStatusHistorySize].(string); ok { 770 if _, err := utils.ParseSize(v); err != nil { 771 return errors.Annotate(err, "invalid max status history size in model configuration") 772 } 773 } 774 775 if v, ok := cfg.defined[MaxActionResultsAge].(string); ok { 776 if _, err := time.ParseDuration(v); err != nil { 777 return errors.Annotate(err, "invalid max action age in model configuration") 778 } 779 } 780 781 if v, ok := cfg.defined[MaxActionResultsSize].(string); ok { 782 if _, err := utils.ParseSize(v); err != nil { 783 return errors.Annotate(err, "invalid max action size in model configuration") 784 } 785 } 786 787 if v, ok := cfg.defined[UpdateStatusHookInterval].(string); ok { 788 duration, err := time.ParseDuration(v) 789 if err != nil { 790 return errors.Annotate(err, "invalid update status hook interval in model configuration") 791 } 792 if duration < 1*time.Minute { 793 return errors.Annotatef(err, "update status hook frequency %v cannot be less than 1m", duration) 794 } 795 if duration > 60*time.Minute { 796 return errors.Annotatef(err, "update status hook frequency %v cannot be greater than 60m", duration) 797 } 798 } 799 800 if v, ok := cfg.defined[EgressSubnets].(string); ok && v != "" { 801 cidrs := strings.Split(v, ",") 802 for _, cidr := range cidrs { 803 if _, _, err := net.ParseCIDR(strings.TrimSpace(cidr)); err != nil { 804 return errors.Annotatef(err, "invalid egress subnet: %v", cidr) 805 } 806 if cidr == "0.0.0.0/0" { 807 return errors.Errorf("CIDR %q not allowed", cidr) 808 } 809 } 810 } 811 812 if v, ok := cfg.defined[FanConfig].(string); ok && v != "" { 813 _, err := network.ParseFanConfig(v) 814 if err != nil { 815 return err 816 } 817 } 818 819 if v, ok := cfg.defined[ContainerNetworkingMethod].(string); ok { 820 switch v { 821 case "fan": 822 if cfg, err := cfg.FanConfig(); err != nil || cfg == nil { 823 return errors.New("container-networking-method cannot be set to 'fan' without fan-config set") 824 } 825 case "provider": // TODO(wpk) FIXME we should check that the provider supports this setting! 826 case "local": 827 case "": // We'll try to autoconfigure it 828 default: 829 return fmt.Errorf("Invalid value for container-networking-method - %v", v) 830 } 831 } 832 833 if raw, ok := cfg.defined[CloudInitUserDataKey].(string); ok && raw != "" { 834 userDataMap, err := ensureStringMaps(raw) 835 if err != nil { 836 return errors.Annotate(err, "cloudinit-userdata") 837 } 838 839 // if there packages, ensure they are strings 840 if packages, ok := userDataMap["packages"].([]interface{}); ok { 841 for _, v := range packages { 842 checker := schema.String() 843 if _, err := checker.Coerce(v, nil); err != nil { 844 return errors.Annotate(err, "cloudinit-userdata: packages must be a list of strings") 845 } 846 } 847 } 848 849 // error if users is specified 850 if _, ok := userDataMap["users"]; ok { 851 return errors.New("cloudinit-userdata: users not allowed") 852 } 853 854 // error if runcmd is specified 855 if _, ok := userDataMap["runcmd"]; ok { 856 return errors.New("cloudinit-userdata: runcmd not allowed, use preruncmd or postruncmd instead") 857 } 858 859 // error if bootcmd is specified 860 if _, ok := userDataMap["bootcmd"]; ok { 861 return errors.New("cloudinit-userdata: bootcmd not allowed") 862 } 863 } 864 865 if raw, ok := cfg.defined[ContainerInheritPropertiesKey].(string); ok && raw != "" { 866 rawProperties := strings.Split(raw, ",") 867 propertySet := set.NewStrings() 868 for _, prop := range rawProperties { 869 propertySet.Add(strings.TrimSpace(prop)) 870 } 871 whiteListSet := set.NewStrings("apt-primary", "apt-sources", "apt-security", "ca-certs") 872 diffSet := propertySet.Difference(whiteListSet) 873 874 if !diffSet.IsEmpty() { 875 return errors.Errorf("container-inherit-properties: %s not allowed", strings.Join(diffSet.SortedValues(), ", ")) 876 } 877 } 878 879 if err := cfg.validateCharmHubURL(); err != nil { 880 return errors.Trace(err) 881 } 882 883 if err := cfg.validateDefaultSpace(); err != nil { 884 return errors.Trace(err) 885 } 886 887 if err := cfg.validateDefaultBase(); err != nil { 888 return errors.Trace(err) 889 } 890 891 if err := cfg.validateMode(); err != nil { 892 return errors.Trace(err) 893 } 894 895 if err := cfg.validateCIDRs(cfg.SSHAllow(), true); err != nil { 896 return errors.Trace(err) 897 } 898 899 if err := cfg.validateCIDRs(cfg.SAASIngressAllow(), false); err != nil { 900 return errors.Trace(err) 901 } 902 903 if err := cfg.validateLoggingOutput(); err != nil { 904 return errors.Trace(err) 905 } 906 907 if err := cfg.validateNumProvisionWorkers(); err != nil { 908 return errors.Trace(err) 909 } 910 911 if err := cfg.validateNumContainerProvisionWorkers(); err != nil { 912 return errors.Trace(err) 913 } 914 915 if old != nil { 916 // Check the immutable config values. These can't change 917 for _, attr := range immutableAttributes { 918 oldv, ok := old.defined[attr] 919 if !ok { 920 continue 921 } 922 if newv := cfg.defined[attr]; newv != oldv { 923 return fmt.Errorf("cannot change %s from %#v to %#v", attr, oldv, newv) 924 } 925 } 926 if _, oldFound := old.AgentVersion(); oldFound { 927 if _, newFound := cfg.AgentVersion(); !newFound { 928 return errors.New("cannot clear agent-version") 929 } 930 } 931 if _, oldFound := old.CharmHubURL(); oldFound { 932 if _, newFound := cfg.CharmHubURL(); !newFound { 933 return errors.New("cannot clear charmhub-url") 934 } 935 } 936 // apt-mirror can't be set back to "" if it has previously been set. 937 if old.AptMirror() != "" && cfg.AptMirror() == "" { 938 return errors.New("cannot clear apt-mirror") 939 } 940 } 941 942 // The user shouldn't specify both old and new proxy values. 943 if cfg.HasLegacyProxy() && cfg.HasJujuProxy() { 944 return errors.New("cannot specify both legacy proxy values and juju proxy values") 945 } 946 947 cfg.defined = ProcessDeprecatedAttributes(cfg.defined) 948 return nil 949 } 950 951 // ensureStringMaps takes in a string and returns YAML in a map 952 // where all keys of any nested maps are strings. 953 func ensureStringMaps(in string) (map[string]interface{}, error) { 954 userDataMap := make(map[string]interface{}) 955 if err := yaml.Unmarshal([]byte(in), &userDataMap); err != nil { 956 return nil, errors.Annotate(err, "must be valid YAML") 957 } 958 out, err := utils.ConformYAML(userDataMap) 959 if err != nil { 960 return nil, err 961 } 962 return out.(map[string]interface{}), nil 963 } 964 965 func isEmpty(val interface{}) bool { 966 switch val := val.(type) { 967 case nil: 968 return true 969 case bool: 970 return false 971 case int: 972 // TODO(rog) fix this to return false when 973 // we can lose backward compatibility. 974 // https://bugs.launchpad.net/juju-core/+bug/1224492 975 return val == 0 976 case string: 977 return val == "" 978 case []interface{}: 979 return len(val) == 0 980 case []string: 981 return len(val) == 0 982 case map[string]string: 983 return len(val) == 0 984 } 985 panic(fmt.Errorf("unexpected type %T in configuration", val)) 986 } 987 988 // asString is a private helper method to keep the ugly string casting 989 // in once place. It returns the given named attribute as a string, 990 // returning "" if it isn't found. 991 func (c *Config) asString(name string) string { 992 value, _ := c.defined[name].(string) 993 return value 994 } 995 996 // mustString returns the named attribute as an string, panicking if 997 // it is not found or is empty. 998 func (c *Config) mustString(name string) string { 999 value, _ := c.defined[name].(string) 1000 if value == "" { 1001 panic(fmt.Errorf("empty value for %q found in configuration (type %T, val %v)", name, c.defined[name], c.defined[name])) 1002 } 1003 return value 1004 } 1005 1006 // Type returns the model's cloud provider type. 1007 func (c *Config) Type() string { 1008 return c.mustString(TypeKey) 1009 } 1010 1011 // Name returns the model name. 1012 func (c *Config) Name() string { 1013 return c.mustString(NameKey) 1014 } 1015 1016 // UUID returns the uuid for the model. 1017 func (c *Config) UUID() string { 1018 return c.mustString(UUIDKey) 1019 } 1020 1021 func (c *Config) validateDefaultSpace() error { 1022 if raw, ok := c.defined[DefaultSpace]; ok { 1023 if v, ok := raw.(string); ok { 1024 if v == "" { 1025 return nil 1026 } 1027 if !names.IsValidSpace(v) { 1028 return errors.NotValidf("default space name %q", raw) 1029 } 1030 } else { 1031 return errors.NotValidf("type for default space name %v", raw) 1032 } 1033 } 1034 return nil 1035 } 1036 1037 // DefaultSpace returns the name of the space for to be used 1038 // for endpoint bindings that are not explicitly set. 1039 func (c *Config) DefaultSpace() string { 1040 return c.asString(DefaultSpace) 1041 } 1042 1043 func (c *Config) validateDefaultBase() error { 1044 defaultBase, configured := c.DefaultBase() 1045 if !configured { 1046 return nil 1047 } 1048 1049 parsedBase, err := corebase.ParseBaseFromString(defaultBase) 1050 if err != nil { 1051 return errors.Annotatef(err, "invalid default base %q", defaultBase) 1052 } 1053 1054 supported, err := corebase.WorkloadBases(time.Now(), corebase.Base{}, "") 1055 if err != nil { 1056 return errors.Annotate(err, "cannot read supported bases") 1057 } 1058 logger.Tracef("supported bases %s", supported) 1059 var found bool 1060 for _, supportedBase := range supported { 1061 if parsedBase.IsCompatible(supportedBase) { 1062 found = true 1063 break 1064 } 1065 } 1066 if !found { 1067 return errors.NotSupportedf("base %q", parsedBase.DisplayString()) 1068 } 1069 return nil 1070 } 1071 1072 // DefaultBase returns the configured default base for the model, and whether 1073 // the default base was explicitly configured on the environment. 1074 func (c *Config) DefaultBase() (string, bool) { 1075 s, ok := c.defined[DefaultBaseKey] 1076 if !ok { 1077 return "", false 1078 } 1079 switch s := s.(type) { 1080 case string: 1081 return s, s != "" 1082 default: 1083 logger.Errorf("invalid default-base: %q", s) 1084 return "", false 1085 } 1086 } 1087 1088 // SecretBackend returns the secret backend name. 1089 func (c *Config) SecretBackend() string { 1090 value, _ := c.defined[SecretBackendKey].(string) 1091 return value 1092 } 1093 1094 // AuthorizedKeys returns the content for ssh's authorized_keys file. 1095 func (c *Config) AuthorizedKeys() string { 1096 value, _ := c.defined[AuthorizedKeysKey].(string) 1097 return value 1098 } 1099 1100 // ProxySSH returns a flag indicating whether SSH commands 1101 // should be proxied through the API server. 1102 func (c *Config) ProxySSH() bool { 1103 value, _ := c.defined["proxy-ssh"].(bool) 1104 return value 1105 } 1106 1107 // NetBondReconfigureDelay returns the duration in seconds that should be 1108 // passed to the bridge script when bridging bonded interfaces. 1109 func (c *Config) NetBondReconfigureDelay() int { 1110 value, _ := c.defined[NetBondReconfigureDelayKey].(int) 1111 return value 1112 } 1113 1114 // ContainerNetworkingMethod returns the method with which 1115 // containers network should be set up. 1116 func (c *Config) ContainerNetworkingMethod() string { 1117 return c.asString(ContainerNetworkingMethod) 1118 } 1119 1120 // LegacyProxySettings returns all four proxy settings; http, https, ftp, and no 1121 // proxy. These are considered legacy as using these values will cause the environment 1122 // to be updated, which has shown to not work in many cases. It is being kept to avoid 1123 // breaking environments where it is sufficient. 1124 func (c *Config) LegacyProxySettings() proxy.Settings { 1125 return proxy.Settings{ 1126 Http: c.HTTPProxy(), 1127 Https: c.HTTPSProxy(), 1128 Ftp: c.FTPProxy(), 1129 NoProxy: c.NoProxy(), 1130 } 1131 } 1132 1133 // HasLegacyProxy returns true if there is any proxy set using the old legacy proxy keys. 1134 func (c *Config) HasLegacyProxy() bool { 1135 // We exclude the no proxy value as it has default value. 1136 return c.HTTPProxy() != "" || 1137 c.HTTPSProxy() != "" || 1138 c.FTPProxy() != "" 1139 } 1140 1141 // HasJujuProxy returns true if there is any proxy set using the new juju-proxy keys. 1142 func (c *Config) HasJujuProxy() bool { 1143 // We exclude the no proxy value as it has default value. 1144 return c.JujuHTTPProxy() != "" || 1145 c.JujuHTTPSProxy() != "" || 1146 c.JujuFTPProxy() != "" 1147 } 1148 1149 // JujuProxySettings returns all four proxy settings that have been set using the 1150 // juju- prefixed proxy settings. These values determine the current best practice 1151 // for proxies. 1152 func (c *Config) JujuProxySettings() proxy.Settings { 1153 return proxy.Settings{ 1154 Http: c.JujuHTTPProxy(), 1155 Https: c.JujuHTTPSProxy(), 1156 Ftp: c.JujuFTPProxy(), 1157 NoProxy: c.JujuNoProxy(), 1158 } 1159 } 1160 1161 // HTTPProxy returns the legacy http proxy for the model. 1162 func (c *Config) HTTPProxy() string { 1163 return c.asString(HTTPProxyKey) 1164 } 1165 1166 // HTTPSProxy returns the legacy https proxy for the model. 1167 func (c *Config) HTTPSProxy() string { 1168 return c.asString(HTTPSProxyKey) 1169 } 1170 1171 // FTPProxy returns the legacy ftp proxy for the model. 1172 func (c *Config) FTPProxy() string { 1173 return c.asString(FTPProxyKey) 1174 } 1175 1176 // NoProxy returns the legacy 'no-proxy' for the model. 1177 func (c *Config) NoProxy() string { 1178 return c.asString(NoProxyKey) 1179 } 1180 1181 // JujuHTTPProxy returns the http proxy for the model. 1182 func (c *Config) JujuHTTPProxy() string { 1183 return c.asString(JujuHTTPProxyKey) 1184 } 1185 1186 // JujuHTTPSProxy returns the https proxy for the model. 1187 func (c *Config) JujuHTTPSProxy() string { 1188 return c.asString(JujuHTTPSProxyKey) 1189 } 1190 1191 // JujuFTPProxy returns the ftp proxy for the model. 1192 func (c *Config) JujuFTPProxy() string { 1193 return c.asString(JujuFTPProxyKey) 1194 } 1195 1196 // JujuNoProxy returns the 'no-proxy' for the model. 1197 // This value can contain CIDR values. 1198 func (c *Config) JujuNoProxy() string { 1199 return c.asString(JujuNoProxyKey) 1200 } 1201 1202 func (c *Config) getWithFallback(key, fallback1, fallback2 string) string { 1203 value := c.asString(key) 1204 if value == "" { 1205 value = c.asString(fallback1) 1206 } 1207 if value == "" { 1208 value = c.asString(fallback2) 1209 } 1210 return value 1211 } 1212 1213 // addSchemeIfMissing adds a scheme to a URL if it is missing 1214 func addSchemeIfMissing(defaultScheme string, url string) string { 1215 if url != "" && !strings.Contains(url, "://") { 1216 url = defaultScheme + "://" + url 1217 } 1218 return url 1219 } 1220 1221 // AptProxySettings returns all three proxy settings; http, https and ftp. 1222 func (c *Config) AptProxySettings() proxy.Settings { 1223 return proxy.Settings{ 1224 Http: c.AptHTTPProxy(), 1225 Https: c.AptHTTPSProxy(), 1226 Ftp: c.AptFTPProxy(), 1227 NoProxy: c.AptNoProxy(), 1228 } 1229 } 1230 1231 // AptHTTPProxy returns the apt http proxy for the model. 1232 // Falls back to the default http-proxy if not specified. 1233 func (c *Config) AptHTTPProxy() string { 1234 return addSchemeIfMissing("http", c.getWithFallback(AptHTTPProxyKey, JujuHTTPProxyKey, HTTPProxyKey)) 1235 } 1236 1237 // AptHTTPSProxy returns the apt https proxy for the model. 1238 // Falls back to the default https-proxy if not specified. 1239 func (c *Config) AptHTTPSProxy() string { 1240 return addSchemeIfMissing("https", c.getWithFallback(AptHTTPSProxyKey, JujuHTTPSProxyKey, HTTPSProxyKey)) 1241 } 1242 1243 // AptFTPProxy returns the apt ftp proxy for the model. 1244 // Falls back to the default ftp-proxy if not specified. 1245 func (c *Config) AptFTPProxy() string { 1246 return addSchemeIfMissing("ftp", c.getWithFallback(AptFTPProxyKey, JujuFTPProxyKey, FTPProxyKey)) 1247 } 1248 1249 // AptNoProxy returns the 'apt-no-proxy' for the model. 1250 func (c *Config) AptNoProxy() string { 1251 value := c.asString(AptNoProxyKey) 1252 if value == "" { 1253 if c.HasLegacyProxy() { 1254 value = c.asString(NoProxyKey) 1255 } else { 1256 value = c.asString(JujuNoProxyKey) 1257 } 1258 } 1259 return value 1260 } 1261 1262 // AptMirror sets the apt mirror for the model. 1263 func (c *Config) AptMirror() string { 1264 return c.asString(AptMirrorKey) 1265 } 1266 1267 // SnapProxySettings returns the two proxy settings; http, and https. 1268 func (c *Config) SnapProxySettings() proxy.Settings { 1269 return proxy.Settings{ 1270 Http: c.SnapHTTPProxy(), 1271 Https: c.SnapHTTPSProxy(), 1272 } 1273 } 1274 1275 // SnapHTTPProxy returns the snap http proxy for the model. 1276 func (c *Config) SnapHTTPProxy() string { 1277 return c.asString(SnapHTTPProxyKey) 1278 } 1279 1280 // SnapHTTPSProxy returns the snap https proxy for the model. 1281 func (c *Config) SnapHTTPSProxy() string { 1282 return c.asString(SnapHTTPSProxyKey) 1283 } 1284 1285 // SnapStoreProxy returns the snap store proxy for the model. 1286 func (c *Config) SnapStoreProxy() string { 1287 return c.asString(SnapStoreProxyKey) 1288 } 1289 1290 // SnapStoreAssertions returns the snap store assertions for the model. 1291 func (c *Config) SnapStoreAssertions() string { 1292 return c.asString(SnapStoreAssertionsKey) 1293 } 1294 1295 // SnapStoreProxyURL returns the snap store proxy URL for the model. 1296 func (c *Config) SnapStoreProxyURL() string { 1297 return c.asString(SnapStoreProxyURLKey) 1298 } 1299 1300 // LogFwdSyslog returns the syslog forwarding config. 1301 func (c *Config) LogFwdSyslog() (*syslog.RawConfig, bool) { 1302 partial := false 1303 var lfCfg syslog.RawConfig 1304 1305 if s, ok := c.defined[LogForwardEnabled]; ok { 1306 partial = true 1307 lfCfg.Enabled = s.(bool) 1308 } 1309 1310 if s, ok := c.defined[LogFwdSyslogHost]; ok && s != "" { 1311 partial = true 1312 lfCfg.Host = s.(string) 1313 } 1314 1315 if s, ok := c.defined[LogFwdSyslogCACert]; ok && s != "" { 1316 partial = true 1317 lfCfg.CACert = s.(string) 1318 } 1319 1320 if s, ok := c.defined[LogFwdSyslogClientCert]; ok && s != "" { 1321 partial = true 1322 lfCfg.ClientCert = s.(string) 1323 } 1324 1325 if s, ok := c.defined[LogFwdSyslogClientKey]; ok && s != "" { 1326 partial = true 1327 lfCfg.ClientKey = s.(string) 1328 } 1329 1330 if !partial { 1331 return nil, false 1332 } 1333 return &lfCfg, true 1334 } 1335 1336 // FirewallMode returns whether the firewall should 1337 // manage ports per machine, globally, or not at all. 1338 // (FwInstance, FwGlobal, or FwNone). 1339 func (c *Config) FirewallMode() string { 1340 return c.mustString("firewall-mode") 1341 } 1342 1343 // AgentVersion returns the proposed version number for the agent tools, 1344 // and whether it has been set. Once an environment is bootstrapped, this 1345 // must always be valid. 1346 func (c *Config) AgentVersion() (version.Number, bool) { 1347 if v, ok := c.defined[AgentVersionKey].(string); ok { 1348 n, err := version.Parse(v) 1349 if err != nil { 1350 panic(err) // We should have checked it earlier. 1351 } 1352 return n, true 1353 } 1354 return version.Zero, false 1355 } 1356 1357 // AgentMetadataURL returns the URL that locates the agent tarballs and metadata, 1358 // and whether it has been set. 1359 func (c *Config) AgentMetadataURL() (string, bool) { 1360 if url, ok := c.defined[AgentMetadataURLKey]; ok && url != "" { 1361 return url.(string), true 1362 } 1363 return "", false 1364 } 1365 1366 // ImageMetadataURL returns the URL at which the metadata used to locate image 1367 // ids is located, and whether it has been set. 1368 func (c *Config) ImageMetadataURL() (string, bool) { 1369 if url, ok := c.defined[ImageMetadataURLKey]; ok && url != "" { 1370 return url.(string), true 1371 } 1372 return "", false 1373 } 1374 1375 // ImageMetadataDefaultsDisabled returns whether or not default image metadata 1376 // sources are disabled. Useful for airgapped installations. 1377 func (c *Config) ImageMetadataDefaultsDisabled() bool { 1378 val, ok := c.defined[ImageMetadataDefaultsDisabledKey].(bool) 1379 if !ok { 1380 // defaults to false. 1381 return false 1382 } 1383 return val 1384 } 1385 1386 // ContainerImageMetadataURL returns the URL at which the metadata used to 1387 // locate container OS image ids is located, and whether it has been set. 1388 func (c *Config) ContainerImageMetadataURL() (string, bool) { 1389 if url, ok := c.defined[ContainerImageMetadataURLKey]; ok && url != "" { 1390 return url.(string), true 1391 } 1392 return "", false 1393 } 1394 1395 // ContainerImageMetadataDefaultsDisabled returns whether or not default image metadata 1396 // sources are disabled for containers. Useful for airgapped installations. 1397 func (c *Config) ContainerImageMetadataDefaultsDisabled() bool { 1398 val, ok := c.defined[ContainerImageMetadataDefaultsDisabledKey].(bool) 1399 if !ok { 1400 // defaults to false. 1401 return false 1402 } 1403 return val 1404 } 1405 1406 // Development returns whether the environment is in development mode. 1407 func (c *Config) Development() bool { 1408 value, _ := c.defined["development"].(bool) 1409 return value 1410 } 1411 1412 // EnableOSRefreshUpdate returns whether or not newly provisioned 1413 // instances should run their respective OS's update capability. 1414 func (c *Config) EnableOSRefreshUpdate() bool { 1415 val, ok := c.defined["enable-os-refresh-update"].(bool) 1416 if !ok { 1417 return true 1418 } 1419 return val 1420 } 1421 1422 // EnableOSUpgrade returns whether or not newly provisioned instances 1423 // should run their respective OS's upgrade capability. 1424 func (c *Config) EnableOSUpgrade() bool { 1425 val, ok := c.defined["enable-os-upgrade"].(bool) 1426 if !ok { 1427 return true 1428 } 1429 return val 1430 } 1431 1432 // SSLHostnameVerification returns weather the environment has requested 1433 // SSL hostname verification to be enabled. 1434 func (c *Config) SSLHostnameVerification() bool { 1435 return c.defined["ssl-hostname-verification"].(bool) 1436 } 1437 1438 // LoggingConfig returns the configuration string for the loggers. 1439 func (c *Config) LoggingConfig() string { 1440 return c.asString("logging-config") 1441 } 1442 1443 // BackupDir returns the configuration string for the temporary files 1444 // backup. 1445 func (c *Config) BackupDir() string { 1446 return c.asString(BackupDirKey) 1447 } 1448 1449 // AutomaticallyRetryHooks returns whether we should automatically retry hooks. 1450 // By default this should be true. 1451 func (c *Config) AutomaticallyRetryHooks() bool { 1452 val, ok := c.defined["automatically-retry-hooks"].(bool) 1453 if !ok { 1454 return true 1455 } 1456 return val 1457 } 1458 1459 // TransmitVendorMetrics returns whether the controller sends charm-collected metrics 1460 // in this model for anonymized aggregate analytics. By default this should be true. 1461 func (c *Config) TransmitVendorMetrics() bool { 1462 val, ok := c.defined[TransmitVendorMetricsKey].(bool) 1463 if !ok { 1464 return true 1465 } 1466 return val 1467 } 1468 1469 // ProvisionerHarvestMode reports the harvesting methodology the 1470 // provisioner should take. 1471 func (c *Config) ProvisionerHarvestMode() HarvestMode { 1472 if v, ok := c.defined[ProvisionerHarvestModeKey].(string); ok { 1473 if method, err := ParseHarvestMode(v); err != nil { 1474 // This setting should have already been validated. Don't 1475 // burden the caller with handling any errors. 1476 panic(err) 1477 } else { 1478 return method 1479 } 1480 } else { 1481 return HarvestDestroyed 1482 } 1483 } 1484 1485 // NumProvisionWorkers returns the number of provisioner workers to use. 1486 func (c *Config) NumProvisionWorkers() int { 1487 value, _ := c.defined[NumProvisionWorkersKey].(int) 1488 return value 1489 } 1490 1491 const ( 1492 MaxNumProvisionWorkers = 100 1493 MaxNumContainerProvisionWorkers = 25 1494 ) 1495 1496 // validateNumProvisionWorkers ensures the number cannot be set to 1497 // more than 100. 1498 // TODO: (hml) 26-Feb-2024 1499 // Once we can better link the controller config and the model config, 1500 // allow the max value to be set in the controller config. 1501 func (c *Config) validateNumProvisionWorkers() error { 1502 value, ok := c.defined[NumProvisionWorkersKey].(int) 1503 if ok && value > MaxNumProvisionWorkers { 1504 return errors.Errorf("%s: must be less than %d", NumProvisionWorkersKey, MaxNumProvisionWorkers) 1505 } 1506 return nil 1507 } 1508 1509 // NumContainerProvisionWorkers returns the number of container provisioner 1510 // workers to use. 1511 func (c *Config) NumContainerProvisionWorkers() int { 1512 value, _ := c.defined[NumContainerProvisionWorkersKey].(int) 1513 return value 1514 } 1515 1516 // validateNumContainerProvisionWorkers ensures the number cannot be set to 1517 // more than 25. 1518 // TODO: (hml) 26-Feb-2024 1519 // Once we can better link the controller config and the model config, 1520 // allow the max value to be set in the controller config. 1521 func (c *Config) validateNumContainerProvisionWorkers() error { 1522 value, ok := c.defined[NumContainerProvisionWorkersKey].(int) 1523 if ok && value > MaxNumContainerProvisionWorkers { 1524 return errors.Errorf("%s: must be less than %d", NumContainerProvisionWorkersKey, MaxNumContainerProvisionWorkers) 1525 } 1526 return nil 1527 } 1528 1529 // ImageStream returns the simplestreams stream 1530 // used to identify which image ids to search 1531 // when starting an instance. 1532 func (c *Config) ImageStream() string { 1533 v, _ := c.defined["image-stream"].(string) 1534 if v != "" { 1535 return v 1536 } 1537 return "released" 1538 } 1539 1540 // AgentStream returns the simplestreams stream 1541 // used to identify which tools to use when 1542 // bootstrapping or upgrading an environment. 1543 func (c *Config) AgentStream() string { 1544 v, _ := c.defined[AgentStreamKey].(string) 1545 if v != "" { 1546 return v 1547 } 1548 return "released" 1549 } 1550 1551 // ContainerImageStream returns the simplestreams stream used to identify which 1552 // image ids to search when starting a container. 1553 func (c *Config) ContainerImageStream() string { 1554 v, _ := c.defined[ContainerImageStreamKey].(string) 1555 if v != "" { 1556 return v 1557 } 1558 return "released" 1559 } 1560 1561 // CharmHubURL returns the URL to use for CharmHub API calls. 1562 func (c *Config) CharmHubURL() (string, bool) { 1563 if v, ok := c.defined[CharmHubURLKey].(string); ok && v != "" { 1564 return v, true 1565 } 1566 return charmhub.DefaultServerURL, false 1567 } 1568 1569 func (c *Config) validateCharmHubURL() error { 1570 if v, ok := c.defined[CharmHubURLKey].(string); ok { 1571 if v == "" { 1572 return errors.NotValidf("charm-hub url") 1573 } 1574 if _, err := url.ParseRequestURI(v); err != nil { 1575 return errors.NotValidf("charm-hub url %q", v) 1576 } 1577 } 1578 return nil 1579 } 1580 1581 const ( 1582 // RequiresPromptsMode is used to tell clients interacting with 1583 // model that confirmation prompts are required when removing 1584 // potentially important resources 1585 RequiresPromptsMode = "requires-prompts" 1586 1587 // StrictMode is currently unused 1588 // TODO(jack-w-shaw) remove this mode 1589 StrictMode = "strict" 1590 ) 1591 1592 var allModes = set.NewStrings(RequiresPromptsMode, StrictMode) 1593 1594 // Mode returns a set of mode types for the configuration. 1595 // Only one option exists at the moment ('requires-prompts') 1596 func (c *Config) Mode() (set.Strings, bool) { 1597 modes, ok := c.defined[ModeKey] 1598 if !ok { 1599 return set.NewStrings(), false 1600 } 1601 if m, ok := modes.(string); ok { 1602 s := set.NewStrings() 1603 for _, v := range strings.Split(strings.TrimSpace(m), ",") { 1604 if v == "" { 1605 continue 1606 } 1607 s.Add(strings.TrimSpace(v)) 1608 } 1609 if s.Size() > 0 { 1610 return s, true 1611 } 1612 } 1613 1614 return set.NewStrings(), false 1615 } 1616 1617 func (c *Config) validateMode() error { 1618 modes, _ := c.Mode() 1619 difference := modes.Difference(allModes) 1620 if !difference.IsEmpty() { 1621 return errors.NotValidf("mode(s) %q", strings.Join(difference.SortedValues(), ", ")) 1622 } 1623 return nil 1624 } 1625 1626 // SSHAllow returns a slice of CIDRs from which machines in 1627 // this model will accept connections to the SSH service 1628 func (c *Config) SSHAllow() []string { 1629 allowList, ok := c.defined[SSHAllowKey].(string) 1630 if !ok { 1631 return []string{"0.0.0.0/0", "::/0"} 1632 } 1633 if allowList == "" { 1634 return []string{} 1635 } 1636 return strings.Split(allowList, ",") 1637 } 1638 1639 // SAASIngressAllow returns a slice of CIDRs specifying what 1640 // ingress can be applied to offers in this model 1641 func (c *Config) SAASIngressAllow() []string { 1642 allowList, ok := c.defined[SAASIngressAllowKey].(string) 1643 if !ok { 1644 return []string{"0.0.0.0/0"} 1645 } 1646 if allowList == "" { 1647 return []string{} 1648 } 1649 return strings.Split(allowList, ",") 1650 } 1651 1652 func (c *Config) validateCIDRs(cidrs []string, allowEmpty bool) error { 1653 if len(cidrs) == 0 && !allowEmpty { 1654 return errors.NotValidf("empty cidrs") 1655 } 1656 for _, cidr := range cidrs { 1657 if _, _, err := net.ParseCIDR(cidr); err != nil { 1658 return errors.NotValidf("cidr %q", cidr) 1659 } 1660 } 1661 return nil 1662 } 1663 1664 // LoggingOutput is a for determining the destination of output for 1665 // logging. 1666 func (c *Config) LoggingOutput() ([]string, bool) { 1667 outputs, ok := c.defined[LoggingOutputKey] 1668 if !ok { 1669 return []string{}, false 1670 } 1671 if m, ok := outputs.(string); ok { 1672 s := set.NewStrings() 1673 for _, v := range strings.Split(strings.TrimSpace(m), ",") { 1674 if v == "" { 1675 continue 1676 } 1677 s.Add(strings.TrimSpace(v)) 1678 } 1679 if s.Size() > 0 { 1680 return s.SortedValues(), true 1681 } 1682 } 1683 return []string{}, false 1684 } 1685 1686 func (c *Config) validateLoggingOutput() error { 1687 outputs, _ := c.LoggingOutput() 1688 for _, output := range outputs { 1689 switch strings.TrimSpace(output) { 1690 case corelogger.DatabaseName, corelogger.SyslogName: 1691 default: 1692 return errors.NotValidf("logging-output %q", output) 1693 } 1694 } 1695 return nil 1696 } 1697 1698 // DisableNetworkManagement reports whether Juju is allowed to 1699 // configure and manage networking inside the environment. 1700 func (c *Config) DisableNetworkManagement() (bool, bool) { 1701 v, ok := c.defined["disable-network-management"].(bool) 1702 return v, ok 1703 } 1704 1705 // IgnoreMachineAddresses reports whether Juju will discover 1706 // and store machine addresses on startup. 1707 func (c *Config) IgnoreMachineAddresses() (bool, bool) { 1708 v, ok := c.defined[IgnoreMachineAddresses].(bool) 1709 return v, ok 1710 } 1711 1712 // StorageDefaultBlockSource returns the default block storage 1713 // source for the model. 1714 func (c *Config) StorageDefaultBlockSource() (string, bool) { 1715 bs := c.asString(StorageDefaultBlockSourceKey) 1716 return bs, bs != "" 1717 } 1718 1719 // StorageDefaultFilesystemSource returns the default filesystem 1720 // storage source for the model. 1721 func (c *Config) StorageDefaultFilesystemSource() (string, bool) { 1722 bs := c.asString(StorageDefaultFilesystemSourceKey) 1723 return bs, bs != "" 1724 } 1725 1726 // ResourceTags returns a set of tags to set on environment resources 1727 // that Juju creates and manages, if the provider supports them. These 1728 // tags have no special meaning to Juju, but may be used for existing 1729 // chargeback accounting schemes or other identification purposes. 1730 func (c *Config) ResourceTags() (map[string]string, bool) { 1731 tags, err := c.resourceTags() 1732 if err != nil { 1733 panic(err) // should be prevented by Validate 1734 } 1735 return tags, tags != nil 1736 } 1737 1738 func (c *Config) resourceTags() (map[string]string, error) { 1739 v, ok := c.defined[ResourceTagsKey].(map[string]string) 1740 if !ok { 1741 return nil, nil 1742 } 1743 for k := range v { 1744 if strings.HasPrefix(k, tags.JujuTagPrefix) { 1745 return nil, errors.Errorf("tag %q uses reserved prefix %q", k, tags.JujuTagPrefix) 1746 } 1747 } 1748 return v, nil 1749 } 1750 1751 // MaxStatusHistoryAge is the maximum age of status history entries 1752 // before being pruned. 1753 func (c *Config) MaxStatusHistoryAge() time.Duration { 1754 // Value has already been validated. 1755 val, _ := time.ParseDuration(c.mustString(MaxStatusHistoryAge)) 1756 return val 1757 } 1758 1759 // MaxStatusHistorySizeMB is the maximum size in MiB which the status history 1760 // collection can grow to before being pruned. 1761 func (c *Config) MaxStatusHistorySizeMB() uint { 1762 // Value has already been validated. 1763 val, _ := utils.ParseSize(c.mustString(MaxStatusHistorySize)) 1764 return uint(val) 1765 } 1766 1767 func (c *Config) MaxActionResultsAge() time.Duration { 1768 // Value has already been validated. 1769 val, _ := time.ParseDuration(c.mustString(MaxActionResultsAge)) 1770 return val 1771 } 1772 1773 func (c *Config) MaxActionResultsSizeMB() uint { 1774 // Value has already been validated. 1775 val, _ := utils.ParseSize(c.mustString(MaxActionResultsSize)) 1776 return uint(val) 1777 } 1778 1779 // UpdateStatusHookInterval is how often to run the charm 1780 // update-status hook. 1781 func (c *Config) UpdateStatusHookInterval() time.Duration { 1782 // Value has already been validated. 1783 val, _ := time.ParseDuration(c.asString(UpdateStatusHookInterval)) 1784 return val 1785 } 1786 1787 // EgressSubnets are the source addresses from which traffic from this model 1788 // originates if the model is deployed such that NAT or similar is in use. 1789 func (c *Config) EgressSubnets() []string { 1790 raw := c.asString(EgressSubnets) 1791 if raw == "" { 1792 return []string{} 1793 } 1794 // Value has already been validated. 1795 rawAddr := strings.Split(raw, ",") 1796 result := make([]string, len(rawAddr)) 1797 for i, addr := range rawAddr { 1798 result[i] = strings.TrimSpace(addr) 1799 } 1800 return result 1801 } 1802 1803 // FanConfig is the configuration of FAN network running in the model. 1804 func (c *Config) FanConfig() (network.FanConfig, error) { 1805 // At this point we are sure that the line is valid. 1806 return network.ParseFanConfig(c.asString(FanConfig)) 1807 } 1808 1809 // CloudInitUserData returns a copy of the raw user data attributes 1810 // that were specified by the user. 1811 func (c *Config) CloudInitUserData() map[string]interface{} { 1812 raw := c.asString(CloudInitUserDataKey) 1813 if raw == "" { 1814 return nil 1815 } 1816 // The raw data has already passed Validate() 1817 conformingUserDataMap, _ := ensureStringMaps(raw) 1818 return conformingUserDataMap 1819 } 1820 1821 // ContainerInheritProperties returns a copy of the raw user data keys 1822 // that were specified by the user. 1823 func (c *Config) ContainerInheritProperties() string { 1824 return c.asString(ContainerInheritPropertiesKey) 1825 } 1826 1827 // LXDSnapChannel returns the channel to be used when installing LXD from a snap. 1828 func (c *Config) LXDSnapChannel() string { 1829 return c.asString(LXDSnapChannel) 1830 } 1831 1832 // Telemetry returns whether telemetry is enabled for the model. 1833 func (c *Config) Telemetry() bool { 1834 value, _ := c.defined[DisableTelemetryKey].(bool) 1835 return !value 1836 } 1837 1838 // UnknownAttrs returns a copy of the raw configuration attributes 1839 // that are supposedly specific to the environment type. They could 1840 // also be wrong attributes, though. Only the specific environment 1841 // implementation can tell. 1842 func (c *Config) UnknownAttrs() map[string]interface{} { 1843 newAttrs := make(map[string]interface{}) 1844 for k, v := range c.unknown { 1845 newAttrs[k] = v 1846 } 1847 return newAttrs 1848 } 1849 1850 // AllAttrs returns a copy of the raw configuration attributes. 1851 func (c *Config) AllAttrs() map[string]interface{} { 1852 allAttrs := c.UnknownAttrs() 1853 for k, v := range c.defined { 1854 allAttrs[k] = v 1855 } 1856 return allAttrs 1857 } 1858 1859 // Remove returns a new configuration that has the attributes of c minus attrs. 1860 func (c *Config) Remove(attrs []string) (*Config, error) { 1861 defined := c.AllAttrs() 1862 for _, k := range attrs { 1863 delete(defined, k) 1864 } 1865 return New(NoDefaults, defined) 1866 } 1867 1868 // Apply returns a new configuration that has the attributes of c plus attrs. 1869 func (c *Config) Apply(attrs map[string]interface{}) (*Config, error) { 1870 defined := c.AllAttrs() 1871 for k, v := range attrs { 1872 defined[k] = v 1873 } 1874 return New(NoDefaults, defined) 1875 } 1876 1877 // fields holds the validation schema fields derived from configSchema. 1878 var fields = func() schema.Fields { 1879 combinedSchema, err := Schema(nil) 1880 if err != nil { 1881 panic(err) 1882 } 1883 fs, _, err := combinedSchema.ValidationSchema() 1884 if err != nil { 1885 panic(err) 1886 } 1887 return fs 1888 } 1889 1890 // alwaysOptional holds configuration defaults for attributes that may 1891 // be unspecified even after a configuration has been created with all 1892 // defaults filled out. 1893 // 1894 // This table is not definitive: it specifies those attributes which are 1895 // optional when the config goes through its initial schema coercion, 1896 // but some fields listed as optional here are actually mandatory 1897 // with NoDefaults and are checked at the later Validate stage. 1898 var alwaysOptional = schema.Defaults{ 1899 AgentVersionKey: schema.Omit, 1900 AuthorizedKeysKey: schema.Omit, 1901 ExtraInfoKey: schema.Omit, 1902 1903 LogForwardEnabled: schema.Omit, 1904 LogFwdSyslogHost: schema.Omit, 1905 LogFwdSyslogCACert: schema.Omit, 1906 LogFwdSyslogClientCert: schema.Omit, 1907 LogFwdSyslogClientKey: schema.Omit, 1908 LoggingOutputKey: schema.Omit, 1909 1910 // Storage related config. 1911 // Environ providers will specify their own defaults. 1912 StorageDefaultBlockSourceKey: schema.Omit, 1913 StorageDefaultFilesystemSourceKey: schema.Omit, 1914 1915 "firewall-mode": schema.Omit, 1916 SSHAllowKey: schema.Omit, 1917 SAASIngressAllowKey: schema.Omit, 1918 1919 "logging-config": schema.Omit, 1920 ProvisionerHarvestModeKey: schema.Omit, 1921 NumProvisionWorkersKey: schema.Omit, 1922 NumContainerProvisionWorkersKey: schema.Omit, 1923 HTTPProxyKey: schema.Omit, 1924 HTTPSProxyKey: schema.Omit, 1925 FTPProxyKey: schema.Omit, 1926 NoProxyKey: schema.Omit, 1927 JujuHTTPProxyKey: schema.Omit, 1928 JujuHTTPSProxyKey: schema.Omit, 1929 JujuFTPProxyKey: schema.Omit, 1930 JujuNoProxyKey: schema.Omit, 1931 AptHTTPProxyKey: schema.Omit, 1932 AptHTTPSProxyKey: schema.Omit, 1933 AptFTPProxyKey: schema.Omit, 1934 AptNoProxyKey: schema.Omit, 1935 SnapHTTPProxyKey: schema.Omit, 1936 SnapHTTPSProxyKey: schema.Omit, 1937 SnapStoreProxyKey: schema.Omit, 1938 SnapStoreAssertionsKey: schema.Omit, 1939 SnapStoreProxyURLKey: schema.Omit, 1940 AptMirrorKey: schema.Omit, 1941 AgentStreamKey: schema.Omit, 1942 ResourceTagsKey: schema.Omit, 1943 "cloudimg-base-url": schema.Omit, 1944 "enable-os-refresh-update": schema.Omit, 1945 "enable-os-upgrade": schema.Omit, 1946 DefaultBaseKey: schema.Omit, 1947 "development": schema.Omit, 1948 "ssl-hostname-verification": schema.Omit, 1949 "proxy-ssh": schema.Omit, 1950 "disable-network-management": schema.Omit, 1951 IgnoreMachineAddresses: schema.Omit, 1952 AutomaticallyRetryHooks: schema.Omit, 1953 TestModeKey: schema.Omit, 1954 DisableTelemetryKey: schema.Omit, 1955 ModeKey: schema.Omit, 1956 TransmitVendorMetricsKey: schema.Omit, 1957 NetBondReconfigureDelayKey: schema.Omit, 1958 ContainerNetworkingMethod: schema.Omit, 1959 MaxStatusHistoryAge: schema.Omit, 1960 MaxStatusHistorySize: schema.Omit, 1961 MaxActionResultsAge: schema.Omit, 1962 MaxActionResultsSize: schema.Omit, 1963 UpdateStatusHookInterval: schema.Omit, 1964 EgressSubnets: schema.Omit, 1965 FanConfig: schema.Omit, 1966 CloudInitUserDataKey: schema.Omit, 1967 ContainerInheritPropertiesKey: schema.Omit, 1968 BackupDirKey: schema.Omit, 1969 DefaultSpace: schema.Omit, 1970 LXDSnapChannel: schema.Omit, 1971 CharmHubURLKey: schema.Omit, 1972 1973 AgentMetadataURLKey: schema.Omit, 1974 ImageStreamKey: schema.Omit, 1975 ImageMetadataURLKey: schema.Omit, 1976 ImageMetadataDefaultsDisabledKey: schema.Omit, 1977 ContainerImageStreamKey: schema.Omit, 1978 ContainerImageMetadataURLKey: schema.Omit, 1979 ContainerImageMetadataDefaultsDisabledKey: schema.Omit, 1980 } 1981 1982 func allowEmpty(attr string) bool { 1983 return alwaysOptional[attr] == "" || alwaysOptional[attr] == schema.Omit 1984 } 1985 1986 // allDefaults returns a schema.Defaults that contains 1987 // defaults to be used when creating a new config with 1988 // UseDefaults. 1989 func allDefaults() schema.Defaults { 1990 d := schema.Defaults{} 1991 configDefaults := ConfigDefaults() 1992 for attr, val := range configDefaults { 1993 d[attr] = val 1994 } 1995 for attr, val := range alwaysOptional { 1996 if developerConfigValue(attr) { 1997 continue 1998 } 1999 if _, ok := d[attr]; !ok { 2000 d[attr] = val 2001 } 2002 } 2003 return d 2004 } 2005 2006 // immutableAttributes holds those attributes 2007 // which are not allowed to change in the lifetime 2008 // of an environment. 2009 var immutableAttributes = []string{ 2010 NameKey, 2011 TypeKey, 2012 UUIDKey, 2013 "firewall-mode", 2014 CharmHubURLKey, 2015 } 2016 2017 var ( 2018 initSchema sync.Once 2019 allFields schema.Fields 2020 defaultsWhenParsing schema.Defaults 2021 withDefaultsChecker schema.Checker 2022 noDefaultsChecker schema.Checker 2023 ) 2024 2025 // ValidateUnknownAttrs checks the unknown attributes of the config against 2026 // the supplied fields and defaults, and returns an error if any fails to 2027 // validate. Unknown fields are warned about, but preserved, on the basis 2028 // that they are reasonably likely to have been written by or for a version 2029 // of juju that does recognise the fields, but that their presence is still 2030 // anomalous to some degree and should be flagged (and that there is thereby 2031 // a mechanism for observing fields that really are typos etc). 2032 func (c *Config) ValidateUnknownAttrs(extrafields schema.Fields, defaults schema.Defaults) (map[string]interface{}, error) { 2033 attrs := c.UnknownAttrs() 2034 checker := schema.FieldMap(extrafields, defaults) 2035 coerced, err := checker.Coerce(attrs, nil) 2036 if err != nil { 2037 logger.Debugf("coercion failed attributes: %#v, checker: %#v, %v", attrs, checker, err) 2038 return nil, err 2039 } 2040 result := coerced.(map[string]interface{}) 2041 for name, value := range attrs { 2042 if extrafields[name] == nil { 2043 // We know this name isn't in the global fields, or it wouldn't be 2044 // an UnknownAttr, it also appears to not be in the extra fields 2045 // that are provider specific. Check to see if an alternative 2046 // spelling is in either the extra fields or the core fields. 2047 if val, isString := value.(string); isString && val != "" { 2048 // only warn about attributes with non-empty string values 2049 altName := strings.Replace(name, "_", "-", -1) 2050 if extrafields[altName] != nil || allFields[altName] != nil { 2051 logger.Warningf("unknown config field %q, did you mean %q?", name, altName) 2052 } else { 2053 logger.Warningf("unknown config field %q", name) 2054 } 2055 } 2056 result[name] = value 2057 // The only allowed types for unknown attributes are string, int, 2058 // float, bool and []interface{} (which is really []string) 2059 switch t := value.(type) { 2060 case string: 2061 continue 2062 case int: 2063 continue 2064 case bool: 2065 continue 2066 case float32: 2067 continue 2068 case float64: 2069 continue 2070 case []interface{}: 2071 for _, val := range t { 2072 if _, ok := val.(string); !ok { 2073 return nil, errors.Errorf("%s: unknown type (%v)", name, value) 2074 } 2075 } 2076 continue 2077 default: 2078 return nil, errors.Errorf("%s: unknown type (%q)", name, value) 2079 } 2080 } 2081 } 2082 return result, nil 2083 } 2084 2085 func addIfNotEmpty(settings map[string]interface{}, key, value string) { 2086 if value != "" { 2087 settings[key] = value 2088 } 2089 } 2090 2091 // ProxyConfigMap returns a map suitable to be applied to a Config to update 2092 // proxy settings. 2093 func ProxyConfigMap(proxySettings proxy.Settings) map[string]interface{} { 2094 settings := make(map[string]interface{}) 2095 addIfNotEmpty(settings, HTTPProxyKey, proxySettings.Http) 2096 addIfNotEmpty(settings, HTTPSProxyKey, proxySettings.Https) 2097 addIfNotEmpty(settings, FTPProxyKey, proxySettings.Ftp) 2098 addIfNotEmpty(settings, NoProxyKey, proxySettings.NoProxy) 2099 return settings 2100 } 2101 2102 // AptProxyConfigMap returns a map suitable to be applied to a Config to update 2103 // proxy settings. 2104 func AptProxyConfigMap(proxySettings proxy.Settings) map[string]interface{} { 2105 settings := make(map[string]interface{}) 2106 addIfNotEmpty(settings, AptHTTPProxyKey, proxySettings.Http) 2107 addIfNotEmpty(settings, AptHTTPSProxyKey, proxySettings.Https) 2108 addIfNotEmpty(settings, AptFTPProxyKey, proxySettings.Ftp) 2109 addIfNotEmpty(settings, AptNoProxyKey, proxySettings.NoProxy) 2110 return settings 2111 } 2112 2113 func developerConfigValue(name string) bool { 2114 if !featureflag.Enabled(feature.DeveloperMode) { 2115 switch name { 2116 // Add developer-mode keys here. 2117 } 2118 } 2119 return false 2120 } 2121 2122 // Schema returns a configuration schema that includes both 2123 // the given extra fields and all the fields defined in this package. 2124 // It returns an error if extra defines any fields defined in this 2125 // package. 2126 func Schema(extra environschema.Fields) (environschema.Fields, error) { 2127 fields := make(environschema.Fields) 2128 for name, field := range configSchema { 2129 if developerConfigValue(name) { 2130 continue 2131 } 2132 if controller.ControllerOnlyAttribute(name) { 2133 return nil, errors.Errorf("config field %q clashes with controller config", name) 2134 } 2135 fields[name] = field 2136 } 2137 for name, field := range extra { 2138 if controller.ControllerOnlyAttribute(name) { 2139 return nil, errors.Errorf("config field %q clashes with controller config", name) 2140 } 2141 if _, ok := fields[name]; ok { 2142 return nil, errors.Errorf("config field %q clashes with global config", name) 2143 } 2144 fields[name] = field 2145 } 2146 return fields, nil 2147 } 2148 2149 // configSchema holds information on all the fields defined by 2150 // the config package. 2151 var configSchema = environschema.Fields{ 2152 AgentMetadataURLKey: { 2153 Description: "URL of private stream", 2154 Type: environschema.Tstring, 2155 Group: environschema.EnvironGroup, 2156 }, 2157 AgentStreamKey: { 2158 Description: `Version of Juju to use for deploy/upgrades.`, 2159 Type: environschema.Tstring, 2160 Group: environschema.EnvironGroup, 2161 }, 2162 AgentVersionKey: { 2163 Description: "The desired Juju agent version to use", 2164 Type: environschema.Tstring, 2165 Group: environschema.JujuGroup, 2166 Immutable: true, 2167 }, 2168 AptFTPProxyKey: { 2169 // TODO document acceptable format 2170 Description: "The APT FTP proxy for the model", 2171 Type: environschema.Tstring, 2172 Group: environschema.EnvironGroup, 2173 }, 2174 AptHTTPProxyKey: { 2175 // TODO document acceptable format 2176 Description: "The APT HTTP proxy for the model", 2177 Type: environschema.Tstring, 2178 Group: environschema.EnvironGroup, 2179 }, 2180 AptHTTPSProxyKey: { 2181 // TODO document acceptable format 2182 Description: "The APT HTTPS proxy for the model", 2183 Type: environschema.Tstring, 2184 Group: environschema.EnvironGroup, 2185 }, 2186 AptNoProxyKey: { 2187 Description: "List of domain addresses not to be proxied for APT (comma-separated)", 2188 Type: environschema.Tstring, 2189 Group: environschema.EnvironGroup, 2190 }, 2191 AptMirrorKey: { 2192 // TODO document acceptable format 2193 Description: "The APT mirror for the model", 2194 Type: environschema.Tstring, 2195 Group: environschema.EnvironGroup, 2196 }, 2197 AuthorizedKeysKey: { 2198 Description: "Any authorized SSH public keys for the model, as found in a ~/.ssh/authorized_keys file", 2199 Type: environschema.Tstring, 2200 Group: environschema.EnvironGroup, 2201 }, 2202 DefaultBaseKey: { 2203 Description: "The default base image to use for deploying charms, will act like --base when deploying charms", 2204 Type: environschema.Tstring, 2205 Group: environschema.EnvironGroup, 2206 }, 2207 // TODO (jack-w-shaw) integrate this into mode 2208 "development": { 2209 Description: "Whether the model is in development mode", 2210 Type: environschema.Tbool, 2211 Group: environschema.EnvironGroup, 2212 }, 2213 "disable-network-management": { 2214 Description: "Whether the provider should control networks (on MAAS models, set to true for MAAS to control networks", 2215 Type: environschema.Tbool, 2216 Group: environschema.EnvironGroup, 2217 }, 2218 IgnoreMachineAddresses: { 2219 Description: "Whether the machine worker should discover machine addresses on startup", 2220 Type: environschema.Tbool, 2221 Group: environschema.EnvironGroup, 2222 }, 2223 "enable-os-refresh-update": { 2224 Description: `Whether newly provisioned instances should run their respective OS's update capability.`, 2225 Type: environschema.Tbool, 2226 Group: environschema.EnvironGroup, 2227 }, 2228 "enable-os-upgrade": { 2229 Description: `Whether newly provisioned instances should run their respective OS's upgrade capability.`, 2230 Type: environschema.Tbool, 2231 Group: environschema.EnvironGroup, 2232 }, 2233 ExtraInfoKey: { 2234 Description: "Arbitrary user specified string data that is stored against the model.", 2235 Type: environschema.Tstring, 2236 Group: environschema.EnvironGroup, 2237 }, 2238 "firewall-mode": { 2239 Description: `The mode to use for network firewalling. 2240 2241 'instance' requests the use of an individual firewall per instance. 2242 2243 'global' uses a single firewall for all instances (access 2244 for a network port is enabled to one instance if any instance requires 2245 that port). 2246 2247 'none' requests that no firewalling should be performed 2248 inside the model. It's useful for clouds without support for either 2249 global or per instance security groups.`, 2250 Type: environschema.Tstring, 2251 Values: []interface{}{FwInstance, FwGlobal, FwNone}, 2252 Immutable: true, 2253 Group: environschema.EnvironGroup, 2254 }, 2255 FTPProxyKey: { 2256 Description: "The FTP proxy value to configure on instances, in the FTP_PROXY environment variable", 2257 Type: environschema.Tstring, 2258 Group: environschema.EnvironGroup, 2259 }, 2260 HTTPProxyKey: { 2261 Description: "The HTTP proxy value to configure on instances, in the HTTP_PROXY environment variable", 2262 Type: environschema.Tstring, 2263 Group: environschema.EnvironGroup, 2264 }, 2265 HTTPSProxyKey: { 2266 Description: "The HTTPS proxy value to configure on instances, in the HTTPS_PROXY environment variable", 2267 Type: environschema.Tstring, 2268 Group: environschema.EnvironGroup, 2269 }, 2270 NoProxyKey: { 2271 Description: "List of domain addresses not to be proxied (comma-separated)", 2272 Type: environschema.Tstring, 2273 Group: environschema.EnvironGroup, 2274 }, 2275 JujuFTPProxyKey: { 2276 Description: "The FTP proxy value to pass to charms in the JUJU_CHARM_FTP_PROXY environment variable", 2277 Type: environschema.Tstring, 2278 Group: environschema.EnvironGroup, 2279 }, 2280 JujuHTTPProxyKey: { 2281 Description: "The HTTP proxy value to pass to charms in the JUJU_CHARM_HTTP_PROXY environment variable", 2282 Type: environschema.Tstring, 2283 Group: environschema.EnvironGroup, 2284 }, 2285 JujuHTTPSProxyKey: { 2286 Description: "The HTTPS proxy value to pass to charms in the JUJU_CHARM_HTTPS_PROXY environment variable", 2287 Type: environschema.Tstring, 2288 Group: environschema.EnvironGroup, 2289 }, 2290 JujuNoProxyKey: { 2291 Description: "List of domain addresses not to be proxied (comma-separated), may contain CIDRs. Passed to charms in the JUJU_CHARM_NO_PROXY environment variable", 2292 Type: environschema.Tstring, 2293 Group: environschema.EnvironGroup, 2294 }, 2295 SnapHTTPProxyKey: { 2296 Description: "The HTTP proxy value for installing snaps", 2297 Type: environschema.Tstring, 2298 Group: environschema.EnvironGroup, 2299 }, 2300 SnapHTTPSProxyKey: { 2301 Description: "The HTTPS proxy value for installing snaps", 2302 Type: environschema.Tstring, 2303 Group: environschema.EnvironGroup, 2304 }, 2305 SnapStoreProxyKey: { 2306 Description: "The snap store proxy for installing snaps", 2307 Type: environschema.Tstring, 2308 Group: environschema.EnvironGroup, 2309 }, 2310 SnapStoreAssertionsKey: { 2311 Description: "The assertions for the defined snap store proxy", 2312 Type: environschema.Tstring, 2313 Group: environschema.EnvironGroup, 2314 }, 2315 SnapStoreProxyURLKey: { 2316 Description: "The URL for the defined snap store proxy", 2317 Type: environschema.Tstring, 2318 Group: environschema.EnvironGroup, 2319 }, 2320 ImageMetadataURLKey: { 2321 Description: "The URL at which the metadata used to locate OS image ids is located", 2322 Type: environschema.Tstring, 2323 Group: environschema.EnvironGroup, 2324 }, 2325 ImageStreamKey: { 2326 Description: `The simplestreams stream used to identify which image ids to search when starting an instance.`, 2327 Type: environschema.Tstring, 2328 Group: environschema.EnvironGroup, 2329 }, 2330 ImageMetadataDefaultsDisabledKey: { 2331 Description: `Whether default simplestreams sources are used for image metadata.`, 2332 Type: environschema.Tbool, 2333 Group: environschema.EnvironGroup, 2334 }, 2335 ContainerImageMetadataURLKey: { 2336 Description: "The URL at which the metadata used to locate container OS image ids is located", 2337 Type: environschema.Tstring, 2338 Group: environschema.EnvironGroup, 2339 }, 2340 ContainerImageStreamKey: { 2341 Description: `The simplestreams stream used to identify which image ids to search when starting a container.`, 2342 Type: environschema.Tstring, 2343 Group: environschema.EnvironGroup, 2344 }, 2345 ContainerImageMetadataDefaultsDisabledKey: { 2346 Description: `Whether default simplestreams sources are used for image metadata with containers.`, 2347 Type: environschema.Tbool, 2348 Group: environschema.EnvironGroup, 2349 }, 2350 "logging-config": { 2351 Description: `The configuration string to use when configuring Juju agent logging (see http://godoc.org/github.com/juju/loggo#ParseConfigurationString for details)`, 2352 Type: environschema.Tstring, 2353 Group: environschema.EnvironGroup, 2354 }, 2355 NameKey: { 2356 Description: "The name of the current model", 2357 Type: environschema.Tstring, 2358 Mandatory: true, 2359 Immutable: true, 2360 Group: environschema.EnvironGroup, 2361 }, 2362 ProvisionerHarvestModeKey: { 2363 // default: destroyed, but also depends on current setting of ProvisionerSafeModeKey 2364 Description: "What to do with unknown machines (default destroyed)", 2365 Type: environschema.Tstring, 2366 Values: []interface{}{"all", "none", "unknown", "destroyed"}, 2367 Group: environschema.EnvironGroup, 2368 }, 2369 NumProvisionWorkersKey: { 2370 Description: "The number of provisioning workers to use per model", 2371 Type: environschema.Tint, 2372 Group: environschema.EnvironGroup, 2373 }, 2374 NumContainerProvisionWorkersKey: { 2375 Description: "The number of container provisioning workers to use per machine", 2376 Type: environschema.Tint, 2377 Group: environschema.EnvironGroup, 2378 }, 2379 "proxy-ssh": { 2380 // default: true 2381 Description: `Whether SSH commands should be proxied through the API server`, 2382 Type: environschema.Tbool, 2383 Group: environschema.EnvironGroup, 2384 }, 2385 ResourceTagsKey: { 2386 Description: "resource tags", 2387 Type: environschema.Tattrs, 2388 Group: environschema.EnvironGroup, 2389 }, 2390 LogForwardEnabled: { 2391 Description: `Whether syslog forwarding is enabled.`, 2392 Type: environschema.Tbool, 2393 Group: environschema.EnvironGroup, 2394 }, 2395 LogFwdSyslogHost: { 2396 Description: `The hostname:port of the syslog server.`, 2397 Type: environschema.Tstring, 2398 Group: environschema.EnvironGroup, 2399 }, 2400 LogFwdSyslogCACert: { 2401 Description: `The certificate of the CA that signed the syslog server certificate, in PEM format.`, 2402 Type: environschema.Tstring, 2403 Group: environschema.EnvironGroup, 2404 }, 2405 LogFwdSyslogClientCert: { 2406 Description: `The syslog client certificate in PEM format.`, 2407 Type: environschema.Tstring, 2408 Group: environschema.EnvironGroup, 2409 }, 2410 LogFwdSyslogClientKey: { 2411 Description: `The syslog client key in PEM format.`, 2412 Type: environschema.Tstring, 2413 Group: environschema.EnvironGroup, 2414 }, 2415 "ssl-hostname-verification": { 2416 Description: "Whether SSL hostname verification is enabled (default true)", 2417 Type: environschema.Tbool, 2418 Group: environschema.EnvironGroup, 2419 }, 2420 StorageDefaultBlockSourceKey: { 2421 Description: "The default block storage source for the model", 2422 Type: environschema.Tstring, 2423 Group: environschema.EnvironGroup, 2424 }, 2425 StorageDefaultFilesystemSourceKey: { 2426 Description: "The default filesystem storage source for the model", 2427 Type: environschema.Tstring, 2428 Group: environschema.EnvironGroup, 2429 }, 2430 TestModeKey: { 2431 Description: `Whether the model is intended for testing. 2432 If true, accessing the charm store does not affect statistical 2433 data of the store. (default false)`, 2434 Type: environschema.Tbool, 2435 Group: environschema.EnvironGroup, 2436 }, 2437 DisableTelemetryKey: { 2438 Description: `Disable telemetry reporting of model information`, 2439 Type: environschema.Tbool, 2440 Group: environschema.EnvironGroup, 2441 }, 2442 ModeKey: { 2443 Description: `Mode is a comma-separated list which sets the 2444 mode the model should run in. So far only one is implemented 2445 - If 'requires-prompts' is present, clients will ask for confirmation before removing 2446 potentially valuable resources. 2447 (default "")`, 2448 Type: environschema.Tstring, 2449 Group: environschema.EnvironGroup, 2450 }, 2451 SSHAllowKey: { 2452 Description: `SSH allowlist is a comma-separated list of CIDRs from 2453 which machines in this model will accept connections to the SSH service. 2454 Currently only the aws & openstack providers support ssh-allow`, 2455 Type: environschema.Tstring, 2456 Group: environschema.EnvironGroup, 2457 }, 2458 SAASIngressAllowKey: { 2459 Description: `Application-offer ingress allowlist is a comma-separated list of 2460 CIDRs specifying what ingress can be applied to offers in this model.`, 2461 Type: environschema.Tstring, 2462 Group: environschema.EnvironGroup, 2463 }, 2464 TypeKey: { 2465 Description: "Type of model, e.g. local, ec2", 2466 Type: environschema.Tstring, 2467 Mandatory: true, 2468 Immutable: true, 2469 Group: environschema.EnvironGroup, 2470 }, 2471 UUIDKey: { 2472 Description: "The UUID of the model", 2473 Type: environschema.Tstring, 2474 Group: environschema.JujuGroup, 2475 Immutable: true, 2476 }, 2477 AutomaticallyRetryHooks: { 2478 Description: "Determines whether the uniter should automatically retry failed hooks", 2479 Type: environschema.Tbool, 2480 Group: environschema.EnvironGroup, 2481 }, 2482 TransmitVendorMetricsKey: { 2483 Description: "Determines whether metrics declared by charms deployed into this model are sent for anonymized aggregate analytics", 2484 Type: environschema.Tbool, 2485 Group: environschema.EnvironGroup, 2486 }, 2487 NetBondReconfigureDelayKey: { 2488 Description: "The amount of time in seconds to sleep between ifdown and ifup when bridging", 2489 Type: environschema.Tint, 2490 Group: environschema.EnvironGroup, 2491 }, 2492 ContainerNetworkingMethod: { 2493 Description: "Method of container networking setup - one of fan, provider, local", 2494 Type: environschema.Tstring, 2495 Group: environschema.EnvironGroup, 2496 }, 2497 MaxStatusHistoryAge: { 2498 Description: "The maximum age for status history entries before they are pruned, in human-readable time format", 2499 Type: environschema.Tstring, 2500 Group: environschema.EnvironGroup, 2501 }, 2502 MaxStatusHistorySize: { 2503 Description: "The maximum size for the status history collection, in human-readable memory format", 2504 Type: environschema.Tstring, 2505 Group: environschema.EnvironGroup, 2506 }, 2507 MaxActionResultsAge: { 2508 Description: "The maximum age for action entries before they are pruned, in human-readable time format", 2509 Type: environschema.Tstring, 2510 Group: environschema.EnvironGroup, 2511 }, 2512 MaxActionResultsSize: { 2513 Description: "The maximum size for the action collection, in human-readable memory format", 2514 Type: environschema.Tstring, 2515 Group: environschema.EnvironGroup, 2516 }, 2517 UpdateStatusHookInterval: { 2518 Description: "How often to run the charm update-status hook, in human-readable time format (default 5m, range 1-60m)", 2519 Type: environschema.Tstring, 2520 Group: environschema.EnvironGroup, 2521 }, 2522 EgressSubnets: { 2523 Description: "Source address(es) for traffic originating from this model", 2524 Type: environschema.Tstring, 2525 Group: environschema.EnvironGroup, 2526 }, 2527 FanConfig: { 2528 Description: "Configuration for fan networking for this model", 2529 Type: environschema.Tstring, 2530 Group: environschema.EnvironGroup, 2531 }, 2532 CloudInitUserDataKey: { 2533 Description: "Cloud-init user-data (in yaml format) to be added to userdata for new machines created in this model", 2534 Type: environschema.Tstring, 2535 Group: environschema.EnvironGroup, 2536 }, 2537 ContainerInheritPropertiesKey: { 2538 Description: "List of properties to be copied from the host machine to new containers created in this model (comma-separated)", 2539 Type: environschema.Tstring, 2540 Group: environschema.EnvironGroup, 2541 }, 2542 BackupDirKey: { 2543 Description: "Directory used to store the backup working directory", 2544 Type: environschema.Tstring, 2545 Group: environschema.EnvironGroup, 2546 }, 2547 DefaultSpace: { 2548 Description: "The default network space used for application endpoints in this model", 2549 Type: environschema.Tstring, 2550 Group: environschema.EnvironGroup, 2551 }, 2552 LXDSnapChannel: { 2553 Description: "The channel to use when installing LXD from a snap (cosmic and later)", 2554 Type: environschema.Tstring, 2555 Group: environschema.EnvironGroup, 2556 }, 2557 CharmHubURLKey: { 2558 Description: `The url for CharmHub API calls`, 2559 Type: environschema.Tstring, 2560 Group: environschema.EnvironGroup, 2561 }, 2562 LoggingOutputKey: { 2563 Description: `The logging output destination: database and/or syslog. (default "")`, 2564 Type: environschema.Tstring, 2565 Group: environschema.EnvironGroup, 2566 }, 2567 SecretBackendKey: { 2568 Description: `The name of the secret store backend. (default "auto")`, 2569 Type: environschema.Tstring, 2570 Group: environschema.EnvironGroup, 2571 }, 2572 }