github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/environs/config.go (about)

     1  // Copyright 2011, 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package environs
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	goyaml "gopkg.in/yaml.v1"
    15  
    16  	"github.com/juju/juju/environs/config"
    17  	"github.com/juju/juju/juju/osenv"
    18  )
    19  
    20  var logger = loggo.GetLogger("juju.environs")
    21  
    22  // environ holds information about one environment.
    23  type environ struct {
    24  	config *config.Config
    25  	err    error // an error if the config data could not be parsed.
    26  }
    27  
    28  // Environs holds information about each named environment
    29  // in an environments.yaml file.
    30  type Environs struct {
    31  	Default     string // The name of the default environment.
    32  	rawEnvirons map[string]map[string]interface{}
    33  }
    34  
    35  // Names returns the list of environment names.
    36  func (e *Environs) Names() (names []string) {
    37  	for name := range e.rawEnvirons {
    38  		names = append(names, name)
    39  	}
    40  	return
    41  }
    42  
    43  func validateEnvironmentKind(rawEnviron map[string]interface{}) error {
    44  	kind, _ := rawEnviron["type"].(string)
    45  	if kind == "" {
    46  		return fmt.Errorf("environment %q has no type", rawEnviron["name"])
    47  	}
    48  	p, _ := Provider(kind)
    49  	if p == nil {
    50  		return fmt.Errorf("environment %q has an unknown provider type %q", rawEnviron["name"], kind)
    51  	}
    52  	return nil
    53  }
    54  
    55  // disallowedWithNew holds those attributes
    56  // that can not be set in an initial environment
    57  // config used to bootstrap (they must only be set
    58  // on a running environment where appropriate
    59  // validation can be performed).
    60  var disallowedWithBootstrap = []string{
    61  	config.StorageDefaultBlockSourceKey,
    62  }
    63  
    64  // Config returns the environment configuration for the environment
    65  // with the given name. If the configuration is not
    66  // found, an errors.NotFoundError is returned.
    67  func (envs *Environs) Config(name string) (*config.Config, error) {
    68  	if name == "" {
    69  		name = envs.Default
    70  		if name == "" {
    71  			return nil, errors.New("no default environment found")
    72  		}
    73  	}
    74  	attrs, ok := envs.rawEnvirons[name]
    75  	if !ok {
    76  		return nil, errors.NotFoundf("environment %q", name)
    77  	}
    78  	if err := validateEnvironmentKind(attrs); err != nil {
    79  		return nil, errors.Trace(err)
    80  	}
    81  
    82  	// Check that we don't have any disallowed fields in new configs used for bootstrap.
    83  	for _, attr := range disallowedWithBootstrap {
    84  		if _, ok := attrs[attr]; ok {
    85  			return nil, fmt.Errorf("attribute %q is not allowed in bootstrap configurations", attr)
    86  		}
    87  	}
    88  
    89  	// If deprecated config attributes are used, log warnings so the user can know
    90  	// that they need to be fixed.
    91  	// We also look up what any new values might be so we can tell the user.
    92  	newAttrs := config.ProcessDeprecatedAttributes(attrs)
    93  	envs.logDeprecatedWarnings(attrs, newAttrs, config.ToolsMetadataURLKey, config.AgentMetadataURLKey)
    94  
    95  	// null has been renamed to manual (with an alias for existing config).
    96  	if oldType, _ := attrs["type"].(string); oldType == "null" {
    97  		logger.Warningf(
    98  			"Provider type \"null\" has been renamed to \"manual\".\n" +
    99  				"Please update your environment configuration.",
   100  		)
   101  	}
   102  	// lxc-use-clone has been renamed to lxc-clone
   103  	envs.logDeprecatedWarnings(attrs, newAttrs, config.LxcUseClone, config.LxcClone)
   104  
   105  	// provisioner-safe-mode has been renamed to provisioner-harvest-mode, so log warnings to the user
   106  	envs.logDeprecatedWarnings(attrs, newAttrs, config.ProvisionerSafeModeKey, config.ProvisionerHarvestModeKey)
   107  
   108  	// tools-stream has been renamed to agent-stream, so log warnings to the user
   109  	envs.logDeprecatedWarnings(attrs, newAttrs, config.ToolsStreamKey, config.AgentStreamKey)
   110  
   111  	// Block attributes only matter if they have been used
   112  	envs.logBlockDeprecationWarnings(attrs)
   113  
   114  	cfg, err := config.New(config.UseDefaults, attrs)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	return cfg, nil
   119  }
   120  
   121  func (envs *Environs) logBlockDeprecationWarnings(attrs map[string]interface{}) {
   122  	checkBlockVar := func(key string) {
   123  		if used, ok := attrs[key]; ok {
   124  			if used.(bool) {
   125  				envs.logBlockWarning(key, used)
   126  			}
   127  		}
   128  	}
   129  	checkBlockVar(config.PreventDestroyEnvironmentKey)
   130  	checkBlockVar(config.PreventRemoveObjectKey)
   131  	checkBlockVar(config.PreventAllChangesKey)
   132  }
   133  
   134  func (envs *Environs) logBlockWarning(key string, value interface{}) {
   135  	logger.Warningf(
   136  		"Config attribute %q (%v) is deprecated and will be ignored since \n"+
   137  			"upgrade process takes care of it. \n"+
   138  			"The attribute %q should be removed from your configuration.",
   139  		key, value, key,
   140  	)
   141  }
   142  
   143  // logDeprecatedWarnings constructs log warning messages for deprecated attributes names.
   144  // It checks if both old and new attribute names are provided.
   145  // When both are provided, the message warns to remove old attribute from configuration.
   146  // When only old attribute name is used, the message advises to replace it with the new name.
   147  func (envs *Environs) logDeprecatedWarnings(attrs, newAttrs map[string]interface{}, oldKey, newKey string) {
   148  	if oldValue := attrs[oldKey]; oldValue != nil {
   149  		// no need to warn if attribute is unused
   150  		if oldStr, ok := oldValue.(string); ok && oldStr == "" {
   151  			return
   152  		}
   153  		newValue, newValueSpecified := attrs[newKey]
   154  		var msg string
   155  		if newValueSpecified {
   156  			msg = fmt.Sprintf(
   157  				"Config attribute %q (%v) is deprecated and will be ignored since \n"+
   158  					"the new %q (%v) attribute has also been used. \n"+
   159  					"The attribute %q should be removed from your configuration.",
   160  				oldKey, oldValue, newKey, newValue, oldKey)
   161  		} else {
   162  			msg = fmt.Sprintf(
   163  				"Config attribute %q (%v) is deprecated. \n"+
   164  					"It is replaced by %q attribute. \n"+
   165  					"Your configuration should be updated to set %q as follows \n%v: %v.",
   166  				oldKey, oldValue, newKey, newKey, newKey, newAttrs[newKey])
   167  		}
   168  		logger.Warningf(msg)
   169  	}
   170  }
   171  
   172  // ProviderRegistry is an interface that provides methods for registering
   173  // and obtaining environment providers by provider name.
   174  type ProviderRegistry interface {
   175  	// RegisterProvider registers a new environment provider with the given
   176  	// name, and zero or more aliases. If a provider already exists with the
   177  	// given name or alias, an error will be returned.
   178  	RegisterProvider(p EnvironProvider, providerType string, providerTypeAliases ...string) error
   179  
   180  	// RegisteredProviders returns the names of the registered environment
   181  	// providers.
   182  	RegisteredProviders() []string
   183  
   184  	// Provider returns the environment provider with the specified name.
   185  	Provider(providerType string) (EnvironProvider, error)
   186  }
   187  
   188  // GlobalProviderRegistry returns the global provider registry.
   189  func GlobalProviderRegistry() ProviderRegistry {
   190  	return globalProviders
   191  }
   192  
   193  type globalProviderRegistry struct {
   194  	// providers maps from provider type to EnvironProvider for
   195  	// each registered provider type.
   196  	providers map[string]EnvironProvider
   197  	// providerAliases is a map of provider type aliases.
   198  	aliases map[string]string
   199  }
   200  
   201  var globalProviders = &globalProviderRegistry{
   202  	providers: map[string]EnvironProvider{},
   203  	aliases:   map[string]string{},
   204  }
   205  
   206  func (r *globalProviderRegistry) RegisterProvider(p EnvironProvider, providerType string, providerTypeAliases ...string) error {
   207  	if r.providers[providerType] != nil || r.aliases[providerType] != "" {
   208  		return errors.Errorf("duplicate provider name %q", providerType)
   209  	}
   210  	r.providers[providerType] = p
   211  	for _, alias := range providerTypeAliases {
   212  		if r.providers[alias] != nil || r.aliases[alias] != "" {
   213  			return errors.Errorf("duplicate provider alias %q", alias)
   214  		}
   215  		r.aliases[alias] = providerType
   216  	}
   217  	return nil
   218  }
   219  
   220  func (r *globalProviderRegistry) RegisteredProviders() []string {
   221  	var p []string
   222  	for k := range r.providers {
   223  		p = append(p, k)
   224  	}
   225  	return p
   226  }
   227  
   228  func (r *globalProviderRegistry) Provider(providerType string) (EnvironProvider, error) {
   229  	if alias, ok := r.aliases[providerType]; ok {
   230  		providerType = alias
   231  	}
   232  	p, ok := r.providers[providerType]
   233  	if !ok {
   234  		return nil, errors.Errorf("no registered provider for %q", providerType)
   235  	}
   236  	return p, nil
   237  }
   238  
   239  // RegisterProvider registers a new environment provider. Name gives the name
   240  // of the provider, and p the interface to that provider.
   241  //
   242  // RegisterProvider will panic if the provider name or any of the aliases
   243  // are registered more than once.
   244  func RegisterProvider(name string, p EnvironProvider, alias ...string) {
   245  	if err := GlobalProviderRegistry().RegisterProvider(p, name, alias...); err != nil {
   246  		panic(fmt.Errorf("juju: %v", err))
   247  	}
   248  }
   249  
   250  // RegisteredProviders enumerate all the environ providers which have been registered.
   251  func RegisteredProviders() []string {
   252  	return GlobalProviderRegistry().RegisteredProviders()
   253  }
   254  
   255  // Provider returns the previously registered provider with the given type.
   256  func Provider(providerType string) (EnvironProvider, error) {
   257  	return GlobalProviderRegistry().Provider(providerType)
   258  }
   259  
   260  // ReadEnvironsBytes parses the contents of an environments.yaml file
   261  // and returns its representation. An environment with an unknown type
   262  // will only generate an error when New is called for that environment.
   263  // Attributes for environments with known types are checked.
   264  func ReadEnvironsBytes(data []byte) (*Environs, error) {
   265  	var raw struct {
   266  		Default      string
   267  		Environments map[string]map[string]interface{}
   268  	}
   269  	err := goyaml.Unmarshal(data, &raw)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  
   274  	if raw.Default != "" && raw.Environments[raw.Default] == nil {
   275  		return nil, errors.Errorf("default environment %q does not exist", raw.Default)
   276  	}
   277  	if raw.Default == "" {
   278  		// If there's a single environment, then we get the default
   279  		// automatically.
   280  		if len(raw.Environments) == 1 {
   281  			for name := range raw.Environments {
   282  				raw.Default = name
   283  				break
   284  			}
   285  		}
   286  	}
   287  	for name, attrs := range raw.Environments {
   288  		// store the name of the this environment in the config itself
   289  		// so that providers can see it.
   290  		attrs["name"] = name
   291  	}
   292  	return &Environs{raw.Default, raw.Environments}, nil
   293  }
   294  
   295  func environsPath(path string) string {
   296  	if path == "" {
   297  		path = osenv.JujuHomePath("environments.yaml")
   298  	}
   299  	return path
   300  }
   301  
   302  // NoEnvError indicates the default environment config file is missing.
   303  type NoEnvError struct {
   304  	error
   305  }
   306  
   307  // IsNoEnv reports whether err is a NoEnvError.
   308  func IsNoEnv(err error) bool {
   309  	_, ok := err.(NoEnvError)
   310  	return ok
   311  }
   312  
   313  // ReadEnvirons reads the juju environments.yaml file
   314  // and returns the result of running ParseEnvironments
   315  // on the file's contents.
   316  // If path is empty, $HOME/.juju/environments.yaml is used.
   317  func ReadEnvirons(path string) (*Environs, error) {
   318  	environsFilepath := environsPath(path)
   319  	data, err := ioutil.ReadFile(environsFilepath)
   320  	if err != nil {
   321  		if os.IsNotExist(err) {
   322  			return nil, NoEnvError{err}
   323  		}
   324  		return nil, err
   325  	}
   326  	e, err := ReadEnvironsBytes(data)
   327  	if err != nil {
   328  		return nil, fmt.Errorf("cannot parse %q: %v", environsFilepath, err)
   329  	}
   330  	return e, nil
   331  }
   332  
   333  // WriteEnvirons creates a new juju environments.yaml file with the specified contents.
   334  func WriteEnvirons(path string, fileContents string) (string, error) {
   335  	environsFilepath := environsPath(path)
   336  	environsDir := filepath.Dir(environsFilepath)
   337  	var info os.FileInfo
   338  	var err error
   339  	if info, err = os.Lstat(environsDir); os.IsNotExist(err) {
   340  		if err = os.MkdirAll(environsDir, 0700); err != nil {
   341  			return "", err
   342  		}
   343  	} else if err != nil {
   344  		return "", err
   345  	} else if info.Mode().Perm() != 0700 {
   346  		logger.Warningf("permission of %q is %q", environsDir, info.Mode().Perm())
   347  	}
   348  	if err := ioutil.WriteFile(environsFilepath, []byte(fileContents), 0600); err != nil {
   349  		return "", err
   350  	}
   351  	// WriteFile does not change permissions of existing files.
   352  	if err := os.Chmod(environsFilepath, 0600); err != nil {
   353  		return "", err
   354  	}
   355  	return environsFilepath, nil
   356  }
   357  
   358  // BootstrapConfig returns a copy of the supplied configuration with the
   359  // admin-secret and ca-private-key attributes removed. If the resulting
   360  // config is not suitable for bootstrapping an environment, an error is
   361  // returned.
   362  func BootstrapConfig(cfg *config.Config) (*config.Config, error) {
   363  	m := cfg.AllAttrs()
   364  	// We never want to push admin-secret or the root CA private key to the cloud.
   365  	delete(m, "admin-secret")
   366  	delete(m, "ca-private-key")
   367  	cfg, err := config.New(config.NoDefaults, m)
   368  	if err != nil {
   369  		return nil, err
   370  	}
   371  	if _, ok := cfg.AgentVersion(); !ok {
   372  		return nil, fmt.Errorf("environment configuration has no agent-version")
   373  	}
   374  	return cfg, nil
   375  }