github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "github.com/juju/errors" 8 "github.com/juju/schema" 9 10 "github.com/juju/juju/environs/config" 11 "github.com/juju/juju/provider/gce/google" 12 ) 13 14 // TODO(ericsnow) While not strictly config-related, we could use some 15 // mechanism by which we can validate the values we've hard-coded in 16 // this provider match up with the external authoritative sources. One 17 // example of this is the data stored in instancetypes.go. Similarly 18 // we should also ensure the cloud-images metadata is correct and 19 // up-to-date, though that is more the responsibility of that team. 20 // Regardless, it may be useful to include a tool somewhere in juju 21 // that we can use to validate this provider's potentially out-of-date 22 // data. 23 24 // The GCE-specific config keys. 25 const ( 26 cfgPrivateKey = "private-key" 27 cfgClientID = "client-id" 28 cfgClientEmail = "client-email" 29 cfgRegion = "region" 30 cfgProjectID = "project-id" 31 cfgImageEndpoint = "image-endpoint" 32 ) 33 34 // configFields is the spec for each GCE config value's type. 35 var configFields = schema.Fields{ 36 cfgPrivateKey: schema.String(), 37 cfgClientID: schema.String(), 38 cfgClientEmail: schema.String(), 39 cfgRegion: schema.String(), 40 cfgProjectID: schema.String(), 41 cfgImageEndpoint: schema.String(), 42 } 43 44 // TODO(ericsnow) Do we need custom defaults for "image-metadata-url" or 45 // "agent-metadata-url"? The defaults are the official ones (e.g. 46 // cloud-images). 47 48 var configDefaults = schema.Defaults{ 49 // See http://cloud-images.ubuntu.com/releases/streams/v1/com.ubuntu.cloud:released:gce.json 50 cfgImageEndpoint: "https://www.googleapis.com", 51 cfgRegion: "us-central1", 52 } 53 54 var configSecretFields = []string{ 55 cfgPrivateKey, 56 } 57 58 var configImmutableFields = []string{ 59 cfgPrivateKey, 60 cfgClientID, 61 cfgClientEmail, 62 cfgRegion, 63 cfgProjectID, 64 cfgImageEndpoint, 65 } 66 67 var configAuthFields = []string{ 68 cfgPrivateKey, 69 cfgProjectID, 70 cfgClientID, 71 cfgClientEmail, 72 } 73 74 // osEnvFields is the mapping from GCE env vars to config keys. 75 var osEnvFields = map[string]string{ 76 google.OSEnvPrivateKey: cfgPrivateKey, 77 google.OSEnvClientID: cfgClientID, 78 google.OSEnvClientEmail: cfgClientEmail, 79 google.OSEnvRegion: cfgRegion, 80 google.OSEnvProjectID: cfgProjectID, 81 google.OSEnvImageEndpoint: cfgImageEndpoint, 82 } 83 84 // handleInvalidField converts a google.InvalidConfigValue into a new 85 // error, translating a {provider/gce/google}.OSEnvVar* value into a 86 // GCE config key in the new error. 87 func handleInvalidField(err error) error { 88 vErr := err.(*google.InvalidConfigValue) 89 if strValue, ok := vErr.Value.(string); ok && strValue == "" { 90 key := osEnvFields[vErr.Key] 91 return errors.Errorf("%s: must not be empty", key) 92 } 93 return err 94 } 95 96 type environConfig struct { 97 *config.Config 98 attrs map[string]interface{} 99 credentials *google.Credentials 100 } 101 102 // newConfig builds a new environConfig from the provided Config and 103 // returns it. 104 func newConfig(cfg *config.Config) *environConfig { 105 return &environConfig{ 106 Config: cfg, 107 attrs: cfg.UnknownAttrs(), 108 } 109 } 110 111 // newValidConfig builds a new environConfig from the provided Config 112 // and returns it. This includes applying the provided defaults 113 // values, if any. The resulting config values are validated. 114 func newValidConfig(cfg *config.Config, defaults map[string]interface{}) (*environConfig, error) { 115 credentials, err := parseCredentials(cfg) 116 if err != nil { 117 return nil, errors.Trace(err) 118 } 119 handled, err := applyCredentials(cfg, credentials) 120 if err != nil { 121 return nil, errors.Trace(err) 122 } 123 cfg = handled 124 125 // Ensure that the provided config is valid. 126 if err := config.Validate(cfg, nil); err != nil { 127 return nil, errors.Trace(err) 128 } 129 130 // Apply the defaults and coerce/validate the custom config attrs. 131 validated, err := cfg.ValidateUnknownAttrs(configFields, defaults) 132 if err != nil { 133 return nil, errors.Trace(err) 134 } 135 validCfg, err := cfg.Apply(validated) 136 if err != nil { 137 return nil, errors.Trace(err) 138 } 139 140 // Build the config. 141 ecfg := newConfig(validCfg) 142 ecfg.credentials = credentials 143 144 // Do final validation. 145 if err := ecfg.validate(); err != nil { 146 return nil, errors.Trace(err) 147 } 148 149 return ecfg, nil 150 } 151 152 func (c *environConfig) privateKey() string { 153 return c.attrs[cfgPrivateKey].(string) 154 } 155 156 func (c *environConfig) clientID() string { 157 return c.attrs[cfgClientID].(string) 158 } 159 160 func (c *environConfig) clientEmail() string { 161 return c.attrs[cfgClientEmail].(string) 162 } 163 164 func (c *environConfig) region() string { 165 return c.attrs[cfgRegion].(string) 166 } 167 168 func (c *environConfig) projectID() string { 169 return c.attrs[cfgProjectID].(string) 170 } 171 172 // imageEndpoint identifies where the provider should look for 173 // cloud images (i.e. for simplestreams). 174 func (c *environConfig) imageEndpoint() string { 175 return c.attrs[cfgImageEndpoint].(string) 176 } 177 178 // auth build a new Credentials based on the config and returns it. 179 func (c *environConfig) auth() *google.Credentials { 180 if c.credentials == nil { 181 c.credentials = &google.Credentials{ 182 ClientID: c.clientID(), 183 ClientEmail: c.clientEmail(), 184 PrivateKey: []byte(c.privateKey()), 185 } 186 } 187 return c.credentials 188 } 189 190 // newConnection build a ConnectionConfig based on the config and returns it. 191 func (c *environConfig) newConnection() google.ConnectionConfig { 192 return google.ConnectionConfig{ 193 Region: c.region(), 194 ProjectID: c.projectID(), 195 } 196 } 197 198 // secret gathers the "secret" config values and returns them. 199 func (c *environConfig) secret() map[string]string { 200 secretAttrs := make(map[string]string, len(configSecretFields)) 201 for _, key := range configSecretFields { 202 secretAttrs[key] = c.attrs[key].(string) 203 } 204 return secretAttrs 205 } 206 207 // validate checks GCE-specific config values. 208 func (c environConfig) validate() error { 209 // All fields must be populated, even with just the default. 210 for field := range configFields { 211 if dflt, ok := configDefaults[field]; ok && dflt == "" { 212 continue 213 } 214 if c.attrs[field].(string) == "" { 215 return errors.Errorf("%s: must not be empty", field) 216 } 217 } 218 219 // Check sanity of GCE fields. 220 if err := c.auth().Validate(); err != nil { 221 return errors.Trace(handleInvalidField(err)) 222 } 223 if err := c.newConnection().Validate(); err != nil { 224 return errors.Trace(handleInvalidField(err)) 225 } 226 227 return nil 228 } 229 230 // update applies changes from the provided config to the env config. 231 // Changes to any immutable attributes result in an error. 232 func (c *environConfig) update(cfg *config.Config) error { 233 // Validate the updates. newValidConfig does not modify the "known" 234 // config attributes so it is safe to call Validate here first. 235 if err := config.Validate(cfg, c.Config); err != nil { 236 return errors.Trace(err) 237 } 238 239 updates, err := newValidConfig(cfg, configDefaults) 240 if err != nil { 241 return errors.Trace(err) 242 } 243 244 // Check that no immutable fields have changed. 245 attrs := updates.UnknownAttrs() 246 for _, field := range configImmutableFields { 247 if attrs[field] != c.attrs[field] { 248 return errors.Errorf("%s: cannot change from %v to %v", field, c.attrs[field], attrs[field]) 249 } 250 } 251 252 // Apply the updates. 253 c.Config = cfg 254 c.attrs = cfg.UnknownAttrs() 255 return nil 256 } 257 258 // parseCredentials extracts the OAuth2 info from the config from the 259 // individual fields (falling back on the JSON file). 260 func parseCredentials(cfg *config.Config) (*google.Credentials, error) { 261 attrs := cfg.UnknownAttrs() 262 values := make(map[string]string) 263 for _, field := range configAuthFields { 264 if existing, ok := attrs[field].(string); ok && existing != "" { 265 for key, candidate := range osEnvFields { 266 if field == candidate { 267 values[key] = existing 268 break 269 } 270 } 271 } 272 } 273 return google.NewCredentials(values) 274 } 275 276 func applyCredentials(cfg *config.Config, creds *google.Credentials) (*config.Config, error) { 277 updates := make(map[string]interface{}) 278 for k, v := range creds.Values() { 279 if v == "" { 280 continue 281 } 282 if field, ok := osEnvFields[k]; ok { 283 for _, authField := range configAuthFields { 284 if field == authField { 285 updates[field] = v 286 break 287 } 288 } 289 } 290 } 291 updated, err := cfg.Apply(updates) 292 if err != nil { 293 return nil, errors.Trace(err) 294 } 295 return updated, nil 296 }