launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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  	"io/ioutil"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/errgo/errgo"
    13  
    14  	"launchpad.net/juju-core/cert"
    15  	"launchpad.net/juju-core/environs/config"
    16  	"launchpad.net/juju-core/environs/configstore"
    17  	"launchpad.net/juju-core/environs/storage"
    18  	"launchpad.net/juju-core/errors"
    19  )
    20  
    21  // File named `VerificationFilename` in the storage will contain
    22  // `verificationContent`.  This is also used to differentiate between
    23  // Python Juju and juju-core environments, so change the content with
    24  // care (and update CheckEnvironment below when you do that).
    25  const (
    26  	VerificationFilename = "bootstrap-verify"
    27  	verificationContent  = "juju-core storage writing verified: ok\n"
    28  )
    29  
    30  var (
    31  	VerifyStorageError error = fmt.Errorf(
    32  		"provider storage is not writable")
    33  	InvalidEnvironmentError = fmt.Errorf(
    34  		"environment is not a juju-core environment")
    35  )
    36  
    37  // ConfigSource represents where some configuration data
    38  // has come from.
    39  // TODO(rog) remove this when we don't have to support
    40  // old environments with no configstore info. See lp#1235217
    41  type ConfigSource int
    42  
    43  const (
    44  	ConfigFromNowhere ConfigSource = iota
    45  	ConfigFromInfo
    46  	ConfigFromEnvirons
    47  )
    48  
    49  // ConfigForName returns the configuration for the environment with the
    50  // given name from the default environments file. If the name is blank,
    51  // the default environment will be used. If the configuration is not
    52  // found, an errors.NotFoundError is returned.
    53  // If the given store contains an entry for the environment
    54  // and it has associated bootstrap config, that configuration
    55  // will be returned.
    56  // ConfigForName also returns where the configuration
    57  // was sourced from (this is also valid even when there
    58  // is an error.
    59  func ConfigForName(name string, store configstore.Storage) (*config.Config, ConfigSource, error) {
    60  	envs, err := ReadEnvirons("")
    61  	if err != nil {
    62  		return nil, ConfigFromNowhere, err
    63  	}
    64  	if name == "" {
    65  		name = envs.Default
    66  	}
    67  	// TODO(rog) 2013-10-04 https://bugs.launchpad.net/juju-core/+bug/1235217
    68  	// Don't fall back to reading from environments.yaml
    69  	// when we can be sure that everyone has a
    70  	// .jenv file for their currently bootstrapped environments.
    71  	if name != "" {
    72  		info, err := store.ReadInfo(name)
    73  		if err == nil {
    74  			if len(info.BootstrapConfig()) == 0 {
    75  				return nil, ConfigFromNowhere, fmt.Errorf("environment has no bootstrap configuration data")
    76  			}
    77  			logger.Debugf("ConfigForName found bootstrap config %#v", info.BootstrapConfig())
    78  			cfg, err := config.New(config.NoDefaults, info.BootstrapConfig())
    79  			return cfg, ConfigFromInfo, err
    80  		}
    81  		if err != nil && !errors.IsNotFoundError(err) {
    82  			return nil, ConfigFromInfo, fmt.Errorf("cannot read environment info for %q: %v", name, err)
    83  		}
    84  	}
    85  	cfg, err := envs.Config(name)
    86  	return cfg, ConfigFromEnvirons, err
    87  }
    88  
    89  // NewFromName opens the environment with the given
    90  // name from the default environments file. If the
    91  // name is blank, the default environment will be used.
    92  // If the given store contains an entry for the environment
    93  // and it has associated bootstrap config, that configuration
    94  // will be returned.
    95  func NewFromName(name string, store configstore.Storage) (Environ, error) {
    96  	// If we get an error when reading from a legacy
    97  	// environments.yaml entry, we pretend it didn't exist
    98  	// because the error is likely to be because
    99  	// configuration attributes don't exist which
   100  	// will be filled in by Prepare.
   101  	cfg, source, err := ConfigForName(name, store)
   102  	if err != nil && source == ConfigFromEnvirons {
   103  		err = ErrNotBootstrapped
   104  	}
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	env, err := New(cfg)
   110  	if err != nil && source == ConfigFromEnvirons {
   111  		err = ErrNotBootstrapped
   112  	}
   113  	return env, err
   114  }
   115  
   116  // PrepareFromName is the same as NewFromName except
   117  // that the environment is is prepared as well as opened,
   118  // and environment information is created using the
   119  // given store. If the environment is already prepared,
   120  // it behaves like NewFromName.
   121  func PrepareFromName(name string, store configstore.Storage) (Environ, error) {
   122  	cfg, _, err := ConfigForName(name, store)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	return Prepare(cfg, store)
   127  }
   128  
   129  // NewFromAttrs returns a new environment based on the provided configuration
   130  // attributes.
   131  // TODO(rog) remove this function - it's almost always wrong to use it.
   132  func NewFromAttrs(attrs map[string]interface{}) (Environ, error) {
   133  	cfg, err := config.New(config.NoDefaults, attrs)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	return New(cfg)
   138  }
   139  
   140  // New returns a new environment based on the provided configuration.
   141  func New(config *config.Config) (Environ, error) {
   142  	p, err := Provider(config.Type())
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	return p.Open(config)
   147  }
   148  
   149  // Prepare prepares a new environment based on the provided configuration.
   150  // If the environment is already prepared, it behaves like New.
   151  func Prepare(cfg *config.Config, store configstore.Storage) (Environ, error) {
   152  	p, err := Provider(cfg.Type())
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	info, err := store.CreateInfo(cfg.Name())
   157  	if err == configstore.ErrEnvironInfoAlreadyExists {
   158  		logger.Infof("environment info already exists; using New not Prepare")
   159  		info, err := store.ReadInfo(cfg.Name())
   160  		if err != nil {
   161  			return nil, fmt.Errorf("error reading environment info %q: %v", cfg.Name(), err)
   162  		}
   163  		if !info.Initialized() {
   164  			return nil, fmt.Errorf("found uninitialized environment info for %q; environment preparation probably in progress or interrupted", cfg.Name())
   165  		}
   166  		if len(info.BootstrapConfig()) == 0 {
   167  			return nil, fmt.Errorf("found environment info but no bootstrap config")
   168  		}
   169  		cfg, err = config.New(config.NoDefaults, info.BootstrapConfig())
   170  		if err != nil {
   171  			return nil, fmt.Errorf("cannot parse bootstrap config: %v", err)
   172  		}
   173  		return New(cfg)
   174  	}
   175  	if err != nil {
   176  		return nil, fmt.Errorf("cannot create new info for environment %q: %v", cfg.Name(), err)
   177  	}
   178  	cfg, err = ensureAdminSecret(cfg)
   179  	if err != nil {
   180  		return nil, fmt.Errorf("cannot generate admin-secret: %v", err)
   181  	}
   182  	cfg, err = ensureCertificate(cfg)
   183  	if err != nil {
   184  		return nil, fmt.Errorf("cannot ensure CA certificate: %v", err)
   185  	}
   186  	env, err := p.Prepare(cfg)
   187  	if err != nil {
   188  		if err := info.Destroy(); err != nil {
   189  			logger.Warningf("cannot destroy newly created environment info: %v", err)
   190  		}
   191  		return nil, err
   192  	}
   193  	info.SetBootstrapConfig(env.Config().AllAttrs())
   194  	if err := info.Write(); err != nil {
   195  		return nil, fmt.Errorf("cannot create environment info %q: %v", env.Config().Name(), err)
   196  	}
   197  	return env, nil
   198  }
   199  
   200  // ensureAdminSecret returns a config with a non-empty admin-secret.
   201  func ensureAdminSecret(cfg *config.Config) (*config.Config, error) {
   202  	if cfg.AdminSecret() != "" {
   203  		return cfg, nil
   204  	}
   205  	return cfg.Apply(map[string]interface{}{
   206  		"admin-secret": randomKey(),
   207  	})
   208  }
   209  
   210  // ensureCertificate generates a new CA certificate and
   211  // attaches it to the given environment configuration,
   212  // unless the configuration already has one.
   213  func ensureCertificate(cfg *config.Config) (*config.Config, error) {
   214  	_, hasCACert := cfg.CACert()
   215  	_, hasCAKey := cfg.CAPrivateKey()
   216  	if hasCACert && hasCAKey {
   217  		return cfg, nil
   218  	}
   219  	if hasCACert && !hasCAKey {
   220  		return nil, fmt.Errorf("environment configuration with a certificate but no CA private key")
   221  	}
   222  
   223  	caCert, caKey, err := cert.NewCA(cfg.Name(), time.Now().UTC().AddDate(10, 0, 0))
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	return cfg.Apply(map[string]interface{}{
   228  		"ca-cert":        string(caCert),
   229  		"ca-private-key": string(caKey),
   230  	})
   231  }
   232  
   233  // Destroy destroys the environment and, if successful,
   234  // its associated configuration data from the given store.
   235  func Destroy(env Environ, store configstore.Storage) error {
   236  	name := env.Name()
   237  	if err := env.Destroy(); err != nil {
   238  		return err
   239  	}
   240  	return DestroyInfo(name, store)
   241  }
   242  
   243  // DestroyInfo destroys the configuration data for the named
   244  // environment from the given store.
   245  func DestroyInfo(envName string, store configstore.Storage) error {
   246  	info, err := store.ReadInfo(envName)
   247  	if err != nil {
   248  		if errors.IsNotFoundError(err) {
   249  			return nil
   250  		}
   251  		return err
   252  	}
   253  	if err := info.Destroy(); err != nil {
   254  		return errgo.Annotate(err, "cannot destroy environment configuration information")
   255  	}
   256  	return nil
   257  }
   258  
   259  // VerifyStorage writes the bootstrap init file to the storage to indicate
   260  // that the storage is writable.
   261  func VerifyStorage(stor storage.Storage) error {
   262  	reader := strings.NewReader(verificationContent)
   263  	err := stor.Put(VerificationFilename, reader,
   264  		int64(len(verificationContent)))
   265  	if err != nil {
   266  		logger.Warningf("failed to write bootstrap-verify file: %v", err)
   267  		return VerifyStorageError
   268  	}
   269  	return nil
   270  }
   271  
   272  // CheckEnvironment checks if an environment has a bootstrap-verify
   273  // that is written by juju-core commands (as compared to one being
   274  // written by Python juju).
   275  //
   276  // If there is no bootstrap-verify file in the storage, it is still
   277  // considered to be a Juju-core environment since early versions have
   278  // not written it out.
   279  //
   280  // Returns InvalidEnvironmentError on failure, nil otherwise.
   281  func CheckEnvironment(environ Environ) error {
   282  	stor := environ.Storage()
   283  	reader, err := storage.Get(stor, VerificationFilename)
   284  	if errors.IsNotFoundError(err) {
   285  		// When verification file does not exist, this is a juju-core
   286  		// environment.
   287  		return nil
   288  	} else if err != nil {
   289  		return err
   290  	} else if content, err := ioutil.ReadAll(reader); err != nil {
   291  		return err
   292  	} else if string(content) != verificationContent {
   293  		return InvalidEnvironmentError
   294  	}
   295  	return nil
   296  }