github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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, ctx BootstrapContext, store configstore.Storage) (Environ, error) { 122 cfg, _, err := ConfigForName(name, store) 123 if err != nil { 124 return nil, err 125 } 126 return Prepare(cfg, ctx, 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, ctx BootstrapContext, 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 env, err := prepare(ctx, cfg, info, p) 179 if err != nil { 180 if err := info.Destroy(); err != nil { 181 logger.Warningf("cannot destroy newly created environment info: %v", err) 182 } 183 return nil, err 184 } 185 info.SetBootstrapConfig(env.Config().AllAttrs()) 186 if err := info.Write(); err != nil { 187 return nil, fmt.Errorf("cannot create environment info %q: %v", env.Config().Name(), err) 188 } 189 return env, nil 190 } 191 192 func prepare(ctx BootstrapContext, cfg *config.Config, info configstore.EnvironInfo, p EnvironProvider) (Environ, error) { 193 cfg, err := ensureAdminSecret(cfg) 194 if err != nil { 195 return nil, fmt.Errorf("cannot generate admin-secret: %v", err) 196 } 197 cfg, err = ensureCertificate(cfg) 198 if err != nil { 199 return nil, fmt.Errorf("cannot ensure CA certificate: %v", err) 200 } 201 return p.Prepare(ctx, cfg) 202 } 203 204 // ensureAdminSecret returns a config with a non-empty admin-secret. 205 func ensureAdminSecret(cfg *config.Config) (*config.Config, error) { 206 if cfg.AdminSecret() != "" { 207 return cfg, nil 208 } 209 return cfg.Apply(map[string]interface{}{ 210 "admin-secret": randomKey(), 211 }) 212 } 213 214 // ensureCertificate generates a new CA certificate and 215 // attaches it to the given environment configuration, 216 // unless the configuration already has one. 217 func ensureCertificate(cfg *config.Config) (*config.Config, error) { 218 _, hasCACert := cfg.CACert() 219 _, hasCAKey := cfg.CAPrivateKey() 220 if hasCACert && hasCAKey { 221 return cfg, nil 222 } 223 if hasCACert && !hasCAKey { 224 return nil, fmt.Errorf("environment configuration with a certificate but no CA private key") 225 } 226 227 caCert, caKey, err := cert.NewCA(cfg.Name(), time.Now().UTC().AddDate(10, 0, 0)) 228 if err != nil { 229 return nil, err 230 } 231 return cfg.Apply(map[string]interface{}{ 232 "ca-cert": string(caCert), 233 "ca-private-key": string(caKey), 234 }) 235 } 236 237 // Destroy destroys the environment and, if successful, 238 // its associated configuration data from the given store. 239 func Destroy(env Environ, store configstore.Storage) error { 240 name := env.Name() 241 if err := env.Destroy(); err != nil { 242 return err 243 } 244 return DestroyInfo(name, store) 245 } 246 247 // DestroyInfo destroys the configuration data for the named 248 // environment from the given store. 249 func DestroyInfo(envName string, store configstore.Storage) error { 250 info, err := store.ReadInfo(envName) 251 if err != nil { 252 if errors.IsNotFoundError(err) { 253 return nil 254 } 255 return err 256 } 257 if err := info.Destroy(); err != nil { 258 return errgo.Annotate(err, "cannot destroy environment configuration information") 259 } 260 return nil 261 } 262 263 // VerifyStorage writes the bootstrap init file to the storage to indicate 264 // that the storage is writable. 265 func VerifyStorage(stor storage.Storage) error { 266 reader := strings.NewReader(verificationContent) 267 err := stor.Put(VerificationFilename, reader, 268 int64(len(verificationContent))) 269 if err != nil { 270 logger.Warningf("failed to write bootstrap-verify file: %v", err) 271 return VerifyStorageError 272 } 273 return nil 274 } 275 276 // CheckEnvironment checks if an environment has a bootstrap-verify 277 // that is written by juju-core commands (as compared to one being 278 // written by Python juju). 279 // 280 // If there is no bootstrap-verify file in the storage, it is still 281 // considered to be a Juju-core environment since early versions have 282 // not written it out. 283 // 284 // Returns InvalidEnvironmentError on failure, nil otherwise. 285 func CheckEnvironment(environ Environ) error { 286 stor := environ.Storage() 287 reader, err := storage.Get(stor, VerificationFilename) 288 if errors.IsNotFoundError(err) { 289 // When verification file does not exist, this is a juju-core 290 // environment. 291 return nil 292 } else if err != nil { 293 return err 294 } else if content, err := ioutil.ReadAll(reader); err != nil { 295 return err 296 } else if string(content) != verificationContent { 297 return InvalidEnvironmentError 298 } 299 return nil 300 }