github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  // Config returns the environment configuration for the environment
    56  // with the given name. If the configuration is not
    57  // found, an errors.NotFoundError is returned.
    58  func (envs *Environs) Config(name string) (*config.Config, error) {
    59  	if name == "" {
    60  		name = envs.Default
    61  		if name == "" {
    62  			return nil, errors.New("no default environment found")
    63  		}
    64  	}
    65  	attrs, ok := envs.rawEnvirons[name]
    66  	if !ok {
    67  		return nil, errors.NotFoundf("environment %q", name)
    68  	}
    69  	if err := validateEnvironmentKind(attrs); err != nil {
    70  		return nil, errors.Trace(err)
    71  	}
    72  
    73  	// If deprecated config attributes are used, log warnings so the user can know
    74  	// that they need to be fixed.
    75  	// We also look up what any new values might be so we can tell the user.
    76  	newAttrs := config.ProcessDeprecatedAttributes(attrs)
    77  	envs.logDeprecatedWarnings(attrs, newAttrs, config.ToolsMetadataURLKey, config.AgentMetadataURLKey)
    78  
    79  	// null has been renamed to manual (with an alias for existing config).
    80  	if oldType, _ := attrs["type"].(string); oldType == "null" {
    81  		logger.Warningf(
    82  			"Provider type \"null\" has been renamed to \"manual\".\n" +
    83  				"Please update your environment configuration.",
    84  		)
    85  	}
    86  	// lxc-use-clone has been renamed to lxc-clone
    87  	envs.logDeprecatedWarnings(attrs, newAttrs, config.LxcUseClone, config.LxcClone)
    88  
    89  	// provisioner-safe-mode has been renamed to provisioner-harvest-mode, so log warnings to the user
    90  	envs.logDeprecatedWarnings(attrs, newAttrs, config.ProvisionerSafeModeKey, config.ProvisionerHarvestModeKey)
    91  
    92  	// tools-stream has been renamed to agent-stream, so log warnings to the user
    93  	envs.logDeprecatedWarnings(attrs, newAttrs, config.ToolsStreamKey, config.AgentStreamKey)
    94  
    95  	cfg, err := config.New(config.UseDefaults, attrs)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	return cfg, nil
   100  }
   101  
   102  // logDeprecatedWarnings constructs log warning messages for deprecated attributes names.
   103  // It checks if both old and new attribute names are provided.
   104  // When both are provided, the message warns to remove old attribute from configuration.
   105  // When only old attribute name is used, the message advises to replace it with the new name.
   106  func (envs *Environs) logDeprecatedWarnings(attrs, newAttrs map[string]interface{}, oldKey, newKey string) {
   107  	if oldValue := attrs[oldKey]; oldValue != nil {
   108  		// no need to warn if attribute is unused
   109  		if oldStr, ok := oldValue.(string); ok && oldStr == "" {
   110  			return
   111  		}
   112  		newValue, newValueSpecified := attrs[newKey]
   113  		var msg string
   114  		if newValueSpecified {
   115  			msg = fmt.Sprintf(
   116  				"Config attribute %q (%v) is deprecated and will be ignored since \n"+
   117  					"the new %q (%v) attribute has also been used. \n"+
   118  					"The attribute %q should be removed from your configuration.",
   119  				oldKey, oldValue, newKey, newValue, oldKey)
   120  		} else {
   121  			msg = fmt.Sprintf(
   122  				"Config attribute %q (%v) is deprecated. \n"+
   123  					"It is replaced by %q attribute. \n"+
   124  					"Your configuration should be updated to set %q as follows \n%v: %v.",
   125  				oldKey, oldValue, newKey, newKey, newKey, newAttrs[newKey])
   126  		}
   127  		logger.Warningf(msg)
   128  	}
   129  }
   130  
   131  // providers maps from provider type to EnvironProvider for
   132  // each registered provider type.
   133  //
   134  // providers should not typically be used directly; the
   135  // Provider function will handle provider type aliases,
   136  // and should be used instead.
   137  var providers = make(map[string]EnvironProvider)
   138  
   139  // providerAliases is a map of provider type aliases.
   140  var providerAliases = make(map[string]string)
   141  
   142  // RegisterProvider registers a new environment provider. Name gives the name
   143  // of the provider, and p the interface to that provider.
   144  //
   145  // RegisterProvider will panic if the provider name or any of the aliases
   146  // are registered more than once.
   147  func RegisterProvider(name string, p EnvironProvider, alias ...string) {
   148  	if providers[name] != nil || providerAliases[name] != "" {
   149  		panic(errors.Errorf("juju: duplicate provider name %q", name))
   150  	}
   151  	providers[name] = p
   152  	for _, alias := range alias {
   153  		if providers[alias] != nil || providerAliases[alias] != "" {
   154  			panic(errors.Errorf("juju: duplicate provider alias %q", alias))
   155  		}
   156  		providerAliases[alias] = name
   157  	}
   158  }
   159  
   160  // RegisteredProviders enumerate all the environ providers which have been registered.
   161  func RegisteredProviders() []string {
   162  	var p []string
   163  	for k := range providers {
   164  		p = append(p, k)
   165  	}
   166  	return p
   167  }
   168  
   169  // Provider returns the previously registered provider with the given type.
   170  func Provider(providerType string) (EnvironProvider, error) {
   171  	if alias, ok := providerAliases[providerType]; ok {
   172  		providerType = alias
   173  	}
   174  	p, ok := providers[providerType]
   175  	if !ok {
   176  		return nil, errors.Errorf("no registered provider for %q", providerType)
   177  	}
   178  	return p, nil
   179  }
   180  
   181  // ReadEnvironsBytes parses the contents of an environments.yaml file
   182  // and returns its representation. An environment with an unknown type
   183  // will only generate an error when New is called for that environment.
   184  // Attributes for environments with known types are checked.
   185  func ReadEnvironsBytes(data []byte) (*Environs, error) {
   186  	var raw struct {
   187  		Default      string
   188  		Environments map[string]map[string]interface{}
   189  	}
   190  	err := goyaml.Unmarshal(data, &raw)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	if raw.Default != "" && raw.Environments[raw.Default] == nil {
   196  		return nil, errors.Errorf("default environment %q does not exist", raw.Default)
   197  	}
   198  	if raw.Default == "" {
   199  		// If there's a single environment, then we get the default
   200  		// automatically.
   201  		if len(raw.Environments) == 1 {
   202  			for name := range raw.Environments {
   203  				raw.Default = name
   204  				break
   205  			}
   206  		}
   207  	}
   208  	for name, attrs := range raw.Environments {
   209  		// store the name of the this environment in the config itself
   210  		// so that providers can see it.
   211  		attrs["name"] = name
   212  	}
   213  	return &Environs{raw.Default, raw.Environments}, nil
   214  }
   215  
   216  func environsPath(path string) string {
   217  	if path == "" {
   218  		path = osenv.JujuHomePath("environments.yaml")
   219  	}
   220  	return path
   221  }
   222  
   223  // NoEnvError indicates the default environment config file is missing.
   224  type NoEnvError struct {
   225  	error
   226  }
   227  
   228  // IsNoEnv reports whether err is a NoEnvError.
   229  func IsNoEnv(err error) bool {
   230  	_, ok := err.(NoEnvError)
   231  	return ok
   232  }
   233  
   234  // ReadEnvirons reads the juju environments.yaml file
   235  // and returns the result of running ParseEnvironments
   236  // on the file's contents.
   237  // If path is empty, $HOME/.juju/environments.yaml is used.
   238  func ReadEnvirons(path string) (*Environs, error) {
   239  	environsFilepath := environsPath(path)
   240  	data, err := ioutil.ReadFile(environsFilepath)
   241  	if err != nil {
   242  		if os.IsNotExist(err) {
   243  			return nil, NoEnvError{err}
   244  		}
   245  		return nil, err
   246  	}
   247  	e, err := ReadEnvironsBytes(data)
   248  	if err != nil {
   249  		return nil, fmt.Errorf("cannot parse %q: %v", environsFilepath, err)
   250  	}
   251  	return e, nil
   252  }
   253  
   254  // WriteEnvirons creates a new juju environments.yaml file with the specified contents.
   255  func WriteEnvirons(path string, fileContents string) (string, error) {
   256  	environsFilepath := environsPath(path)
   257  	environsDir := filepath.Dir(environsFilepath)
   258  	var info os.FileInfo
   259  	var err error
   260  	if info, err = os.Lstat(environsDir); os.IsNotExist(err) {
   261  		if err = os.MkdirAll(environsDir, 0700); err != nil {
   262  			return "", err
   263  		}
   264  	} else if err != nil {
   265  		return "", err
   266  	} else if info.Mode().Perm() != 0700 {
   267  		logger.Warningf("permission of %q is %q", environsDir, info.Mode().Perm())
   268  	}
   269  	if err := ioutil.WriteFile(environsFilepath, []byte(fileContents), 0600); err != nil {
   270  		return "", err
   271  	}
   272  	// WriteFile does not change permissions of existing files.
   273  	if err := os.Chmod(environsFilepath, 0600); err != nil {
   274  		return "", err
   275  	}
   276  	return environsFilepath, nil
   277  }
   278  
   279  // BootstrapConfig returns a copy of the supplied configuration with the
   280  // admin-secret and ca-private-key attributes removed. If the resulting
   281  // config is not suitable for bootstrapping an environment, an error is
   282  // returned.
   283  func BootstrapConfig(cfg *config.Config) (*config.Config, error) {
   284  	m := cfg.AllAttrs()
   285  	// We never want to push admin-secret or the root CA private key to the cloud.
   286  	delete(m, "admin-secret")
   287  	delete(m, "ca-private-key")
   288  	cfg, err := config.New(config.NoDefaults, m)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  	if _, ok := cfg.AgentVersion(); !ok {
   293  		return nil, fmt.Errorf("environment configuration has no agent-version")
   294  	}
   295  	return cfg, nil
   296  }