github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/gce/config.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package gce 5 6 import ( 7 "os" 8 9 "github.com/juju/errors" 10 "github.com/juju/schema" 11 12 "github.com/juju/juju/environs/config" 13 "github.com/juju/juju/provider/gce/google" 14 ) 15 16 // TODO(ericsnow) While not strictly config-related, we could use some 17 // mechanism by which we can validate the values we've hard-coded in 18 // this provider match up with the external authoritative sources. One 19 // example of this is the data stored in instancetypes.go. Similarly 20 // we should also ensure the cloud-images metadata is correct and 21 // up-to-date, though that is more the responsibility of that team. 22 // Regardless, it may be useful to include a tool somewhere in juju 23 // that we can use to validate this provider's potentially out-of-date 24 // data. 25 26 // The GCE-specific config keys. 27 const ( 28 cfgAuthFile = "auth-file" 29 cfgPrivateKey = "private-key" 30 cfgClientID = "client-id" 31 cfgClientEmail = "client-email" 32 cfgRegion = "region" 33 cfgProjectID = "project-id" 34 cfgImageEndpoint = "image-endpoint" 35 ) 36 37 // boilerplateConfig will be shown in help output, so please keep it up to 38 // date when you change environment configuration below. 39 var boilerplateConfig = ` 40 gce: 41 type: gce 42 43 # Google Auth Info 44 # The GCE provider uses OAuth to authenticate. This requires that 45 # you set it up and get the relevant credentials. For more information 46 # see https://cloud.google.com/compute/docs/api/how-tos/authorization. 47 # The key information can be downloaded as a JSON file, or copied, from: 48 # https://console.developers.google.com/project/<projet>/apiui/credential 49 # Either set the path to the downloaded JSON file here: 50 auth-file: 51 52 # ...or set the individual fields for the credentials. Either way, all 53 # three of these are required and have specific meaning to GCE. 54 # private-key: 55 # client-email: 56 # client-id: 57 58 # Google instance info 59 # To provision instances and perform related operations, the provider 60 # will need to know which GCE project to use and into which region to 61 # provision. While the region has a default, the project ID is 62 # required. For information on the project ID, see 63 # https://cloud.google.com/compute/docs/projects and regarding regions 64 # see https://cloud.google.com/compute/docs/zones. 65 project-id: 66 # region: us-central1 67 68 # The GCE provider uses pre-built images when provisioning instances. 69 # You can customize the location in which to find them with the 70 # image-endpoint setting. The default value is the a location within 71 # GCE, so it will give you the best speed when bootstrapping or adding 72 # machines. For more information on the image cache see 73 # https://cloud-images.ubuntu.com/. 74 # image-endpoint: https://www.googleapis.com 75 `[1:] 76 77 // configFields is the spec for each GCE config value's type. 78 var configFields = schema.Fields{ 79 cfgAuthFile: schema.String(), 80 cfgPrivateKey: schema.String(), 81 cfgClientID: schema.String(), 82 cfgClientEmail: schema.String(), 83 cfgRegion: schema.String(), 84 cfgProjectID: schema.String(), 85 cfgImageEndpoint: schema.String(), 86 } 87 88 // TODO(ericsnow) Do we need custom defaults for "image-metadata-url" or 89 // "agent-metadata-url"? The defaults are the official ones (e.g. 90 // cloud-images). 91 92 var configDefaults = schema.Defaults{ 93 cfgAuthFile: "", 94 // See http://cloud-images.ubuntu.com/releases/streams/v1/com.ubuntu.cloud:released:gce.json 95 cfgImageEndpoint: "https://www.googleapis.com", 96 cfgRegion: "us-central1", 97 } 98 99 var configSecretFields = []string{ 100 cfgPrivateKey, 101 } 102 103 var configImmutableFields = []string{ 104 cfgAuthFile, 105 cfgPrivateKey, 106 cfgClientID, 107 cfgClientEmail, 108 cfgRegion, 109 cfgProjectID, 110 cfgImageEndpoint, 111 } 112 113 var configAuthFields = []string{ 114 cfgPrivateKey, 115 cfgClientID, 116 cfgClientEmail, 117 } 118 119 // osEnvFields is the mapping from GCE env vars to config keys. 120 var osEnvFields = map[string]string{ 121 google.OSEnvPrivateKey: cfgPrivateKey, 122 google.OSEnvClientID: cfgClientID, 123 google.OSEnvClientEmail: cfgClientEmail, 124 google.OSEnvRegion: cfgRegion, 125 google.OSEnvProjectID: cfgProjectID, 126 google.OSEnvImageEndpoint: cfgImageEndpoint, 127 } 128 129 // handleInvalidField converts a google.InvalidConfigValue into a new 130 // error, translating a {provider/gce/google}.OSEnvVar* value into a 131 // GCE config key in the new error. 132 func handleInvalidField(err error) error { 133 vErr := err.(*google.InvalidConfigValue) 134 if strValue, ok := vErr.Value.(string); ok && strValue == "" { 135 key := osEnvFields[vErr.Key] 136 return errors.Errorf("%s: must not be empty", key) 137 } 138 return err 139 } 140 141 type environConfig struct { 142 *config.Config 143 attrs map[string]interface{} 144 credentials *google.Credentials 145 } 146 147 // newConfig builds a new environConfig from the provided Config and 148 // returns it. 149 func newConfig(cfg *config.Config) *environConfig { 150 return &environConfig{ 151 Config: cfg, 152 attrs: cfg.UnknownAttrs(), 153 } 154 } 155 156 // newValidConfig builds a new environConfig from the provided Config 157 // and returns it. This includes applying the provided defaults 158 // values, if any. The resulting config values are validated. 159 func newValidConfig(cfg *config.Config, defaults map[string]interface{}) (*environConfig, error) { 160 credentials, err := parseCredentials(cfg) 161 if err != nil { 162 return nil, errors.Trace(err) 163 } 164 handled, err := applyCredentials(cfg, credentials) 165 if err != nil { 166 return nil, errors.Trace(err) 167 } 168 cfg = handled 169 170 // Ensure that the provided config is valid. 171 if err := config.Validate(cfg, nil); err != nil { 172 return nil, errors.Trace(err) 173 } 174 175 // Apply the defaults and coerce/validate the custom config attrs. 176 validated, err := cfg.ValidateUnknownAttrs(configFields, defaults) 177 if err != nil { 178 return nil, errors.Trace(err) 179 } 180 validCfg, err := cfg.Apply(validated) 181 if err != nil { 182 return nil, errors.Trace(err) 183 } 184 185 // Build the config. 186 ecfg := newConfig(validCfg) 187 ecfg.credentials = credentials 188 189 // Do final validation. 190 if err := ecfg.validate(); err != nil { 191 return nil, errors.Trace(err) 192 } 193 194 return ecfg, nil 195 } 196 197 func (c *environConfig) authFile() string { 198 if c.attrs[cfgAuthFile] == nil { 199 return "" 200 } 201 return c.attrs[cfgAuthFile].(string) 202 } 203 204 func (c *environConfig) privateKey() string { 205 return c.attrs[cfgPrivateKey].(string) 206 } 207 208 func (c *environConfig) clientID() string { 209 return c.attrs[cfgClientID].(string) 210 } 211 212 func (c *environConfig) clientEmail() string { 213 return c.attrs[cfgClientEmail].(string) 214 } 215 216 func (c *environConfig) region() string { 217 return c.attrs[cfgRegion].(string) 218 } 219 220 func (c *environConfig) projectID() string { 221 return c.attrs[cfgProjectID].(string) 222 } 223 224 // imageEndpoint identifies where the provider should look for 225 // cloud images (i.e. for simplestreams). 226 func (c *environConfig) imageEndpoint() string { 227 return c.attrs[cfgImageEndpoint].(string) 228 } 229 230 // auth build a new Credentials based on the config and returns it. 231 func (c *environConfig) auth() *google.Credentials { 232 if c.credentials == nil { 233 c.credentials = &google.Credentials{ 234 ClientID: c.clientID(), 235 ClientEmail: c.clientEmail(), 236 PrivateKey: []byte(c.privateKey()), 237 } 238 } 239 return c.credentials 240 } 241 242 // newConnection build a ConnectionConfig based on the config and returns it. 243 func (c *environConfig) newConnection() google.ConnectionConfig { 244 return google.ConnectionConfig{ 245 Region: c.region(), 246 ProjectID: c.projectID(), 247 } 248 } 249 250 // secret gathers the "secret" config values and returns them. 251 func (c *environConfig) secret() map[string]string { 252 secretAttrs := make(map[string]string, len(configSecretFields)) 253 for _, key := range configSecretFields { 254 secretAttrs[key] = c.attrs[key].(string) 255 } 256 return secretAttrs 257 } 258 259 // validate checks GCE-specific config values. 260 func (c environConfig) validate() error { 261 // All fields must be populated, even with just the default. 262 for field := range configFields { 263 if dflt, ok := configDefaults[field]; ok && dflt == "" { 264 continue 265 } 266 if c.attrs[field].(string) == "" { 267 return errors.Errorf("%s: must not be empty", field) 268 } 269 } 270 271 // Check sanity of GCE fields. 272 if err := c.auth().Validate(); err != nil { 273 return errors.Trace(handleInvalidField(err)) 274 } 275 if err := c.newConnection().Validate(); err != nil { 276 return errors.Trace(handleInvalidField(err)) 277 } 278 279 return nil 280 } 281 282 // update applies changes from the provided config to the env config. 283 // Changes to any immutable attributes result in an error. 284 func (c *environConfig) update(cfg *config.Config) error { 285 // Validate the updates. newValidConfig does not modify the "known" 286 // config attributes so it is safe to call Validate here first. 287 if err := config.Validate(cfg, c.Config); err != nil { 288 return errors.Trace(err) 289 } 290 291 updates, err := newValidConfig(cfg, configDefaults) 292 if err != nil { 293 return errors.Trace(err) 294 } 295 296 // Check that no immutable fields have changed. 297 attrs := updates.UnknownAttrs() 298 for _, field := range configImmutableFields { 299 if attrs[field] != c.attrs[field] { 300 return errors.Errorf("%s: cannot change from %v to %v", field, c.attrs[field], attrs[field]) 301 } 302 } 303 304 // Apply the updates. 305 c.Config = cfg 306 c.attrs = cfg.UnknownAttrs() 307 return nil 308 } 309 310 // parseCredentials extracts the OAuth2 info from the config from the 311 // individual fields (falling back on the JSON file). 312 func parseCredentials(cfg *config.Config) (*google.Credentials, error) { 313 attrs := cfg.UnknownAttrs() 314 315 // Try the auth fields first. 316 values := make(map[string]string) 317 for _, field := range configAuthFields { 318 if existing, ok := attrs[field].(string); ok && existing != "" { 319 for key, candidate := range osEnvFields { 320 if field == candidate { 321 values[key] = existing 322 break 323 } 324 } 325 } 326 } 327 if len(values) > 0 { 328 creds, err := google.NewCredentials(values) 329 if err != nil { 330 return nil, errors.Trace(err) 331 } 332 return creds, nil 333 } 334 335 // Fall back to the auth file. 336 filename, ok := attrs[cfgAuthFile].(string) 337 if !ok || filename == "" { 338 // The missing credentials will be caught later. 339 return nil, nil 340 } 341 authFile, err := os.Open(filename) 342 if err != nil { 343 return nil, errors.Trace(err) 344 } 345 defer authFile.Close() 346 creds, err := google.ParseJSONKey(authFile) 347 if err != nil { 348 return nil, errors.Trace(err) 349 } 350 return creds, nil 351 } 352 353 func applyCredentials(cfg *config.Config, creds *google.Credentials) (*config.Config, error) { 354 updates := make(map[string]interface{}) 355 for k, v := range creds.Values() { 356 if v == "" { 357 continue 358 } 359 if field, ok := osEnvFields[k]; ok { 360 for _, authField := range configAuthFields { 361 if field == authField { 362 updates[field] = v 363 break 364 } 365 } 366 } 367 } 368 updated, err := cfg.Apply(updates) 369 if err != nil { 370 return nil, errors.Trace(err) 371 } 372 return updated, nil 373 }