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