launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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/loggo/loggo"
    13  	"launchpad.net/goyaml"
    14  
    15  	"launchpad.net/juju-core/environs/config"
    16  	"launchpad.net/juju-core/errors"
    17  	"launchpad.net/juju-core/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, fmt.Errorf("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, 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  	if oldToolsURL := attrs["tools-url"]; oldToolsURL != nil && oldToolsURL.(string) != "" {
    76  		_, newToolsSpecified := attrs["tools-metadata-url"]
    77  		var msg string
    78  		if newToolsSpecified {
    79  			msg = fmt.Sprintf(
    80  				"Config attribute %q (%v) is deprecated and will be ignored since\n"+
    81  					"the new tools URL attribute %q has also been used.\n"+
    82  					"The attribute %q should be removed from your configuration.",
    83  				"tools-url", oldToolsURL, "tools-metadata-url", "tools-url")
    84  		} else {
    85  			msg = fmt.Sprintf(
    86  				"Config attribute %q (%v) is deprecated.\n"+
    87  					"The location to find tools is now specified using the %q attribute.\n"+
    88  					"Your configuration should be updated to set %q as follows\n%v: %v.",
    89  				"tools-url", oldToolsURL, "tools-metadata-url", "tools-metadata-url", "tools-metadata-url", oldToolsURL)
    90  		}
    91  		logger.Warningf(msg)
    92  	}
    93  	// null has been renamed to manual (with an alias for existing config).
    94  	if oldType, _ := attrs["type"].(string); oldType == "null" {
    95  		logger.Warningf(
    96  			"Provider type \"null\" has been renamed to \"manual\".\n" +
    97  				"Please update your environment configuration.",
    98  		)
    99  	}
   100  
   101  	cfg, err := config.New(config.UseDefaults, attrs)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	return cfg, nil
   106  }
   107  
   108  // providers maps from provider type to EnvironProvider for
   109  // each registered provider type.
   110  //
   111  // providers should not typically be used directly; the
   112  // Provider function will handle provider type aliases,
   113  // and should be used instead.
   114  var providers = make(map[string]EnvironProvider)
   115  
   116  // providerAliases is a map of provider type aliases.
   117  var providerAliases = make(map[string]string)
   118  
   119  // RegisterProvider registers a new environment provider. Name gives the name
   120  // of the provider, and p the interface to that provider.
   121  //
   122  // RegisterProvider will panic if the provider name or any of the aliases
   123  // are registered more than once.
   124  func RegisterProvider(name string, p EnvironProvider, alias ...string) {
   125  	if providers[name] != nil || providerAliases[name] != "" {
   126  		panic(fmt.Errorf("juju: duplicate provider name %q", name))
   127  	}
   128  	providers[name] = p
   129  	for _, alias := range alias {
   130  		if providers[alias] != nil || providerAliases[alias] != "" {
   131  			panic(fmt.Errorf("juju: duplicate provider alias %q", alias))
   132  		}
   133  		providerAliases[alias] = name
   134  	}
   135  }
   136  
   137  // Provider returns the previously registered provider with the given type.
   138  func Provider(typ string) (EnvironProvider, error) {
   139  	if alias, ok := providerAliases[typ]; ok {
   140  		typ = alias
   141  	}
   142  	p, ok := providers[typ]
   143  	if !ok {
   144  		return nil, fmt.Errorf("no registered provider for %q", typ)
   145  	}
   146  	return p, nil
   147  }
   148  
   149  // ReadEnvironsBytes parses the contents of an environments.yaml file
   150  // and returns its representation. An environment with an unknown type
   151  // will only generate an error when New is called for that environment.
   152  // Attributes for environments with known types are checked.
   153  func ReadEnvironsBytes(data []byte) (*Environs, error) {
   154  	var raw struct {
   155  		Default      string
   156  		Environments map[string]map[string]interface{}
   157  	}
   158  	err := goyaml.Unmarshal(data, &raw)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	if raw.Default != "" && raw.Environments[raw.Default] == nil {
   164  		return nil, fmt.Errorf("default environment %q does not exist", raw.Default)
   165  	}
   166  	if raw.Default == "" {
   167  		// If there's a single environment, then we get the default
   168  		// automatically.
   169  		if len(raw.Environments) == 1 {
   170  			for name := range raw.Environments {
   171  				raw.Default = name
   172  				break
   173  			}
   174  		}
   175  	}
   176  	for name, attrs := range raw.Environments {
   177  		// store the name of the this environment in the config itself
   178  		// so that providers can see it.
   179  		attrs["name"] = name
   180  	}
   181  	return &Environs{raw.Default, raw.Environments}, nil
   182  }
   183  
   184  func environsPath(path string) string {
   185  	if path == "" {
   186  		path = osenv.JujuHomePath("environments.yaml")
   187  	}
   188  	return path
   189  }
   190  
   191  // NoEnvError indicates the default environment config file is missing.
   192  type NoEnvError struct {
   193  	error
   194  }
   195  
   196  // IsNoEnv reports whether err is a NoEnvError.
   197  func IsNoEnv(err error) bool {
   198  	_, ok := err.(NoEnvError)
   199  	return ok
   200  }
   201  
   202  // ReadEnvirons reads the juju environments.yaml file
   203  // and returns the result of running ParseEnvironments
   204  // on the file's contents.
   205  // If path is empty, $HOME/.juju/environments.yaml is used.
   206  func ReadEnvirons(path string) (*Environs, error) {
   207  	environsFilepath := environsPath(path)
   208  	data, err := ioutil.ReadFile(environsFilepath)
   209  	if err != nil {
   210  		if os.IsNotExist(err) {
   211  			return nil, NoEnvError{err}
   212  		}
   213  		return nil, err
   214  	}
   215  	e, err := ReadEnvironsBytes(data)
   216  	if err != nil {
   217  		return nil, fmt.Errorf("cannot parse %q: %v", environsFilepath, err)
   218  	}
   219  	return e, nil
   220  }
   221  
   222  // WriteEnvirons creates a new juju environments.yaml file with the specified contents.
   223  func WriteEnvirons(path string, fileContents string) (string, error) {
   224  	environsFilepath := environsPath(path)
   225  	environsDir := filepath.Dir(environsFilepath)
   226  	var info os.FileInfo
   227  	var err error
   228  	if info, err = os.Lstat(environsDir); os.IsNotExist(err) {
   229  		if err = os.MkdirAll(environsDir, 0700); err != nil {
   230  			return "", err
   231  		}
   232  	} else if err != nil {
   233  		return "", err
   234  	} else if info.Mode().Perm() != 0700 {
   235  		logger.Warningf("permission of %q is %q", environsDir, info.Mode().Perm())
   236  	}
   237  	if err := ioutil.WriteFile(environsFilepath, []byte(fileContents), 0600); err != nil {
   238  		return "", err
   239  	}
   240  	// WriteFile does not change permissions of existing files.
   241  	if err := os.Chmod(environsFilepath, 0600); err != nil {
   242  		return "", err
   243  	}
   244  	return environsFilepath, nil
   245  }
   246  
   247  // BootstrapConfig returns a copy of the supplied configuration with the
   248  // admin-secret and ca-private-key attributes removed. If the resulting
   249  // config is not suitable for bootstrapping an environment, an error is
   250  // returned.
   251  func BootstrapConfig(cfg *config.Config) (*config.Config, error) {
   252  	m := cfg.AllAttrs()
   253  	// We never want to push admin-secret or the root CA private key to the cloud.
   254  	delete(m, "admin-secret")
   255  	delete(m, "ca-private-key")
   256  	cfg, err := config.New(config.NoDefaults, m)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  	if _, ok := cfg.AgentVersion(); !ok {
   261  		return nil, fmt.Errorf("environment configuration has no agent-version")
   262  	}
   263  	return cfg, nil
   264  }