github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/config/config.go (about)

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