github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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 "crypto/rand" 8 "fmt" 9 "io" 10 "time" 11 12 "github.com/juju/errors" 13 14 "github.com/juju/juju/cert" 15 "github.com/juju/juju/cloud" 16 "github.com/juju/juju/environs/config" 17 "github.com/juju/juju/jujuclient" 18 ) 19 20 // ControllerModelName is the name of the admin model in each controller. 21 const ControllerModelName = "admin" 22 23 // adminUser is the initial admin user created for all controllers. 24 const AdminUser = "admin@local" 25 26 // New returns a new environment based on the provided configuration. 27 func New(config *config.Config) (Environ, error) { 28 p, err := Provider(config.Type()) 29 if err != nil { 30 return nil, errors.Trace(err) 31 } 32 return p.Open(config) 33 } 34 35 // PrepareParams contains the parameters for preparing a controller Environ 36 // for bootstrapping. 37 type PrepareParams struct { 38 // BaseConfig contains the base configuration for the controller. 39 // 40 // This includes the model name, cloud type, and any user-supplied 41 // configuration. It does not include any default attributes. 42 BaseConfig map[string]interface{} 43 44 // ControllerName is the name of the controller being prepared. 45 ControllerName string 46 47 // CloudName is the name of the cloud that the controller is being 48 // prepared for. 49 CloudName string 50 51 // CloudRegion is the name of the region of the cloud to create 52 // the Juju controller in. This will be empty for clouds without 53 // regions. 54 CloudRegion string 55 56 // CloudEndpoint is the location of the primary API endpoint to 57 // use when communicating with the cloud. 58 CloudEndpoint string 59 60 // CloudStorageEndpoint is the location of the API endpoint to use 61 // when communicating with the cloud's storage service. This will 62 // be empty for clouds that have no cloud-specific API endpoint. 63 CloudStorageEndpoint string 64 65 // Credential is the credential to use to bootstrap. 66 Credential cloud.Credential 67 68 // CredentialName is the name of the credential to use to bootstrap. 69 // This will be empty for auto-detected credentials. 70 CredentialName string 71 } 72 73 // Prepare prepares a new controller based on the provided configuration. 74 // It is an error to prepare a controller if there already exists an 75 // entry in the client store with the same name. 76 // 77 // Upon success, Prepare will update the ClientStore with the details of 78 // the controller, admin account, and admin model. 79 func Prepare( 80 ctx BootstrapContext, 81 store jujuclient.ClientStore, 82 args PrepareParams, 83 ) (_ Environ, resultErr error) { 84 85 _, err := store.ControllerByName(args.ControllerName) 86 if err == nil { 87 return nil, errors.AlreadyExistsf("controller %q", args.ControllerName) 88 } else if !errors.IsNotFound(err) { 89 return nil, errors.Annotatef(err, "error reading controller %q info", args.ControllerName) 90 } 91 92 cloudType, ok := args.BaseConfig["type"].(string) 93 if !ok { 94 return nil, errors.NotFoundf("cloud type in base configuration") 95 } 96 97 p, err := Provider(cloudType) 98 if err != nil { 99 return nil, errors.Trace(err) 100 } 101 102 env, details, err := prepare(ctx, p, args) 103 if err != nil { 104 return nil, errors.Trace(err) 105 } 106 details.Cloud = args.CloudName 107 details.Credential = args.CredentialName 108 109 if err := decorateAndWriteInfo( 110 store, details, args.ControllerName, env.Config().Name(), 111 ); err != nil { 112 return nil, errors.Annotatef(err, "cannot create controller %q info", args.ControllerName) 113 } 114 return env, nil 115 } 116 117 // decorateAndWriteInfo decorates the info struct with information 118 // from the given cfg, and the writes that out to the filesystem. 119 func decorateAndWriteInfo( 120 store jujuclient.ClientStore, 121 details prepareDetails, 122 controllerName, modelName string, 123 ) error { 124 accountName := details.User 125 if err := store.UpdateController(controllerName, details.ControllerDetails); err != nil { 126 return errors.Trace(err) 127 } 128 if err := store.UpdateBootstrapConfig(controllerName, details.BootstrapConfig); err != nil { 129 return errors.Trace(err) 130 } 131 if err := store.UpdateAccount(controllerName, accountName, details.AccountDetails); err != nil { 132 return errors.Trace(err) 133 } 134 if err := store.SetCurrentAccount(controllerName, accountName); err != nil { 135 return errors.Trace(err) 136 } 137 if err := store.UpdateModel(controllerName, accountName, modelName, details.ModelDetails); err != nil { 138 return errors.Trace(err) 139 } 140 if err := store.SetCurrentModel(controllerName, accountName, modelName); err != nil { 141 return errors.Trace(err) 142 } 143 return nil 144 } 145 146 func prepare( 147 ctx BootstrapContext, 148 p EnvironProvider, 149 args PrepareParams, 150 ) (Environ, prepareDetails, error) { 151 var details prepareDetails 152 153 cfg, err := config.New(config.UseDefaults, args.BaseConfig) 154 if err != nil { 155 return nil, details, errors.Trace(err) 156 } 157 cfg, adminSecret, err := ensureAdminSecret(cfg) 158 if err != nil { 159 return nil, details, errors.Annotate(err, "cannot generate admin-secret") 160 } 161 cfg, caCert, err := ensureCertificate(cfg) 162 if err != nil { 163 return nil, details, errors.Annotate(err, "cannot ensure CA certificate") 164 } 165 166 cfg, err = p.BootstrapConfig(BootstrapConfigParams{ 167 cfg, args.Credential, args.CloudRegion, 168 args.CloudEndpoint, args.CloudStorageEndpoint, 169 }) 170 if err != nil { 171 return nil, details, errors.Trace(err) 172 } 173 env, err := p.PrepareForBootstrap(ctx, cfg) 174 if err != nil { 175 return nil, details, errors.Trace(err) 176 } 177 178 // We store the base configuration only; we don't want the 179 // default attributes, generated secrets/certificates, or 180 // UUIDs stored in the bootstrap config. Make a copy, so 181 // we don't disturb the caller's config map. 182 details.Config = make(map[string]interface{}) 183 for k, v := range args.BaseConfig { 184 details.Config[k] = v 185 } 186 delete(details.Config, config.ControllerUUIDKey) 187 delete(details.Config, config.UUIDKey) 188 189 details.CACert = caCert 190 details.ControllerUUID = cfg.ControllerUUID() 191 details.User = AdminUser 192 details.Password = adminSecret 193 details.ModelUUID = cfg.UUID() 194 details.CloudRegion = args.CloudRegion 195 details.CloudEndpoint = args.CloudEndpoint 196 details.CloudStorageEndpoint = args.CloudStorageEndpoint 197 198 return env, details, nil 199 } 200 201 type prepareDetails struct { 202 jujuclient.ControllerDetails 203 jujuclient.BootstrapConfig 204 jujuclient.AccountDetails 205 jujuclient.ModelDetails 206 } 207 208 // ensureAdminSecret returns a config with a non-empty admin-secret. 209 func ensureAdminSecret(cfg *config.Config) (*config.Config, string, error) { 210 if secret := cfg.AdminSecret(); secret != "" { 211 return cfg, secret, nil 212 } 213 214 // Generate a random string. 215 buf := make([]byte, 16) 216 if _, err := io.ReadFull(rand.Reader, buf); err != nil { 217 return nil, "", errors.Annotate(err, "generating random secret") 218 } 219 secret := fmt.Sprintf("%x", buf) 220 221 cfg, err := cfg.Apply(map[string]interface{}{"admin-secret": secret}) 222 if err != nil { 223 return nil, "", errors.Trace(err) 224 } 225 return cfg, secret, nil 226 } 227 228 // ensureCertificate generates a new CA certificate and 229 // attaches it to the given controller configuration, 230 // unless the configuration already has one. 231 func ensureCertificate(cfg *config.Config) (*config.Config, string, error) { 232 caCert, hasCACert := cfg.CACert() 233 _, hasCAKey := cfg.CAPrivateKey() 234 if hasCACert && hasCAKey { 235 return cfg, caCert, nil 236 } 237 if hasCACert && !hasCAKey { 238 return nil, "", errors.Errorf("controller configuration with a certificate but no CA private key") 239 } 240 241 caCert, caKey, err := cert.NewCA(cfg.Name(), cfg.UUID(), time.Now().UTC().AddDate(10, 0, 0)) 242 if err != nil { 243 return nil, "", errors.Trace(err) 244 } 245 cfg, err = cfg.Apply(map[string]interface{}{ 246 config.CACertKey: string(caCert), 247 "ca-private-key": string(caKey), 248 }) 249 if err != nil { 250 return nil, "", errors.Trace(err) 251 } 252 return cfg, string(caCert), nil 253 } 254 255 // Destroy destroys the controller and, if successful, 256 // its associated configuration data from the given store. 257 func Destroy( 258 controllerName string, 259 env Environ, 260 store jujuclient.ControllerRemover, 261 ) error { 262 err := store.RemoveController(controllerName) 263 if err != nil && !errors.IsNotFound(err) { 264 return errors.Trace(err) 265 } 266 return errors.Trace(env.Destroy()) 267 }