github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/environs/config_test.go (about)

     1  // Copyright 2011, 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package environs_test
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/juju/loggo"
    15  	gitjujutesting "github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	"github.com/juju/juju/environs"
    20  	"github.com/juju/juju/environs/config"
    21  	"github.com/juju/juju/provider/dummy"
    22  	_ "github.com/juju/juju/provider/manual"
    23  	"github.com/juju/juju/testing"
    24  )
    25  
    26  type suite struct {
    27  	testing.FakeJujuHomeSuite
    28  }
    29  
    30  var _ = gc.Suite(&suite{})
    31  
    32  func (s *suite) TearDownTest(c *gc.C) {
    33  	dummy.Reset()
    34  	s.FakeJujuHomeSuite.TearDownTest(c)
    35  }
    36  
    37  // dummySampleConfig returns the dummy sample config without
    38  // the state server configured.
    39  // This function also exists in cloudconfig/userdata_test
    40  // Maybe place it in dummy and export it?
    41  func dummySampleConfig() testing.Attrs {
    42  	return dummy.SampleConfig().Merge(testing.Attrs{
    43  		"state-server": false,
    44  	})
    45  }
    46  
    47  var invalidConfigTests = []struct {
    48  	env string
    49  	err string
    50  }{
    51  	{"'", "YAML error:.*"},
    52  	{`
    53  default: unknown
    54  environments:
    55      only:
    56          type: unknown
    57  `, `default environment .* does not exist`,
    58  	},
    59  }
    60  
    61  func (*suite) TestInvalidConfig(c *gc.C) {
    62  	for i, t := range invalidConfigTests {
    63  		c.Logf("running test %v", i)
    64  		_, err := environs.ReadEnvironsBytes([]byte(t.env))
    65  		c.Check(err, gc.ErrorMatches, t.err)
    66  	}
    67  }
    68  
    69  var invalidEnvTests = []struct {
    70  	env  string
    71  	name string
    72  	err  string
    73  }{
    74  	{`
    75  environments:
    76      only:
    77          foo: bar
    78  `, "", `environment "only" has no type`,
    79  	}, {`
    80  environments:
    81      only:
    82          foo: bar
    83  `, "only", `environment "only" has no type`,
    84  	}, {`
    85  environments:
    86      only:
    87          foo: bar
    88          type: crazy
    89  `, "only", `environment "only" has an unknown provider type "crazy"`,
    90  	},
    91  }
    92  
    93  func (*suite) TestInvalidEnv(c *gc.C) {
    94  	for i, t := range invalidEnvTests {
    95  		c.Logf("running test %v", i)
    96  		es, err := environs.ReadEnvironsBytes([]byte(t.env))
    97  		c.Check(err, jc.ErrorIsNil)
    98  		cfg, err := es.Config(t.name)
    99  		c.Check(err, gc.ErrorMatches, t.err)
   100  		c.Check(cfg, gc.IsNil)
   101  	}
   102  }
   103  
   104  func (*suite) TestNoWarningForDeprecatedButUnusedEnv(c *gc.C) {
   105  	// This tests that a config that has a deprecated field doesn't
   106  	// generate a Warning if we don't actually ask for that environment.
   107  	// However, we can only really trigger that when we have a deprecated
   108  	// field. If support for the field is removed entirely, another
   109  	// mechanism will need to be used
   110  	content := `
   111  environments:
   112      valid:
   113          type: dummy
   114          state-server: false
   115      deprecated:
   116          type: dummy
   117          state-server: false
   118          tools-metadata-url: aknowndeprecatedfield
   119          lxc-use-clone: true
   120  `
   121  	var tw loggo.TestWriter
   122  	// we only capture Warning or above
   123  	c.Assert(loggo.RegisterWriter("invalid-env-tester", &tw, loggo.WARNING), gc.IsNil)
   124  	defer loggo.RemoveWriter("invalid-env-tester")
   125  
   126  	envs, err := environs.ReadEnvironsBytes([]byte(content))
   127  	c.Check(err, jc.ErrorIsNil)
   128  	names := envs.Names()
   129  	sort.Strings(names)
   130  	c.Check(names, gc.DeepEquals, []string{"deprecated", "valid"})
   131  	// There should be no warning in the log
   132  	c.Check(tw.Log(), gc.HasLen, 0)
   133  	// Now we actually grab the 'valid' entry
   134  	_, err = envs.Config("valid")
   135  	c.Check(err, jc.ErrorIsNil)
   136  	// And still we have no warnings
   137  	c.Check(tw.Log(), gc.HasLen, 0)
   138  	// Only once we grab the deprecated one do we see any warnings
   139  	_, err = envs.Config("deprecated")
   140  	c.Check(err, jc.ErrorIsNil)
   141  	c.Check(tw.Log(), gc.HasLen, 2)
   142  }
   143  
   144  func (*suite) TestNoHomeBeforeConfig(c *gc.C) {
   145  	// Test that we don't actually need HOME set until we call envs.Config()
   146  	os.Setenv("HOME", "")
   147  	content := `
   148  environments:
   149      valid:
   150          type: dummy
   151      amazon:
   152          type: ec2
   153  `
   154  	_, err := environs.ReadEnvironsBytes([]byte(content))
   155  	c.Check(err, jc.ErrorIsNil)
   156  }
   157  
   158  func (*suite) TestNoEnv(c *gc.C) {
   159  	envPath := gitjujutesting.HomePath(".juju", "environments.yaml")
   160  	err := os.Remove(envPath)
   161  	c.Assert(err, jc.ErrorIsNil)
   162  	es, err := environs.ReadEnvirons("")
   163  	c.Assert(es, gc.IsNil)
   164  	c.Assert(err, jc.Satisfies, environs.IsNoEnv)
   165  }
   166  
   167  var configTests = []struct {
   168  	env   string
   169  	check func(c *gc.C, envs *environs.Environs)
   170  }{
   171  	{`
   172  environments:
   173      only:
   174          type: dummy
   175          state-server: false
   176  `, func(c *gc.C, envs *environs.Environs) {
   177  		cfg, err := envs.Config("")
   178  		c.Assert(err, jc.ErrorIsNil)
   179  		c.Assert(cfg.Name(), gc.Equals, "only")
   180  	}}, {`
   181  default:
   182      invalid
   183  environments:
   184      valid:
   185          type: dummy
   186          state-server: false
   187      invalid:
   188          type: crazy
   189  `, func(c *gc.C, envs *environs.Environs) {
   190  		cfg, err := envs.Config("")
   191  		c.Assert(err, gc.ErrorMatches, `environment "invalid" has an unknown provider type "crazy"`)
   192  		c.Assert(cfg, gc.IsNil)
   193  		cfg, err = envs.Config("valid")
   194  		c.Assert(err, jc.ErrorIsNil)
   195  		c.Assert(cfg.Name(), gc.Equals, "valid")
   196  	}}, {`
   197  environments:
   198      one:
   199          type: dummy
   200          state-server: false
   201      two:
   202          type: dummy
   203          state-server: false
   204  `, func(c *gc.C, envs *environs.Environs) {
   205  		cfg, err := envs.Config("")
   206  		c.Assert(err, gc.ErrorMatches, `no default environment found`)
   207  		c.Assert(cfg, gc.IsNil)
   208  	}},
   209  }
   210  
   211  func (*suite) TestConfig(c *gc.C) {
   212  	for i, t := range configTests {
   213  		c.Logf("running test %v", i)
   214  		envs, err := environs.ReadEnvironsBytes([]byte(t.env))
   215  		c.Assert(err, jc.ErrorIsNil)
   216  		t.check(c, envs)
   217  	}
   218  }
   219  
   220  func (*suite) TestDefaultConfigFile(c *gc.C) {
   221  	env := `
   222  environments:
   223      only:
   224          type: dummy
   225          state-server: false
   226          authorized-keys: i-am-a-key
   227  `
   228  	outfile, err := environs.WriteEnvirons("", env)
   229  	c.Assert(err, jc.ErrorIsNil)
   230  	path := gitjujutesting.HomePath(".juju", "environments.yaml")
   231  	c.Assert(path, gc.Equals, outfile)
   232  
   233  	envs, err := environs.ReadEnvirons("")
   234  	c.Assert(err, jc.ErrorIsNil)
   235  	cfg, err := envs.Config("")
   236  	c.Assert(err, jc.ErrorIsNil)
   237  	c.Assert(cfg.Name(), gc.Equals, "only")
   238  }
   239  
   240  func (s *suite) TestConfigPerm(c *gc.C) {
   241  	testing.MakeSampleJujuHome(c)
   242  
   243  	path := gitjujutesting.HomePath(".juju")
   244  	info, err := os.Lstat(path)
   245  	c.Assert(err, jc.ErrorIsNil)
   246  	oldPerm := info.Mode().Perm()
   247  	env := `
   248  environments:
   249      only:
   250          type: dummy
   251          state-server: false
   252          authorized-keys: i-am-a-key
   253  `
   254  	outfile, err := environs.WriteEnvirons("", env)
   255  	c.Assert(err, jc.ErrorIsNil)
   256  
   257  	info, err = os.Lstat(outfile)
   258  	c.Assert(err, jc.ErrorIsNil)
   259  	// Windows is not fully POSIX compliant. Normal permission
   260  	// checking will yield unexpected results
   261  	if runtime.GOOS != "windows" {
   262  		c.Assert(info.Mode().Perm(), gc.Equals, os.FileMode(0600))
   263  	}
   264  
   265  	info, err = os.Lstat(filepath.Dir(outfile))
   266  	c.Assert(err, jc.ErrorIsNil)
   267  	if runtime.GOOS != "windows" {
   268  		c.Assert(info.Mode().Perm(), gc.Equals, oldPerm)
   269  	}
   270  
   271  }
   272  
   273  func (*suite) TestNamedConfigFile(c *gc.C) {
   274  
   275  	env := `
   276  environments:
   277      only:
   278          type: dummy
   279          state-server: false
   280          authorized-keys: i-am-a-key
   281  `
   282  	path := filepath.Join(c.MkDir(), "a-file")
   283  	outfile, err := environs.WriteEnvirons(path, env)
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	c.Assert(path, gc.Equals, outfile)
   286  
   287  	envs, err := environs.ReadEnvirons(path)
   288  	c.Assert(err, jc.ErrorIsNil)
   289  	cfg, err := envs.Config("")
   290  	c.Assert(err, jc.ErrorIsNil)
   291  	c.Assert(cfg.Name(), gc.Equals, "only")
   292  }
   293  
   294  func inMap(attrs testing.Attrs, attr string) bool {
   295  	_, ok := attrs[attr]
   296  	return ok
   297  }
   298  
   299  func (*suite) TestBootstrapConfig(c *gc.C) {
   300  	attrs := dummySampleConfig().Merge(testing.Attrs{
   301  		"agent-version": "1.2.3",
   302  	})
   303  	c.Assert(inMap(attrs, "secret"), jc.IsTrue)
   304  	c.Assert(inMap(attrs, "ca-private-key"), jc.IsTrue)
   305  	c.Assert(inMap(attrs, "admin-secret"), jc.IsTrue)
   306  
   307  	cfg, err := config.New(config.NoDefaults, attrs)
   308  	c.Assert(err, jc.ErrorIsNil)
   309  	c.Assert(err, jc.ErrorIsNil)
   310  
   311  	cfg1, err := environs.BootstrapConfig(cfg)
   312  	c.Assert(err, jc.ErrorIsNil)
   313  
   314  	expect := cfg.AllAttrs()
   315  	expect["admin-secret"] = ""
   316  	expect["ca-private-key"] = ""
   317  	c.Assert(cfg1.AllAttrs(), gc.DeepEquals, expect)
   318  }
   319  
   320  func (s *suite) TestDisallowedInBootstrap(c *gc.C) {
   321  	content := `
   322  environments:
   323      dummy:
   324          type: dummy
   325          state-server: false
   326  `
   327  	for key, value := range map[string]interface{}{
   328  		"storage-default-block-source": "loop",
   329  	} {
   330  		envContent := fmt.Sprintf("%s\n        %s: %s", content, key, value)
   331  		envs, err := environs.ReadEnvironsBytes([]byte(envContent))
   332  		c.Check(err, jc.ErrorIsNil)
   333  		_, err = envs.Config("dummy")
   334  		c.Assert(err, gc.ErrorMatches, "attribute .* is not allowed in bootstrap configurations")
   335  	}
   336  }
   337  
   338  type dummyProvider struct {
   339  	environs.EnvironProvider
   340  }
   341  
   342  func (s *suite) TestRegisterProvider(c *gc.C) {
   343  	s.PatchValue(environs.Providers, make(map[string]environs.EnvironProvider))
   344  	s.PatchValue(environs.ProviderAliases, make(map[string]string))
   345  	type step struct {
   346  		name    string
   347  		aliases []string
   348  		err     string
   349  	}
   350  	type test []step
   351  
   352  	tests := []test{
   353  		[]step{{
   354  			name: "providerName",
   355  		}},
   356  		[]step{{
   357  			name:    "providerName",
   358  			aliases: []string{"providerName"},
   359  			err:     "juju: duplicate provider alias \"providerName\"",
   360  		}},
   361  		[]step{{
   362  			name:    "providerName",
   363  			aliases: []string{"providerAlias", "providerAlias"},
   364  			err:     "juju: duplicate provider alias \"providerAlias\"",
   365  		}},
   366  		[]step{{
   367  			name:    "providerName",
   368  			aliases: []string{"providerAlias1", "providerAlias2"},
   369  		}},
   370  		[]step{{
   371  			name: "providerName",
   372  		}, {
   373  			name: "providerName",
   374  			err:  "juju: duplicate provider name \"providerName\"",
   375  		}},
   376  		[]step{{
   377  			name: "providerName1",
   378  		}, {
   379  			name:    "providerName2",
   380  			aliases: []string{"providerName"},
   381  		}},
   382  		[]step{{
   383  			name: "providerName1",
   384  		}, {
   385  			name:    "providerName2",
   386  			aliases: []string{"providerName1"},
   387  			err:     "juju: duplicate provider alias \"providerName1\"",
   388  		}},
   389  	}
   390  
   391  	registerProvider := func(name string, aliases []string) (err error) {
   392  		defer func() { err, _ = recover().(error) }()
   393  		registered := &dummyProvider{}
   394  		environs.RegisterProvider(name, registered, aliases...)
   395  		p, err := environs.Provider(name)
   396  		c.Assert(err, jc.ErrorIsNil)
   397  		c.Assert(p, gc.Equals, registered)
   398  		for _, alias := range aliases {
   399  			p, err := environs.Provider(alias)
   400  			c.Assert(err, jc.ErrorIsNil)
   401  			c.Assert(p, gc.Equals, registered)
   402  			c.Assert(p, gc.Equals, registered)
   403  		}
   404  		return nil
   405  	}
   406  	for i, test := range tests {
   407  		c.Logf("test %d: %v", i, test)
   408  		for k := range *environs.Providers {
   409  			delete(*environs.Providers, k)
   410  		}
   411  		for k := range *environs.ProviderAliases {
   412  			delete(*environs.ProviderAliases, k)
   413  		}
   414  		for _, step := range test {
   415  			err := registerProvider(step.name, step.aliases)
   416  			if step.err == "" {
   417  				c.Assert(err, jc.ErrorIsNil)
   418  			} else {
   419  				c.Assert(err, gc.ErrorMatches, step.err)
   420  			}
   421  		}
   422  	}
   423  }
   424  
   425  type ConfigDeprecationSuite struct {
   426  	suite
   427  	writer *loggo.TestWriter
   428  }
   429  
   430  var _ = gc.Suite(&ConfigDeprecationSuite{})
   431  
   432  func (s *ConfigDeprecationSuite) setupLogger(c *gc.C) func() {
   433  	var err error
   434  	s.writer = &loggo.TestWriter{}
   435  	err = loggo.RegisterWriter("test", s.writer, loggo.WARNING)
   436  	c.Assert(err, jc.ErrorIsNil)
   437  	return func() {
   438  		_, _, err := loggo.RemoveWriter("test")
   439  		c.Assert(err, jc.ErrorIsNil)
   440  	}
   441  }
   442  
   443  func (s *ConfigDeprecationSuite) checkDeprecationWarning(c *gc.C, attrs testing.Attrs, expectedMsg string) {
   444  	content := `
   445  environments:
   446      deprecated:
   447          type: dummy
   448          state-server: false
   449  `
   450  	restore := s.setupLogger(c)
   451  	defer restore()
   452  
   453  	envs, err := environs.ReadEnvironsBytes([]byte(content))
   454  	c.Assert(err, jc.ErrorIsNil)
   455  	environs.UpdateEnvironAttrs(envs, "deprecated", attrs)
   456  	_, err = envs.Config("deprecated")
   457  	c.Assert(err, jc.ErrorIsNil)
   458  
   459  	var stripped string
   460  	if log := s.writer.Log(); len(log) == 1 {
   461  		stripped = strings.Replace(log[0].Message, "\n", "", -1)
   462  	}
   463  
   464  	c.Check(stripped, gc.Matches, expectedMsg)
   465  }
   466  
   467  const (
   468  	// This is a standard configuration warning when old attribute was specified.
   469  	standardDeprecationWarning = `.*Your configuration should be updated to set .* %v.*`
   470  
   471  	// This is a standard deprecation warning when both old and new attributes were specified.
   472  	standardDeprecationWarningWithNew = `.*is deprecated and will be ignored since the new .*`
   473  )
   474  
   475  func (s *ConfigDeprecationSuite) TestDeprecatedToolsURLWarning(c *gc.C) {
   476  	attrs := testing.Attrs{
   477  		"tools-metadata-url": "aknowndeprecatedfield",
   478  	}
   479  	expected := fmt.Sprintf(standardDeprecationWarning, "aknowndeprecatedfield")
   480  	s.checkDeprecationWarning(c, attrs, expected)
   481  }
   482  
   483  func (s *ConfigDeprecationSuite) TestDeprecatedSafeModeWarning(c *gc.C) {
   484  	// Test that the warning is logged.
   485  	attrs := testing.Attrs{"provisioner-safe-mode": true}
   486  	expected := fmt.Sprintf(standardDeprecationWarning, "destroyed")
   487  	s.checkDeprecationWarning(c, attrs, expected)
   488  }
   489  
   490  func (s *ConfigDeprecationSuite) TestDeprecatedSafeModeWarningWithHarvest(c *gc.C) {
   491  	attrs := testing.Attrs{
   492  		"provisioner-safe-mode":    true,
   493  		"provisioner-harvest-mode": "none",
   494  	}
   495  	// Test that the warning is logged.
   496  	expected := fmt.Sprintf(standardDeprecationWarningWithNew)
   497  	s.checkDeprecationWarning(c, attrs, expected)
   498  }
   499  
   500  func (s *ConfigDeprecationSuite) TestDeprecatedToolsURLWithNewURLWarning(c *gc.C) {
   501  	attrs := testing.Attrs{
   502  		"tools-metadata-url": "aknowndeprecatedfield",
   503  		"agent-metadata-url": "newvalue",
   504  	}
   505  	expected := fmt.Sprintf(standardDeprecationWarningWithNew)
   506  	s.checkDeprecationWarning(c, attrs, expected)
   507  }
   508  
   509  func (s *ConfigDeprecationSuite) TestDeprecatedTypeNullWarning(c *gc.C) {
   510  	attrs := testing.Attrs{"type": "null"}
   511  	expected := `Provider type "null" has been renamed to "manual".Please update your environment configuration.`
   512  	s.checkDeprecationWarning(c, attrs, expected)
   513  }
   514  
   515  func (s *ConfigDeprecationSuite) TestDeprecatedLxcUseCloneWarning(c *gc.C) {
   516  	attrs := testing.Attrs{"lxc-use-clone": true}
   517  	expected := fmt.Sprintf(standardDeprecationWarning, true)
   518  	s.checkDeprecationWarning(c, attrs, expected)
   519  }
   520  
   521  func (s *ConfigDeprecationSuite) TestDeprecatedToolsStreamWarning(c *gc.C) {
   522  	attrs := testing.Attrs{"tools-stream": "devel"}
   523  	expected := fmt.Sprintf(standardDeprecationWarning, "devel")
   524  	s.checkDeprecationWarning(c, attrs, expected)
   525  }
   526  
   527  func (s *ConfigDeprecationSuite) TestDeprecatedToolsStreamWIthAgentWarning(c *gc.C) {
   528  	attrs := testing.Attrs{
   529  		"tools-stream": "devel",
   530  		"agent-stream": "proposed",
   531  	}
   532  	expected := fmt.Sprintf(standardDeprecationWarningWithNew)
   533  	s.checkDeprecationWarning(c, attrs, expected)
   534  }
   535  
   536  func (s *ConfigDeprecationSuite) TestDeprecatedBlockWarning(c *gc.C) {
   537  	assertBlockWarning := func(tst string) {
   538  		attrs := testing.Attrs{tst: true}
   539  		s.checkDeprecationWarning(c, attrs, ".*is deprecated and will be ignored since.*")
   540  	}
   541  	assertBlockWarning("block-destroy-environment")
   542  	assertBlockWarning("block-remove-object")
   543  	assertBlockWarning("block-all-changes")
   544  }