예제 #1
0
func (s *StateSuite) TestEnvironConfigWithAdminSecret(c *C) {
	attrs := map[string]interface{}{
		"name":            "test",
		"type":            "test",
		"authorized-keys": "i-am-a-key",
		"default-series":  "precise",
		"development":     true,
		"admin-secret":    "foo",
		"ca-cert":         testing.CACert,
		"ca-private-key":  "",
	}
	cfg, err := config.New(attrs)
	c.Assert(err, IsNil)
	_, err = state.Initialize(state.TestingStateInfo(), cfg)
	c.Assert(err, ErrorMatches, "admin-secret should never be written to the state")

	delete(attrs, "admin-secret")
	cfg, err = config.New(attrs)
	st, err := state.Initialize(state.TestingStateInfo(), cfg)
	c.Assert(err, IsNil)
	st.Close()

	cfg, err = cfg.Apply(map[string]interface{}{"admin-secret": "foo"})
	err = s.State.SetEnvironConfig(cfg)
	c.Assert(err, ErrorMatches, "admin-secret should never be written to the state")
}
예제 #2
0
func (s *StateSuite) TestEnvironConfig(c *C) {
	initial := map[string]interface{}{
		"name":                      "test",
		"type":                      "test",
		"authorized-keys":           "i-am-a-key",
		"default-series":            "precise",
		"development":               true,
		"firewall-mode":             "",
		"admin-secret":              "",
		"ca-cert":                   testing.CACert,
		"ca-private-key":            "",
		"ssl-hostname-verification": true,
	}
	cfg, err := config.New(initial)
	c.Assert(err, IsNil)
	st, err := state.Initialize(state.TestingStateInfo(), cfg)
	c.Assert(err, IsNil)
	st.Close()
	c.Assert(err, IsNil)
	cfg, err = s.State.EnvironConfig()
	c.Assert(err, IsNil)
	current := cfg.AllAttrs()
	c.Assert(current, DeepEquals, initial)

	current["authorized-keys"] = "i-am-a-new-key"
	cfg, err = config.New(current)
	c.Assert(err, IsNil)
	err = s.State.SetEnvironConfig(cfg)
	c.Assert(err, IsNil)
	cfg, err = s.State.EnvironConfig()
	c.Assert(err, IsNil)
	final := cfg.AllAttrs()
	c.Assert(final, DeepEquals, current)
}
예제 #3
0
func (*ConfigSuite) TestValidateRejectsChangingStorageContainer(c *C) {
	attrs := makeAzureConfigMap(c)
	newConfig, err := config.New(attrs)
	c.Assert(err, IsNil)
	provider := azureEnvironProvider{}
	attrs["storage-container-name"] = "another name"
	oldConfig, err := config.New(attrs)
	c.Assert(err, IsNil)
	_, err = provider.Validate(newConfig, oldConfig)
	c.Check(err, ErrorMatches, ".*cannot change storage-container-name.*")
}
예제 #4
0
func (*ConfigSuite) TestValidateAcceptsUnchangedConfig(c *C) {
	attrs := makeAzureConfigMap(c)
	provider := azureEnvironProvider{}
	oldConfig, err := config.New(attrs)
	c.Assert(err, IsNil)
	newConfig, err := config.New(attrs)
	c.Assert(err, IsNil)
	result, err := provider.Validate(newConfig, oldConfig)
	c.Assert(err, IsNil)
	c.Check(result.Name(), Equals, attrs["name"])
}
예제 #5
0
func (*ConfigSuite) TestValidateChecksConfigChanges(c *C) {
	provider := azureEnvironProvider{}
	oldAttrs := makeBaseConfigMap()
	oldConfig, err := config.New(oldAttrs)
	c.Assert(err, IsNil)
	newAttrs := makeBaseConfigMap()
	newAttrs["name"] = "different-name"
	newConfig, err := config.New(newAttrs)
	c.Assert(err, IsNil)
	_, err = provider.Validate(newConfig, oldConfig)
	c.Check(err, NotNil)
}
예제 #6
0
func (*ConfigSuite) TestValidateParsesAzureConfig(c *C) {
	managementSubscriptionId := "subscription-id"
	certificate := "certificate content"
	storageAccountName := "account-name"
	storageAccountKey := "account-key"
	storageContainerName := "container-name"
	publicStorageAccountName := "public-account-name"
	publicStorageContainerName := "public-container-name"
	unknownFutureSetting := "preserved"
	azureConfig := map[string]interface{}{
		"management-subscription-id":    managementSubscriptionId,
		"management-certificate":        certificate,
		"storage-account-name":          storageAccountName,
		"storage-account-key":           storageAccountKey,
		"storage-container-name":        storageContainerName,
		"public-storage-account-name":   publicStorageAccountName,
		"public-storage-container-name": publicStorageContainerName,
		"unknown-future-setting":        unknownFutureSetting,
	}
	attrs := makeConfigMap(azureConfig)
	provider := azureEnvironProvider{}
	config, err := config.New(attrs)
	c.Assert(err, IsNil)
	azConfig, err := provider.newConfig(config)
	c.Assert(err, IsNil)
	c.Check(azConfig.Name(), Equals, attrs["name"])
	c.Check(azConfig.ManagementSubscriptionId(), Equals, managementSubscriptionId)
	c.Check(azConfig.ManagementCertificate(), Equals, certificate)
	c.Check(azConfig.StorageAccountName(), Equals, storageAccountName)
	c.Check(azConfig.StorageAccountKey(), Equals, storageAccountKey)
	c.Check(azConfig.StorageContainerName(), Equals, storageContainerName)
	c.Check(azConfig.PublicStorageAccountName(), Equals, publicStorageAccountName)
	c.Check(azConfig.PublicStorageContainerName(), Equals, publicStorageContainerName)
	c.Check(azConfig.UnknownAttrs()["unknown-future-setting"], Equals, unknownFutureSetting)
}
예제 #7
0
func (*ConfigSuite) TestFirewallMode(c *C) {
	for _, test := range firewallModeTests {
		c.Logf("test firewall mode %q", test.configFirewallMode)
		cfgMap := map[string]interface{}{
			"name":            "only",
			"type":            "dummy",
			"state-server":    true,
			"authorized-keys": "none",
			"ca-cert":         testing.CACert,
			"ca-private-key":  "",
		}
		if test.configFirewallMode != "" {
			cfgMap["firewall-mode"] = test.configFirewallMode
		}
		cfg, err := config.New(cfgMap)
		if err != nil {
			c.Assert(err, ErrorMatches, test.errorMsg)
			continue
		}

		env, err := environs.New(cfg)
		if err != nil {
			c.Assert(err, ErrorMatches, test.errorMsg)
			continue
		}

		firewallMode := env.Config().FirewallMode()
		c.Assert(firewallMode, Equals, test.firewallMode)
	}
}
예제 #8
0
func (suite) TestBootstrapConfig(c *C) {
	defer makeFakeHome(c, "bladaam").restore()
	cfg, err := config.New(map[string]interface{}{
		"name":            "bladaam",
		"type":            "dummy",
		"state-server":    false,
		"admin-secret":    "highly",
		"secret":          "um",
		"authorized-keys": "i-am-a-key",
		"ca-cert":         testing.CACert,
		"ca-private-key":  testing.CAKey,
	})
	c.Assert(err, IsNil)
	provider, err := environs.Provider(cfg.Type())
	c.Assert(err, IsNil)

	tools := &state.Tools{
		URL:    "http://x",
		Binary: version.MustParseBinary("1.2.3-foo-bar"),
	}
	cfg1, err := environs.BootstrapConfig(provider, cfg, tools)
	c.Assert(err, IsNil)

	expect := cfg.AllAttrs()
	delete(expect, "secret")
	expect["admin-secret"] = ""
	expect["ca-private-key"] = ""
	expect["agent-version"] = "1.2.3"
	c.Assert(cfg1.AllAttrs(), DeepEquals, expect)
}
예제 #9
0
func (s *StateSuite) TestWatchEnvironConfig(c *C) {
	watcher := s.State.WatchEnvironConfig()
	defer func() {
		c.Assert(watcher.Stop(), IsNil)
	}()
	for i, test := range watchEnvironConfigTests {
		c.Logf("test %d", i)
		change, err := config.New(test)
		c.Assert(err, IsNil)
		if i == 0 {
			st, err := state.Initialize(state.TestingStateInfo(), change)
			c.Assert(err, IsNil)
			st.Close()
		} else {
			err = s.State.SetEnvironConfig(change)
			c.Assert(err, IsNil)
		}
		c.Assert(err, IsNil)
		s.State.StartSync()
		select {
		case got, ok := <-watcher.Changes():
			c.Assert(ok, Equals, true)
			c.Assert(got.AllAttrs(), DeepEquals, change.AllAttrs())
		case <-time.After(500 * time.Millisecond):
			c.Fatalf("did not get change: %#v", test)
		}
	}

	select {
	case got := <-watcher.Changes():
		c.Fatalf("got unexpected change: %#v", got)
	case <-time.After(50 * time.Millisecond):
	}
}
예제 #10
0
func (*ConfigSuite) TestConfigAttrs(c *gc.C) {
	attrs := map[string]interface{}{
		"type":                      "my-type",
		"name":                      "my-name",
		"authorized-keys":           "my-keys",
		"firewall-mode":             string(config.FwDefault),
		"admin-secret":              "foo",
		"unknown":                   "my-unknown",
		"ca-private-key":            "",
		"ca-cert":                   caCert,
		"ssl-hostname-verification": true,
	}
	cfg, err := config.New(attrs)
	c.Assert(err, gc.IsNil)

	// These attributes are added if not set.
	attrs["development"] = false
	attrs["default-series"] = config.DefaultSeries
	// Default firewall mode is instance
	attrs["firewall-mode"] = string(config.FwInstance)
	c.Assert(cfg.AllAttrs(), gc.DeepEquals, attrs)
	c.Assert(cfg.UnknownAttrs(), gc.DeepEquals, map[string]interface{}{"unknown": "my-unknown"})

	newcfg, err := cfg.Apply(map[string]interface{}{
		"name":        "new-name",
		"new-unknown": "my-new-unknown",
	})

	attrs["name"] = "new-name"
	attrs["new-unknown"] = "my-new-unknown"
	c.Assert(newcfg.AllAttrs(), gc.DeepEquals, attrs)
}
예제 #11
0
func (createServiceAndUnit) step(c *C, ctx *context) {
	cfg, err := config.New(map[string]interface{}{
		"name":            "testenv",
		"type":            "dummy",
		"default-series":  "abominable",
		"agent-version":   "1.2.3",
		"authorized-keys": "we-are-the-keys",
		"ca-cert":         coretesting.CACert,
		"ca-private-key":  "",
	})
	c.Assert(err, IsNil)
	err = ctx.st.SetEnvironConfig(cfg)
	c.Assert(err, IsNil)
	sch, err := ctx.st.Charm(curl(0))
	c.Assert(err, IsNil)
	svc, err := ctx.st.AddService("u", sch)
	c.Assert(err, IsNil)
	unit, err := svc.AddUnit()
	c.Assert(err, IsNil)

	// Assign the unit to a provisioned machine to match expected state.
	err = unit.AssignToNewMachine()
	c.Assert(err, IsNil)
	mid, err := unit.AssignedMachineId()
	c.Assert(err, IsNil)
	machine, err := ctx.st.Machine(mid)
	c.Assert(err, IsNil)
	err = machine.SetProvisioned("i-exist", "fake_nonce", nil)
	c.Assert(err, IsNil)
	ctx.svc = svc
	ctx.unit = unit
}
예제 #12
0
func (*ConfigSuite) TestConfigAttrs(c *C) {
	attrs := map[string]interface{}{
		"type":                      "my-type",
		"name":                      "my-name",
		"authorized-keys":           "my-keys",
		"firewall-mode":             string(config.FwDefault),
		"default-series":            version.Current.Series,
		"admin-secret":              "foo",
		"unknown":                   "my-unknown",
		"ca-private-key":            "",
		"ca-cert":                   caCert,
		"ssl-hostname-verification": true,
	}
	cfg, err := config.New(attrs)
	c.Assert(err, IsNil)

	attrs["development"] = false // This attribute is added if not set.
	c.Assert(cfg.AllAttrs(), DeepEquals, attrs)
	c.Assert(cfg.UnknownAttrs(), DeepEquals, map[string]interface{}{"unknown": "my-unknown"})

	newcfg, err := cfg.Apply(map[string]interface{}{
		"name":        "new-name",
		"new-unknown": "my-new-unknown",
	})

	attrs["name"] = "new-name"
	attrs["new-unknown"] = "my-new-unknown"
	c.Assert(newcfg.AllAttrs(), DeepEquals, attrs)
}
예제 #13
0
// BootstrapConfig returns a copy of the supplied configuration with
// secret attributes removed. If the resulting config is not suitable
// for bootstrapping an environment, an error is returned.
func BootstrapConfig(cfg *config.Config) (*config.Config, error) {
	p, err := Provider(cfg.Type())
	if err != nil {
		return nil, err
	}
	secrets, err := p.SecretAttrs(cfg)
	if err != nil {
		return nil, err
	}
	m := cfg.AllAttrs()
	for k := range secrets {
		delete(m, k)
	}

	// We never want to push admin-secret or the root CA private key to the cloud.
	delete(m, "admin-secret")
	m["ca-private-key"] = ""
	if cfg, err = config.New(m); err != nil {
		return nil, err
	}
	if _, ok := cfg.AgentVersion(); !ok {
		return nil, fmt.Errorf("environment configuration has no agent-version")
	}
	return cfg, nil
}
예제 #14
0
// Run initializes state for an environment.
func (c *BootstrapCommand) Run(_ *cmd.Context) error {
	if err := c.Conf.read("bootstrap"); err != nil {
		return err
	}
	cfg, err := config.New(c.EnvConfig)
	if err != nil {
		return err
	}
	// There is no entity that's created at init time.
	c.Conf.StateInfo.EntityName = ""
	st, err := state.Initialize(c.Conf.StateInfo, cfg)
	if err != nil {
		return err
	}
	defer st.Close()
	m, err := st.InjectMachine(state.InstanceId(c.InstanceId), state.JobManageEnviron)
	if err != nil {
		return err
	}
	if c.Conf.OldPassword != "" {
		if err := m.SetMongoPassword(c.Conf.OldPassword); err != nil {
			return err
		}
		if err := st.SetAdminMongoPassword(c.Conf.OldPassword); err != nil {
			return err
		}
	}
	return nil
}
예제 #15
0
파일: watcher.go 프로젝트: rayleyva/gosf
func (w *EnvironConfigWatcher) loop() (err error) {
	sw := w.st.watchSettings(environGlobalKey)
	defer sw.Stop()
	out := w.out
	out = nil
	cfg := &config.Config{}
	for {
		select {
		case <-w.st.watcher.Dead():
			return watcher.MustErr(w.st.watcher)
		case <-w.tomb.Dying():
			return tomb.ErrDying
		case settings, ok := <-sw.Changes():
			if !ok {
				return watcher.MustErr(sw)
			}
			cfg, err = config.New(settings.Map())
			if err == nil {
				out = w.out
			} else {
				out = nil
			}
		case out <- cfg:
			out = nil
		}
	}
	return nil
}
예제 #16
0
func (*EnvironSuite) TestSetConfigLocksEnviron(c *C) {
	env := makeEnviron(c)
	cfg, err := config.New(makeAzureConfigMap(c))
	c.Assert(err, IsNil)

	testing.TestLockingFunction(&env.Mutex, func() { env.SetConfig(cfg) })
}
예제 #17
0
// NewFromAttrs returns a new environment based on the provided configuration
// attributes.
func NewFromAttrs(attrs map[string]interface{}) (Environ, error) {
	cfg, err := config.New(attrs)
	if err != nil {
		return nil, err
	}
	return New(cfg)
}
예제 #18
0
func mustNewConfig(m map[string]interface{}) *config.Config {
	cfg, err := config.New(m)
	if err != nil {
		panic(err)
	}
	return cfg
}
예제 #19
0
func (st *State) EnvironConfig() (*config.Config, error) {
	settings, err := readSettings(st, environGlobalKey)
	if err != nil {
		return nil, err
	}
	attrs := settings.Map()
	return config.New(attrs)
}
예제 #20
0
func makeEnviron(c *C) *azureEnviron {
	attrs := makeAzureConfigMap(c)
	cfg, err := config.New(attrs)
	c.Assert(err, IsNil)
	env, err := NewEnviron(cfg)
	c.Assert(err, IsNil)
	return env
}
예제 #21
0
func (*CloudInitSuite) TestUserData(c *C) {
	testJujuHome := c.MkDir()
	defer config.SetJujuHome(config.SetJujuHome(testJujuHome))
	tools := &tools.Tools{
		URL:    "http://foo.com/tools/juju1.2.3-linux-amd64.tgz",
		Binary: version.MustParseBinary("1.2.3-linux-amd64"),
	}
	envConfig, err := config.New(map[string]interface{}{
		"type":            "maas",
		"name":            "foo",
		"default-series":  "series",
		"authorized-keys": "keys",
		"ca-cert":         testing.CACert,
	})
	c.Assert(err, IsNil)

	cfg := &cloudinit.MachineConfig{
		MachineId:       "10",
		MachineNonce:    "5432",
		Tools:           tools,
		StateServerCert: []byte(testing.ServerCert),
		StateServerKey:  []byte(testing.ServerKey),
		StateInfo: &state.Info{
			Password: "******",
			CACert:   []byte("CA CERT\n" + testing.CACert),
		},
		APIInfo: &api.Info{
			Password: "******",
			CACert:   []byte("CA CERT\n" + testing.CACert),
		},
		DataDir:      environs.DataDir,
		Config:       envConfig,
		StatePort:    envConfig.StatePort(),
		APIPort:      envConfig.APIPort(),
		StateServer:  true,
		ProviderType: "dummy",
	}
	script1 := "script1"
	script2 := "script2"
	scripts := []string{script1, script2}
	result, err := environs.ComposeUserData(cfg, scripts...)
	c.Assert(err, IsNil)

	unzipped, err := utils.Gunzip(result)
	c.Assert(err, IsNil)

	config := make(map[interface{}]interface{})
	err = goyaml.Unmarshal(unzipped, &config)
	c.Assert(err, IsNil)

	// Just check that the cloudinit config looks good.
	c.Check(config["apt_upgrade"], Equals, true)
	// The scripts given to userData where added as the first
	// commands to be run.
	runCmd := config["runcmd"].([]interface{})
	c.Check(runCmd[0], Equals, script1)
	c.Check(runCmd[1], Equals, script2)
}
예제 #22
0
func (s *StateSuite) TestDoubleInitialize(c *C) {
	m := map[string]interface{}{
		"type":                      "dummy",
		"name":                      "lisboa",
		"authorized-keys":           "i-am-a-key",
		"default-series":            "precise",
		"development":               true,
		"firewall-mode":             "",
		"admin-secret":              "",
		"ca-cert":                   testing.CACert,
		"ca-private-key":            "",
		"ssl-hostname-verification": true,
	}
	cfg, err := config.New(m)
	c.Assert(err, IsNil)
	st, err := state.Initialize(state.TestingStateInfo(), cfg)
	c.Assert(err, IsNil)
	c.Assert(st, NotNil)
	env1, err := st.EnvironConfig()
	st.Close()

	// initialize again, there should be no error and the
	// environ config should not change.
	m = map[string]interface{}{
		"type":                      "dummy",
		"name":                      "sydney",
		"authorized-keys":           "i-am-not-an-animal",
		"default-series":            "xanadu",
		"development":               false,
		"firewall-mode":             "",
		"admin-secret":              "",
		"ca-cert":                   testing.CACert,
		"ca-private-key":            "",
		"ssl-hostname-verification": false,
	}
	cfg, err = config.New(m)
	c.Assert(err, IsNil)
	st, err = state.Initialize(state.TestingStateInfo(), cfg)
	c.Assert(err, IsNil)
	c.Assert(st, NotNil)
	env2, err := st.EnvironConfig()
	st.Close()

	c.Assert(env1.AllAttrs(), DeepEquals, env2.AllAttrs())
}
예제 #23
0
func (*configSuite) TestChecksLocationIsRequired(c *C) {
	attrs := makeAzureConfigMap(c)
	attrs["location"] = ""
	provider := azureEnvironProvider{}
	newConfig, err := config.New(attrs)
	c.Assert(err, IsNil)
	_, err = provider.Validate(newConfig, nil)
	c.Check(err, ErrorMatches, ".*environment has no location.*")
}
예제 #24
0
func newTestConfig(c *gc.C, explicit attrs) *config.Config {
	final := attrs{"type": "my-type", "name": "my-name"}
	for key, value := range explicit {
		final[key] = value
	}
	result, err := config.New(final)
	c.Assert(err, gc.IsNil)
	return result
}
예제 #25
0
func (*ConfigSuite) TestChecksPublicStorageContainerNameCannotBeDefinedAlone(c *C) {
	attrs := makeAzureConfigMap(c)
	attrs["public-storage-account-name"] = ""
	provider := azureEnvironProvider{}
	newConfig, err := config.New(attrs)
	c.Assert(err, IsNil)
	_, err = provider.Validate(newConfig, nil)
	c.Check(err, ErrorMatches, ".*both or none of them.*")
}
예제 #26
0
// makeEnviron creates a fake azureEnviron with arbitrary configuration.
func makeEnviron(c *C) *azureEnviron {
	attrs := makeAzureConfigMap(c)
	cfg, err := config.New(attrs)
	c.Assert(err, IsNil)
	env, err := NewEnviron(cfg)
	c.Assert(err, IsNil)
	// Prevent the test from trying to query for a storage-account key.
	env.storageAccountKey = "fake-storage-account-key"
	return env
}
예제 #27
0
func (*EnvironSuite) TestPublicStorageReturnsEmptyStorageIfNoInfo(c *C) {
	attrs := makeAzureConfigMap(c)
	attrs["public-storage-container-name"] = ""
	attrs["public-storage-account-name"] = ""
	cfg, err := config.New(attrs)
	c.Assert(err, IsNil)
	env, err := NewEnviron(cfg)
	c.Assert(err, IsNil)
	c.Check(env.PublicStorage(), Equals, environs.EmptyStorage)
}
예제 #28
0
// Run initializes state for an environment.
func (c *BootstrapCommand) Run(_ *cmd.Context) error {
	if err := c.Conf.read("bootstrap"); err != nil {
		return err
	}
	cfg, err := config.New(c.EnvConfig)
	if err != nil {
		return err
	}

	// There is no entity that's created at init time.
	c.Conf.StateInfo.Tag = ""
	st, err := state.Initialize(c.Conf.StateInfo, cfg, state.DefaultDialOpts())
	if err != nil {
		return err
	}
	defer st.Close()

	if err := environs.BootstrapUsers(st, cfg, c.Conf.OldPassword); err != nil {
		return err
	}

	// TODO(fwereade): we need to be able to customize machine jobs,
	// not just hardcode these values; in particular, JobHostUnits
	// on a machine, like this one, that is running JobManageEnviron
	// (not to mention the actual state server itself...) will allow
	// a malicious or compromised unit to trivially access to the
	// user's environment credentials. However, given that this point
	// is currently moot (see Upgrader in this package), the pseudo-
	// local provider mode (in which everything is deployed with
	// `--to 0`) offers enough value to enough people that
	// JobHostUnits is currently always enabled. This will one day
	// have to change, but it's strictly less important than fixing
	// Upgrader, and it's a capability we'll always want to have
	// available for the aforementioned use case.
	jobs := []state.MachineJob{
		state.JobManageEnviron, state.JobManageState, state.JobHostUnits,
	}

	data, err := ioutil.ReadFile(providerStateURLFile)
	if err != nil {
		return fmt.Errorf("cannot read provider-state-url file: %v", err)
	}
	stateInfoURL := strings.Split(string(data), "\n")[0]
	bsState, err := environs.LoadStateFromURL(stateInfoURL)
	if err != nil {
		return fmt.Errorf("cannot load state from URL %q (read from %q): %v", stateInfoURL, providerStateURLFile, err)
	}
	instId := bsState.StateInstances[0]
	var characteristics instance.HardwareCharacteristics
	if len(bsState.Characteristics) > 0 {
		characteristics = bsState.Characteristics[0]
	}

	return environs.ConfigureBootstrapMachine(st, c.Constraints, c.Conf.DataDir, jobs, instance.Id(instId), characteristics)
}
예제 #29
0
func (s *agentSuite) proposeVersion(c *C, vers version.Number, development bool) {
	cfg, err := s.State.EnvironConfig()
	c.Assert(err, IsNil)
	attrs := cfg.AllAttrs()
	attrs["agent-version"] = vers.String()
	attrs["development"] = development
	newCfg, err := config.New(attrs)
	c.Assert(err, IsNil)
	err = s.State.SetEnvironConfig(newCfg)
	c.Assert(err, IsNil)
}
예제 #30
0
// ReadEnvironsBytes parses the contents of an environments.yaml file
// and returns its representation. An environment with an unknown type
// will only generate an error when New is called for that environment.
// Attributes for environments with known types are checked.
func ReadEnvironsBytes(data []byte) (*Environs, error) {
	var raw struct {
		Default      string
		Environments map[string]map[string]interface{}
	}
	err := goyaml.Unmarshal(data, &raw)
	if err != nil {
		return nil, err
	}

	if raw.Default != "" && raw.Environments[raw.Default] == nil {
		return nil, fmt.Errorf("default environment %q does not exist", raw.Default)
	}
	if raw.Default == "" {
		// If there's a single environment, then we get the default
		// automatically.
		if len(raw.Environments) == 1 {
			for name := range raw.Environments {
				raw.Default = name
				break
			}
		}
	}

	environs := make(map[string]environ)
	for name, attrs := range raw.Environments {
		kind, _ := attrs["type"].(string)
		if kind == "" {
			environs[name] = environ{
				err: fmt.Errorf("environment %q has no type", name),
			}
			continue
		}
		p := providers[kind]
		if p == nil {
			environs[name] = environ{
				err: fmt.Errorf("environment %q has an unknown provider type %q", name, kind),
			}
			continue
		}
		// store the name of the this environment in the config itself
		// so that providers can see it.
		attrs["name"] = name
		cfg, err := config.New(attrs)
		if err != nil {
			environs[name] = environ{
				err: fmt.Errorf("error parsing environment %q: %v", name, err),
			}
			continue
		}
		environs[name] = environ{config: cfg}
	}
	return &Environs{raw.Default, environs}, nil
}