blob: 48c7df20459c5f51a171187bd114e2fd0f2c3332 [file] [log] [blame]
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -08001package servers
2
3import (
Ash Wilson6a310e02014-09-29 08:24:02 -04004 "encoding/base64"
Kevin Pikea2bfaea2015-04-21 11:45:59 -07005 "encoding/json"
Jon Perrittcc77da62014-11-16 13:14:21 -07006 "errors"
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -07007 "fmt"
Ash Wilson01626a32014-09-17 10:38:07 -04008
Ash Wilson01626a32014-09-17 10:38:07 -04009 "github.com/rackspace/gophercloud"
Jon Perrittad5f1cb2015-05-20 10:38:13 -060010 "github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
11 "github.com/rackspace/gophercloud/openstack/compute/v2/images"
Ash Wilson01626a32014-09-17 10:38:07 -040012 "github.com/rackspace/gophercloud/pagination"
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080013)
14
Jamie Hannafordbfe33b22014-10-16 12:45:40 +020015// ListOptsBuilder allows extensions to add additional parameters to the
16// List request.
17type ListOptsBuilder interface {
18 ToServerListQuery() (string, error)
19}
Kevin Pike9748b7b2015-05-05 07:34:07 -070020
Jamie Hannafordbfe33b22014-10-16 12:45:40 +020021// ListOpts allows the filtering and sorting of paginated collections through
22// the API. Filtering is achieved by passing in struct field values that map to
23// the server attributes you want to see returned. Marker and Limit are used
24// for pagination.
25type ListOpts struct {
26 // A time/date stamp for when the server last changed status.
27 ChangesSince string `q:"changes-since"`
28
29 // Name of the image in URL format.
30 Image string `q:"image"`
31
32 // Name of the flavor in URL format.
33 Flavor string `q:"flavor"`
34
35 // Name of the server as a string; can be queried with regular expressions.
36 // Realize that ?name=bob returns both bob and bobb. If you need to match bob
37 // only, you can use a regular expression matching the syntax of the
38 // underlying database server implemented for Compute.
39 Name string `q:"name"`
40
41 // Value of the status of the server so that you can filter on "ACTIVE" for example.
42 Status string `q:"status"`
43
44 // Name of the host as a string.
45 Host string `q:"host"`
46
47 // UUID of the server at which you want to set a marker.
48 Marker string `q:"marker"`
49
50 // Integer value for the limit of values to return.
51 Limit int `q:"limit"`
52}
53
54// ToServerListQuery formats a ListOpts into a query string.
55func (opts ListOpts) ToServerListQuery() (string, error) {
56 q, err := gophercloud.BuildQueryString(opts)
57 if err != nil {
58 return "", err
59 }
60 return q.String(), nil
61}
62
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080063// List makes a request against the API to list servers accessible to you.
Jamie Hannafordbfe33b22014-10-16 12:45:40 +020064func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
65 url := listDetailURL(client)
66
67 if opts != nil {
68 query, err := opts.ToServerListQuery()
69 if err != nil {
70 return pagination.Pager{Err: err}
71 }
72 url += query
73 }
74
Ash Wilsonb8b16f82014-10-20 10:19:49 -040075 createPageFn := func(r pagination.PageResult) pagination.Page {
76 return ServerPage{pagination.LinkedPageBase{PageResult: r}}
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080077 }
78
Jamie Hannafordbfe33b22014-10-16 12:45:40 +020079 return pagination.NewPager(client, url, createPageFn)
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080080}
81
Ash Wilson2206a112014-10-02 10:57:38 -040082// CreateOptsBuilder describes struct types that can be accepted by the Create call.
Ash Wilson6a310e02014-09-29 08:24:02 -040083// The CreateOpts struct in this package does.
Ash Wilson2206a112014-10-02 10:57:38 -040084type CreateOptsBuilder interface {
Jon Perritt4149d7c2014-10-23 21:23:46 -050085 ToServerCreateMap() (map[string]interface{}, error)
Ash Wilson6a310e02014-09-29 08:24:02 -040086}
87
88// Network is used within CreateOpts to control a new server's network attachments.
89type Network struct {
90 // UUID of a nova-network to attach to the newly provisioned server.
91 // Required unless Port is provided.
92 UUID string
93
94 // Port of a neutron network to attach to the newly provisioned server.
95 // Required unless UUID is provided.
96 Port string
97
98 // FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
99 FixedIP string
100}
101
Kevin Pike92e10b52015-04-10 15:16:57 -0700102// Personality is an array of files that are injected into the server at launch.
Kevin Pikea2bfaea2015-04-21 11:45:59 -0700103type Personality []*File
Kevin Pike92e10b52015-04-10 15:16:57 -0700104
105// File is used within CreateOpts and RebuildOpts to inject a file into the server at launch.
Kevin Pike9748b7b2015-05-05 07:34:07 -0700106// File implements the json.Marshaler interface, so when a Create or Rebuild operation is requested,
107// json.Marshal will call File's MarshalJSON method.
Kevin Pike92e10b52015-04-10 15:16:57 -0700108type File struct {
109 // Path of the file
Kevin Pikea2bfaea2015-04-21 11:45:59 -0700110 Path string
Kevin Pike92e10b52015-04-10 15:16:57 -0700111 // Contents of the file. Maximum content size is 255 bytes.
Kevin Pikea2bfaea2015-04-21 11:45:59 -0700112 Contents []byte
Kevin Pike92e10b52015-04-10 15:16:57 -0700113}
114
Kevin Pikea2bfaea2015-04-21 11:45:59 -0700115// MarshalJSON marshals the escaped file, base64 encoding the contents.
116func (f *File) MarshalJSON() ([]byte, error) {
117 file := struct {
118 Path string `json:"path"`
119 Contents string `json:"contents"`
120 }{
121 Path: f.Path,
122 Contents: base64.StdEncoding.EncodeToString(f.Contents),
Kevin Pike92e10b52015-04-10 15:16:57 -0700123 }
Kevin Pikea2bfaea2015-04-21 11:45:59 -0700124 return json.Marshal(file)
Kevin Pike92e10b52015-04-10 15:16:57 -0700125}
126
Ash Wilson6a310e02014-09-29 08:24:02 -0400127// CreateOpts specifies server creation parameters.
128type CreateOpts struct {
129 // Name [required] is the name to assign to the newly launched server.
130 Name string
131
Jon Perrittad5f1cb2015-05-20 10:38:13 -0600132 // ImageRef [optional; required if ImageName is not provided] is the ID or full
133 // URL to the image that contains the server's OS and initial state.
134 // Also optional if using the boot-from-volume extension.
Ash Wilson6a310e02014-09-29 08:24:02 -0400135 ImageRef string
136
Jon Perrittad5f1cb2015-05-20 10:38:13 -0600137 // ImageName [optional; required if ImageRef is not provided] is the name of the
138 // image that contains the server's OS and initial state.
139 // Also optional if using the boot-from-volume extension.
140 ImageName string
141
142 // FlavorRef [optional; required if FlavorName is not provided] is the ID or
143 // full URL to the flavor that describes the server's specs.
Ash Wilson6a310e02014-09-29 08:24:02 -0400144 FlavorRef string
145
Jon Perrittad5f1cb2015-05-20 10:38:13 -0600146 // FlavorName [optional; required if FlavorRef is not provided] is the name of
147 // the flavor that describes the server's specs.
148 FlavorName string
149
Ash Wilson6a310e02014-09-29 08:24:02 -0400150 // SecurityGroups [optional] lists the names of the security groups to which this server should belong.
151 SecurityGroups []string
152
153 // UserData [optional] contains configuration information or scripts to use upon launch.
154 // Create will base64-encode it for you.
155 UserData []byte
156
157 // AvailabilityZone [optional] in which to launch the server.
158 AvailabilityZone string
159
160 // Networks [optional] dictates how this server will be attached to available networks.
161 // By default, the server will be attached to all isolated networks for the tenant.
162 Networks []Network
163
164 // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
165 Metadata map[string]string
166
Kevin Pike92e10b52015-04-10 15:16:57 -0700167 // Personality [optional] includes files to inject into the server at launch.
168 // Create will base64-encode file contents for you.
169 Personality Personality
Ash Wilson6a310e02014-09-29 08:24:02 -0400170
171 // ConfigDrive [optional] enables metadata injection through a configuration drive.
172 ConfigDrive bool
Jon Perrittf3b2e142014-11-04 16:00:19 -0600173
174 // AdminPass [optional] sets the root user password. If not set, a randomly-generated
175 // password will be created and returned in the response.
176 AdminPass string
Jon Perritt7b9671c2015-02-01 22:03:14 -0700177
178 // AccessIPv4 [optional] specifies an IPv4 address for the instance.
179 AccessIPv4 string
180
181 // AccessIPv6 [optional] specifies an IPv6 address for the instance.
182 AccessIPv6 string
Ash Wilson6a310e02014-09-29 08:24:02 -0400183}
184
Ash Wilsone45c9732014-09-29 10:54:12 -0400185// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
Jon Perritt4149d7c2014-10-23 21:23:46 -0500186func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
Ash Wilson6a310e02014-09-29 08:24:02 -0400187 server := make(map[string]interface{})
188
189 server["name"] = opts.Name
190 server["imageRef"] = opts.ImageRef
Jon Perrittad5f1cb2015-05-20 10:38:13 -0600191 server["imageName"] = opts.ImageName
Ash Wilson6a310e02014-09-29 08:24:02 -0400192 server["flavorRef"] = opts.FlavorRef
Jon Perrittad5f1cb2015-05-20 10:38:13 -0600193 server["flavorName"] = opts.FlavorName
Ash Wilson6a310e02014-09-29 08:24:02 -0400194
195 if opts.UserData != nil {
196 encoded := base64.StdEncoding.EncodeToString(opts.UserData)
197 server["user_data"] = &encoded
198 }
Ash Wilson6a310e02014-09-29 08:24:02 -0400199 if opts.ConfigDrive {
200 server["config_drive"] = "true"
201 }
202 if opts.AvailabilityZone != "" {
203 server["availability_zone"] = opts.AvailabilityZone
204 }
205 if opts.Metadata != nil {
206 server["metadata"] = opts.Metadata
207 }
Jon Perrittf3b2e142014-11-04 16:00:19 -0600208 if opts.AdminPass != "" {
209 server["adminPass"] = opts.AdminPass
210 }
Jon Perritt7b9671c2015-02-01 22:03:14 -0700211 if opts.AccessIPv4 != "" {
212 server["accessIPv4"] = opts.AccessIPv4
213 }
214 if opts.AccessIPv6 != "" {
215 server["accessIPv6"] = opts.AccessIPv6
216 }
Ash Wilson6a310e02014-09-29 08:24:02 -0400217
218 if len(opts.SecurityGroups) > 0 {
219 securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
220 for i, groupName := range opts.SecurityGroups {
221 securityGroups[i] = map[string]interface{}{"name": groupName}
222 }
eselldf709942014-11-13 21:07:11 -0700223 server["security_groups"] = securityGroups
Ash Wilson6a310e02014-09-29 08:24:02 -0400224 }
Jon Perritt2a7797d2014-10-21 15:08:43 -0500225
Ash Wilson6a310e02014-09-29 08:24:02 -0400226 if len(opts.Networks) > 0 {
227 networks := make([]map[string]interface{}, len(opts.Networks))
228 for i, net := range opts.Networks {
229 networks[i] = make(map[string]interface{})
230 if net.UUID != "" {
231 networks[i]["uuid"] = net.UUID
232 }
233 if net.Port != "" {
234 networks[i]["port"] = net.Port
235 }
236 if net.FixedIP != "" {
237 networks[i]["fixed_ip"] = net.FixedIP
238 }
239 }
Jon Perritt2a7797d2014-10-21 15:08:43 -0500240 server["networks"] = networks
Ash Wilson6a310e02014-09-29 08:24:02 -0400241 }
242
Kevin Pike92e10b52015-04-10 15:16:57 -0700243 if len(opts.Personality) > 0 {
Kevin Pikea2bfaea2015-04-21 11:45:59 -0700244 server["personality"] = opts.Personality
Kevin Pike92e10b52015-04-10 15:16:57 -0700245 }
246
Jon Perritt4149d7c2014-10-23 21:23:46 -0500247 return map[string]interface{}{"server": server}, nil
Ash Wilson6a310e02014-09-29 08:24:02 -0400248}
249
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800250// Create requests a server to be provisioned to the user in the current tenant.
Ash Wilson2206a112014-10-02 10:57:38 -0400251func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
Jon Perritt4149d7c2014-10-23 21:23:46 -0500252 var res CreateResult
253
254 reqBody, err := opts.ToServerCreateMap()
255 if err != nil {
256 res.Err = err
257 return res
258 }
259
Jon Perrittad5f1cb2015-05-20 10:38:13 -0600260 // If ImageRef isn't provided, use ImageName to ascertain the image ID.
261 if reqBody["server"].(map[string]interface{})["imageRef"].(string) == "" {
262 imageName := reqBody["server"].(map[string]interface{})["imageName"].(string)
263 if imageName == "" {
264 res.Err = errors.New("One and only one of ImageRef and ImageName must be provided.")
265 return res
266 }
267 imageID, err := images.IDFromName(client, imageName)
268 if err != nil {
269 res.Err = err
270 return res
271 }
272 reqBody["server"].(map[string]interface{})["imageRef"] = imageID
273 }
274 delete(reqBody["server"].(map[string]interface{}), "imageName")
275
276 // If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID.
277 if reqBody["server"].(map[string]interface{})["flavorRef"].(string) == "" {
278 flavorName := reqBody["server"].(map[string]interface{})["flavorName"].(string)
279 if flavorName == "" {
280 res.Err = errors.New("One and only one of FlavorRef and FlavorName must be provided.")
281 return res
282 }
283 flavorID, err := flavors.IDFromName(client, flavorName)
284 if err != nil {
285 res.Err = err
286 return res
287 }
288 reqBody["server"].(map[string]interface{})["flavorRef"] = flavorID
289 }
290 delete(reqBody["server"].(map[string]interface{}), "flavorName")
291
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100292 _, res.Err = client.Post(listURL(client), reqBody, &res.Body, nil)
Jon Perritt4149d7c2014-10-23 21:23:46 -0500293 return res
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800294}
295
296// Delete requests that a server previously provisioned be removed from your account.
Jamie Hannaford34732fe2014-10-27 11:29:36 +0100297func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
298 var res DeleteResult
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100299 _, res.Err = client.Delete(deleteURL(client, id), nil)
Jamie Hannaford34732fe2014-10-27 11:29:36 +0100300 return res
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800301}
302
Ash Wilson7ddf0362014-09-17 10:59:09 -0400303// Get requests details on a single server, by ID.
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400304func Get(client *gophercloud.ServiceClient, id string) GetResult {
305 var result GetResult
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100306 _, result.Err = client.Get(getURL(client, id), &result.Body, &gophercloud.RequestOpts{
307 OkCodes: []int{200, 203},
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800308 })
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400309 return result
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800310}
311
Alex Gaynora6d5f9f2014-10-27 10:52:32 -0700312// UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
Jon Perritt82048212014-10-13 22:33:13 -0500313type UpdateOptsBuilder interface {
Ash Wilsone45c9732014-09-29 10:54:12 -0400314 ToServerUpdateMap() map[string]interface{}
Ash Wilsondcbc8fb2014-09-29 09:05:44 -0400315}
316
317// UpdateOpts specifies the base attributes that may be updated on an existing server.
318type UpdateOpts struct {
319 // Name [optional] changes the displayed name of the server.
320 // The server host name will *not* change.
321 // Server names are not constrained to be unique, even within the same tenant.
322 Name string
323
324 // AccessIPv4 [optional] provides a new IPv4 address for the instance.
325 AccessIPv4 string
326
327 // AccessIPv6 [optional] provides a new IPv6 address for the instance.
328 AccessIPv6 string
329}
330
Ash Wilsone45c9732014-09-29 10:54:12 -0400331// ToServerUpdateMap formats an UpdateOpts structure into a request body.
332func (opts UpdateOpts) ToServerUpdateMap() map[string]interface{} {
Ash Wilsondcbc8fb2014-09-29 09:05:44 -0400333 server := make(map[string]string)
334 if opts.Name != "" {
335 server["name"] = opts.Name
336 }
337 if opts.AccessIPv4 != "" {
338 server["accessIPv4"] = opts.AccessIPv4
339 }
340 if opts.AccessIPv6 != "" {
341 server["accessIPv6"] = opts.AccessIPv6
342 }
343 return map[string]interface{}{"server": server}
344}
345
Samuel A. Falvo II22d3b772014-02-13 19:27:05 -0800346// Update requests that various attributes of the indicated server be changed.
Jon Perritt82048212014-10-13 22:33:13 -0500347func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400348 var result UpdateResult
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100349 reqBody := opts.ToServerUpdateMap()
350 _, result.Err = client.Put(updateURL(client, id), reqBody, &result.Body, &gophercloud.RequestOpts{
351 OkCodes: []int{200},
Samuel A. Falvo II22d3b772014-02-13 19:27:05 -0800352 })
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400353 return result
Samuel A. Falvo II22d3b772014-02-13 19:27:05 -0800354}
Samuel A. Falvo IIca5f9a32014-03-11 17:52:58 -0700355
Ash Wilson01626a32014-09-17 10:38:07 -0400356// ChangeAdminPassword alters the administrator or root password for a specified server.
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200357func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) ActionResult {
Ash Wilsondc7daa82014-09-23 16:34:42 -0400358 var req struct {
359 ChangePassword struct {
360 AdminPass string `json:"adminPass"`
361 } `json:"changePassword"`
362 }
363
364 req.ChangePassword.AdminPass = newPassword
365
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200366 var res ActionResult
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100367 _, res.Err = client.Post(actionURL(client, id), req, nil, nil)
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200368 return res
Samuel A. Falvo IIca5f9a32014-03-11 17:52:58 -0700369}
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700370
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700371// ErrArgument errors occur when an argument supplied to a package function
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700372// fails to fall within acceptable values. For example, the Reboot() function
373// expects the "how" parameter to be one of HardReboot or SoftReboot. These
374// constants are (currently) strings, leading someone to wonder if they can pass
375// other string values instead, perhaps in an effort to break the API of their
376// provider. Reboot() returns this error in this situation.
377//
378// Function identifies which function was called/which function is generating
379// the error.
380// Argument identifies which formal argument was responsible for producing the
381// error.
382// Value provides the value as it was passed into the function.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700383type ErrArgument struct {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700384 Function, Argument string
Jon Perritt30558642014-04-14 17:07:12 -0500385 Value interface{}
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700386}
387
388// Error yields a useful diagnostic for debugging purposes.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700389func (e *ErrArgument) Error() string {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700390 return fmt.Sprintf("Bad argument in call to %s, formal parameter %s, value %#v", e.Function, e.Argument, e.Value)
391}
392
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700393func (e *ErrArgument) String() string {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700394 return e.Error()
395}
396
Ash Wilson01626a32014-09-17 10:38:07 -0400397// RebootMethod describes the mechanisms by which a server reboot can be requested.
398type RebootMethod string
399
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700400// These constants determine how a server should be rebooted.
401// See the Reboot() function for further details.
402const (
Ash Wilson01626a32014-09-17 10:38:07 -0400403 SoftReboot RebootMethod = "SOFT"
404 HardReboot RebootMethod = "HARD"
405 OSReboot = SoftReboot
406 PowerCycle = HardReboot
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700407)
408
409// Reboot requests that a given server reboot.
410// Two methods exist for rebooting a server:
411//
Ash Wilson01626a32014-09-17 10:38:07 -0400412// HardReboot (aka PowerCycle) restarts the server instance by physically cutting power to the machine, or if a VM,
413// terminating it at the hypervisor level.
414// It's done. Caput. Full stop.
415// Then, after a brief while, power is restored or the VM instance restarted.
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700416//
Ash Wilson01626a32014-09-17 10:38:07 -0400417// SoftReboot (aka OSReboot) simply tells the OS to restart under its own procedures.
418// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to restart the machine.
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200419func Reboot(client *gophercloud.ServiceClient, id string, how RebootMethod) ActionResult {
420 var res ActionResult
421
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700422 if (how != SoftReboot) && (how != HardReboot) {
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200423 res.Err = &ErrArgument{
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700424 Function: "Reboot",
425 Argument: "how",
Jon Perritt30558642014-04-14 17:07:12 -0500426 Value: how,
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700427 }
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200428 return res
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700429 }
Jon Perritt30558642014-04-14 17:07:12 -0500430
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100431 reqBody := struct {
432 C map[string]string `json:"reboot"`
433 }{
434 map[string]string{"type": string(how)},
435 }
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200436
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100437 _, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200438 return res
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700439}
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700440
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200441// RebuildOptsBuilder is an interface that allows extensions to override the
442// default behaviour of rebuild options
443type RebuildOptsBuilder interface {
444 ToServerRebuildMap() (map[string]interface{}, error)
445}
446
447// RebuildOpts represents the configuration options used in a server rebuild
448// operation
449type RebuildOpts struct {
450 // Required. The ID of the image you want your server to be provisioned on
Jamie Hannaforddcb8c272014-10-16 16:34:41 +0200451 ImageID string
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200452
453 // Name to set the server to
Jamie Hannaforddcb8c272014-10-16 16:34:41 +0200454 Name string
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200455
456 // Required. The server's admin password
Jamie Hannaforddcb8c272014-10-16 16:34:41 +0200457 AdminPass string
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200458
459 // AccessIPv4 [optional] provides a new IPv4 address for the instance.
Jamie Hannaforddcb8c272014-10-16 16:34:41 +0200460 AccessIPv4 string
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200461
462 // AccessIPv6 [optional] provides a new IPv6 address for the instance.
Jamie Hannaforddcb8c272014-10-16 16:34:41 +0200463 AccessIPv6 string
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200464
465 // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
Jamie Hannaforddcb8c272014-10-16 16:34:41 +0200466 Metadata map[string]string
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200467
Kevin Pike92e10b52015-04-10 15:16:57 -0700468 // Personality [optional] includes files to inject into the server at launch.
469 // Rebuild will base64-encode file contents for you.
470 Personality Personality
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200471}
472
473// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON
474func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
475 var err error
476 server := make(map[string]interface{})
477
478 if opts.AdminPass == "" {
479 err = fmt.Errorf("AdminPass is required")
480 }
481
482 if opts.ImageID == "" {
483 err = fmt.Errorf("ImageID is required")
484 }
485
486 if err != nil {
487 return server, err
488 }
489
490 server["name"] = opts.Name
491 server["adminPass"] = opts.AdminPass
492 server["imageRef"] = opts.ImageID
493
494 if opts.AccessIPv4 != "" {
495 server["accessIPv4"] = opts.AccessIPv4
496 }
497
498 if opts.AccessIPv6 != "" {
499 server["accessIPv6"] = opts.AccessIPv6
500 }
501
502 if opts.Metadata != nil {
503 server["metadata"] = opts.Metadata
504 }
505
Kevin Pike92e10b52015-04-10 15:16:57 -0700506 if len(opts.Personality) > 0 {
Kevin Pikea2bfaea2015-04-21 11:45:59 -0700507 server["personality"] = opts.Personality
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200508 }
509
510 return map[string]interface{}{"rebuild": server}, nil
511}
512
513// Rebuild will reprovision the server according to the configuration options
514// provided in the RebuildOpts struct.
515func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) RebuildResult {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400516 var result RebuildResult
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700517
518 if id == "" {
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200519 result.Err = fmt.Errorf("ID is required")
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400520 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700521 }
522
Jamie Hannaford6c9eb602014-10-16 16:28:07 +0200523 reqBody, err := opts.ToServerRebuildMap()
524 if err != nil {
525 result.Err = err
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400526 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700527 }
528
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100529 _, result.Err = client.Post(actionURL(client, id), reqBody, &result.Body, nil)
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400530 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700531}
532
Ash Wilson5f7cf182014-10-23 08:35:24 -0400533// ResizeOptsBuilder is an interface that allows extensions to override the default structure of
534// a Resize request.
535type ResizeOptsBuilder interface {
536 ToServerResizeMap() (map[string]interface{}, error)
537}
538
539// ResizeOpts represents the configuration options used to control a Resize operation.
540type ResizeOpts struct {
541 // FlavorRef is the ID of the flavor you wish your server to become.
542 FlavorRef string
543}
544
Alex Gaynor266e9332014-10-28 14:44:04 -0700545// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON request body for the
Ash Wilson5f7cf182014-10-23 08:35:24 -0400546// Resize request.
547func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) {
548 resize := map[string]interface{}{
549 "flavorRef": opts.FlavorRef,
550 }
551
552 return map[string]interface{}{"resize": resize}, nil
553}
554
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700555// Resize instructs the provider to change the flavor of the server.
Ash Wilson01626a32014-09-17 10:38:07 -0400556// Note that this implies rebuilding it.
557// Unfortunately, one cannot pass rebuild parameters to the resize function.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700558// When the resize completes, the server will be in RESIZE_VERIFY state.
559// While in this state, you can explore the use of the new server's configuration.
560// If you like it, call ConfirmResize() to commit the resize permanently.
561// Otherwise, call RevertResize() to restore the old configuration.
Ash Wilson9e87a922014-10-23 14:29:22 -0400562func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) ActionResult {
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200563 var res ActionResult
Ash Wilson5f7cf182014-10-23 08:35:24 -0400564 reqBody, err := opts.ToServerResizeMap()
565 if err != nil {
566 res.Err = err
567 return res
568 }
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200569
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100570 _, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200571 return res
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700572}
573
574// ConfirmResize confirms a previous resize operation on a server.
575// See Resize() for more details.
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200576func ConfirmResize(client *gophercloud.ServiceClient, id string) ActionResult {
577 var res ActionResult
578
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100579 reqBody := map[string]interface{}{"confirmResize": nil}
580 _, res.Err = client.Post(actionURL(client, id), reqBody, nil, &gophercloud.RequestOpts{
581 OkCodes: []int{201, 202, 204},
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700582 })
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200583 return res
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700584}
585
586// RevertResize cancels a previous resize operation on a server.
587// See Resize() for more details.
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200588func RevertResize(client *gophercloud.ServiceClient, id string) ActionResult {
589 var res ActionResult
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100590 reqBody := map[string]interface{}{"revertResize": nil}
591 _, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
Jamie Hannaford8c072a32014-10-16 14:33:32 +0200592 return res
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700593}
Alex Gaynor39584a02014-10-28 13:59:21 -0700594
Alex Gaynor266e9332014-10-28 14:44:04 -0700595// RescueOptsBuilder is an interface that allows extensions to override the
596// default structure of a Rescue request.
Alex Gaynor39584a02014-10-28 13:59:21 -0700597type RescueOptsBuilder interface {
598 ToServerRescueMap() (map[string]interface{}, error)
599}
600
Alex Gaynor266e9332014-10-28 14:44:04 -0700601// RescueOpts represents the configuration options used to control a Rescue
602// option.
Alex Gaynor39584a02014-10-28 13:59:21 -0700603type RescueOpts struct {
Alex Gaynor266e9332014-10-28 14:44:04 -0700604 // AdminPass is the desired administrative password for the instance in
Alex Gaynorcfec7722014-11-13 13:33:49 -0800605 // RESCUE mode. If it's left blank, the server will generate a password.
Alex Gaynor39584a02014-10-28 13:59:21 -0700606 AdminPass string
607}
608
Jon Perrittcc77da62014-11-16 13:14:21 -0700609// ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON
Alex Gaynor266e9332014-10-28 14:44:04 -0700610// request body for the Rescue request.
Alex Gaynor39584a02014-10-28 13:59:21 -0700611func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) {
612 server := make(map[string]interface{})
613 if opts.AdminPass != "" {
614 server["adminPass"] = opts.AdminPass
615 }
616 return map[string]interface{}{"rescue": server}, nil
617}
618
Alex Gaynor266e9332014-10-28 14:44:04 -0700619// Rescue instructs the provider to place the server into RESCUE mode.
Alex Gaynorfbe61bb2014-11-12 13:35:03 -0800620func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) RescueResult {
621 var result RescueResult
Alex Gaynor39584a02014-10-28 13:59:21 -0700622
623 if id == "" {
624 result.Err = fmt.Errorf("ID is required")
625 return result
626 }
627 reqBody, err := opts.ToServerRescueMap()
628 if err != nil {
629 result.Err = err
630 return result
631 }
632
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100633 _, result.Err = client.Post(actionURL(client, id), reqBody, &result.Body, &gophercloud.RequestOpts{
634 OkCodes: []int{200},
Alex Gaynor39584a02014-10-28 13:59:21 -0700635 })
636
637 return result
638}
Jon Perrittcc77da62014-11-16 13:14:21 -0700639
Jon Perritt789f8322014-11-21 08:20:04 -0700640// ResetMetadataOptsBuilder allows extensions to add additional parameters to the
641// Reset request.
642type ResetMetadataOptsBuilder interface {
643 ToMetadataResetMap() (map[string]interface{}, error)
Jon Perrittcc77da62014-11-16 13:14:21 -0700644}
645
Jon Perritt78c57ce2014-11-20 11:07:18 -0700646// MetadataOpts is a map that contains key-value pairs.
647type MetadataOpts map[string]string
Jon Perrittcc77da62014-11-16 13:14:21 -0700648
Jon Perritt789f8322014-11-21 08:20:04 -0700649// ToMetadataResetMap assembles a body for a Reset request based on the contents of a MetadataOpts.
650func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) {
Jon Perrittcc77da62014-11-16 13:14:21 -0700651 return map[string]interface{}{"metadata": opts}, nil
652}
653
Jon Perritt78c57ce2014-11-20 11:07:18 -0700654// ToMetadataUpdateMap assembles a body for an Update request based on the contents of a MetadataOpts.
655func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) {
Jon Perrittcc77da62014-11-16 13:14:21 -0700656 return map[string]interface{}{"metadata": opts}, nil
657}
658
Jon Perritt789f8322014-11-21 08:20:04 -0700659// ResetMetadata will create multiple new key-value pairs for the given server ID.
Jon Perrittcc77da62014-11-16 13:14:21 -0700660// Note: Using this operation will erase any already-existing metadata and create
661// the new metadata provided. To keep any already-existing metadata, use the
662// UpdateMetadatas or UpdateMetadata function.
Jon Perritt789f8322014-11-21 08:20:04 -0700663func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) ResetMetadataResult {
664 var res ResetMetadataResult
665 metadata, err := opts.ToMetadataResetMap()
Jon Perrittcc77da62014-11-16 13:14:21 -0700666 if err != nil {
667 res.Err = err
668 return res
669 }
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100670 _, res.Err = client.Put(metadataURL(client, id), metadata, &res.Body, &gophercloud.RequestOpts{
671 OkCodes: []int{200},
Jon Perrittcc77da62014-11-16 13:14:21 -0700672 })
673 return res
674}
675
Jon Perritt78c57ce2014-11-20 11:07:18 -0700676// Metadata requests all the metadata for the given server ID.
677func Metadata(client *gophercloud.ServiceClient, id string) GetMetadataResult {
Jon Perrittcc77da62014-11-16 13:14:21 -0700678 var res GetMetadataResult
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100679 _, res.Err = client.Get(metadataURL(client, id), &res.Body, nil)
Jon Perrittcc77da62014-11-16 13:14:21 -0700680 return res
681}
682
Jon Perritt78c57ce2014-11-20 11:07:18 -0700683// UpdateMetadataOptsBuilder allows extensions to add additional parameters to the
684// Create request.
685type UpdateMetadataOptsBuilder interface {
686 ToMetadataUpdateMap() (map[string]interface{}, error)
687}
688
689// UpdateMetadata updates (or creates) all the metadata specified by opts for the given server ID.
690// This operation does not affect already-existing metadata that is not specified
691// by opts.
692func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) UpdateMetadataResult {
693 var res UpdateMetadataResult
694 metadata, err := opts.ToMetadataUpdateMap()
695 if err != nil {
696 res.Err = err
697 return res
698 }
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100699 _, res.Err = client.Post(metadataURL(client, id), metadata, &res.Body, &gophercloud.RequestOpts{
700 OkCodes: []int{200},
Jon Perritt78c57ce2014-11-20 11:07:18 -0700701 })
702 return res
703}
704
705// MetadatumOptsBuilder allows extensions to add additional parameters to the
706// Create request.
707type MetadatumOptsBuilder interface {
708 ToMetadatumCreateMap() (map[string]interface{}, string, error)
709}
710
711// MetadatumOpts is a map of length one that contains a key-value pair.
712type MetadatumOpts map[string]string
713
714// ToMetadatumCreateMap assembles a body for a Create request based on the contents of a MetadataumOpts.
715func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) {
716 if len(opts) != 1 {
717 return nil, "", errors.New("CreateMetadatum operation must have 1 and only 1 key-value pair.")
718 }
719 metadatum := map[string]interface{}{"meta": opts}
720 var key string
721 for k := range metadatum["meta"].(MetadatumOpts) {
722 key = k
723 }
724 return metadatum, key, nil
725}
726
727// CreateMetadatum will create or update the key-value pair with the given key for the given server ID.
728func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) CreateMetadatumResult {
729 var res CreateMetadatumResult
730 metadatum, key, err := opts.ToMetadatumCreateMap()
731 if err != nil {
732 res.Err = err
733 return res
734 }
735
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100736 _, res.Err = client.Put(metadatumURL(client, id, key), metadatum, &res.Body, &gophercloud.RequestOpts{
737 OkCodes: []int{200},
Jon Perritt78c57ce2014-11-20 11:07:18 -0700738 })
739 return res
740}
741
742// Metadatum requests the key-value pair with the given key for the given server ID.
743func Metadatum(client *gophercloud.ServiceClient, id, key string) GetMetadatumResult {
744 var res GetMetadatumResult
Ash Wilson59fb6c42015-02-12 16:21:13 -0500745 _, res.Err = client.Request("GET", metadatumURL(client, id, key), gophercloud.RequestOpts{
746 JSONResponse: &res.Body,
Jon Perritt78c57ce2014-11-20 11:07:18 -0700747 })
748 return res
749}
750
751// DeleteMetadatum will delete the key-value pair with the given key for the given server ID.
752func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) DeleteMetadatumResult {
753 var res DeleteMetadatumResult
Jamie Hannaford6a3a78f2015-03-24 14:56:12 +0100754 _, res.Err = client.Delete(metadatumURL(client, id, key), &gophercloud.RequestOpts{
Ash Wilson59fb6c42015-02-12 16:21:13 -0500755 JSONResponse: &res.Body,
Jon Perrittcc77da62014-11-16 13:14:21 -0700756 })
757 return res
758}
Jon Perritt5cb49482015-02-19 12:19:58 -0700759
760// ListAddresses makes a request against the API to list the servers IP addresses.
761func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager {
762 createPageFn := func(r pagination.PageResult) pagination.Page {
763 return AddressPage{pagination.SinglePageBase(r)}
764 }
765 return pagination.NewPager(client, listAddressesURL(client, id), createPageFn)
766}
Jon Perritt04d073c2015-02-19 21:46:23 -0700767
768// ListAddressesByNetwork makes a request against the API to list the servers IP addresses
769// for the given network.
770func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager {
771 createPageFn := func(r pagination.PageResult) pagination.Page {
772 return NetworkAddressPage{pagination.SinglePageBase(r)}
773 }
774 return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), createPageFn)
775}
einarf2fc665e2015-04-16 20:16:21 +0000776
einarf4e5fdaf2015-04-16 23:14:59 +0000777type CreateImageOpts struct {
einarf2fc665e2015-04-16 20:16:21 +0000778 // Name [required] of the image/snapshot
779 Name string
780 // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the created image.
781 Metadata map[string]string
782}
783
einarf4e5fdaf2015-04-16 23:14:59 +0000784type CreateImageOptsBuilder interface {
785 ToServerCreateImageMap() (map[string]interface{}, error)
einarf2fc665e2015-04-16 20:16:21 +0000786}
787
einarf4e5fdaf2015-04-16 23:14:59 +0000788// ToServerCreateImageMap formats a CreateImageOpts structure into a request body.
789func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) {
einarf2fc665e2015-04-16 20:16:21 +0000790 var err error
791 img := make(map[string]interface{})
792 if opts.Name == "" {
einarf4e5fdaf2015-04-16 23:14:59 +0000793 return nil, fmt.Errorf("Cannot create a server image without a name")
einarf2fc665e2015-04-16 20:16:21 +0000794 }
795 img["name"] = opts.Name
796 if opts.Metadata != nil {
797 img["metadata"] = opts.Metadata
798 }
799 createImage := make(map[string]interface{})
800 createImage["createImage"] = img
801 return createImage, err
802}
803
einarf4e5fdaf2015-04-16 23:14:59 +0000804// CreateImage makes a request against the nova API to schedule an image to be created of the server
805func CreateImage(client *gophercloud.ServiceClient, serverId string, opts CreateImageOptsBuilder) CreateImageResult {
806 var res CreateImageResult
807 reqBody, err := opts.ToServerCreateImageMap()
einarf2fc665e2015-04-16 20:16:21 +0000808 if err != nil {
809 res.Err = err
810 return res
811 }
812 response, err := client.Post(actionURL(client, serverId), reqBody, nil, &gophercloud.RequestOpts{
813 OkCodes: []int{202},
814 })
815 res.Err = err
einarf4e5fdaf2015-04-16 23:14:59 +0000816 res.Header = response.Header
Kevin Pike9748b7b2015-05-05 07:34:07 -0700817 return res
einarf2fc665e2015-04-16 20:16:21 +0000818}