1package servers
2
3import (
4	"crypto/rsa"
5	"encoding/base64"
6	"encoding/json"
7	"fmt"
8	"net/url"
9	"path"
10	"time"
11
12	"github.com/gophercloud/gophercloud"
13	"github.com/gophercloud/gophercloud/pagination"
14)
15
16type serverResult struct {
17	gophercloud.Result
18}
19
20// Extract interprets any serverResult as a Server, if possible.
21func (r serverResult) Extract() (*Server, error) {
22	var s Server
23	err := r.ExtractInto(&s)
24	return &s, err
25}
26
27func (r serverResult) ExtractInto(v interface{}) error {
28	return r.Result.ExtractIntoStructPtr(v, "server")
29}
30
31func ExtractServersInto(r pagination.Page, v interface{}) error {
32	return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers")
33}
34
35// CreateResult is the response from a Create operation. Call its Extract
36// method to interpret it as a Server.
37type CreateResult struct {
38	serverResult
39}
40
41// GetResult is the response from a Get operation. Call its Extract
42// method to interpret it as a Server.
43type GetResult struct {
44	serverResult
45}
46
47// UpdateResult is the response from an Update operation. Call its Extract
48// method to interpret it as a Server.
49type UpdateResult struct {
50	serverResult
51}
52
53// DeleteResult is the response from a Delete operation. Call its ExtractErr
54// method to determine if the call succeeded or failed.
55type DeleteResult struct {
56	gophercloud.ErrResult
57}
58
59// RebuildResult is the response from a Rebuild operation. Call its Extract
60// method to interpret it as a Server.
61type RebuildResult struct {
62	serverResult
63}
64
65// ActionResult represents the result of server action operations, like reboot.
66// Call its ExtractErr method to determine if the action succeeded or failed.
67type ActionResult struct {
68	gophercloud.ErrResult
69}
70
71// CreateImageResult is the response from a CreateImage operation. Call its
72// ExtractImageID method to retrieve the ID of the newly created image.
73type CreateImageResult struct {
74	gophercloud.Result
75}
76
77// ShowConsoleOutputResult represents the result of console output from a server
78type ShowConsoleOutputResult struct {
79	gophercloud.Result
80}
81
82// Extract will return the console output from a ShowConsoleOutput request.
83func (r ShowConsoleOutputResult) Extract() (string, error) {
84	var s struct {
85		Output string `json:"output"`
86	}
87
88	err := r.ExtractInto(&s)
89	return s.Output, err
90}
91
92// GetPasswordResult represent the result of a get os-server-password operation.
93// Call its ExtractPassword method to retrieve the password.
94type GetPasswordResult struct {
95	gophercloud.Result
96}
97
98// ExtractPassword gets the encrypted password.
99// If privateKey != nil the password is decrypted with the private key.
100// If privateKey == nil the encrypted password is returned and can be decrypted
101// with:
102//   echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
103func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
104	var s struct {
105		Password string `json:"password"`
106	}
107	err := r.ExtractInto(&s)
108	if err == nil && privateKey != nil && s.Password != "" {
109		return decryptPassword(s.Password, privateKey)
110	}
111	return s.Password, err
112}
113
114func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) {
115	b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword)))
116
117	n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword))
118	if err != nil {
119		return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err)
120	}
121	password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n])
122	if err != nil {
123		return "", fmt.Errorf("Failed to decrypt password: %s", err)
124	}
125
126	return string(password), nil
127}
128
129// ExtractImageID gets the ID of the newly created server image from the header.
130func (r CreateImageResult) ExtractImageID() (string, error) {
131	if r.Err != nil {
132		return "", r.Err
133	}
134	// Get the image id from the header
135	u, err := url.ParseRequestURI(r.Header.Get("Location"))
136	if err != nil {
137		return "", err
138	}
139	imageID := path.Base(u.Path)
140	if imageID == "." || imageID == "/" {
141		return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u)
142	}
143	return imageID, nil
144}
145
146// Server represents a server/instance in the OpenStack cloud.
147type Server struct {
148	// ID uniquely identifies this server amongst all other servers,
149	// including those not accessible to the current tenant.
150	ID string `json:"id"`
151
152	// TenantID identifies the tenant owning this server resource.
153	TenantID string `json:"tenant_id"`
154
155	// UserID uniquely identifies the user account owning the tenant.
156	UserID string `json:"user_id"`
157
158	// Name contains the human-readable name for the server.
159	Name string `json:"name"`
160
161	// Updated and Created contain ISO-8601 timestamps of when the state of the
162	// server last changed, and when it was created.
163	Updated time.Time `json:"updated"`
164	Created time.Time `json:"created"`
165
166	// HostID is the host where the server is located in the cloud.
167	HostID string `json:"hostid"`
168
169	// Status contains the current operational status of the server,
170	// such as IN_PROGRESS or ACTIVE.
171	Status string `json:"status"`
172
173	// Progress ranges from 0..100.
174	// A request made against the server completes only once Progress reaches 100.
175	Progress int `json:"progress"`
176
177	// AccessIPv4 and AccessIPv6 contain the IP addresses of the server,
178	// suitable for remote access for administration.
179	AccessIPv4 string `json:"accessIPv4"`
180	AccessIPv6 string `json:"accessIPv6"`
181
182	// Image refers to a JSON object, which itself indicates the OS image used to
183	// deploy the server.
184	Image map[string]interface{} `json:"-"`
185
186	// Flavor refers to a JSON object, which itself indicates the hardware
187	// configuration of the deployed server.
188	Flavor map[string]interface{} `json:"flavor"`
189
190	// Addresses includes a list of all IP addresses assigned to the server,
191	// keyed by pool.
192	Addresses map[string]interface{} `json:"addresses"`
193
194	// Metadata includes a list of all user-specified key-value pairs attached
195	// to the server.
196	Metadata map[string]string `json:"metadata"`
197
198	// Links includes HTTP references to the itself, useful for passing along to
199	// other APIs that might want a server reference.
200	Links []interface{} `json:"links"`
201
202	// KeyName indicates which public key was injected into the server on launch.
203	KeyName string `json:"key_name"`
204
205	// AdminPass will generally be empty ("").  However, it will contain the
206	// administrative password chosen when provisioning a new server without a
207	// set AdminPass setting in the first place.
208	// Note that this is the ONLY time this field will be valid.
209	AdminPass string `json:"adminPass"`
210
211	// SecurityGroups includes the security groups that this instance has applied
212	// to it.
213	SecurityGroups []map[string]interface{} `json:"security_groups"`
214
215	// Fault contains failure information about a server.
216	Fault Fault `json:"fault"`
217}
218
219type Fault struct {
220	Code    int       `json:"code"`
221	Created time.Time `json:"created"`
222	Details string    `json:"details"`
223	Message string    `json:"message"`
224}
225
226func (r *Server) UnmarshalJSON(b []byte) error {
227	type tmp Server
228	var s struct {
229		tmp
230		Image interface{} `json:"image"`
231	}
232	err := json.Unmarshal(b, &s)
233	if err != nil {
234		return err
235	}
236
237	*r = Server(s.tmp)
238
239	switch t := s.Image.(type) {
240	case map[string]interface{}:
241		r.Image = t
242	case string:
243		switch t {
244		case "":
245			r.Image = nil
246		}
247	}
248
249	return err
250}
251
252// ServerPage abstracts the raw results of making a List() request against
253// the API. As OpenStack extensions may freely alter the response bodies of
254// structures returned to the client, you may only safely access the data
255// provided through the ExtractServers call.
256type ServerPage struct {
257	pagination.LinkedPageBase
258}
259
260// IsEmpty returns true if a page contains no Server results.
261func (r ServerPage) IsEmpty() (bool, error) {
262	s, err := ExtractServers(r)
263	return len(s) == 0, err
264}
265
266// NextPageURL uses the response's embedded link reference to navigate to the
267// next page of results.
268func (r ServerPage) NextPageURL() (string, error) {
269	var s struct {
270		Links []gophercloud.Link `json:"servers_links"`
271	}
272	err := r.ExtractInto(&s)
273	if err != nil {
274		return "", err
275	}
276	return gophercloud.ExtractNextURL(s.Links)
277}
278
279// ExtractServers interprets the results of a single page from a List() call,
280// producing a slice of Server entities.
281func ExtractServers(r pagination.Page) ([]Server, error) {
282	var s []Server
283	err := ExtractServersInto(r, &s)
284	return s, err
285}
286
287// MetadataResult contains the result of a call for (potentially) multiple
288// key-value pairs. Call its Extract method to interpret it as a
289// map[string]interface.
290type MetadataResult struct {
291	gophercloud.Result
292}
293
294// GetMetadataResult contains the result of a Get operation. Call its Extract
295// method to interpret it as a map[string]interface.
296type GetMetadataResult struct {
297	MetadataResult
298}
299
300// ResetMetadataResult contains the result of a Reset operation. Call its
301// Extract method to interpret it as a map[string]interface.
302type ResetMetadataResult struct {
303	MetadataResult
304}
305
306// UpdateMetadataResult contains the result of an Update operation. Call its
307// Extract method to interpret it as a map[string]interface.
308type UpdateMetadataResult struct {
309	MetadataResult
310}
311
312// MetadatumResult contains the result of a call for individual a single
313// key-value pair.
314type MetadatumResult struct {
315	gophercloud.Result
316}
317
318// GetMetadatumResult contains the result of a Get operation. Call its Extract
319// method to interpret it as a map[string]interface.
320type GetMetadatumResult struct {
321	MetadatumResult
322}
323
324// CreateMetadatumResult contains the result of a Create operation. Call its
325// Extract method to interpret it as a map[string]interface.
326type CreateMetadatumResult struct {
327	MetadatumResult
328}
329
330// DeleteMetadatumResult contains the result of a Delete operation. Call its
331// ExtractErr method to determine if the call succeeded or failed.
332type DeleteMetadatumResult struct {
333	gophercloud.ErrResult
334}
335
336// Extract interprets any MetadataResult as a Metadata, if possible.
337func (r MetadataResult) Extract() (map[string]string, error) {
338	var s struct {
339		Metadata map[string]string `json:"metadata"`
340	}
341	err := r.ExtractInto(&s)
342	return s.Metadata, err
343}
344
345// Extract interprets any MetadatumResult as a Metadatum, if possible.
346func (r MetadatumResult) Extract() (map[string]string, error) {
347	var s struct {
348		Metadatum map[string]string `json:"meta"`
349	}
350	err := r.ExtractInto(&s)
351	return s.Metadatum, err
352}
353
354// Address represents an IP address.
355type Address struct {
356	Version int    `json:"version"`
357	Address string `json:"addr"`
358}
359
360// AddressPage abstracts the raw results of making a ListAddresses() request
361// against the API. As OpenStack extensions may freely alter the response bodies
362// of structures returned to the client, you may only safely access the data
363// provided through the ExtractAddresses call.
364type AddressPage struct {
365	pagination.SinglePageBase
366}
367
368// IsEmpty returns true if an AddressPage contains no networks.
369func (r AddressPage) IsEmpty() (bool, error) {
370	addresses, err := ExtractAddresses(r)
371	return len(addresses) == 0, err
372}
373
374// ExtractAddresses interprets the results of a single page from a
375// ListAddresses() call, producing a map of addresses.
376func ExtractAddresses(r pagination.Page) (map[string][]Address, error) {
377	var s struct {
378		Addresses map[string][]Address `json:"addresses"`
379	}
380	err := (r.(AddressPage)).ExtractInto(&s)
381	return s.Addresses, err
382}
383
384// NetworkAddressPage abstracts the raw results of making a
385// ListAddressesByNetwork() request against the API.
386// As OpenStack extensions may freely alter the response bodies of structures
387// returned to the client, you may only safely access the data provided through
388// the ExtractAddresses call.
389type NetworkAddressPage struct {
390	pagination.SinglePageBase
391}
392
393// IsEmpty returns true if a NetworkAddressPage contains no addresses.
394func (r NetworkAddressPage) IsEmpty() (bool, error) {
395	addresses, err := ExtractNetworkAddresses(r)
396	return len(addresses) == 0, err
397}
398
399// ExtractNetworkAddresses interprets the results of a single page from a
400// ListAddressesByNetwork() call, producing a slice of addresses.
401func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) {
402	var s map[string][]Address
403	err := (r.(NetworkAddressPage)).ExtractInto(&s)
404	if err != nil {
405		return nil, err
406	}
407
408	var key string
409	for k := range s {
410		key = k
411	}
412
413	return s[key], err
414}
415