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