github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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 func parseOSEnv() (map[string]interface{}, error) { 130 // TODO(ericsnow) Support pulling ID/PK from shell environment variables. 131 return nil, nil 132 } 133 134 // handleInvalidField converts a google.InvalidConfigValue into a new 135 // error, translating a {provider/gce/google}.OSEnvVar* value into a 136 // GCE config key in the new error. 137 func handleInvalidField(err error) error { 138 vErr := err.(*google.InvalidConfigValue) 139 if strValue, ok := vErr.Value.(string); ok && strValue == "" { 140 key := osEnvFields[vErr.Key] 141 return errors.Errorf("%s: must not be empty", key) 142 } 143 return err 144 } 145 146 type environConfig struct { 147 *config.Config 148 attrs map[string]interface{} 149 credentials *google.Credentials 150 } 151 152 // newConfig builds a new environConfig from the provided Config and 153 // returns it. 154 func newConfig(cfg *config.Config) *environConfig { 155 return &environConfig{ 156 Config: cfg, 157 attrs: cfg.UnknownAttrs(), 158 } 159 } 160 161 // newValidConfig builds a new environConfig from the provided Config 162 // and returns it. This includes applying the provided defaults 163 // values, if any. The resulting config values are validated. 164 func newValidConfig(cfg *config.Config, defaults map[string]interface{}) (*environConfig, error) { 165 credentials, err := parseCredentials(cfg) 166 if err != nil { 167 return nil, errors.Trace(err) 168 } 169 handled, err := applyCredentials(cfg, credentials) 170 if err != nil { 171 return nil, errors.Trace(err) 172 } 173 cfg = handled 174 175 // Ensure that the provided config is valid. 176 if err := config.Validate(cfg, nil); err != nil { 177 return nil, errors.Trace(err) 178 } 179 180 // Apply the defaults and coerce/validate the custom config attrs. 181 validated, err := cfg.ValidateUnknownAttrs(configFields, defaults) 182 if err != nil { 183 return nil, errors.Trace(err) 184 } 185 validCfg, err := cfg.Apply(validated) 186 if err != nil { 187 return nil, errors.Trace(err) 188 } 189 190 // Build the config. 191 ecfg := newConfig(validCfg) 192 ecfg.credentials = credentials 193 194 // Do final validation. 195 if err := ecfg.validate(); err != nil { 196 return nil, errors.Trace(err) 197 } 198 199 return ecfg, nil 200 } 201 202 func (c *environConfig) authFile() string { 203 if c.attrs[cfgAuthFile] == nil { 204 return "" 205 } 206 return c.attrs[cfgAuthFile].(string) 207 } 208 209 func (c *environConfig) privateKey() string { 210 return c.attrs[cfgPrivateKey].(string) 211 } 212 213 func (c *environConfig) clientID() string { 214 return c.attrs[cfgClientID].(string) 215 } 216 217 func (c *environConfig) clientEmail() string { 218 return c.attrs[cfgClientEmail].(string) 219 } 220 221 func (c *environConfig) region() string { 222 return c.attrs[cfgRegion].(string) 223 } 224 225 func (c *environConfig) projectID() string { 226 return c.attrs[cfgProjectID].(string) 227 } 228 229 // imageEndpoint identifies where the provider should look for 230 // cloud images (i.e. for simplestreams). 231 func (c *environConfig) imageEndpoint() string { 232 return c.attrs[cfgImageEndpoint].(string) 233 } 234 235 // auth build a new Credentials based on the config and returns it. 236 func (c *environConfig) auth() *google.Credentials { 237 if c.credentials == nil { 238 c.credentials = &google.Credentials{ 239 ClientID: c.clientID(), 240 ClientEmail: c.clientEmail(), 241 PrivateKey: []byte(c.privateKey()), 242 } 243 } 244 return c.credentials 245 } 246 247 // newConnection build a ConnectionConfig based on the config and returns it. 248 func (c *environConfig) newConnection() google.ConnectionConfig { 249 return google.ConnectionConfig{ 250 Region: c.region(), 251 ProjectID: c.projectID(), 252 } 253 } 254 255 // secret gathers the "secret" config values and returns them. 256 func (c *environConfig) secret() map[string]string { 257 secretAttrs := make(map[string]string, len(configSecretFields)) 258 for _, key := range configSecretFields { 259 secretAttrs[key] = c.attrs[key].(string) 260 } 261 return secretAttrs 262 } 263 264 // validate checks GCE-specific config values. 265 func (c environConfig) validate() error { 266 // All fields must be populated, even with just the default. 267 for field := range configFields { 268 if dflt, ok := configDefaults[field]; ok && dflt == "" { 269 continue 270 } 271 if c.attrs[field].(string) == "" { 272 return errors.Errorf("%s: must not be empty", field) 273 } 274 } 275 276 // Check sanity of GCE fields. 277 if err := c.auth().Validate(); err != nil { 278 return errors.Trace(handleInvalidField(err)) 279 } 280 if err := c.newConnection().Validate(); err != nil { 281 return errors.Trace(handleInvalidField(err)) 282 } 283 284 return nil 285 } 286 287 // update applies changes from the provided config to the env config. 288 // Changes to any immutable attributes result in an error. 289 func (c *environConfig) update(cfg *config.Config) error { 290 // Validate the updates. newValidConfig does not modify the "known" 291 // config attributes so it is safe to call Validate here first. 292 if err := config.Validate(cfg, c.Config); err != nil { 293 return errors.Trace(err) 294 } 295 296 updates, err := newValidConfig(cfg, configDefaults) 297 if err != nil { 298 return errors.Trace(err) 299 } 300 301 // Check that no immutable fields have changed. 302 attrs := updates.UnknownAttrs() 303 for _, field := range configImmutableFields { 304 if attrs[field] != c.attrs[field] { 305 return errors.Errorf("%s: cannot change from %v to %v", field, c.attrs[field], attrs[field]) 306 } 307 } 308 309 // Apply the updates. 310 c.Config = cfg 311 c.attrs = cfg.UnknownAttrs() 312 return nil 313 } 314 315 // parseCredentials extracts the OAuth2 info from the config from the 316 // individual fields (falling back on the JSON file). 317 func parseCredentials(cfg *config.Config) (*google.Credentials, error) { 318 attrs := cfg.UnknownAttrs() 319 320 // Try the auth fields first. 321 values := make(map[string]string) 322 for _, field := range configAuthFields { 323 if existing, ok := attrs[field].(string); ok && existing != "" { 324 for key, candidate := range osEnvFields { 325 if field == candidate { 326 values[key] = existing 327 break 328 } 329 } 330 } 331 } 332 if len(values) > 0 { 333 creds, err := google.NewCredentials(values) 334 if err != nil { 335 return nil, errors.Trace(err) 336 } 337 return creds, nil 338 } 339 340 // Fall back to the auth file. 341 filename, ok := attrs[cfgAuthFile].(string) 342 if !ok || filename == "" { 343 // The missing credentials will be caught later. 344 return nil, nil 345 } 346 authFile, err := os.Open(filename) 347 if err != nil { 348 return nil, errors.Trace(err) 349 } 350 defer authFile.Close() 351 creds, err := google.ParseJSONKey(authFile) 352 if err != nil { 353 return nil, errors.Trace(err) 354 } 355 return creds, nil 356 } 357 358 func applyCredentials(cfg *config.Config, creds *google.Credentials) (*config.Config, error) { 359 updates := make(map[string]interface{}) 360 for k, v := range creds.Values() { 361 if v == "" { 362 continue 363 } 364 if field, ok := osEnvFields[k]; ok { 365 for _, authField := range configAuthFields { 366 if field == authField { 367 updates[field] = v 368 break 369 } 370 } 371 } 372 } 373 updated, err := cfg.Apply(updates) 374 if err != nil { 375 return nil, errors.Trace(err) 376 } 377 return updated, nil 378 }