github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/environs/config/config.go (about)

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