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

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package config_test
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"regexp"
    10  	"strings"
    11  	stdtesting "testing"
    12  	"time"
    13  
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/schema"
    16  	gitjujutesting "github.com/juju/testing"
    17  	jc "github.com/juju/testing/checkers"
    18  	"github.com/juju/utils/proxy"
    19  	"github.com/juju/utils/series"
    20  	"github.com/juju/version"
    21  	gc "gopkg.in/check.v1"
    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/config"
    28  	"github.com/juju/juju/juju/osenv"
    29  	"github.com/juju/juju/testing"
    30  )
    31  
    32  func Test(t *stdtesting.T) {
    33  	gc.TestingT(t)
    34  }
    35  
    36  type ConfigSuite struct {
    37  	testing.FakeJujuXDGDataHomeSuite
    38  	home string
    39  }
    40  
    41  var _ = gc.Suite(&ConfigSuite{})
    42  
    43  func (s *ConfigSuite) SetUpTest(c *gc.C) {
    44  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    45  	// Make sure that the defaults are used, which
    46  	// is <root>=WARNING
    47  	loggo.ResetLoggers()
    48  }
    49  
    50  // sampleConfig holds a configuration with all required
    51  // attributes set.
    52  var sampleConfig = testing.Attrs{
    53  	"type":                      "my-type",
    54  	"name":                      "my-name",
    55  	"uuid":                      testing.ModelTag.Id(),
    56  	"controller-uuid":           testing.ModelTag.Id(),
    57  	"authorized-keys":           testing.FakeAuthKeys,
    58  	"firewall-mode":             config.FwInstance,
    59  	"admin-secret":              "foo",
    60  	"unknown":                   "my-unknown",
    61  	"ca-cert":                   caCert,
    62  	"ssl-hostname-verification": true,
    63  	"development":               false,
    64  	"state-port":                1234,
    65  	"api-port":                  4321,
    66  	"default-series":            series.LatestLts(),
    67  }
    68  
    69  type configTest struct {
    70  	about       string
    71  	useDefaults config.Defaulting
    72  	attrs       testing.Attrs
    73  	expected    testing.Attrs
    74  	err         string
    75  }
    76  
    77  var testResourceTags = []string{"a=b", "c=", "d=e"}
    78  var testResourceTagsMap = map[string]string{
    79  	"a": "b", "c": "", "d": "e",
    80  }
    81  
    82  var quotedPathSeparator = regexp.QuoteMeta(string(os.PathSeparator))
    83  
    84  var minimalConfigAttrs = testing.Attrs{
    85  	"type":            "my-type",
    86  	"name":            "my-name",
    87  	"uuid":            testing.ModelTag.Id(),
    88  	"controller-uuid": testing.ModelTag.Id(),
    89  }
    90  
    91  var configTests = []configTest{
    92  	{
    93  		about:       "The minimum good configuration",
    94  		useDefaults: config.UseDefaults,
    95  		attrs:       minimalConfigAttrs,
    96  	}, {
    97  		about:       "Agent Stream",
    98  		useDefaults: config.UseDefaults,
    99  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   100  			"image-metadata-url": "image-url",
   101  			"agent-stream":       "released",
   102  		}),
   103  	}, {
   104  		about:       "Deprecated tools-stream used",
   105  		useDefaults: config.UseDefaults,
   106  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   107  			"tools-stream": "tools-stream-value",
   108  		}),
   109  	}, {
   110  		about:       "Deprecated tools-stream ignored",
   111  		useDefaults: config.UseDefaults,
   112  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   113  			"agent-stream": "released",
   114  			"tools-stream": "ignore-me",
   115  		}),
   116  	}, {
   117  		about:       "Metadata URLs",
   118  		useDefaults: config.UseDefaults,
   119  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   120  			"image-metadata-url": "image-url",
   121  			"agent-metadata-url": "agent-metadata-url-value",
   122  		}),
   123  	}, {
   124  		about:       "Deprecated tools metadata URL used",
   125  		useDefaults: config.UseDefaults,
   126  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   127  			"tools-metadata-url": "tools-metadata-url-value",
   128  		}),
   129  	}, {
   130  		about:       "Deprecated tools metadata URL ignored",
   131  		useDefaults: config.UseDefaults,
   132  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   133  			"agent-metadata-url": "agent-metadata-url-value",
   134  			"tools-metadata-url": "ignore-me",
   135  		}),
   136  	}, {
   137  		about:       "Explicit series",
   138  		useDefaults: config.UseDefaults,
   139  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   140  			"default-series": "my-series",
   141  		}),
   142  	}, {
   143  		about:       "Implicit series with empty value",
   144  		useDefaults: config.UseDefaults,
   145  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   146  			"default-series": "",
   147  		}),
   148  	}, {
   149  		about:       "Explicit logging",
   150  		useDefaults: config.UseDefaults,
   151  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   152  			"logging-config": "juju=INFO",
   153  		}),
   154  	}, {
   155  		about:       "Explicit authorized-keys",
   156  		useDefaults: config.UseDefaults,
   157  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   158  			"authorized-keys": testing.FakeAuthKeys,
   159  		}),
   160  	}, {
   161  		about:       "Load authorized-keys from path",
   162  		useDefaults: config.UseDefaults,
   163  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   164  			"authorized-keys-path": "~/.ssh/authorized_keys2",
   165  		}),
   166  	}, {
   167  		about:       "LXC clone values",
   168  		useDefaults: config.UseDefaults,
   169  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   170  			"default-series": "precise",
   171  			"lxc-clone":      true,
   172  			"lxc-clone-aufs": true,
   173  		}),
   174  	}, {
   175  		about:       "Deprecated lxc-use-clone used",
   176  		useDefaults: config.UseDefaults,
   177  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   178  			"lxc-use-clone": true,
   179  		}),
   180  	}, {
   181  		about:       "Deprecated lxc-use-clone ignored",
   182  		useDefaults: config.UseDefaults,
   183  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   184  			"lxc-use-clone": false,
   185  			"lxc-clone":     true,
   186  		}),
   187  	}, {
   188  		about:       "Allow LXC loop mounts true",
   189  		useDefaults: config.UseDefaults,
   190  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   191  			"allow-lxc-loop-mounts": "true",
   192  		}),
   193  	}, {
   194  		about:       "Allow LXC loop mounts default",
   195  		useDefaults: config.UseDefaults,
   196  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   197  			"allow-lxc-loop-mounts": "false",
   198  		}),
   199  		expected: minimalConfigAttrs.Merge(testing.Attrs{
   200  			"allow-lxc-loop-mounts": false,
   201  		}),
   202  	}, {
   203  		about:       "LXC default MTU not set",
   204  		useDefaults: config.UseDefaults,
   205  		attrs:       minimalConfigAttrs,
   206  	}, {
   207  		about:       "LXC default MTU set explicitly",
   208  		useDefaults: config.UseDefaults,
   209  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   210  			"lxc-default-mtu": 9000,
   211  		}),
   212  	}, {
   213  		about:       "LXC default MTU invalid (not a number)",
   214  		useDefaults: config.UseDefaults,
   215  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   216  			"lxc-default-mtu": "foo",
   217  		}),
   218  		err: `lxc-default-mtu: expected number, got string\("foo"\)`,
   219  	}, {
   220  		about:       "LXC default MTU invalid (negative)",
   221  		useDefaults: config.UseDefaults,
   222  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   223  			"lxc-default-mtu": -42,
   224  		}),
   225  		err: `lxc-default-mtu: expected positive integer, got -42`,
   226  	}, {
   227  		about:       "CA cert & key from path",
   228  		useDefaults: config.UseDefaults,
   229  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   230  			"ca-cert-path":        "cacert2.pem",
   231  			"ca-private-key-path": "cakey2.pem",
   232  		}),
   233  	}, {
   234  		about:       "CA cert & key from path; cert attribute set too",
   235  		useDefaults: config.UseDefaults,
   236  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   237  			"ca-cert-path":        "cacert2.pem",
   238  			"ca-cert":             "ignored",
   239  			"ca-private-key-path": "cakey2.pem",
   240  		}),
   241  	}, {
   242  		about:       "CA cert & key from ~ path",
   243  		useDefaults: config.UseDefaults,
   244  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   245  			"ca-cert-path":        "~/othercert.pem",
   246  			"ca-private-key-path": "~/otherkey.pem",
   247  		}),
   248  	}, {
   249  		about:       "CA cert and key as attributes",
   250  		useDefaults: config.UseDefaults,
   251  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   252  			"ca-cert":        caCert,
   253  			"ca-private-key": caKey,
   254  		}),
   255  	}, {
   256  		about:       "Mismatched CA cert and key",
   257  		useDefaults: config.UseDefaults,
   258  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   259  			"ca-cert":        caCert,
   260  			"ca-private-key": caKey2,
   261  		}),
   262  		err: "bad CA certificate/key in configuration: crypto/tls: private key does not match public key",
   263  	}, {
   264  		about:       "Invalid CA cert",
   265  		useDefaults: config.UseDefaults,
   266  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   267  			"ca-cert": invalidCACert,
   268  		}),
   269  		err: `bad CA certificate/key in configuration: (asn1:|ASN\.1) syntax error:.*`,
   270  	}, {
   271  		about:       "Invalid CA key",
   272  		useDefaults: config.UseDefaults,
   273  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   274  			"ca-cert":        caCert,
   275  			"ca-private-key": invalidCAKey,
   276  		}),
   277  		err: "bad CA certificate/key in configuration: crypto/tls:.*",
   278  	}, {
   279  		about:       "CA cert specified as non-existent file",
   280  		useDefaults: config.UseDefaults,
   281  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   282  			"ca-cert-path": "no-such-file",
   283  		}),
   284  		err: fmt.Sprintf(`open .*\.local%sshare%sjuju%sno-such-file: .*`, quotedPathSeparator, quotedPathSeparator, quotedPathSeparator),
   285  	}, {
   286  		about:       "CA key specified as non-existent file",
   287  		useDefaults: config.UseDefaults,
   288  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   289  			"ca-private-key-path": "no-such-file",
   290  		}),
   291  		err: fmt.Sprintf(`open .*\.local%sshare%sjuju%sno-such-file: .*`, quotedPathSeparator, quotedPathSeparator, quotedPathSeparator),
   292  	}, {
   293  		about:       "Specified agent version",
   294  		useDefaults: config.UseDefaults,
   295  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   296  			"authorized-keys": testing.FakeAuthKeys,
   297  			"agent-version":   "1.2.3",
   298  		}),
   299  	}, {
   300  		about:       "Specified development flag",
   301  		useDefaults: config.UseDefaults,
   302  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   303  			"authorized-keys": testing.FakeAuthKeys,
   304  			"development":     true,
   305  		}),
   306  	}, {
   307  		about:       "Specified admin secret",
   308  		useDefaults: config.UseDefaults,
   309  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   310  			"authorized-keys": testing.FakeAuthKeys,
   311  			"development":     false,
   312  			"admin-secret":    "pork",
   313  		}),
   314  	}, {
   315  		about:       "Invalid development flag",
   316  		useDefaults: config.UseDefaults,
   317  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   318  			"authorized-keys": testing.FakeAuthKeys,
   319  			"development":     "invalid",
   320  		}),
   321  		err: `development: expected bool, got string\("invalid"\)`,
   322  	}, {
   323  		about:       "Invalid disable-network-management flag",
   324  		useDefaults: config.UseDefaults,
   325  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   326  			"authorized-keys":            testing.FakeAuthKeys,
   327  			"disable-network-management": "invalid",
   328  		}),
   329  		err: `disable-network-management: expected bool, got string\("invalid"\)`,
   330  	}, {
   331  		about:       "disable-network-management off",
   332  		useDefaults: config.UseDefaults,
   333  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   334  			"disable-network-management": false,
   335  		}),
   336  	}, {
   337  		about:       "disable-network-management on",
   338  		useDefaults: config.UseDefaults,
   339  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   340  			"disable-network-management": true,
   341  		}),
   342  	}, {
   343  		about:       "Invalid ignore-machine-addresses flag",
   344  		useDefaults: config.UseDefaults,
   345  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   346  			"ignore-machine-addresses": "invalid",
   347  		}),
   348  		err: `ignore-machine-addresses: expected bool, got string\("invalid"\)`,
   349  	}, {
   350  		about:       "ignore-machine-addresses off",
   351  		useDefaults: config.UseDefaults,
   352  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   353  			"ignore-machine-addresses": false,
   354  		}),
   355  	}, {
   356  		about:       "ignore-machine-addresses on",
   357  		useDefaults: config.UseDefaults,
   358  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   359  			"ignore-machine-addresses": true,
   360  		}),
   361  	}, {
   362  		about:       "set-numa-control-policy on",
   363  		useDefaults: config.UseDefaults,
   364  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   365  			"set-numa-control-policy": true,
   366  		}),
   367  	}, {
   368  		about:       "set-numa-control-policy off",
   369  		useDefaults: config.UseDefaults,
   370  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   371  			"set-numa-control-policy": false,
   372  		}),
   373  	}, {
   374  		about:       "block-destroy-model on",
   375  		useDefaults: config.UseDefaults,
   376  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   377  			"block-destroy-model": true,
   378  		}),
   379  	}, {
   380  		about:       "block-destroy-model off",
   381  		useDefaults: config.UseDefaults,
   382  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   383  			"block-destroy-model": false,
   384  		}),
   385  	}, {
   386  		about:       "block-remove-object on",
   387  		useDefaults: config.UseDefaults,
   388  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   389  			"block-remove-object": true,
   390  		}),
   391  	}, {
   392  		about:       "block-remove-object off",
   393  		useDefaults: config.UseDefaults,
   394  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   395  			"block-remove-object": false,
   396  		}),
   397  	}, {
   398  		about:       "block-all-changes on",
   399  		useDefaults: config.UseDefaults,
   400  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   401  			"block-all-changes": true,
   402  		}),
   403  	}, {
   404  		about:       "block-all-changes off",
   405  		useDefaults: config.UseDefaults,
   406  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   407  			"block-all-changest": false,
   408  		}),
   409  	}, {
   410  		about:       "Invalid prefer-ipv6 flag",
   411  		useDefaults: config.UseDefaults,
   412  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   413  			"authorized-keys": testing.FakeAuthKeys,
   414  			"prefer-ipv6":     "invalid",
   415  		}),
   416  		err: `prefer-ipv6: expected bool, got string\("invalid"\)`,
   417  	}, {
   418  		about:       "prefer-ipv6 off",
   419  		useDefaults: config.UseDefaults,
   420  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   421  			"prefer-ipv6": false,
   422  		}),
   423  	}, {
   424  		about:       "prefer-ipv6 on",
   425  		useDefaults: config.UseDefaults,
   426  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   427  			"prefer-ipv6": true,
   428  		}),
   429  	}, {
   430  		about:       "Invalid agent version",
   431  		useDefaults: config.UseDefaults,
   432  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   433  			"authorized-keys": testing.FakeAuthKeys,
   434  			"agent-version":   "2",
   435  		}),
   436  		err: `invalid agent version in model configuration: "2"`,
   437  	}, {
   438  		about:       "Missing type",
   439  		useDefaults: config.UseDefaults,
   440  		attrs:       minimalConfigAttrs.Delete("type"),
   441  		err:         "type: expected string, got nothing",
   442  	}, {
   443  		about:       "Empty type",
   444  		useDefaults: config.UseDefaults,
   445  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   446  			"type": "",
   447  		}),
   448  		err: "empty type in model configuration",
   449  	}, {
   450  		about:       "Missing name",
   451  		useDefaults: config.UseDefaults,
   452  		attrs:       minimalConfigAttrs.Delete("name"),
   453  		err:         "name: expected string, got nothing",
   454  	}, {
   455  		about:       "Bad name, no slash",
   456  		useDefaults: config.UseDefaults,
   457  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   458  			"name": "foo/bar",
   459  		}),
   460  		err: "model name contains unsafe characters",
   461  	}, {
   462  		about:       "Bad name, no backslash",
   463  		useDefaults: config.UseDefaults,
   464  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   465  			"name": "foo\\bar",
   466  		}),
   467  		err: "model name contains unsafe characters",
   468  	}, {
   469  		about:       "Empty name",
   470  		useDefaults: config.UseDefaults,
   471  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   472  			"name": "",
   473  		}),
   474  		err: "empty name in model configuration",
   475  	}, {
   476  		about:       "Default firewall mode",
   477  		useDefaults: config.UseDefaults,
   478  		attrs:       minimalConfigAttrs,
   479  	}, {
   480  		about:       "Empty firewall mode",
   481  		useDefaults: config.UseDefaults,
   482  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   483  			"firewall-mode": "",
   484  		}),
   485  	}, {
   486  		about:       "Instance firewall mode",
   487  		useDefaults: config.UseDefaults,
   488  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   489  			"firewall-mode": config.FwInstance,
   490  		}),
   491  	}, {
   492  		about:       "Global firewall mode",
   493  		useDefaults: config.UseDefaults,
   494  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   495  			"firewall-mode": config.FwGlobal,
   496  		}),
   497  	}, {
   498  		about:       "None firewall mode",
   499  		useDefaults: config.UseDefaults,
   500  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   501  			"firewall-mode": config.FwNone,
   502  		}),
   503  	}, {
   504  		about:       "Illegal firewall mode",
   505  		useDefaults: config.UseDefaults,
   506  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   507  			"firewall-mode": "illegal",
   508  		}),
   509  		err: `firewall-mode: expected one of \[instance global none ], got "illegal"`,
   510  	}, {
   511  		about:       "ssl-hostname-verification off",
   512  		useDefaults: config.UseDefaults,
   513  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   514  			"ssl-hostname-verification": false,
   515  		}),
   516  	}, {
   517  		about:       "ssl-hostname-verification incorrect",
   518  		useDefaults: config.UseDefaults,
   519  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   520  			"ssl-hostname-verification": "yes please",
   521  		}),
   522  		err: `ssl-hostname-verification: expected bool, got string\("yes please"\)`,
   523  	}, {
   524  		about: fmt.Sprintf(
   525  			"%s: %s",
   526  			"provisioner-harvest-mode",
   527  			config.HarvestAll.String(),
   528  		),
   529  		useDefaults: config.UseDefaults,
   530  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   531  			"provisioner-harvest-mode": config.HarvestAll.String(),
   532  		}),
   533  	}, {
   534  		about: fmt.Sprintf(
   535  			"%s: %s",
   536  			"provisioner-harvest-mode",
   537  			config.HarvestDestroyed.String(),
   538  		),
   539  		useDefaults: config.UseDefaults,
   540  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   541  			"provisioner-harvest-mode": config.HarvestDestroyed.String(),
   542  		}),
   543  	}, {
   544  		about: fmt.Sprintf(
   545  			"%s: %s",
   546  			"provisioner-harvest-mode",
   547  			config.HarvestUnknown.String(),
   548  		),
   549  		useDefaults: config.UseDefaults,
   550  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   551  			"provisioner-harvest-mode": config.HarvestUnknown.String(),
   552  		}),
   553  	}, {
   554  		about: fmt.Sprintf(
   555  			"%s: %s",
   556  			"provisioner-harvest-mode",
   557  			config.HarvestNone.String(),
   558  		),
   559  		useDefaults: config.UseDefaults,
   560  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   561  			"provisioner-harvest-mode": config.HarvestNone.String(),
   562  		}),
   563  	}, {
   564  		about:       "provisioner-harvest-mode: incorrect",
   565  		useDefaults: config.UseDefaults,
   566  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   567  			"provisioner-harvest-mode": "yes please",
   568  		}),
   569  		err: `provisioner-harvest-mode: expected one of \[all none unknown destroyed], got "yes please"`,
   570  	}, {
   571  		about:       "default image stream",
   572  		useDefaults: config.UseDefaults,
   573  		attrs:       minimalConfigAttrs,
   574  	}, {
   575  		about:       "explicit image stream",
   576  		useDefaults: config.UseDefaults,
   577  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   578  			"image-stream": "daily",
   579  		}),
   580  	}, {
   581  		about:       "explicit tools stream",
   582  		useDefaults: config.UseDefaults,
   583  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   584  			"agent-stream": "proposed",
   585  		}),
   586  	}, {
   587  		about:       "Explicit state port",
   588  		useDefaults: config.UseDefaults,
   589  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   590  			"state-port": 37042,
   591  		}),
   592  	}, {
   593  		about:       "Invalid state port",
   594  		useDefaults: config.UseDefaults,
   595  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   596  			"state-port": "illegal",
   597  		}),
   598  		err: `state-port: expected number, got string\("illegal"\)`,
   599  	}, {
   600  		about:       "Explicit API port",
   601  		useDefaults: config.UseDefaults,
   602  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   603  			"api-port": 77042,
   604  		}),
   605  	}, {
   606  		about:       "Invalid API port",
   607  		useDefaults: config.UseDefaults,
   608  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   609  			"api-port": "illegal",
   610  		}),
   611  		err: `api-port: expected number, got string\("illegal"\)`,
   612  	}, {
   613  		about:       "Explicit bootstrap timeout",
   614  		useDefaults: config.UseDefaults,
   615  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   616  			"bootstrap-timeout": 300,
   617  		}),
   618  	}, {
   619  		about:       "Invalid bootstrap timeout",
   620  		useDefaults: config.UseDefaults,
   621  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   622  			"bootstrap-timeout": "illegal",
   623  		}),
   624  		err: `bootstrap-timeout: expected number, got string\("illegal"\)`,
   625  	}, {
   626  		about:       "Explicit bootstrap retry delay",
   627  		useDefaults: config.UseDefaults,
   628  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   629  			"bootstrap-retry-delay": 5,
   630  		}),
   631  	}, {
   632  		about:       "Invalid bootstrap retry delay",
   633  		useDefaults: config.UseDefaults,
   634  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   635  			"bootstrap-retry-delay": "illegal",
   636  		}),
   637  		err: `bootstrap-retry-delay: expected number, got string\("illegal"\)`,
   638  	}, {
   639  		about:       "Explicit bootstrap addresses delay",
   640  		useDefaults: config.UseDefaults,
   641  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   642  			"bootstrap-addresses-delay": 15,
   643  		}),
   644  	}, {
   645  		about:       "Invalid bootstrap addresses delay",
   646  		useDefaults: config.UseDefaults,
   647  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   648  			"bootstrap-addresses-delay": "illegal",
   649  		}),
   650  		err: `bootstrap-addresses-delay: expected number, got string\("illegal"\)`,
   651  	}, {
   652  		about:       "Invalid logging configuration",
   653  		useDefaults: config.UseDefaults,
   654  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   655  			"logging-config": "foo=bar",
   656  		}),
   657  		err: `unknown severity level "bar"`,
   658  	}, {
   659  		about:       "Sample configuration",
   660  		useDefaults: config.UseDefaults,
   661  		attrs:       sampleConfig,
   662  	}, {
   663  		about:       "No defaults: sample configuration",
   664  		useDefaults: config.NoDefaults,
   665  		attrs:       sampleConfig,
   666  	}, {
   667  		about:       "No defaults: with ca-cert-path",
   668  		useDefaults: config.NoDefaults,
   669  		attrs:       sampleConfig.Merge(testing.Attrs{"ca-cert-path": "arble"}),
   670  		err:         `attribute "ca-cert-path" is not allowed in configuration`,
   671  	}, {
   672  		about:       "No defaults: with ca-private-key-path",
   673  		useDefaults: config.NoDefaults,
   674  		attrs:       sampleConfig.Merge(testing.Attrs{"ca-private-key-path": "arble"}),
   675  		err:         `attribute "ca-private-key-path" is not allowed in configuration`,
   676  	}, {
   677  		about:       "No defaults: with authorized-keys-path",
   678  		useDefaults: config.NoDefaults,
   679  		attrs:       sampleConfig.Merge(testing.Attrs{"authorized-keys-path": "arble"}),
   680  		err:         `attribute "authorized-keys-path" is not allowed in configuration`,
   681  	}, {
   682  		about:       "No defaults: missing authorized-keys",
   683  		useDefaults: config.NoDefaults,
   684  		attrs:       sampleConfig.Delete("authorized-keys"),
   685  		err:         `authorized-keys missing from model configuration`,
   686  	}, {
   687  		about:       "Config settings from juju 1.13.3 actual installation",
   688  		useDefaults: config.NoDefaults,
   689  		attrs: map[string]interface{}{
   690  			"name":                      "sample",
   691  			"development":               false,
   692  			"admin-secret":              "",
   693  			"ssl-hostname-verification": true,
   694  			"authorized-keys":           "ssh-rsa mykeys rog@rog-x220\n",
   695  			"region":                    "us-east-1",
   696  			"image-metadata-url":        "",
   697  			"ca-private-key":            "",
   698  			"default-series":            "precise",
   699  			"agent-metadata-url":        "",
   700  			"secret-key":                "a-secret-key",
   701  			"access-key":                "an-access-key",
   702  			"agent-version":             "1.13.2",
   703  			"ca-cert":                   caCert,
   704  			"firewall-mode":             "instance",
   705  			"type":                      "ec2",
   706  			// These ones weren't actual values, but we require
   707  			// them now, and have no backwards-compatibility
   708  			// with the old config.
   709  			"uuid":            testing.ModelTag.Id(),
   710  			"controller-uuid": testing.ModelTag.Id(),
   711  		},
   712  	}, {
   713  		about:       "Provider type null is replaced with manual",
   714  		useDefaults: config.UseDefaults,
   715  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   716  			"type": "null",
   717  		}),
   718  	}, {
   719  		about:       "TestMode flag specified",
   720  		useDefaults: config.UseDefaults,
   721  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   722  			"test-mode": true,
   723  		}),
   724  	}, {
   725  		about:       "valid uuid",
   726  		useDefaults: config.UseDefaults,
   727  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   728  			"uuid": "dcfbdb4a-bca2-49ad-aa7c-f011424e0fe4",
   729  		}),
   730  	}, {
   731  		about:       "valid controller-uuid",
   732  		useDefaults: config.UseDefaults,
   733  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   734  			"controller-uuid": "dcfbdb4a-bca2-49ad-aa7c-f011424e0fe4",
   735  		}),
   736  	}, {
   737  		about:       "invalid uuid 1",
   738  		useDefaults: config.UseDefaults,
   739  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   740  			"uuid": "dcfbdb4abca249adaa7cf011424e0fe4",
   741  		}),
   742  		err: `uuid: expected UUID, got string\("dcfbdb4abca249adaa7cf011424e0fe4"\)`,
   743  	}, {
   744  		about:       "invalid uuid 2",
   745  		useDefaults: config.UseDefaults,
   746  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   747  			"uuid": "uuid",
   748  		}),
   749  		err: `uuid: expected UUID, got string\("uuid"\)`,
   750  	}, {
   751  		about:       "blank uuid",
   752  		useDefaults: config.UseDefaults,
   753  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   754  			"uuid": "",
   755  		}),
   756  		err: `empty uuid in model configuration`,
   757  	},
   758  	missingAttributeNoDefault("firewall-mode"),
   759  	missingAttributeNoDefault("development"),
   760  	missingAttributeNoDefault("ssl-hostname-verification"),
   761  	// TODO(rog) reinstate these tests when we can lose
   762  	// backward compatibility with pre-1.13 config.
   763  	// missingAttributeNoDefault("state-port"),
   764  	// missingAttributeNoDefault("api-port"),
   765  	{
   766  		about:       "Deprecated safe-mode failover",
   767  		useDefaults: config.UseDefaults,
   768  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   769  			"provisioner-safe-mode":    true,
   770  			"provisioner-harvest-mode": config.HarvestNone.String(),
   771  		}),
   772  	},
   773  	{
   774  		about:       "Explicit apt-mirror",
   775  		useDefaults: config.UseDefaults,
   776  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   777  			"apt-mirror": "http://my.archive.ubuntu.com",
   778  		}),
   779  	},
   780  	{
   781  		about:       "Resource tags as space-separated string",
   782  		useDefaults: config.UseDefaults,
   783  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   784  			"resource-tags": strings.Join(testResourceTags, " "),
   785  		}),
   786  	},
   787  	{
   788  		about:       "Resource tags as list of strings",
   789  		useDefaults: config.UseDefaults,
   790  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   791  			"resource-tags": testResourceTags,
   792  		}),
   793  	},
   794  	{
   795  		about:       "Resource tags contains non-keyvalues",
   796  		useDefaults: config.UseDefaults,
   797  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   798  			"resource-tags": []string{"a"},
   799  		}),
   800  		err: `resource-tags: expected "key=value", got "a"`,
   801  	},
   802  	{
   803  		about:       "Invalid identity URL value",
   804  		useDefaults: config.UseDefaults,
   805  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   806  			"identity-url": "%",
   807  		}),
   808  		err: `invalid identity URL: parse %: invalid URL escape "%"`,
   809  	}, {
   810  		about:       "Not using https in identity URL",
   811  		useDefaults: config.UseDefaults,
   812  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   813  			"identity-url": "http://test-identity",
   814  		}),
   815  		err: `URL needs to be https`,
   816  	},
   817  	{
   818  		about:       "Invalid identity public key",
   819  		useDefaults: config.UseDefaults,
   820  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   821  			"identity-public-key": "_",
   822  		}),
   823  		err: `invalid identity public key: cannot decode base64 key: illegal base64 data at input byte 0`,
   824  	},
   825  	{
   826  		about:       "Valid identity URL and public key values",
   827  		useDefaults: config.UseDefaults,
   828  		attrs: minimalConfigAttrs.Merge(testing.Attrs{
   829  			"identity-url":        "https://test-identity",
   830  			"identity-public-key": "o/yOqSNWncMo1GURWuez/dGR30TscmmuIxgjztpoHEY=",
   831  		}),
   832  	},
   833  }
   834  
   835  func missingAttributeNoDefault(attrName string) configTest {
   836  	return configTest{
   837  		about:       fmt.Sprintf("No default: missing %s", attrName),
   838  		useDefaults: config.NoDefaults,
   839  		attrs:       sampleConfig.Delete(attrName),
   840  		err:         fmt.Sprintf("%s: expected [a-z]+, got nothing", attrName),
   841  	}
   842  }
   843  
   844  type testFile struct {
   845  	name, data string
   846  }
   847  
   848  func (s *ConfigSuite) TestConfig(c *gc.C) {
   849  	files := []gitjujutesting.TestFile{
   850  		{".ssh/id_dsa.pub", "dsa"},
   851  		{".ssh/id_rsa.pub", "rsa\n"},
   852  		{".ssh/identity.pub", "identity"},
   853  		{".ssh/authorized_keys", "auth0\n# first\nauth1\n\n"},
   854  		{".ssh/authorized_keys2", "auth2\nauth3\n"},
   855  
   856  		{".local/share/juju/my-name-cert.pem", caCert},
   857  		{".local/share/juju/my-name-private-key.pem", caKey},
   858  		{".local/share/juju/cacert2.pem", caCert2},
   859  		{".local/share/juju/cakey2.pem", caKey2},
   860  		{"othercert.pem", caCert3},
   861  		{"otherkey.pem", caKey3},
   862  	}
   863  	s.FakeHomeSuite.Home.AddFiles(c, files...)
   864  	for i, test := range configTests {
   865  		c.Logf("test %d. %s", i, test.about)
   866  		test.check(c, s.FakeHomeSuite.Home)
   867  	}
   868  }
   869  
   870  var noCertFilesTests = []configTest{
   871  	{
   872  		about:       "Unspecified certificate and key",
   873  		useDefaults: config.UseDefaults,
   874  		attrs: testing.Attrs{
   875  			"type":            "my-type",
   876  			"name":            "my-name",
   877  			"uuid":            testing.ModelTag.Id(),
   878  			"controller-uuid": testing.ModelTag.Id(),
   879  			"authorized-keys": testing.FakeAuthKeys,
   880  		},
   881  	}, {
   882  		about:       "Unspecified certificate, specified key",
   883  		useDefaults: config.UseDefaults,
   884  		attrs: testing.Attrs{
   885  			"type":            "my-type",
   886  			"name":            "my-name",
   887  			"uuid":            testing.ModelTag.Id(),
   888  			"controller-uuid": testing.ModelTag.Id(),
   889  			"authorized-keys": testing.FakeAuthKeys,
   890  			"ca-private-key":  caKey,
   891  		},
   892  		err: "bad CA certificate/key in configuration: crypto/tls:.*",
   893  	},
   894  }
   895  
   896  func (s *ConfigSuite) TestConfigNoCertFiles(c *gc.C) {
   897  	for i, test := range noCertFilesTests {
   898  		c.Logf("test %d. %s", i, test.about)
   899  		test.check(c, s.FakeHomeSuite.Home)
   900  	}
   901  }
   902  
   903  var emptyCertFilesTests = []configTest{
   904  	{
   905  		about:       "Cert unspecified; key specified",
   906  		useDefaults: config.UseDefaults,
   907  		attrs: testing.Attrs{
   908  			"type":            "my-type",
   909  			"name":            "my-name",
   910  			"uuid":            testing.ModelTag.Id(),
   911  			"controller-uuid": testing.ModelTag.Id(),
   912  			"authorized-keys": testing.FakeAuthKeys,
   913  			"ca-private-key":  caKey,
   914  		},
   915  		err: fmt.Sprintf(`file ".*%smy-name-cert.pem" is empty`, regexp.QuoteMeta(string(os.PathSeparator))),
   916  	}, {
   917  		about:       "Cert and key unspecified",
   918  		useDefaults: config.UseDefaults,
   919  		attrs: testing.Attrs{
   920  			"type":            "my-type",
   921  			"name":            "my-name",
   922  			"uuid":            testing.ModelTag.Id(),
   923  			"controller-uuid": testing.ModelTag.Id(),
   924  			"authorized-keys": testing.FakeAuthKeys,
   925  		},
   926  		err: fmt.Sprintf(`file ".*%smy-name-cert.pem" is empty`, regexp.QuoteMeta(string(os.PathSeparator))),
   927  	}, {
   928  		about:       "Cert specified, key unspecified",
   929  		useDefaults: config.UseDefaults,
   930  		attrs: testing.Attrs{
   931  			"type":            "my-type",
   932  			"name":            "my-name",
   933  			"uuid":            testing.ModelTag.Id(),
   934  			"controller-uuid": testing.ModelTag.Id(),
   935  			"authorized-keys": testing.FakeAuthKeys,
   936  			"ca-cert":         caCert,
   937  		},
   938  		err: fmt.Sprintf(`file ".*%smy-name-private-key.pem" is empty`, regexp.QuoteMeta(string(os.PathSeparator))),
   939  	}, /* {
   940  		about: "Cert and key specified as absent",
   941  		useDefaults: config.UseDefaults,
   942  		attrs: testing.Attrs{
   943  			"type":            "my-type",
   944  			"name":            "my-name",
   945  			"authorized-keys": testing.FakeAuthKeys,
   946  			"ca-cert":         "",
   947  			"ca-private-key":  "",
   948  		},
   949  	}, {
   950  		about: "Cert specified as absent",
   951  		useDefaults: config.UseDefaults,
   952  		attrs: testing.Attrs{
   953  			"type":            "my-type",
   954  			"name":            "my-name",
   955  			"authorized-keys": testing.FakeAuthKeys,
   956  			"ca-cert":         "",
   957  		},
   958  		err: "bad CA certificate/key in configuration: crypto/tls: .*",
   959  	}, */
   960  }
   961  
   962  func (s *ConfigSuite) TestConfigEmptyCertFiles(c *gc.C) {
   963  	files := []gitjujutesting.TestFile{
   964  		{".local/share/juju/my-name-cert.pem", ""},
   965  		{".local/share/juju/my-name-private-key.pem", ""},
   966  	}
   967  	s.FakeHomeSuite.Home.AddFiles(c, files...)
   968  
   969  	for i, test := range emptyCertFilesTests {
   970  		c.Logf("test %d. %s", i, test.about)
   971  		test.check(c, s.FakeHomeSuite.Home)
   972  	}
   973  }
   974  
   975  func (s *ConfigSuite) TestNoDefinedPrivateCert(c *gc.C) {
   976  	// Server-side there is no juju home.
   977  	osenv.SetJujuXDGDataHome("")
   978  	attrs := testing.Attrs{
   979  		"type":            "my-type",
   980  		"name":            "my-name",
   981  		"uuid":            testing.ModelTag.Id(),
   982  		"controller-uuid": testing.ModelTag.Id(),
   983  		"authorized-keys": testing.FakeAuthKeys,
   984  		"ca-cert":         testing.CACert,
   985  		"ca-private-key":  "",
   986  	}
   987  
   988  	_, err := config.New(config.UseDefaults, attrs)
   989  	c.Assert(err, jc.ErrorIsNil)
   990  }
   991  
   992  func (s *ConfigSuite) TestSafeModeDeprecatesGracefully(c *gc.C) {
   993  
   994  	cfg, err := config.New(config.UseDefaults, testing.Attrs{
   995  		"name":                  "name",
   996  		"type":                  "type",
   997  		"uuid":                  testing.ModelTag.Id(),
   998  		"controller-uuid":       testing.ModelTag.Id(),
   999  		"provisioner-safe-mode": false,
  1000  	})
  1001  	c.Assert(err, jc.ErrorIsNil)
  1002  
  1003  	c.Check(
  1004  		cfg.ProvisionerHarvestMode().String(),
  1005  		gc.Equals,
  1006  		config.HarvestAll.String(),
  1007  	)
  1008  
  1009  	cfg, err = config.New(config.UseDefaults, testing.Attrs{
  1010  		"name":                  "name",
  1011  		"type":                  "type",
  1012  		"uuid":                  testing.ModelTag.Id(),
  1013  		"controller-uuid":       testing.ModelTag.Id(),
  1014  		"provisioner-safe-mode": true,
  1015  	})
  1016  	c.Assert(err, jc.ErrorIsNil)
  1017  
  1018  	c.Check(
  1019  		cfg.ProvisionerHarvestMode().String(),
  1020  		gc.Equals,
  1021  		config.HarvestDestroyed.String(),
  1022  	)
  1023  }
  1024  
  1025  func (test configTest) check(c *gc.C, home *gitjujutesting.FakeHome) {
  1026  	cfg, err := config.New(test.useDefaults, test.attrs)
  1027  	if test.err != "" {
  1028  		c.Check(cfg, gc.IsNil)
  1029  		c.Assert(err, gc.ErrorMatches, test.err)
  1030  		return
  1031  	}
  1032  	c.Assert(err, jc.ErrorIsNil)
  1033  
  1034  	typ, _ := test.attrs["type"].(string)
  1035  	// "null" has been deprecated in favour of "manual",
  1036  	// and is automatically switched.
  1037  	if typ == "null" {
  1038  		typ = "manual"
  1039  	}
  1040  	name, _ := test.attrs["name"].(string)
  1041  	c.Assert(cfg.Type(), gc.Equals, typ)
  1042  	c.Assert(cfg.Name(), gc.Equals, name)
  1043  	agentVersion, ok := cfg.AgentVersion()
  1044  	if s := test.attrs["agent-version"]; s != nil {
  1045  		c.Assert(ok, jc.IsTrue)
  1046  		c.Assert(agentVersion, gc.Equals, version.MustParse(s.(string)))
  1047  	} else {
  1048  		c.Assert(ok, jc.IsFalse)
  1049  		c.Assert(agentVersion, gc.Equals, version.Zero)
  1050  	}
  1051  
  1052  	if statePort, ok := test.attrs["state-port"]; ok {
  1053  		c.Assert(cfg.StatePort(), gc.Equals, statePort)
  1054  	}
  1055  	if apiPort, ok := test.attrs["api-port"]; ok {
  1056  		c.Assert(cfg.APIPort(), gc.Equals, apiPort)
  1057  	}
  1058  	if expected, ok := test.attrs["uuid"]; ok {
  1059  		c.Assert(cfg.UUID(), gc.Equals, expected)
  1060  	}
  1061  	if expected, ok := test.attrs["controller-uuid"]; ok {
  1062  		c.Assert(cfg.ControllerUUID(), gc.Equals, expected)
  1063  	}
  1064  	if identityURL, ok := test.attrs["identity-url"]; ok {
  1065  		c.Assert(cfg.IdentityURL(), gc.Equals, identityURL)
  1066  	}
  1067  	if identityPublicKey, ok := test.attrs["identity-public-key"]; ok {
  1068  		var pk bakery.PublicKey
  1069  		err := pk.UnmarshalText([]byte(identityPublicKey.(string)))
  1070  		c.Assert(err, jc.ErrorIsNil)
  1071  		c.Assert(cfg.IdentityPublicKey(), gc.DeepEquals, &pk)
  1072  	}
  1073  
  1074  	dev, _ := test.attrs["development"].(bool)
  1075  	c.Assert(cfg.Development(), gc.Equals, dev)
  1076  
  1077  	testmode, _ := test.attrs["test-mode"].(bool)
  1078  	c.Assert(cfg.TestMode(), gc.Equals, testmode)
  1079  
  1080  	series, _ := test.attrs["default-series"].(string)
  1081  	if defaultSeries, ok := cfg.DefaultSeries(); ok {
  1082  		c.Assert(defaultSeries, gc.Equals, series)
  1083  	} else {
  1084  		c.Assert(series, gc.Equals, "")
  1085  		c.Assert(defaultSeries, gc.Equals, "")
  1086  	}
  1087  
  1088  	if m, _ := test.attrs["firewall-mode"].(string); m != "" {
  1089  		c.Assert(cfg.FirewallMode(), gc.Equals, m)
  1090  	}
  1091  	if secret, _ := test.attrs["admin-secret"].(string); secret != "" {
  1092  		c.Assert(cfg.AdminSecret(), gc.Equals, secret)
  1093  	}
  1094  
  1095  	if path, _ := test.attrs["authorized-keys-path"].(string); path != "" {
  1096  		c.Assert(cfg.AuthorizedKeys(), gc.Equals, home.FileContents(c, path))
  1097  		c.Assert(cfg.AllAttrs()["authorized-keys-path"], gc.IsNil)
  1098  	} else if keys, _ := test.attrs["authorized-keys"].(string); keys != "" {
  1099  		c.Assert(cfg.AuthorizedKeys(), gc.Equals, keys)
  1100  	} else {
  1101  		// Content of all the files that are read by default.
  1102  		c.Assert(cfg.AuthorizedKeys(), gc.Equals, "dsa\nrsa\nidentity\n")
  1103  	}
  1104  
  1105  	cert, certPresent := cfg.CACert()
  1106  	if path, _ := test.attrs["ca-cert-path"].(string); path != "" {
  1107  		c.Assert(certPresent, jc.IsTrue)
  1108  		c.Assert(string(cert), gc.Equals, home.FileContents(c, path))
  1109  	} else if v, ok := test.attrs["ca-cert"].(string); v != "" {
  1110  		c.Assert(certPresent, jc.IsTrue)
  1111  		c.Assert(string(cert), gc.Equals, v)
  1112  	} else if ok {
  1113  		c.Check(cert, gc.HasLen, 0)
  1114  		c.Assert(certPresent, jc.IsFalse)
  1115  	} else if bool(test.useDefaults) && home.FileExists(".local/share/juju/my-name-cert.pem") {
  1116  		c.Assert(certPresent, jc.IsTrue)
  1117  		c.Assert(string(cert), gc.Equals, home.FileContents(c, "my-name-cert.pem"))
  1118  	} else {
  1119  		c.Check(cert, gc.HasLen, 0)
  1120  		c.Assert(certPresent, jc.IsFalse)
  1121  	}
  1122  
  1123  	key, keyPresent := cfg.CAPrivateKey()
  1124  	if path, _ := test.attrs["ca-private-key-path"].(string); path != "" {
  1125  		c.Assert(keyPresent, jc.IsTrue)
  1126  		c.Assert(string(key), gc.Equals, home.FileContents(c, path))
  1127  	} else if v, ok := test.attrs["ca-private-key"].(string); v != "" {
  1128  		c.Assert(keyPresent, jc.IsTrue)
  1129  		c.Assert(string(key), gc.Equals, v)
  1130  	} else if ok {
  1131  		c.Check(key, gc.HasLen, 0)
  1132  		c.Assert(keyPresent, jc.IsFalse)
  1133  	} else if bool(test.useDefaults) && home.FileExists(".local/share/juju/my-name-private-key.pem") {
  1134  		c.Assert(keyPresent, jc.IsTrue)
  1135  		c.Assert(string(key), gc.Equals, home.FileContents(c, "my-name-private-key.pem"))
  1136  	} else {
  1137  		c.Check(key, gc.HasLen, 0)
  1138  		c.Assert(keyPresent, jc.IsFalse)
  1139  	}
  1140  
  1141  	if v, ok := test.attrs["ssl-hostname-verification"]; ok {
  1142  		c.Assert(cfg.SSLHostnameVerification(), gc.Equals, v)
  1143  	}
  1144  
  1145  	if v, ok := test.attrs["provisioner-harvest-mode"]; ok {
  1146  		hvstMeth, err := config.ParseHarvestMode(v.(string))
  1147  		c.Assert(err, jc.ErrorIsNil)
  1148  		c.Assert(cfg.ProvisionerHarvestMode(), gc.Equals, hvstMeth)
  1149  	} else {
  1150  		c.Assert(cfg.ProvisionerHarvestMode(), gc.Equals, config.HarvestDestroyed)
  1151  	}
  1152  	sshOpts := cfg.BootstrapSSHOpts()
  1153  	test.assertDuration(
  1154  		c,
  1155  		"bootstrap-timeout",
  1156  		sshOpts.Timeout,
  1157  		config.DefaultBootstrapSSHTimeout,
  1158  	)
  1159  	test.assertDuration(
  1160  		c,
  1161  		"bootstrap-retry-delay",
  1162  		sshOpts.RetryDelay,
  1163  		config.DefaultBootstrapSSHRetryDelay,
  1164  	)
  1165  	test.assertDuration(
  1166  		c,
  1167  		"bootstrap-addresses-delay",
  1168  		sshOpts.AddressesDelay,
  1169  		config.DefaultBootstrapSSHAddressesDelay,
  1170  	)
  1171  
  1172  	if v, ok := test.attrs["image-stream"]; ok {
  1173  		c.Assert(cfg.ImageStream(), gc.Equals, v)
  1174  	} else {
  1175  		c.Assert(cfg.ImageStream(), gc.Equals, "released")
  1176  	}
  1177  
  1178  	url, urlPresent := cfg.ImageMetadataURL()
  1179  	if v, _ := test.attrs["image-metadata-url"].(string); v != "" {
  1180  		c.Assert(url, gc.Equals, v)
  1181  		c.Assert(urlPresent, jc.IsTrue)
  1182  	} else {
  1183  		c.Assert(urlPresent, jc.IsFalse)
  1184  	}
  1185  
  1186  	toolsURL, urlPresent := cfg.AgentMetadataURL()
  1187  	oldToolsURL := cfg.AllAttrs()["tools-metadata-url"]
  1188  	oldToolsURLAttrValue, oldTSTPresent := test.attrs["tools-metadata-url"]
  1189  	expectedToolsURLValue := test.attrs["agent-metadata-url"]
  1190  	expectedToolsURLPresent := true
  1191  	if expectedToolsURLValue == nil || expectedToolsURLValue == "" {
  1192  		if oldTSTPresent {
  1193  			expectedToolsURLValue = oldToolsURLAttrValue
  1194  		} else {
  1195  			expectedToolsURLValue = oldToolsURL
  1196  			expectedToolsURLPresent = false
  1197  		}
  1198  	}
  1199  	c.Assert(toolsURL, gc.Equals, expectedToolsURLValue)
  1200  	c.Assert(urlPresent, gc.Equals, expectedToolsURLPresent)
  1201  
  1202  	// assertions for deprecated tools-stream attribute used with new agent-stream
  1203  	agentStreamValue := cfg.AgentStream()
  1204  	oldTstToolsStreamAttr, oldTstOk := test.attrs["tools-stream"]
  1205  	expectedAgentStreamAttr := test.attrs["agent-stream"]
  1206  
  1207  	// When no agent-stream provided, look for tools-stream
  1208  	if expectedAgentStreamAttr == nil {
  1209  		if oldTstOk {
  1210  			expectedAgentStreamAttr = oldTstToolsStreamAttr
  1211  		} else {
  1212  			// If it's still nil, then hard-coded default is used
  1213  			expectedAgentStreamAttr = "released"
  1214  		}
  1215  	}
  1216  	c.Assert(agentStreamValue, gc.Equals, expectedAgentStreamAttr)
  1217  
  1218  	useLxcClone, useLxcClonePresent := cfg.LXCUseClone()
  1219  	oldUseClone, oldUseClonePresent := cfg.AllAttrs()["lxc-use-clone"]
  1220  	if v, ok := test.attrs["lxc-clone"]; ok {
  1221  		c.Assert(useLxcClone, gc.Equals, v)
  1222  		c.Assert(useLxcClonePresent, jc.IsTrue)
  1223  	} else {
  1224  		if oldUseClonePresent {
  1225  			c.Assert(useLxcClonePresent, jc.IsTrue)
  1226  			c.Assert(useLxcClone, gc.Equals, oldUseClone)
  1227  		} else {
  1228  			c.Assert(useLxcClonePresent, jc.IsFalse)
  1229  			c.Assert(useLxcClone, jc.IsFalse)
  1230  		}
  1231  	}
  1232  	useLxcCloneAufs, ok := cfg.LXCUseCloneAUFS()
  1233  	if v, ok := test.attrs["lxc-clone-aufs"]; ok {
  1234  		c.Assert(useLxcCloneAufs, gc.Equals, v)
  1235  	} else {
  1236  		c.Assert(useLxcCloneAufs, jc.IsFalse)
  1237  	}
  1238  
  1239  	resourceTags, cfgHasResourceTags := cfg.ResourceTags()
  1240  	if _, ok := test.attrs["resource-tags"]; ok {
  1241  		c.Assert(cfgHasResourceTags, jc.IsTrue)
  1242  		c.Assert(resourceTags, jc.DeepEquals, testResourceTagsMap)
  1243  	} else {
  1244  		c.Assert(cfgHasResourceTags, jc.IsFalse)
  1245  	}
  1246  }
  1247  
  1248  func (test configTest) assertDuration(c *gc.C, name string, actual time.Duration, defaultInSeconds int) {
  1249  	value, ok := test.attrs[name].(int)
  1250  	if !ok || value == 0 {
  1251  		c.Assert(actual, gc.Equals, time.Duration(defaultInSeconds)*time.Second)
  1252  	} else {
  1253  		c.Assert(actual, gc.Equals, time.Duration(value)*time.Second)
  1254  	}
  1255  }
  1256  
  1257  func (s *ConfigSuite) TestConfigAttrs(c *gc.C) {
  1258  	// Normally this is handled by gitjujutesting.FakeHome
  1259  	s.PatchEnvironment(osenv.JujuLoggingConfigEnvKey, "")
  1260  	attrs := map[string]interface{}{
  1261  		"type":                      "my-type",
  1262  		"name":                      "my-name",
  1263  		"uuid":                      "90168e4c-2f10-4e9c-83c2-1fb55a58e5a9",
  1264  		"controller-uuid":           "90168e4c-2f10-4e9c-83c2-1fb55a58e5a9",
  1265  		"authorized-keys":           testing.FakeAuthKeys,
  1266  		"firewall-mode":             config.FwInstance,
  1267  		"admin-secret":              "foo",
  1268  		"unknown":                   "my-unknown",
  1269  		"ca-cert":                   caCert,
  1270  		"ssl-hostname-verification": true,
  1271  		"development":               false,
  1272  		"state-port":                1234,
  1273  		"api-port":                  4321,
  1274  		"bootstrap-timeout":         3600,
  1275  		"bootstrap-retry-delay":     30,
  1276  		"bootstrap-addresses-delay": 10,
  1277  		"default-series":            series.LatestLts(),
  1278  		"test-mode":                 false,
  1279  	}
  1280  	cfg, err := config.New(config.NoDefaults, attrs)
  1281  	c.Assert(err, jc.ErrorIsNil)
  1282  
  1283  	// These attributes are added if not set.
  1284  	attrs["development"] = false
  1285  	attrs["logging-config"] = "<root>=WARNING;unit=DEBUG"
  1286  	attrs["ca-private-key"] = ""
  1287  	attrs["image-metadata-url"] = ""
  1288  	attrs["agent-metadata-url"] = ""
  1289  	attrs["tools-metadata-url"] = ""
  1290  	attrs["image-stream"] = ""
  1291  	attrs["proxy-ssh"] = false
  1292  	attrs["lxc-clone-aufs"] = false
  1293  	attrs["prefer-ipv6"] = false
  1294  	attrs["set-numa-control-policy"] = false
  1295  	attrs["allow-lxc-loop-mounts"] = false
  1296  
  1297  	// Default firewall mode is instance
  1298  	attrs["firewall-mode"] = string(config.FwInstance)
  1299  	c.Assert(cfg.AllAttrs(), jc.DeepEquals, attrs)
  1300  	c.Assert(cfg.UnknownAttrs(), jc.DeepEquals, map[string]interface{}{"unknown": "my-unknown"})
  1301  
  1302  	// Verify that default provisioner-harvest-mode is good.
  1303  	c.Assert(cfg.ProvisionerHarvestMode(), gc.Equals, config.HarvestDestroyed)
  1304  
  1305  	newcfg, err := cfg.Apply(map[string]interface{}{
  1306  		"name":            "new-name",
  1307  		"uuid":            "6216dfc3-6e82-408f-9f74-8565e63e6158",
  1308  		"controller-uuid": "6216dfc3-6e82-408f-9f74-8565e63e6158",
  1309  		"new-unknown":     "my-new-unknown",
  1310  	})
  1311  	c.Assert(err, jc.ErrorIsNil)
  1312  
  1313  	attrs["name"] = "new-name"
  1314  	attrs["uuid"] = "6216dfc3-6e82-408f-9f74-8565e63e6158"
  1315  	attrs["controller-uuid"] = "6216dfc3-6e82-408f-9f74-8565e63e6158"
  1316  	attrs["new-unknown"] = "my-new-unknown"
  1317  	c.Assert(newcfg.AllAttrs(), jc.DeepEquals, attrs)
  1318  }
  1319  
  1320  type validationTest struct {
  1321  	about string
  1322  	new   testing.Attrs
  1323  	old   testing.Attrs
  1324  	err   string
  1325  }
  1326  
  1327  var validationTests = []validationTest{{
  1328  	about: "Can't change the type",
  1329  	new:   testing.Attrs{"type": "new-type"},
  1330  	err:   `cannot change type from "my-type" to "new-type"`,
  1331  }, {
  1332  	about: "Can't change the name",
  1333  	new:   testing.Attrs{"name": "new-name"},
  1334  	err:   `cannot change name from "my-name" to "new-name"`,
  1335  }, {
  1336  	about: "Can set agent version",
  1337  	new:   testing.Attrs{"agent-version": "1.9.13"},
  1338  }, {
  1339  	about: "Can change agent version",
  1340  	old:   testing.Attrs{"agent-version": "1.9.13"},
  1341  	new:   testing.Attrs{"agent-version": "1.9.27"},
  1342  }, {
  1343  	about: "Can't clear agent version",
  1344  	old:   testing.Attrs{"agent-version": "1.9.27"},
  1345  	err:   `cannot clear agent-version`,
  1346  }, {
  1347  	about: "Can't change the firewall-mode (global->instance)",
  1348  	old:   testing.Attrs{"firewall-mode": config.FwGlobal},
  1349  	new:   testing.Attrs{"firewall-mode": config.FwInstance},
  1350  	err:   `cannot change firewall-mode from "global" to "instance"`,
  1351  }, {
  1352  	about: "Can't change the firewall-mode (global->none)",
  1353  	old:   testing.Attrs{"firewall-mode": config.FwGlobal},
  1354  	new:   testing.Attrs{"firewall-mode": config.FwNone},
  1355  	err:   `cannot change firewall-mode from "global" to "none"`,
  1356  }, {
  1357  	about: "Cannot change the state-port",
  1358  	old:   testing.Attrs{"state-port": config.DefaultStatePort},
  1359  	new:   testing.Attrs{"state-port": 42},
  1360  	err:   `cannot change state-port from 37017 to 42`,
  1361  }, {
  1362  	about: "Cannot change the api-port",
  1363  	old:   testing.Attrs{"api-port": config.DefaultAPIPort},
  1364  	new:   testing.Attrs{"api-port": 42},
  1365  	err:   `cannot change api-port from 17070 to 42`,
  1366  }, {
  1367  	about: "Can change the state-port from explicit-default to implicit-default",
  1368  	old:   testing.Attrs{"state-port": config.DefaultStatePort},
  1369  }, {
  1370  	about: "Can change the api-port from explicit-default to implicit-default",
  1371  	old:   testing.Attrs{"api-port": config.DefaultAPIPort},
  1372  }, {
  1373  	about: "Can change the state-port from implicit-default to explicit-default",
  1374  	new:   testing.Attrs{"state-port": config.DefaultStatePort},
  1375  }, {
  1376  	about: "Can change the api-port from implicit-default to explicit-default",
  1377  	new:   testing.Attrs{"api-port": config.DefaultAPIPort},
  1378  }, {
  1379  	about: "Cannot change the state-port from implicit-default to different value",
  1380  	new:   testing.Attrs{"state-port": 42},
  1381  	err:   `cannot change state-port from 37017 to 42`,
  1382  }, {
  1383  	about: "Cannot change the api-port from implicit-default to different value",
  1384  	new:   testing.Attrs{"api-port": 42},
  1385  	err:   `cannot change api-port from 17070 to 42`,
  1386  }, {
  1387  	about: "Cannot change the bootstrap-timeout from implicit-default to different value",
  1388  	new:   testing.Attrs{"bootstrap-timeout": 5},
  1389  	err:   `cannot change bootstrap-timeout from 600 to 5`,
  1390  }, {
  1391  	about: "Cannot change lxc-clone",
  1392  	old:   testing.Attrs{"lxc-clone": false},
  1393  	new:   testing.Attrs{"lxc-clone": true},
  1394  	err:   `cannot change lxc-clone from false to true`,
  1395  }, {
  1396  	about: "Cannot change lxc-clone-aufs",
  1397  	old:   testing.Attrs{"lxc-clone-aufs": false},
  1398  	new:   testing.Attrs{"lxc-clone-aufs": true},
  1399  	err:   `cannot change lxc-clone-aufs from false to true`,
  1400  }, {
  1401  	about: "Cannot change lxc-default-mtu",
  1402  	old:   testing.Attrs{"lxc-default-mtu": 9000},
  1403  	new:   testing.Attrs{"lxc-default-mtu": 42},
  1404  	err:   `cannot change lxc-default-mtu from 9000 to 42`,
  1405  }, {
  1406  	about: "Cannot change prefer-ipv6",
  1407  	old:   testing.Attrs{"prefer-ipv6": false},
  1408  	new:   testing.Attrs{"prefer-ipv6": true},
  1409  	err:   `cannot change prefer-ipv6 from false to true`,
  1410  }, {
  1411  	about: "Cannot change uuid",
  1412  	old:   testing.Attrs{"uuid": "90168e4c-2f10-4e9c-83c2-1fb55a58e5a9"},
  1413  	new:   testing.Attrs{"uuid": "dcfbdb4a-bca2-49ad-aa7c-f011424e0fe4"},
  1414  	err:   "cannot change uuid from \"90168e4c-2f10-4e9c-83c2-1fb55a58e5a9\" to \"dcfbdb4a-bca2-49ad-aa7c-f011424e0fe4\"",
  1415  }, {
  1416  	about: "Cannot change controller-uuid",
  1417  	old:   testing.Attrs{"controller-uuid": "90168e4c-2f10-4e9c-83c2-1fb55a58e5a9"},
  1418  	new:   testing.Attrs{"controller-uuid": "dcfbdb4a-bca2-49ad-aa7c-f011424e0fe4"},
  1419  	err:   "cannot change controller-uuid from \"90168e4c-2f10-4e9c-83c2-1fb55a58e5a9\" to \"dcfbdb4a-bca2-49ad-aa7c-f011424e0fe4\"",
  1420  }}
  1421  
  1422  func (s *ConfigSuite) TestValidateChange(c *gc.C) {
  1423  	files := []gitjujutesting.TestFile{
  1424  		{".ssh/identity.pub", "identity"},
  1425  	}
  1426  	s.FakeHomeSuite.Home.AddFiles(c, files...)
  1427  
  1428  	for i, test := range validationTests {
  1429  		c.Logf("test %d: %s", i, test.about)
  1430  		newConfig := newTestConfig(c, test.new)
  1431  		oldConfig := newTestConfig(c, test.old)
  1432  		err := config.Validate(newConfig, oldConfig)
  1433  		if test.err == "" {
  1434  			c.Check(err, jc.ErrorIsNil)
  1435  		} else {
  1436  			c.Check(err, gc.ErrorMatches, test.err)
  1437  		}
  1438  	}
  1439  }
  1440  
  1441  func (s *ConfigSuite) addJujuFiles(c *gc.C) {
  1442  	s.FakeHomeSuite.Home.AddFiles(c, []gitjujutesting.TestFile{
  1443  		{".ssh/id_rsa.pub", "rsa\n"},
  1444  		{".local/share/juju/myenv-cert.pem", caCert},
  1445  		{".local/share/juju/myenv-private-key.pem", caKey},
  1446  	}...)
  1447  }
  1448  
  1449  func (s *ConfigSuite) TestValidateUnknownAttrs(c *gc.C) {
  1450  	s.addJujuFiles(c)
  1451  	cfg, err := config.New(config.UseDefaults, map[string]interface{}{
  1452  		"name":            "myenv",
  1453  		"type":            "other",
  1454  		"uuid":            testing.ModelTag.Id(),
  1455  		"controller-uuid": testing.ModelTag.Id(),
  1456  		"known":           "this",
  1457  		"unknown":         "that",
  1458  	})
  1459  	c.Assert(err, jc.ErrorIsNil)
  1460  
  1461  	// No fields: all attrs passed through.
  1462  	attrs, err := cfg.ValidateUnknownAttrs(nil, nil)
  1463  	c.Assert(err, jc.ErrorIsNil)
  1464  	c.Assert(attrs, gc.DeepEquals, map[string]interface{}{
  1465  		"known":   "this",
  1466  		"unknown": "that",
  1467  	})
  1468  
  1469  	// Valid field: that and other attrs passed through.
  1470  	fields := schema.Fields{"known": schema.String()}
  1471  	attrs, err = cfg.ValidateUnknownAttrs(fields, nil)
  1472  	c.Assert(err, jc.ErrorIsNil)
  1473  	c.Assert(attrs, gc.DeepEquals, map[string]interface{}{
  1474  		"known":   "this",
  1475  		"unknown": "that",
  1476  	})
  1477  
  1478  	// Default field: inserted.
  1479  	fields["default"] = schema.String()
  1480  	defaults := schema.Defaults{"default": "the other"}
  1481  	attrs, err = cfg.ValidateUnknownAttrs(fields, defaults)
  1482  	c.Assert(err, jc.ErrorIsNil)
  1483  	c.Assert(attrs, gc.DeepEquals, map[string]interface{}{
  1484  		"known":   "this",
  1485  		"unknown": "that",
  1486  		"default": "the other",
  1487  	})
  1488  
  1489  	// Invalid field: failure.
  1490  	fields["known"] = schema.Int()
  1491  	_, err = cfg.ValidateUnknownAttrs(fields, defaults)
  1492  	c.Assert(err, gc.ErrorMatches, `known: expected int, got string\("this"\)`)
  1493  }
  1494  
  1495  type testAttr struct {
  1496  	message string
  1497  	aKey    string
  1498  	aValue  string
  1499  	checker gc.Checker
  1500  }
  1501  
  1502  var emptyAttributeTests = []testAttr{
  1503  	{
  1504  		message: "Warning message about unknown attribute (%v) is expected because attribute value exists",
  1505  		aKey:    "unknown",
  1506  		aValue:  "unknown value",
  1507  		checker: gc.Matches,
  1508  	}, {
  1509  		message: "Warning message about unknown attribute (%v) is unexpected because attribute value is empty",
  1510  		aKey:    "unknown-empty",
  1511  		aValue:  "",
  1512  		checker: gc.Not(gc.Matches),
  1513  	},
  1514  }
  1515  
  1516  func (s *ConfigSuite) TestValidateUnknownEmptyAttr(c *gc.C) {
  1517  	s.addJujuFiles(c)
  1518  	cfg, err := config.New(config.UseDefaults, map[string]interface{}{
  1519  		"name":            "myenv",
  1520  		"type":            "other",
  1521  		"uuid":            testing.ModelTag.Id(),
  1522  		"controller-uuid": testing.ModelTag.Id(),
  1523  	})
  1524  	c.Assert(err, jc.ErrorIsNil)
  1525  	warningTxt := `.* unknown config field %q.*`
  1526  
  1527  	for i, test := range emptyAttributeTests {
  1528  		c.Logf("test %d: %v\n", i, fmt.Sprintf(test.message, test.aKey))
  1529  		testCfg, err := cfg.Apply(map[string]interface{}{test.aKey: test.aValue})
  1530  		c.Assert(err, jc.ErrorIsNil)
  1531  		attrs, err := testCfg.ValidateUnknownAttrs(nil, nil)
  1532  		c.Assert(err, jc.ErrorIsNil)
  1533  		// all attrs passed through
  1534  		c.Assert(attrs, gc.DeepEquals, map[string]interface{}{test.aKey: test.aValue})
  1535  		expectedWarning := fmt.Sprintf(warningTxt, test.aKey)
  1536  		logOutputText := strings.Replace(c.GetTestLog(), "\n", "", -1)
  1537  		// warning displayed or not based on test expectation
  1538  		c.Assert(logOutputText, test.checker, expectedWarning, gc.Commentf(test.message, test.aKey))
  1539  	}
  1540  }
  1541  
  1542  func newTestConfig(c *gc.C, explicit testing.Attrs) *config.Config {
  1543  	final := testing.Attrs{
  1544  		"type": "my-type", "name": "my-name",
  1545  		"uuid":            testing.ModelTag.Id(),
  1546  		"controller-uuid": testing.ModelTag.Id(),
  1547  	}
  1548  	for key, value := range explicit {
  1549  		final[key] = value
  1550  	}
  1551  	result, err := config.New(config.UseDefaults, final)
  1552  	c.Assert(err, jc.ErrorIsNil)
  1553  	return result
  1554  }
  1555  
  1556  func (s *ConfigSuite) TestLoggingConfig(c *gc.C) {
  1557  	s.addJujuFiles(c)
  1558  	config := newTestConfig(c, testing.Attrs{
  1559  		"logging-config": "<root>=WARNING;juju=DEBUG"})
  1560  	c.Assert(config.LoggingConfig(), gc.Equals, "<root>=WARNING;juju=DEBUG;unit=DEBUG")
  1561  }
  1562  
  1563  func (s *ConfigSuite) TestLoggingConfigWithUnit(c *gc.C) {
  1564  	s.addJujuFiles(c)
  1565  	config := newTestConfig(c, testing.Attrs{
  1566  		"logging-config": "<root>=WARNING;unit=INFO"})
  1567  	c.Assert(config.LoggingConfig(), gc.Equals, "<root>=WARNING;unit=INFO")
  1568  }
  1569  
  1570  func (s *ConfigSuite) TestLoggingConfigFromEnvironment(c *gc.C) {
  1571  	s.addJujuFiles(c)
  1572  	s.PatchEnvironment(osenv.JujuLoggingConfigEnvKey, "<root>=INFO")
  1573  
  1574  	config := newTestConfig(c, nil)
  1575  	c.Assert(config.LoggingConfig(), gc.Equals, "<root>=INFO;unit=DEBUG")
  1576  }
  1577  
  1578  func (s *ConfigSuite) TestAutoHookRetryDefault(c *gc.C) {
  1579  	config := newTestConfig(c, testing.Attrs{})
  1580  	c.Assert(config.AutomaticallyRetryHooks(), gc.Equals, true)
  1581  }
  1582  
  1583  func (s *ConfigSuite) TestAutoHookRetryFalseEnv(c *gc.C) {
  1584  	config := newTestConfig(c, testing.Attrs{
  1585  		"automatically-retry-hooks": "false"})
  1586  	c.Assert(config.AutomaticallyRetryHooks(), gc.Equals, false)
  1587  }
  1588  
  1589  func (s *ConfigSuite) TestAutoHookRetryTrueEnv(c *gc.C) {
  1590  	config := newTestConfig(c, testing.Attrs{
  1591  		"automatically-retry-hooks": "true"})
  1592  	c.Assert(config.AutomaticallyRetryHooks(), gc.Equals, true)
  1593  }
  1594  
  1595  func (s *ConfigSuite) TestCloudImageBaseURL(c *gc.C) {
  1596  	s.addJujuFiles(c)
  1597  	config := newTestConfig(c, testing.Attrs{})
  1598  	c.Assert(config.CloudImageBaseURL(), gc.Equals, "")
  1599  }
  1600  
  1601  func (s *ConfigSuite) TestCloudImageBaseURLSet(c *gc.C) {
  1602  	s.addJujuFiles(c)
  1603  	config := newTestConfig(c, testing.Attrs{
  1604  		"cloudimg-base-url": "http://local.foo/query"})
  1605  	c.Assert(config.CloudImageBaseURL(), gc.Equals, "http://local.foo/query")
  1606  }
  1607  
  1608  func (s *ConfigSuite) TestProxyValuesWithFallback(c *gc.C) {
  1609  	s.addJujuFiles(c)
  1610  
  1611  	config := newTestConfig(c, testing.Attrs{
  1612  		"http-proxy":  "http://user@10.0.0.1",
  1613  		"https-proxy": "https://user@10.0.0.1",
  1614  		"ftp-proxy":   "ftp://user@10.0.0.1",
  1615  		"no-proxy":    "localhost,10.0.3.1",
  1616  	})
  1617  	c.Assert(config.HttpProxy(), gc.Equals, "http://user@10.0.0.1")
  1618  	c.Assert(config.AptHttpProxy(), gc.Equals, "http://user@10.0.0.1")
  1619  	c.Assert(config.HttpsProxy(), gc.Equals, "https://user@10.0.0.1")
  1620  	c.Assert(config.AptHttpsProxy(), gc.Equals, "https://user@10.0.0.1")
  1621  	c.Assert(config.FtpProxy(), gc.Equals, "ftp://user@10.0.0.1")
  1622  	c.Assert(config.AptFtpProxy(), gc.Equals, "ftp://user@10.0.0.1")
  1623  	c.Assert(config.NoProxy(), gc.Equals, "localhost,10.0.3.1")
  1624  }
  1625  
  1626  func (s *ConfigSuite) TestProxyValuesWithFallbackNoScheme(c *gc.C) {
  1627  	s.addJujuFiles(c)
  1628  
  1629  	config := newTestConfig(c, testing.Attrs{
  1630  		"http-proxy":  "user@10.0.0.1",
  1631  		"https-proxy": "user@10.0.0.1",
  1632  		"ftp-proxy":   "user@10.0.0.1",
  1633  		"no-proxy":    "localhost,10.0.3.1",
  1634  	})
  1635  	c.Assert(config.HttpProxy(), gc.Equals, "user@10.0.0.1")
  1636  	c.Assert(config.AptHttpProxy(), gc.Equals, "http://user@10.0.0.1")
  1637  	c.Assert(config.HttpsProxy(), gc.Equals, "user@10.0.0.1")
  1638  	c.Assert(config.AptHttpsProxy(), gc.Equals, "https://user@10.0.0.1")
  1639  	c.Assert(config.FtpProxy(), gc.Equals, "user@10.0.0.1")
  1640  	c.Assert(config.AptFtpProxy(), gc.Equals, "ftp://user@10.0.0.1")
  1641  	c.Assert(config.NoProxy(), gc.Equals, "localhost,10.0.3.1")
  1642  }
  1643  
  1644  func (s *ConfigSuite) TestProxyValues(c *gc.C) {
  1645  	s.addJujuFiles(c)
  1646  	config := newTestConfig(c, testing.Attrs{
  1647  		"http-proxy":      "http://user@10.0.0.1",
  1648  		"https-proxy":     "https://user@10.0.0.1",
  1649  		"ftp-proxy":       "ftp://user@10.0.0.1",
  1650  		"apt-http-proxy":  "http://user@10.0.0.2",
  1651  		"apt-https-proxy": "https://user@10.0.0.2",
  1652  		"apt-ftp-proxy":   "ftp://user@10.0.0.2",
  1653  	})
  1654  	c.Assert(config.HttpProxy(), gc.Equals, "http://user@10.0.0.1")
  1655  	c.Assert(config.AptHttpProxy(), gc.Equals, "http://user@10.0.0.2")
  1656  	c.Assert(config.HttpsProxy(), gc.Equals, "https://user@10.0.0.1")
  1657  	c.Assert(config.AptHttpsProxy(), gc.Equals, "https://user@10.0.0.2")
  1658  	c.Assert(config.FtpProxy(), gc.Equals, "ftp://user@10.0.0.1")
  1659  	c.Assert(config.AptFtpProxy(), gc.Equals, "ftp://user@10.0.0.2")
  1660  }
  1661  
  1662  func (s *ConfigSuite) TestProxyValuesNotSet(c *gc.C) {
  1663  	s.addJujuFiles(c)
  1664  	config := newTestConfig(c, testing.Attrs{})
  1665  	c.Assert(config.HttpProxy(), gc.Equals, "")
  1666  	c.Assert(config.AptHttpProxy(), gc.Equals, "")
  1667  	c.Assert(config.HttpsProxy(), gc.Equals, "")
  1668  	c.Assert(config.AptHttpsProxy(), gc.Equals, "")
  1669  	c.Assert(config.FtpProxy(), gc.Equals, "")
  1670  	c.Assert(config.AptFtpProxy(), gc.Equals, "")
  1671  	c.Assert(config.NoProxy(), gc.Equals, "")
  1672  }
  1673  
  1674  func (s *ConfigSuite) TestProxyConfigMap(c *gc.C) {
  1675  	s.addJujuFiles(c)
  1676  	cfg := newTestConfig(c, testing.Attrs{})
  1677  	proxySettings := proxy.Settings{
  1678  		Http:    "http proxy",
  1679  		Https:   "https proxy",
  1680  		Ftp:     "ftp proxy",
  1681  		NoProxy: "no proxy",
  1682  	}
  1683  	expectedProxySettings := proxy.Settings{
  1684  		Http:    "http://http proxy",
  1685  		Https:   "https://https proxy",
  1686  		Ftp:     "ftp://ftp proxy",
  1687  		NoProxy: "",
  1688  	}
  1689  	cfg, err := cfg.Apply(config.ProxyConfigMap(proxySettings))
  1690  	c.Assert(err, jc.ErrorIsNil)
  1691  	c.Assert(cfg.ProxySettings(), gc.DeepEquals, proxySettings)
  1692  	// Apt proxy settings always include the scheme. NoProxy is empty.
  1693  	c.Assert(cfg.AptProxySettings(), gc.DeepEquals, expectedProxySettings)
  1694  }
  1695  
  1696  func (s *ConfigSuite) TestAptProxyConfigMap(c *gc.C) {
  1697  	s.addJujuFiles(c)
  1698  	cfg := newTestConfig(c, testing.Attrs{})
  1699  	proxySettings := proxy.Settings{
  1700  		Http:  "http://httpproxy",
  1701  		Https: "https://httpsproxy",
  1702  		Ftp:   "ftp://ftpproxy",
  1703  	}
  1704  	cfg, err := cfg.Apply(config.AptProxyConfigMap(proxySettings))
  1705  	c.Assert(err, jc.ErrorIsNil)
  1706  	// The default proxy settings should still be empty.
  1707  	c.Assert(cfg.ProxySettings(), gc.DeepEquals, proxy.Settings{})
  1708  	c.Assert(cfg.AptProxySettings(), gc.DeepEquals, proxySettings)
  1709  }
  1710  
  1711  func (s *ConfigSuite) TestSchemaNoExtra(c *gc.C) {
  1712  	schema, err := config.Schema(nil)
  1713  	c.Assert(err, gc.IsNil)
  1714  	orig := config.ConfigSchema
  1715  	c.Assert(schema, jc.DeepEquals, orig)
  1716  	// Check that we actually returned a copy, not the original.
  1717  	schema["foo"] = environschema.Attr{}
  1718  	_, ok := orig["foo"]
  1719  	c.Assert(ok, jc.IsFalse)
  1720  }
  1721  
  1722  func (s *ConfigSuite) TestSchemaWithExtraFields(c *gc.C) {
  1723  	extraField := environschema.Attr{
  1724  		Description: "fooish",
  1725  		Type:        environschema.Tstring,
  1726  	}
  1727  	schema, err := config.Schema(environschema.Fields{
  1728  		"foo": extraField,
  1729  	})
  1730  	c.Assert(err, gc.IsNil)
  1731  	c.Assert(schema["foo"], gc.DeepEquals, extraField)
  1732  	delete(schema, "foo")
  1733  	orig := config.ConfigSchema
  1734  	c.Assert(schema, jc.DeepEquals, orig)
  1735  }
  1736  
  1737  func (s *ConfigSuite) TestSchemaWithExtraOverlap(c *gc.C) {
  1738  	schema, err := config.Schema(environschema.Fields{
  1739  		"type": environschema.Attr{
  1740  			Description: "duplicate",
  1741  			Type:        environschema.Tstring,
  1742  		},
  1743  	})
  1744  	c.Assert(err, gc.ErrorMatches, `config field "type" clashes with global config`)
  1745  	c.Assert(schema, gc.IsNil)
  1746  }
  1747  
  1748  func (s *ConfigSuite) TestGenerateControllerCertAndKey(c *gc.C) {
  1749  	// Add a cert.
  1750  	s.FakeHomeSuite.Home.AddFiles(c, gitjujutesting.TestFile{".ssh/id_rsa.pub", "rsa\n"})
  1751  
  1752  	for _, test := range []struct {
  1753  		configValues map[string]interface{}
  1754  		sanValues    []string
  1755  		errMatch     string
  1756  	}{{
  1757  		configValues: map[string]interface{}{
  1758  			"name":            "test-no-certs",
  1759  			"type":            "dummy",
  1760  			"uuid":            testing.ModelTag.Id(),
  1761  			"controller-uuid": testing.ModelTag.Id(),
  1762  		},
  1763  		errMatch: "model configuration has no ca-cert",
  1764  	}, {
  1765  		configValues: map[string]interface{}{
  1766  			"name":            "test-no-certs",
  1767  			"type":            "dummy",
  1768  			"uuid":            testing.ModelTag.Id(),
  1769  			"controller-uuid": testing.ModelTag.Id(),
  1770  			"ca-cert":         testing.CACert,
  1771  		},
  1772  		errMatch: "model configuration has no ca-private-key",
  1773  	}, {
  1774  		configValues: map[string]interface{}{
  1775  			"name":            "test-no-certs",
  1776  			"type":            "dummy",
  1777  			"uuid":            testing.ModelTag.Id(),
  1778  			"controller-uuid": testing.ModelTag.Id(),
  1779  			"ca-cert":         testing.CACert,
  1780  			"ca-private-key":  testing.CAKey,
  1781  		},
  1782  	}, {
  1783  		configValues: map[string]interface{}{
  1784  			"name":            "test-no-certs",
  1785  			"type":            "dummy",
  1786  			"uuid":            testing.ModelTag.Id(),
  1787  			"controller-uuid": testing.ModelTag.Id(),
  1788  			"ca-cert":         testing.CACert,
  1789  			"ca-private-key":  testing.CAKey,
  1790  		},
  1791  		sanValues: []string{"10.0.0.1", "192.168.1.1"},
  1792  	}} {
  1793  		cfg, err := config.New(config.UseDefaults, test.configValues)
  1794  		c.Assert(err, jc.ErrorIsNil)
  1795  		certPEM, keyPEM, err := cfg.GenerateControllerCertAndKey(test.sanValues)
  1796  		if test.errMatch == "" {
  1797  			c.Assert(err, jc.ErrorIsNil)
  1798  
  1799  			_, _, err = cert.ParseCertAndKey(certPEM, keyPEM)
  1800  			c.Check(err, jc.ErrorIsNil)
  1801  
  1802  			err = cert.Verify(certPEM, testing.CACert, time.Now())
  1803  			c.Assert(err, jc.ErrorIsNil)
  1804  			err = cert.Verify(certPEM, testing.CACert, time.Now().AddDate(9, 0, 0))
  1805  			c.Assert(err, jc.ErrorIsNil)
  1806  			err = cert.Verify(certPEM, testing.CACert, time.Now().AddDate(10, 0, 1))
  1807  			c.Assert(err, gc.NotNil)
  1808  			srvCert, err := cert.ParseCert(certPEM)
  1809  			c.Assert(err, jc.ErrorIsNil)
  1810  			sanIPs := make([]string, len(srvCert.IPAddresses))
  1811  			for i, ip := range srvCert.IPAddresses {
  1812  				sanIPs[i] = ip.String()
  1813  			}
  1814  			c.Assert(sanIPs, jc.SameContents, test.sanValues)
  1815  		} else {
  1816  			c.Assert(err, gc.ErrorMatches, test.errMatch)
  1817  			c.Assert(certPEM, gc.Equals, "")
  1818  			c.Assert(keyPEM, gc.Equals, "")
  1819  		}
  1820  	}
  1821  }
  1822  
  1823  var specializeCharmRepoTests = []struct {
  1824  	about    string
  1825  	testMode bool
  1826  	repo     charmrepo.Interface
  1827  }{{
  1828  	about: "test mode disabled, charm store",
  1829  	repo:  &specializedCharmRepo{},
  1830  }, {
  1831  	about:    "test mode enabled, charm store",
  1832  	testMode: true,
  1833  	repo:     &specializedCharmRepo{},
  1834  }}
  1835  
  1836  func (s *ConfigSuite) TestSpecializeCharmRepo(c *gc.C) {
  1837  	for i, test := range specializeCharmRepoTests {
  1838  		c.Logf("test %d: %s", i, test.about)
  1839  		cfg := newTestConfig(c, testing.Attrs{"test-mode": test.testMode})
  1840  		repo := config.SpecializeCharmRepo(test.repo, cfg)
  1841  		store := repo.(*specializedCharmRepo)
  1842  		c.Assert(store.testMode, gc.Equals, test.testMode)
  1843  	}
  1844  }
  1845  
  1846  type specializedCharmRepo struct {
  1847  	*charmrepo.CharmStore
  1848  	testMode bool
  1849  }
  1850  
  1851  func (s *specializedCharmRepo) WithTestMode() charmrepo.Interface {
  1852  	s.testMode = true
  1853  	return s
  1854  }
  1855  
  1856  var caCert = `
  1857  -----BEGIN CERTIFICATE-----
  1858  MIIBjDCCATigAwIBAgIBADALBgkqhkiG9w0BAQUwHjENMAsGA1UEChMEanVqdTEN
  1859  MAsGA1UEAxMEcm9vdDAeFw0xMjExMDkxNjQwMjhaFw0yMjExMDkxNjQ1MjhaMB4x
  1860  DTALBgNVBAoTBGp1anUxDTALBgNVBAMTBHJvb3QwWTALBgkqhkiG9w0BAQEDSgAw
  1861  RwJAduA1Gnb2VJLxNGfG4St0Qy48Y3q5Z5HheGtTGmti/FjlvQvScCFGCnJG7fKA
  1862  Knd7ia3vWg7lxYkIvMPVP88LAQIDAQABo2YwZDAOBgNVHQ8BAf8EBAMCAKQwEgYD
  1863  VR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUlvKX8vwp0o+VdhdhoA9O6KlOm00w
  1864  HwYDVR0jBBgwFoAUlvKX8vwp0o+VdhdhoA9O6KlOm00wCwYJKoZIhvcNAQEFA0EA
  1865  LlNpevtFr8gngjAFFAO/FXc7KiZcCrA5rBfb/rEy297lIqmKt5++aVbLEPyxCIFC
  1866  r71Sj63TUTFWtRZAxvn9qQ==
  1867  -----END CERTIFICATE-----
  1868  `[1:]
  1869  
  1870  var caKey = `
  1871  -----BEGIN RSA PRIVATE KEY-----
  1872  MIIBOQIBAAJAduA1Gnb2VJLxNGfG4St0Qy48Y3q5Z5HheGtTGmti/FjlvQvScCFG
  1873  CnJG7fKAKnd7ia3vWg7lxYkIvMPVP88LAQIDAQABAkEAsFOdMSYn+AcF1M/iBfjo
  1874  uQWJ+Zz+CgwuvumjGNsUtmwxjA+hh0fCn0Ah2nAt4Ma81vKOKOdQ8W6bapvsVDH0
  1875  6QIhAJOkLmEKm4H5POQV7qunRbRsLbft/n/SHlOBz165WFvPAiEAzh9fMf70std1
  1876  sVCHJRQWKK+vw3oaEvPKvkPiV5ui0C8CIGNsvybuo8ald5IKCw5huRlFeIxSo36k
  1877  m3OVCXc6zfwVAiBnTUe7WcivPNZqOC6TAZ8dYvdWo4Ifz3jjpEfymjid1wIgBIJv
  1878  ERPyv2NQqIFQZIyzUP7LVRIWfpFFOo9/Ww/7s5Y=
  1879  -----END RSA PRIVATE KEY-----
  1880  `[1:]
  1881  
  1882  var caCert2 = `
  1883  -----BEGIN CERTIFICATE-----
  1884  MIIBjTCCATmgAwIBAgIBADALBgkqhkiG9w0BAQUwHjENMAsGA1UEChMEanVqdTEN
  1885  MAsGA1UEAxMEcm9vdDAeFw0xMjExMDkxNjQxMDhaFw0yMjExMDkxNjQ2MDhaMB4x
  1886  DTALBgNVBAoTBGp1anUxDTALBgNVBAMTBHJvb3QwWjALBgkqhkiG9w0BAQEDSwAw
  1887  SAJBAJkSWRrr81y8pY4dbNgt+8miSKg4z6glp2KO2NnxxAhyyNtQHKvC+fJALJj+
  1888  C2NhuvOv9xImxOl3Hg8fFPCXCtcCAwEAAaNmMGQwDgYDVR0PAQH/BAQDAgCkMBIG
  1889  A1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFOsX/ZCqKzWCAaTTVcWsWKT5Msow
  1890  MB8GA1UdIwQYMBaAFOsX/ZCqKzWCAaTTVcWsWKT5MsowMAsGCSqGSIb3DQEBBQNB
  1891  AAVV57jetEzJQnjgBzhvx/UwauFn78jGhXfV5BrQmxIb4SF4DgSCFstPwUQOAr8h
  1892  XXzJqBQH92KYmp+y3YXDoMQ=
  1893  -----END CERTIFICATE-----
  1894  `[1:]
  1895  
  1896  var caKey2 = `
  1897  -----BEGIN RSA PRIVATE KEY-----
  1898  MIIBOQIBAAJBAJkSWRrr81y8pY4dbNgt+8miSKg4z6glp2KO2NnxxAhyyNtQHKvC
  1899  +fJALJj+C2NhuvOv9xImxOl3Hg8fFPCXCtcCAwEAAQJATQNzO11NQvJS5U6eraFt
  1900  FgSFQ8XZjILtVWQDbJv8AjdbEgKMHEy33icsAKIUAx8jL9kjq6K9kTdAKXZi9grF
  1901  UQIhAPD7jccIDUVm785E5eR9eisq0+xpgUIa24Jkn8cAlst5AiEAopxVFl1auer3
  1902  GP2In3pjdL4ydzU/gcRcYisoJqwHpM8CIHtqmaXBPeq5WT9ukb5/dL3+5SJCtmxA
  1903  jQMuvZWRe6khAiBvMztYtPSDKXRbCZ4xeQ+kWSDHtok8Y5zNoTeu4nvDrwIgb3Al
  1904  fikzPveC5g6S6OvEQmyDz59tYBubm2XHgvxqww0=
  1905  -----END RSA PRIVATE KEY-----
  1906  `[1:]
  1907  
  1908  var caCert3 = `
  1909  -----BEGIN CERTIFICATE-----
  1910  MIIBjTCCATmgAwIBAgIBADALBgkqhkiG9w0BAQUwHjENMAsGA1UEChMEanVqdTEN
  1911  MAsGA1UEAxMEcm9vdDAeFw0xMjExMDkxNjQxMjlaFw0yMjExMDkxNjQ2MjlaMB4x
  1912  DTALBgNVBAoTBGp1anUxDTALBgNVBAMTBHJvb3QwWjALBgkqhkiG9w0BAQEDSwAw
  1913  SAJBAIW7CbHFJivvV9V6mO8AGzJS9lqjUf6MdEPsdF6wx2Cpzr/lSFIggCwRA138
  1914  9MuFxflxb/3U8Nq+rd8rVtTgFMECAwEAAaNmMGQwDgYDVR0PAQH/BAQDAgCkMBIG
  1915  A1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFJafrxqByMN9BwGfcmuF0Lw/1QII
  1916  MB8GA1UdIwQYMBaAFJafrxqByMN9BwGfcmuF0Lw/1QIIMAsGCSqGSIb3DQEBBQNB
  1917  AHq3vqNhxya3s33DlQfSj9whsnqM0Nm+u8mBX/T76TF5rV7+B33XmYzSyfA3yBi/
  1918  zHaUR/dbHuiNTO+KXs3/+Y4=
  1919  -----END CERTIFICATE-----
  1920  `[1:]
  1921  
  1922  var caKey3 = `
  1923  -----BEGIN RSA PRIVATE KEY-----
  1924  MIIBOgIBAAJBAIW7CbHFJivvV9V6mO8AGzJS9lqjUf6MdEPsdF6wx2Cpzr/lSFIg
  1925  gCwRA1389MuFxflxb/3U8Nq+rd8rVtTgFMECAwEAAQJAaivPi4qJPrJb2onl50H/
  1926  VZnWKqmljGF4YQDWduMEt7GTPk+76x9SpO7W4gfY490Ivd9DEXfbr/KZqhwWikNw
  1927  LQIhALlLfRXLF2ZfToMfB1v1v+jith5onAu24O68mkdRc5PLAiEAuMJ/6U07hggr
  1928  Ckf9OT93wh84DK66h780HJ/FUHKcoCMCIDsPZaJBpoa50BOZG0ZjcTTwti3BGCPf
  1929  uZg+w0oCGz27AiEAsUCYKqEXy/ymHhT2kSecozYENdajyXvcaOG3EPkD3nUCICOP
  1930  zatzs7c/4mx4a0JBG6Za0oEPUcm2I34is50KSohz
  1931  -----END RSA PRIVATE KEY-----
  1932  `[1:]
  1933  
  1934  var invalidCAKey = `
  1935  -----BEGIN RSA PRIVATE KEY-----
  1936  MIIBOgIBAAJAZabKgKInuOxj5vDWLwHHQtK3/45KB+32D15w94Nt83BmuGxo90lw
  1937  -----END RSA PRIVATE KEY-----
  1938  `[1:]
  1939  
  1940  var invalidCACert = `
  1941  -----BEGIN CERTIFICATE-----
  1942  MIIBOgIBAAJAZabKgKInuOxj5vDWLwHHQtK3/45KB+32D15w94Nt83BmuGxo90lw
  1943  -----END CERTIFICATE-----
  1944  `[1:]