Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 // Copyright 2013 Canonical Ltd. | 1 // Copyright 2013 Canonical Ltd. |
2 // Licensed under the AGPLv3, see LICENCE file for details. | 2 // Licensed under the AGPLv3, see LICENCE file for details. |
3 | 3 |
4 package juju | 4 package juju |
5 | 5 |
6 import ( | 6 import ( |
7 "fmt" | 7 "fmt" |
8 "io" | 8 "io" |
9 "time" | 9 "time" |
10 | 10 |
(...skipping 15 matching lines...) Expand all Loading... | |
26 // changed by tests. | 26 // changed by tests. |
27 var ( | 27 var ( |
28 apiOpen = api.Open | 28 apiOpen = api.Open |
29 apiClose = (*api.State).Close | 29 apiClose = (*api.State).Close |
30 providerConnectDelay = 2 * time.Second | 30 providerConnectDelay = 2 * time.Second |
31 ) | 31 ) |
32 | 32 |
33 // apiState wraps an api.State, redefining its Close method | 33 // apiState wraps an api.State, redefining its Close method |
34 // so we can abuse it for testing purposes. | 34 // so we can abuse it for testing purposes. |
35 type apiState struct { | 35 type apiState struct { |
36 » st *api.State | 36 » st *api.State |
37 » connectedInfo *api.Info | 37 » // If cachedInfo is non-nil, it indicates that the info has been |
38 » // fromEnviron is true if the connection was made using the | 38 » // newly retrieved, and should be cached in the config store. |
39 » // environment, rather than the local .jenv connection info. | 39 » cachedInfo *api.Info |
40 » fromEnviron bool | |
rog
2014/01/15 13:31:09
We could lose this field and rename connectedInfo
dimitern
2014/01/15 14:24:03
Done.
| |
41 } | 40 } |
42 | 41 |
43 func (st apiState) Close() error { | 42 func (st apiState) Close() error { |
44 return apiClose(st.st) | 43 return apiClose(st.st) |
45 } | 44 } |
46 | 45 |
47 // APIConn holds a connection to a juju environment and its | 46 // APIConn holds a connection to a juju environment and its |
48 // associated state through its API interface. | 47 // associated state through its API interface. |
49 type APIConn struct { | 48 type APIConn struct { |
50 Environ environs.Environ | 49 Environ environs.Environ |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
168 // Delay the config connection until we've spent | 167 // Delay the config connection until we've spent |
169 // some time trying to connect to the cached info. | 168 // some time trying to connect to the cached info. |
170 delay = providerConnectDelay | 169 delay = providerConnectDelay |
171 } else { | 170 } else { |
172 logger.Debugf("no cached API connection settings found") | 171 logger.Debugf("no cached API connection settings found") |
173 } | 172 } |
174 try.Start(func(stop <-chan struct{}) (io.Closer, error) { | 173 try.Start(func(stop <-chan struct{}) (io.Closer, error) { |
175 return apiConfigConnect(info, envs, envName, stop, delay) | 174 return apiConfigConnect(info, envs, envName, stop, delay) |
176 }) | 175 }) |
177 try.Close() | 176 try.Close() |
178 » val, err := try.Result() | 177 » val0, err := try.Result() |
179 if err != nil { | 178 if err != nil { |
180 if ierr, ok := err.(*infoConnectError); ok { | 179 if ierr, ok := err.(*infoConnectError); ok { |
181 // lose error encapsulation: | 180 // lose error encapsulation: |
182 err = ierr.error | 181 err = ierr.error |
183 } | 182 } |
184 return nil, err | 183 return nil, err |
185 } | 184 } |
rog
2014/01/15 13:31:09
Please do the dynamic type conversion once only.
dimitern
2014/01/15 14:24:03
Done.
| |
186 » connectedInfo := val.(apiState).connectedInfo | 185 » val := val0.(apiState) |
187 » connectedState := val.(apiState).st | 186 |
188 » connectedFromEnviron := val.(apiState).fromEnviron | 187 » if val.cachedInfo != nil && info != nil { |
189 | 188 » » // Cache the connection settings only if we used the |
190 » if connectedFromEnviron && info != nil { | 189 » » // environment config, but any errors are just logged |
191 » » // Cache the successful connection info for future use, but only if we | 190 » » // as warnings, because they're not fatal. |
rog
2014/01/15 13:31:09
How about putting the body of this into its own fu
dimitern
2014/01/15 14:24:03
Done.
| |
192 » » // connected using the environment config. | 191 » » err = cacheAPIInfo(info, val.cachedInfo) |
193 » » info.SetAPIEndpoint(configstore.APIEndpoint{ | |
194 » » » Addresses: connectedInfo.Addrs, | |
195 » » » CACert: string(connectedInfo.CACert), | |
196 » » }) | |
197 » » _, username, err := names.ParseTag(connectedInfo.Tag, names.User TagKind) | |
198 if err != nil { | 192 if err != nil { |
199 » » » logger.Warningf("not caching API connection settings: in valid API user tag: %v", err) | 193 » » » logger.Warningf(err.Error()) |
200 » » » return connectedState, nil | 194 » » } else { |
201 » » } | 195 » » » logger.Debugf("updated API connection settings cache") |
202 » » info.SetAPICredentials(configstore.APICredentials{ | 196 » » } |
203 » » » User: username, | 197 » } |
204 » » » Password: connectedInfo.Password, | 198 » return val.st, nil |
205 » » }) | |
206 » » if err := info.Write(); err != nil { | |
207 » » » // Not fatal, just the cache won't be updated. | |
208 » » » logger.Warningf("cannot cache API connection settings: % v", err) | |
209 » » } | |
210 » » logger.Debugf("updated API connection settings cache") | |
211 » } | |
212 » return connectedState, nil | |
213 } | 199 } |
214 | 200 |
215 func errorImportance(err error) int { | 201 func errorImportance(err error) int { |
216 if err == nil { | 202 if err == nil { |
217 return 0 | 203 return 0 |
218 } | 204 } |
219 if errors.IsNotFoundError(err) { | 205 if errors.IsNotFoundError(err) { |
220 // An error from an actual connection attempt | 206 // An error from an actual connection attempt |
221 // is more interesting than the fact that there's | 207 // is more interesting than the fact that there's |
222 // no environment info available. | 208 // no environment info available. |
(...skipping 22 matching lines...) Expand all Loading... | |
245 apiInfo := &api.Info{ | 231 apiInfo := &api.Info{ |
246 Addrs: endpoint.Addresses, | 232 Addrs: endpoint.Addresses, |
247 CACert: []byte(endpoint.CACert), | 233 CACert: []byte(endpoint.CACert), |
248 Tag: names.UserTag(info.APICredentials().User), | 234 Tag: names.UserTag(info.APICredentials().User), |
249 Password: info.APICredentials().Password, | 235 Password: info.APICredentials().Password, |
250 } | 236 } |
251 st, err := apiOpen(apiInfo, api.DefaultDialOpts()) | 237 st, err := apiOpen(apiInfo, api.DefaultDialOpts()) |
252 if err != nil { | 238 if err != nil { |
253 return apiState{}, &infoConnectError{err} | 239 return apiState{}, &infoConnectError{err} |
254 } | 240 } |
255 » return apiState{st, apiInfo, false}, err | 241 » return apiState{st, nil}, err |
256 } | 242 } |
257 | 243 |
258 // apiConfigConnect looks for configuration info on the given environment, | 244 // apiConfigConnect looks for configuration info on the given environment, |
259 // and tries to use an Environ constructed from that to connect to | 245 // and tries to use an Environ constructed from that to connect to |
260 // its endpoint. It only starts the attempt after the given delay, | 246 // its endpoint. It only starts the attempt after the given delay, |
261 // to allow the faster apiInfoConnect to hopefully succeed first. | 247 // to allow the faster apiInfoConnect to hopefully succeed first. |
262 // It returns nil if there was no configuration information found. | 248 // It returns nil if there was no configuration information found. |
263 func apiConfigConnect(info configstore.EnvironInfo, envs *environs.Environs, env Name string, stop <-chan struct{}, delay time.Duration) (apiState, error) { | 249 func apiConfigConnect(info configstore.EnvironInfo, envs *environs.Environs, env Name string, stop <-chan struct{}, delay time.Duration) (apiState, error) { |
264 var cfg *config.Config | 250 var cfg *config.Config |
265 var err error | 251 var err error |
(...skipping 18 matching lines...) Expand all Loading... | |
284 } | 270 } |
285 apiInfo, err := environAPIInfo(environ) | 271 apiInfo, err := environAPIInfo(environ) |
286 if err != nil { | 272 if err != nil { |
287 return apiState{}, err | 273 return apiState{}, err |
288 } | 274 } |
289 st, err := apiOpen(apiInfo, api.DefaultDialOpts()) | 275 st, err := apiOpen(apiInfo, api.DefaultDialOpts()) |
290 // TODO(rog): handle errUnauthorized when the API handles passwords. | 276 // TODO(rog): handle errUnauthorized when the API handles passwords. |
291 if err != nil { | 277 if err != nil { |
292 return apiState{}, err | 278 return apiState{}, err |
293 } | 279 } |
294 » return apiState{st, apiInfo, true}, nil | 280 » return apiState{st, apiInfo}, nil |
295 } | 281 } |
296 | 282 |
297 func environAPIInfo(environ environs.Environ) (*api.Info, error) { | 283 func environAPIInfo(environ environs.Environ) (*api.Info, error) { |
298 _, info, err := environ.StateInfo() | 284 _, info, err := environ.StateInfo() |
299 if err != nil { | 285 if err != nil { |
300 return nil, err | 286 return nil, err |
301 } | 287 } |
302 info.Tag = "user-admin" | 288 info.Tag = "user-admin" |
303 password := environ.Config().AdminSecret() | 289 password := environ.Config().AdminSecret() |
304 if password == "" { | 290 if password == "" { |
305 return nil, fmt.Errorf("cannot connect without admin-secret") | 291 return nil, fmt.Errorf("cannot connect without admin-secret") |
306 } | 292 } |
307 info.Password = password | 293 info.Password = password |
308 return info, nil | 294 return info, nil |
309 } | 295 } |
296 | |
297 // cacheAPIInfo updates the local environment settings (.jenv file) | |
298 // with the provided apiInfo, assuming we've just successfully | |
299 // connected to the API server. | |
300 func cacheAPIInfo(info configstore.EnvironInfo, apiInfo *api.Info) error { | |
301 info.SetAPIEndpoint(configstore.APIEndpoint{ | |
302 Addresses: apiInfo.Addrs, | |
303 CACert: string(apiInfo.CACert), | |
304 }) | |
305 _, username, err := names.ParseTag(apiInfo.Tag, names.UserTagKind) | |
306 if err != nil { | |
307 return fmt.Errorf("not caching API connection settings: invalid API user tag: %v", err) | |
308 } | |
309 info.SetAPICredentials(configstore.APICredentials{ | |
310 User: username, | |
311 Password: apiInfo.Password, | |
312 }) | |
313 if err := info.Write(); err != nil { | |
314 return fmt.Errorf("cannot cache API connection settings: %v", er r) | |
315 } | |
316 return nil | |
317 } | |
LEFT | RIGHT |