github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/environs/open.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  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/utils"
    12  	"github.com/juju/utils/featureflag"
    13  
    14  	"github.com/juju/juju/cert"
    15  	"github.com/juju/juju/environs/config"
    16  	"github.com/juju/juju/environs/configstore"
    17  	"github.com/juju/juju/feature"
    18  )
    19  
    20  var (
    21  	InvalidEnvironmentError = fmt.Errorf(
    22  		"environment is not a juju-core environment")
    23  )
    24  
    25  // ConfigSource represents where some configuration data
    26  // has come from.
    27  // TODO(rog) remove this when we don't have to support
    28  // old environments with no configstore info. See lp#1235217
    29  type ConfigSource int
    30  
    31  const (
    32  	ConfigFromNowhere ConfigSource = iota
    33  	ConfigFromInfo
    34  	ConfigFromEnvirons
    35  )
    36  
    37  // EmptyConfig indicates the .jenv file is empty.
    38  type EmptyConfig struct {
    39  	error
    40  }
    41  
    42  // IsEmptyConfig reports whether err is a EmptyConfig.
    43  func IsEmptyConfig(err error) bool {
    44  	_, ok := err.(EmptyConfig)
    45  	return ok
    46  }
    47  
    48  // ConfigForName returns the configuration for the environment with
    49  // the given name from the default environments file. If the name is
    50  // blank, the default environment will be used. If the configuration
    51  // is not found, an errors.NotFoundError is returned. If the given
    52  // store contains an entry for the environment and it has associated
    53  // bootstrap config, that configuration will be returned.
    54  // ConfigForName also returns where the configuration was sourced from
    55  // (this is also valid even when there is an error.
    56  func ConfigForName(name string, store configstore.Storage) (*config.Config, ConfigSource, error) {
    57  	envs, err := ReadEnvirons("")
    58  	if err != nil {
    59  		return nil, ConfigFromNowhere, err
    60  	}
    61  	if name == "" {
    62  		name = envs.Default
    63  	}
    64  
    65  	info, err := store.ReadInfo(name)
    66  	if err == nil {
    67  		if len(info.BootstrapConfig()) == 0 {
    68  			return nil, ConfigFromNowhere, EmptyConfig{fmt.Errorf("environment has no bootstrap configuration data")}
    69  		}
    70  		logger.Debugf("ConfigForName found bootstrap config %#v", info.BootstrapConfig())
    71  		cfg, err := config.New(config.NoDefaults, info.BootstrapConfig())
    72  		return cfg, ConfigFromInfo, err
    73  	} else if !errors.IsNotFound(err) {
    74  		return nil, ConfigFromInfo, fmt.Errorf("cannot read environment info for %q: %v", name, err)
    75  	}
    76  
    77  	cfg, err := envs.Config(name)
    78  	return cfg, ConfigFromEnvirons, err
    79  }
    80  
    81  // maybeNotBootstrapped takes an error and source, returned by
    82  // ConfigForName and returns ErrNotBootstrapped if it looks like the
    83  // environment is not bootstrapped, or err as-is otherwise.
    84  func maybeNotBootstrapped(err error, source ConfigSource) error {
    85  	if err != nil && source == ConfigFromEnvirons {
    86  		return ErrNotBootstrapped
    87  	}
    88  	return err
    89  }
    90  
    91  // NewFromName opens the environment with the given
    92  // name from the default environments file. If the
    93  // name is blank, the default environment will be used.
    94  // If the given store contains an entry for the environment
    95  // and it has associated bootstrap config, that configuration
    96  // will be returned.
    97  func NewFromName(name string, store configstore.Storage) (Environ, error) {
    98  	// If we get an error when reading from a legacy
    99  	// environments.yaml entry, we pretend it didn't exist
   100  	// because the error is likely to be because
   101  	// configuration attributes don't exist which
   102  	// will be filled in by Prepare.
   103  	cfg, source, err := ConfigForName(name, store)
   104  	if err := maybeNotBootstrapped(err, source); err != nil {
   105  		return nil, err
   106  	}
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	env, err := New(cfg)
   112  	if err := maybeNotBootstrapped(err, source); err != nil {
   113  		return nil, err
   114  	}
   115  	return env, err
   116  }
   117  
   118  // PrepareFromName is the same as NewFromName except
   119  // that the environment is is prepared as well as opened,
   120  // and environment information is created using the
   121  // given store. If the environment is already prepared,
   122  // it behaves like NewFromName.
   123  var PrepareFromName = prepareFromNameProductionFunc
   124  
   125  func prepareFromNameProductionFunc(name string, ctx BootstrapContext, store configstore.Storage) (Environ, error) {
   126  	cfg, _, err := ConfigForName(name, store)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	return Prepare(cfg, ctx, store)
   131  }
   132  
   133  // NewFromAttrs returns a new environment based on the provided configuration
   134  // attributes.
   135  // TODO(rog) remove this function - it's almost always wrong to use it.
   136  func NewFromAttrs(attrs map[string]interface{}) (Environ, error) {
   137  	cfg, err := config.New(config.NoDefaults, attrs)
   138  	if err != nil {
   139  		return nil, errors.Trace(err)
   140  	}
   141  	return New(cfg)
   142  }
   143  
   144  // New returns a new environment based on the provided configuration.
   145  func New(config *config.Config) (Environ, error) {
   146  	p, err := Provider(config.Type())
   147  	if err != nil {
   148  		return nil, errors.Trace(err)
   149  	}
   150  	return p.Open(config)
   151  }
   152  
   153  // Prepare prepares a new environment based on the provided configuration.
   154  // If the environment is already prepared, it behaves like New.
   155  func Prepare(cfg *config.Config, ctx BootstrapContext, store configstore.Storage) (Environ, error) {
   156  
   157  	if p, err := Provider(cfg.Type()); err != nil {
   158  		return nil, errors.Trace(err)
   159  	} else if info, err := store.ReadInfo(cfg.Name()); errors.IsNotFound(errors.Cause(err)) {
   160  		info = store.CreateInfo(cfg.Name())
   161  		if env, err := prepare(ctx, cfg, info, p); err == nil {
   162  			return env, decorateAndWriteInfo(info, env.Config())
   163  		} else {
   164  			if err := info.Destroy(); err != nil {
   165  				logger.Warningf("cannot destroy newly created environment info: %v", err)
   166  			}
   167  			return nil, errors.Trace(err)
   168  		}
   169  	} else if err != nil {
   170  		return nil, errors.Annotatef(err, "error reading environment info %q", cfg.Name())
   171  	} else if !info.Initialized() {
   172  		return nil,
   173  			errors.Errorf(
   174  				"found uninitialized environment info for %q; environment preparation probably in progress or interrupted",
   175  				cfg.Name(),
   176  			)
   177  	} else if len(info.BootstrapConfig()) == 0 {
   178  		return nil, errors.New("found environment info but no bootstrap config")
   179  	} else {
   180  		cfg, err = config.New(config.NoDefaults, info.BootstrapConfig())
   181  		if err != nil {
   182  			return nil, errors.Annotate(err, "cannot parse bootstrap config")
   183  		}
   184  		return New(cfg)
   185  	}
   186  }
   187  
   188  // decorateAndWriteInfo decorates the info struct with information
   189  // from the given cfg, and the writes that out to the filesystem.
   190  func decorateAndWriteInfo(info configstore.EnvironInfo, cfg *config.Config) error {
   191  
   192  	// Sanity check our config.
   193  	var endpoint configstore.APIEndpoint
   194  	if cert, ok := cfg.CACert(); !ok {
   195  		return errors.Errorf("CACert is not set")
   196  	} else if uuid, ok := cfg.UUID(); !ok {
   197  		return errors.Errorf("UUID is not set")
   198  	} else if adminSecret := cfg.AdminSecret(); adminSecret == "" {
   199  		return errors.Errorf("admin-secret is not set")
   200  	} else {
   201  		endpoint = configstore.APIEndpoint{
   202  			CACert:      cert,
   203  			EnvironUUID: uuid,
   204  		}
   205  	}
   206  
   207  	creds := configstore.APICredentials{
   208  		User:     configstore.DefaultAdminUsername,
   209  		Password: cfg.AdminSecret(),
   210  	}
   211  	if featureflag.Enabled(feature.JES) {
   212  		endpoint.ServerUUID = endpoint.EnvironUUID
   213  	}
   214  	info.SetAPICredentials(creds)
   215  	info.SetAPIEndpoint(endpoint)
   216  	info.SetBootstrapConfig(cfg.AllAttrs())
   217  
   218  	if err := info.Write(); err != nil {
   219  		return errors.Annotatef(err, "cannot create environment info %q", cfg.Name())
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func prepare(ctx BootstrapContext, cfg *config.Config, info configstore.EnvironInfo, p EnvironProvider) (Environ, error) {
   226  	cfg, err := ensureAdminSecret(cfg)
   227  	if err != nil {
   228  		return nil, errors.Annotate(err, "cannot generate admin-secret")
   229  	}
   230  	cfg, err = ensureCertificate(cfg)
   231  	if err != nil {
   232  		return nil, errors.Annotate(err, "cannot ensure CA certificate")
   233  	}
   234  	cfg, err = ensureUUID(cfg)
   235  	if err != nil {
   236  		return nil, errors.Annotate(err, "cannot ensure uuid")
   237  	}
   238  
   239  	return p.PrepareForBootstrap(ctx, cfg)
   240  }
   241  
   242  // ensureAdminSecret returns a config with a non-empty admin-secret.
   243  func ensureAdminSecret(cfg *config.Config) (*config.Config, error) {
   244  	if cfg.AdminSecret() != "" {
   245  		return cfg, nil
   246  	}
   247  	return cfg.Apply(map[string]interface{}{
   248  		"admin-secret": randomKey(),
   249  	})
   250  }
   251  
   252  // ensureCertificate generates a new CA certificate and
   253  // attaches it to the given environment configuration,
   254  // unless the configuration already has one.
   255  func ensureCertificate(cfg *config.Config) (*config.Config, error) {
   256  	_, hasCACert := cfg.CACert()
   257  	_, hasCAKey := cfg.CAPrivateKey()
   258  	if hasCACert && hasCAKey {
   259  		return cfg, nil
   260  	}
   261  	if hasCACert && !hasCAKey {
   262  		return nil, fmt.Errorf("environment configuration with a certificate but no CA private key")
   263  	}
   264  
   265  	caCert, caKey, err := cert.NewCA(cfg.Name(), time.Now().UTC().AddDate(10, 0, 0))
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	return cfg.Apply(map[string]interface{}{
   270  		"ca-cert":        string(caCert),
   271  		"ca-private-key": string(caKey),
   272  	})
   273  }
   274  
   275  // ensureUUID generates a new uuid and attaches it to
   276  // the given environment configuration, unless the
   277  // configuration already has one.
   278  func ensureUUID(cfg *config.Config) (*config.Config, error) {
   279  	_, hasUUID := cfg.UUID()
   280  	if hasUUID {
   281  		return cfg, nil
   282  	}
   283  	uuid, err := utils.NewUUID()
   284  	if err != nil {
   285  		return nil, errors.Trace(err)
   286  	}
   287  	return cfg.Apply(map[string]interface{}{
   288  		"uuid": uuid.String(),
   289  	})
   290  }
   291  
   292  // Destroy destroys the environment and, if successful,
   293  // its associated configuration data from the given store.
   294  func Destroy(env Environ, store configstore.Storage) error {
   295  	name := env.Config().Name()
   296  	if err := env.Destroy(); err != nil {
   297  		return err
   298  	}
   299  	return DestroyInfo(name, store)
   300  }
   301  
   302  // DestroyInfo destroys the configuration data for the named
   303  // environment from the given store.
   304  func DestroyInfo(envName string, store configstore.Storage) error {
   305  	info, err := store.ReadInfo(envName)
   306  	if err != nil {
   307  		if errors.IsNotFound(err) {
   308  			return nil
   309  		}
   310  		return err
   311  	}
   312  	if err := info.Destroy(); err != nil {
   313  		return errors.Annotate(err, "cannot destroy environment configuration information")
   314  	}
   315  	return nil
   316  }