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 }