1package servers
2
3import (
4	"reflect"
5
6	"github.com/mitchellh/mapstructure"
7	"github.com/rackspace/gophercloud"
8	"github.com/rackspace/gophercloud/pagination"
9)
10
11type serverResult struct {
12	gophercloud.Result
13}
14
15// Extract interprets any serverResult as a Server, if possible.
16func (r serverResult) Extract() (*Server, error) {
17	if r.Err != nil {
18		return nil, r.Err
19	}
20
21	var response struct {
22		Server Server `mapstructure:"server"`
23	}
24
25	config := &mapstructure.DecoderConfig{
26		DecodeHook: toMapFromString,
27		Result:     &response,
28	}
29	decoder, err := mapstructure.NewDecoder(config)
30	if err != nil {
31		return nil, err
32	}
33
34	err = decoder.Decode(r.Body)
35	if err != nil {
36		return nil, err
37	}
38
39	return &response.Server, nil
40}
41
42// CreateResult temporarily contains the response from a Create call.
43type CreateResult struct {
44	serverResult
45}
46
47// GetResult temporarily contains the response from a Get call.
48type GetResult struct {
49	serverResult
50}
51
52// UpdateResult temporarily contains the response from an Update call.
53type UpdateResult struct {
54	serverResult
55}
56
57// DeleteResult temporarily contains the response from a Delete call.
58type DeleteResult struct {
59	gophercloud.ErrResult
60}
61
62// RebuildResult temporarily contains the response from a Rebuild call.
63type RebuildResult struct {
64	serverResult
65}
66
67// ActionResult represents the result of server action operations, like reboot
68type ActionResult struct {
69	gophercloud.ErrResult
70}
71
72// RescueResult represents the result of a server rescue operation
73type RescueResult struct {
74	ActionResult
75}
76
77// Extract interprets any RescueResult as an AdminPass, if possible.
78func (r RescueResult) Extract() (string, error) {
79	if r.Err != nil {
80		return "", r.Err
81	}
82
83	var response struct {
84		AdminPass string `mapstructure:"adminPass"`
85	}
86
87	err := mapstructure.Decode(r.Body, &response)
88	return response.AdminPass, err
89}
90
91// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account.
92type Server struct {
93	// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
94	ID string
95
96	// TenantID identifies the tenant owning this server resource.
97	TenantID string `mapstructure:"tenant_id"`
98
99	// UserID uniquely identifies the user account owning the tenant.
100	UserID string `mapstructure:"user_id"`
101
102	// Name contains the human-readable name for the server.
103	Name string
104
105	// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
106	Updated string
107	Created string
108
109	HostID string
110
111	// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
112	Status string
113
114	// Progress ranges from 0..100.
115	// A request made against the server completes only once Progress reaches 100.
116	Progress int
117
118	// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
119	AccessIPv4, AccessIPv6 string
120
121	// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
122	Image map[string]interface{}
123
124	// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
125	Flavor map[string]interface{}
126
127	// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
128	Addresses map[string]interface{}
129
130	// Metadata includes a list of all user-specified key-value pairs attached to the server.
131	Metadata map[string]interface{}
132
133	// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
134	Links []interface{}
135
136	// KeyName indicates which public key was injected into the server on launch.
137	KeyName string `json:"key_name" mapstructure:"key_name"`
138
139	// 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.
140	// Note that this is the ONLY time this field will be valid.
141	AdminPass string `json:"adminPass" mapstructure:"adminPass"`
142
143	// SecurityGroups includes the security groups that this instance has applied to it
144	SecurityGroups []map[string]interface{} `json:"security_groups" mapstructure:"security_groups"`
145}
146
147// ServerPage abstracts the raw results of making a List() request against the API.
148// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
149// data provided through the ExtractServers call.
150type ServerPage struct {
151	pagination.LinkedPageBase
152}
153
154// IsEmpty returns true if a page contains no Server results.
155func (page ServerPage) IsEmpty() (bool, error) {
156	servers, err := ExtractServers(page)
157	if err != nil {
158		return true, err
159	}
160	return len(servers) == 0, nil
161}
162
163// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
164func (page ServerPage) NextPageURL() (string, error) {
165	type resp struct {
166		Links []gophercloud.Link `mapstructure:"servers_links"`
167	}
168
169	var r resp
170	err := mapstructure.Decode(page.Body, &r)
171	if err != nil {
172		return "", err
173	}
174
175	return gophercloud.ExtractNextURL(r.Links)
176}
177
178// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities.
179func ExtractServers(page pagination.Page) ([]Server, error) {
180	casted := page.(ServerPage).Body
181
182	var response struct {
183		Servers []Server `mapstructure:"servers"`
184	}
185
186	config := &mapstructure.DecoderConfig{
187		DecodeHook: toMapFromString,
188		Result:     &response,
189	}
190	decoder, err := mapstructure.NewDecoder(config)
191	if err != nil {
192		return nil, err
193	}
194
195	err = decoder.Decode(casted)
196
197	return response.Servers, err
198}
199
200// MetadataResult contains the result of a call for (potentially) multiple key-value pairs.
201type MetadataResult struct {
202	gophercloud.Result
203}
204
205// GetMetadataResult temporarily contains the response from a metadata Get call.
206type GetMetadataResult struct {
207	MetadataResult
208}
209
210// ResetMetadataResult temporarily contains the response from a metadata Reset call.
211type ResetMetadataResult struct {
212	MetadataResult
213}
214
215// UpdateMetadataResult temporarily contains the response from a metadata Update call.
216type UpdateMetadataResult struct {
217	MetadataResult
218}
219
220// MetadatumResult contains the result of a call for individual a single key-value pair.
221type MetadatumResult struct {
222	gophercloud.Result
223}
224
225// GetMetadatumResult temporarily contains the response from a metadatum Get call.
226type GetMetadatumResult struct {
227	MetadatumResult
228}
229
230// CreateMetadatumResult temporarily contains the response from a metadatum Create call.
231type CreateMetadatumResult struct {
232	MetadatumResult
233}
234
235// DeleteMetadatumResult temporarily contains the response from a metadatum Delete call.
236type DeleteMetadatumResult struct {
237	gophercloud.ErrResult
238}
239
240// Extract interprets any MetadataResult as a Metadata, if possible.
241func (r MetadataResult) Extract() (map[string]string, error) {
242	if r.Err != nil {
243		return nil, r.Err
244	}
245
246	var response struct {
247		Metadata map[string]string `mapstructure:"metadata"`
248	}
249
250	err := mapstructure.Decode(r.Body, &response)
251	return response.Metadata, err
252}
253
254// Extract interprets any MetadatumResult as a Metadatum, if possible.
255func (r MetadatumResult) Extract() (map[string]string, error) {
256	if r.Err != nil {
257		return nil, r.Err
258	}
259
260	var response struct {
261		Metadatum map[string]string `mapstructure:"meta"`
262	}
263
264	err := mapstructure.Decode(r.Body, &response)
265	return response.Metadatum, err
266}
267
268func toMapFromString(from reflect.Kind, to reflect.Kind, data interface{}) (interface{}, error) {
269	if (from == reflect.String) && (to == reflect.Map) {
270		return map[string]interface{}{}, nil
271	}
272	return data, nil
273}
274
275// Address represents an IP address.
276type Address struct {
277	Version int    `mapstructure:"version"`
278	Address string `mapstructure:"addr"`
279}
280
281// AddressPage abstracts the raw results of making a ListAddresses() request against the API.
282// As OpenStack extensions may freely alter the response bodies of structures returned
283// to the client, you may only safely access the data provided through the ExtractAddresses call.
284type AddressPage struct {
285	pagination.SinglePageBase
286}
287
288// IsEmpty returns true if an AddressPage contains no networks.
289func (r AddressPage) IsEmpty() (bool, error) {
290	addresses, err := ExtractAddresses(r)
291	if err != nil {
292		return true, err
293	}
294	return len(addresses) == 0, nil
295}
296
297// ExtractAddresses interprets the results of a single page from a ListAddresses() call,
298// producing a map of addresses.
299func ExtractAddresses(page pagination.Page) (map[string][]Address, error) {
300	casted := page.(AddressPage).Body
301
302	var response struct {
303		Addresses map[string][]Address `mapstructure:"addresses"`
304	}
305
306	err := mapstructure.Decode(casted, &response)
307	if err != nil {
308		return nil, err
309	}
310
311	return response.Addresses, err
312}
313
314// NetworkAddressPage abstracts the raw results of making a ListAddressesByNetwork() request against the API.
315// As OpenStack extensions may freely alter the response bodies of structures returned
316// to the client, you may only safely access the data provided through the ExtractAddresses call.
317type NetworkAddressPage struct {
318	pagination.SinglePageBase
319}
320
321// IsEmpty returns true if a NetworkAddressPage contains no addresses.
322func (r NetworkAddressPage) IsEmpty() (bool, error) {
323	addresses, err := ExtractNetworkAddresses(r)
324	if err != nil {
325		return true, err
326	}
327	return len(addresses) == 0, nil
328}
329
330// ExtractNetworkAddresses interprets the results of a single page from a ListAddressesByNetwork() call,
331// producing a slice of addresses.
332func ExtractNetworkAddresses(page pagination.Page) ([]Address, error) {
333	casted := page.(NetworkAddressPage).Body
334
335	var response map[string][]Address
336	err := mapstructure.Decode(casted, &response)
337	if err != nil {
338		return nil, err
339	}
340
341	var key string
342	for k := range response {
343		key = k
344	}
345
346	return response[key], err
347}
348