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