blob: 406f68935eae13c11b995924f2920c59e7d94281 [file] [log] [blame]
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -08001package servers
2
3import (
Rickard von Essen5b8bbff2016-02-16 07:48:20 +01004 "crypto/rsa"
5 "encoding/base64"
einarf4e5fdaf2015-04-16 23:14:59 +00006 "fmt"
einarf4e5fdaf2015-04-16 23:14:59 +00007 "net/url"
Rickard von Essen5b8bbff2016-02-16 07:48:20 +01008 "path"
9 "reflect"
Jon Perritt9a0980e2015-01-14 21:29:44 -070010
Samuel A. Falvo IIe246ac02014-02-13 23:20:09 -080011 "github.com/mitchellh/mapstructure"
Ash Wilsond27e0ff2014-09-25 11:50:31 -040012 "github.com/rackspace/gophercloud"
Ash Wilson01626a32014-09-17 10:38:07 -040013 "github.com/rackspace/gophercloud/pagination"
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080014)
15
Ash Wilson397c78b2014-09-25 15:19:14 -040016type serverResult struct {
Ash Wilsonf548aad2014-10-20 08:35:34 -040017 gophercloud.Result
Ash Wilson397c78b2014-09-25 15:19:14 -040018}
19
20// Extract interprets any serverResult as a Server, if possible.
21func (r serverResult) Extract() (*Server, error) {
22 if r.Err != nil {
23 return nil, r.Err
24 }
25
26 var response struct {
27 Server Server `mapstructure:"server"`
28 }
29
Jon Perritt9a0980e2015-01-14 21:29:44 -070030 config := &mapstructure.DecoderConfig{
Jon Perritt0e19f602015-01-15 09:35:46 -070031 DecodeHook: toMapFromString,
32 Result: &response,
Jon Perritt9a0980e2015-01-14 21:29:44 -070033 }
34 decoder, err := mapstructure.NewDecoder(config)
35 if err != nil {
36 return nil, err
37 }
38
39 err = decoder.Decode(r.Body)
40 if err != nil {
41 return nil, err
42 }
43
44 return &response.Server, nil
Ash Wilson397c78b2014-09-25 15:19:14 -040045}
46
47// CreateResult temporarily contains the response from a Create call.
48type CreateResult struct {
49 serverResult
50}
51
52// GetResult temporarily contains the response from a Get call.
53type GetResult struct {
54 serverResult
55}
56
57// UpdateResult temporarily contains the response from an Update call.
58type UpdateResult struct {
59 serverResult
60}
61
Jon Perrittcc77da62014-11-16 13:14:21 -070062// DeleteResult temporarily contains the response from a Delete call.
Jamie Hannaford34732fe2014-10-27 11:29:36 +010063type DeleteResult struct {
Jon Perrittba2395e2014-10-27 15:23:21 -050064 gophercloud.ErrResult
Jamie Hannaford34732fe2014-10-27 11:29:36 +010065}
66
Ash Wilson397c78b2014-09-25 15:19:14 -040067// RebuildResult temporarily contains the response from a Rebuild call.
Jamie Hannaford01c1efe2014-10-16 15:08:59 +020068type RebuildResult struct {
Ash Wilson397c78b2014-09-25 15:19:14 -040069 serverResult
70}
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080071
Jamie Hannaford01c1efe2014-10-16 15:08:59 +020072// ActionResult represents the result of server action operations, like reboot
73type ActionResult struct {
Jon Perrittba2395e2014-10-27 15:23:21 -050074 gophercloud.ErrResult
Jamie Hannaford01c1efe2014-10-16 15:08:59 +020075}
76
Alex Gaynor587e3e32014-11-13 10:39:09 -080077// RescueResult represents the result of a server rescue operation
Alex Gaynorfbe61bb2014-11-12 13:35:03 -080078type RescueResult struct {
79 ActionResult
Alex Gaynor7f3b06e2014-11-13 09:54:03 -080080}
81
einarf4e5fdaf2015-04-16 23:14:59 +000082// CreateImageResult represents the result of an image creation operation
83type CreateImageResult struct {
einarf2fc665e2015-04-16 20:16:21 +000084 gophercloud.Result
85}
86
Rickard von Essen5b8bbff2016-02-16 07:48:20 +010087// GetPasswordResult represent the result of a get os-server-password operation.
88type GetPasswordResult struct {
89 gophercloud.Result
90}
91
92// ExtractPassword gets the encrypted password.
93// If privateKey != nil the password is decrypted with the private key.
94// If privateKey == nil the encrypted password is returned and can be decrypted with:
95// echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
96func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
97
98 if r.Err != nil {
99 return "", r.Err
100 }
101
102 var response struct {
103 Password string `mapstructure:"password"`
104 }
105
106 err := mapstructure.Decode(r.Body, &response)
107 if err == nil && privateKey != nil && response.Password != "" {
108 return decryptPassword(response.Password, privateKey)
109 }
110 return response.Password, err
111}
112
113func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) {
114 b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword)))
115
116 n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword))
117 if err != nil {
118 return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err)
119 }
120 password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n])
121 if err != nil {
122 return "", fmt.Errorf("Failed to decrypt password: %s", err)
123 }
124
125 return string(password), nil
126}
127
einarf4e5fdaf2015-04-16 23:14:59 +0000128// ExtractImageID gets the ID of the newly created server image from the header
129func (res CreateImageResult) ExtractImageID() (string, error) {
130 if res.Err != nil {
131 return "", res.Err
132 }
133 // Get the image id from the header
134 u, err := url.ParseRequestURI(res.Header.Get("Location"))
135 if err != nil {
136 return "", fmt.Errorf("Failed to parse the image id: %s", err.Error())
137 }
138 imageId := path.Base(u.Path)
139 if imageId == "." || imageId == "/" {
140 return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u)
141 }
142 return imageId, nil
143}
144
Jon Perrittcc77da62014-11-16 13:14:21 -0700145// Extract interprets any RescueResult as an AdminPass, if possible.
Alex Gaynor7f3b06e2014-11-13 09:54:03 -0800146func (r RescueResult) Extract() (string, error) {
147 if r.Err != nil {
Alex Gaynor94a28aa2014-11-13 10:09:56 -0800148 return "", r.Err
Alex Gaynor7f3b06e2014-11-13 09:54:03 -0800149 }
150
151 var response struct {
152 AdminPass string `mapstructure:"adminPass"`
153 }
154
155 err := mapstructure.Decode(r.Body, &response)
Alex Gaynor94a28aa2014-11-13 10:09:56 -0800156 return response.AdminPass, err
Alex Gaynorfbe61bb2014-11-12 13:35:03 -0800157}
158
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -0800159// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account.
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -0800160type Server struct {
Ash Wilson01626a32014-09-17 10:38:07 -0400161 // ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
162 ID string
163
164 // TenantID identifies the tenant owning this server resource.
165 TenantID string `mapstructure:"tenant_id"`
166
167 // UserID uniquely identifies the user account owning the tenant.
168 UserID string `mapstructure:"user_id"`
169
170 // Name contains the human-readable name for the server.
171 Name string
172
173 // Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
174 Updated string
175 Created string
176
177 HostID string
178
179 // Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
180 Status string
181
182 // Progress ranges from 0..100.
183 // A request made against the server completes only once Progress reaches 100.
184 Progress int
185
186 // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
Jamie Hannaford908e1e92014-10-27 14:41:17 +0100187 AccessIPv4, AccessIPv6 string
Ash Wilson01626a32014-09-17 10:38:07 -0400188
189 // Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
190 Image map[string]interface{}
191
192 // Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
193 Flavor map[string]interface{}
194
195 // Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
Ash Wilson01626a32014-09-17 10:38:07 -0400196 Addresses map[string]interface{}
197
198 // Metadata includes a list of all user-specified key-value pairs attached to the server.
199 Metadata map[string]interface{}
200
201 // Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
202 Links []interface{}
203
Ash Wilsonad21c712014-09-25 10:15:22 -0400204 // KeyName indicates which public key was injected into the server on launch.
Ash Wilson69b144f2014-10-21 14:03:52 -0400205 KeyName string `json:"key_name" mapstructure:"key_name"`
Ash Wilsonad21c712014-09-25 10:15:22 -0400206
Ash Wilson01626a32014-09-17 10:38:07 -0400207 // AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place.
208 // Note that this is the ONLY time this field will be valid.
Ash Wilson69b144f2014-10-21 14:03:52 -0400209 AdminPass string `json:"adminPass" mapstructure:"adminPass"`
Joe Topjian978bb502015-02-12 20:55:31 +0000210
211 // SecurityGroups includes the security groups that this instance has applied to it
212 SecurityGroups []map[string]interface{} `json:"security_groups" mapstructure:"security_groups"`
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -0800213}
214
Ash Wilson397c78b2014-09-25 15:19:14 -0400215// ServerPage abstracts the raw results of making a List() request against the API.
216// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
217// data provided through the ExtractServers call.
218type ServerPage struct {
219 pagination.LinkedPageBase
Ash Wilsonf57381e2014-09-25 13:21:34 -0400220}
221
Ash Wilson397c78b2014-09-25 15:19:14 -0400222// IsEmpty returns true if a page contains no Server results.
223func (page ServerPage) IsEmpty() (bool, error) {
224 servers, err := ExtractServers(page)
225 if err != nil {
226 return true, err
227 }
228 return len(servers) == 0, nil
229}
230
231// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
232func (page ServerPage) NextPageURL() (string, error) {
Ash Wilson397c78b2014-09-25 15:19:14 -0400233 type resp struct {
Jamie Hannaforda581acd2014-10-08 17:14:13 +0200234 Links []gophercloud.Link `mapstructure:"servers_links"`
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400235 }
236
Ash Wilson397c78b2014-09-25 15:19:14 -0400237 var r resp
238 err := mapstructure.Decode(page.Body, &r)
239 if err != nil {
240 return "", err
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400241 }
242
Jamie Hannaforda581acd2014-10-08 17:14:13 +0200243 return gophercloud.ExtractNextURL(r.Links)
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400244}
245
Ash Wilson01626a32014-09-17 10:38:07 -0400246// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities.
247func ExtractServers(page pagination.Page) ([]Server, error) {
Ash Wilson397c78b2014-09-25 15:19:14 -0400248 casted := page.(ServerPage).Body
Ash Wilson12259392014-09-17 10:50:02 -0400249
250 var response struct {
251 Servers []Server `mapstructure:"servers"`
252 }
Jon Perritt9a0980e2015-01-14 21:29:44 -0700253
254 config := &mapstructure.DecoderConfig{
Jon Perritt0e19f602015-01-15 09:35:46 -0700255 DecodeHook: toMapFromString,
256 Result: &response,
Jon Perritt9a0980e2015-01-14 21:29:44 -0700257 }
258 decoder, err := mapstructure.NewDecoder(config)
259 if err != nil {
260 return nil, err
261 }
262
263 err = decoder.Decode(casted)
264
Ash Wilson12259392014-09-17 10:50:02 -0400265 return response.Servers, err
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -0800266}
Jon Perrittcc77da62014-11-16 13:14:21 -0700267
Jon Perritt78c57ce2014-11-20 11:07:18 -0700268// MetadataResult contains the result of a call for (potentially) multiple key-value pairs.
Jon Perrittcc77da62014-11-16 13:14:21 -0700269type MetadataResult struct {
270 gophercloud.Result
271}
272
273// GetMetadataResult temporarily contains the response from a metadata Get call.
274type GetMetadataResult struct {
275 MetadataResult
276}
277
Jon Perritt789f8322014-11-21 08:20:04 -0700278// ResetMetadataResult temporarily contains the response from a metadata Reset call.
279type ResetMetadataResult struct {
Jon Perrittcc77da62014-11-16 13:14:21 -0700280 MetadataResult
281}
282
Jon Perritt78c57ce2014-11-20 11:07:18 -0700283// UpdateMetadataResult temporarily contains the response from a metadata Update call.
284type UpdateMetadataResult struct {
285 MetadataResult
Jon Perrittcc77da62014-11-16 13:14:21 -0700286}
287
Jon Perritt78c57ce2014-11-20 11:07:18 -0700288// MetadatumResult contains the result of a call for individual a single key-value pair.
289type MetadatumResult struct {
290 gophercloud.Result
291}
Jon Perrittcc77da62014-11-16 13:14:21 -0700292
Jon Perritt78c57ce2014-11-20 11:07:18 -0700293// GetMetadatumResult temporarily contains the response from a metadatum Get call.
294type GetMetadatumResult struct {
295 MetadatumResult
296}
Jon Perrittcc77da62014-11-16 13:14:21 -0700297
Jon Perritt78c57ce2014-11-20 11:07:18 -0700298// CreateMetadatumResult temporarily contains the response from a metadatum Create call.
299type CreateMetadatumResult struct {
300 MetadatumResult
301}
302
303// DeleteMetadatumResult temporarily contains the response from a metadatum Delete call.
304type DeleteMetadatumResult struct {
305 gophercloud.ErrResult
Jon Perrittcc77da62014-11-16 13:14:21 -0700306}
307
308// Extract interprets any MetadataResult as a Metadata, if possible.
309func (r MetadataResult) Extract() (map[string]string, error) {
310 if r.Err != nil {
311 return nil, r.Err
312 }
313
314 var response struct {
Jon Perritt78c57ce2014-11-20 11:07:18 -0700315 Metadata map[string]string `mapstructure:"metadata"`
Jon Perrittcc77da62014-11-16 13:14:21 -0700316 }
317
318 err := mapstructure.Decode(r.Body, &response)
319 return response.Metadata, err
320}
Jon Perritt78c57ce2014-11-20 11:07:18 -0700321
322// Extract interprets any MetadatumResult as a Metadatum, if possible.
323func (r MetadatumResult) Extract() (map[string]string, error) {
324 if r.Err != nil {
325 return nil, r.Err
326 }
327
328 var response struct {
329 Metadatum map[string]string `mapstructure:"meta"`
330 }
331
332 err := mapstructure.Decode(r.Body, &response)
333 return response.Metadatum, err
334}
Jon Perritt0e19f602015-01-15 09:35:46 -0700335
336func toMapFromString(from reflect.Kind, to reflect.Kind, data interface{}) (interface{}, error) {
337 if (from == reflect.String) && (to == reflect.Map) {
338 return map[string]interface{}{}, nil
339 }
340 return data, nil
341}
Jon Perritt5cb49482015-02-19 12:19:58 -0700342
343// Address represents an IP address.
344type Address struct {
345 Version int `mapstructure:"version"`
346 Address string `mapstructure:"addr"`
347}
348
Jon Perritt04d073c2015-02-19 21:46:23 -0700349// AddressPage abstracts the raw results of making a ListAddresses() request against the API.
Jon Perritt5cb49482015-02-19 12:19:58 -0700350// As OpenStack extensions may freely alter the response bodies of structures returned
351// to the client, you may only safely access the data provided through the ExtractAddresses call.
352type AddressPage struct {
353 pagination.SinglePageBase
354}
355
Jon Perritt04d073c2015-02-19 21:46:23 -0700356// IsEmpty returns true if an AddressPage contains no networks.
Jon Perritt5cb49482015-02-19 12:19:58 -0700357func (r AddressPage) IsEmpty() (bool, error) {
358 addresses, err := ExtractAddresses(r)
359 if err != nil {
360 return true, err
361 }
362 return len(addresses) == 0, nil
363}
364
Jon Perritt04d073c2015-02-19 21:46:23 -0700365// ExtractAddresses interprets the results of a single page from a ListAddresses() call,
Jon Perritt5cb49482015-02-19 12:19:58 -0700366// producing a map of addresses.
367func ExtractAddresses(page pagination.Page) (map[string][]Address, error) {
368 casted := page.(AddressPage).Body
369
370 var response struct {
371 Addresses map[string][]Address `mapstructure:"addresses"`
372 }
373
374 err := mapstructure.Decode(casted, &response)
375 if err != nil {
376 return nil, err
377 }
378
379 return response.Addresses, err
380}
Jon Perritt04d073c2015-02-19 21:46:23 -0700381
382// NetworkAddressPage abstracts the raw results of making a ListAddressesByNetwork() request against the API.
383// As OpenStack extensions may freely alter the response bodies of structures returned
384// to the client, you may only safely access the data provided through the ExtractAddresses call.
385type NetworkAddressPage struct {
386 pagination.SinglePageBase
387}
388
389// IsEmpty returns true if a NetworkAddressPage contains no addresses.
390func (r NetworkAddressPage) IsEmpty() (bool, error) {
391 addresses, err := ExtractNetworkAddresses(r)
392 if err != nil {
393 return true, err
394 }
395 return len(addresses) == 0, nil
396}
397
398// ExtractNetworkAddresses interprets the results of a single page from a ListAddressesByNetwork() call,
Jon Perrittb51ba9c2015-02-23 10:56:35 -0700399// producing a slice of addresses.
400func ExtractNetworkAddresses(page pagination.Page) ([]Address, error) {
Jon Perritt04d073c2015-02-19 21:46:23 -0700401 casted := page.(NetworkAddressPage).Body
402
403 var response map[string][]Address
404 err := mapstructure.Decode(casted, &response)
405 if err != nil {
406 return nil, err
407 }
408
Jon Perrittb51ba9c2015-02-23 10:56:35 -0700409 var key string
410 for k := range response {
411 key = k
412 }
413
414 return response[key], err
Jon Perritt04d073c2015-02-19 21:46:23 -0700415}