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  }