Merge pull request #209 from smashwilson/map-no-more
Provide structs for server creation and update
diff --git a/acceptance/openstack/compute/v2/servers_test.go b/acceptance/openstack/compute/v2/servers_test.go
index 620ef1b..131b089 100644
--- a/acceptance/openstack/compute/v2/servers_test.go
+++ b/acceptance/openstack/compute/v2/servers_test.go
@@ -46,10 +46,10 @@
name := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create server: %s\n", name)
- server, err := servers.Create(client, map[string]interface{}{
- "flavorRef": choices.FlavorID,
- "imageRef": choices.ImageID,
- "name": name,
+ server, err := servers.Create(client, servers.CreateOpts{
+ Name: name,
+ FlavorRef: choices.FlavorID,
+ ImageRef: choices.ImageID,
}).Extract()
if err != nil {
t.Fatalf("Unable to create server: %v", err)
@@ -114,9 +114,7 @@
t.Logf("Attempting to rename the server to %s.", alternateName)
- updated, err := servers.Update(client, server.ID, map[string]interface{}{
- "name": alternateName,
- }).Extract()
+ updated, err := servers.Update(client, server.ID, servers.UpdateOpts{Name: alternateName}).Extract()
if err != nil {
t.Fatalf("Unable to rename server: %v", err)
}
diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go
index 6c72a0a..20ca52e 100644
--- a/openstack/compute/v2/servers/requests.go
+++ b/openstack/compute/v2/servers/requests.go
@@ -1,6 +1,7 @@
package servers
import (
+ "encoding/base64"
"fmt"
"github.com/racker/perigee"
@@ -17,14 +18,120 @@
return pagination.NewPager(client, detailURL(client), createPage)
}
+// CreateOptsBuilder describes struct types that can be accepted by the Create call.
+// The CreateOpts struct in this package does.
+type CreateOptsBuilder interface {
+ ToServerCreateMap() map[string]interface{}
+}
+
+// Network is used within CreateOpts to control a new server's network attachments.
+type Network struct {
+ // UUID of a nova-network to attach to the newly provisioned server.
+ // Required unless Port is provided.
+ UUID string
+
+ // Port of a neutron network to attach to the newly provisioned server.
+ // Required unless UUID is provided.
+ Port string
+
+ // FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
+ FixedIP string
+}
+
+// CreateOpts specifies server creation parameters.
+type CreateOpts struct {
+ // Name [required] is the name to assign to the newly launched server.
+ Name string
+
+ // ImageRef [required] is the ID or full URL to the image that contains the server's OS and initial state.
+ // Optional if using the boot-from-volume extension.
+ ImageRef string
+
+ // FlavorRef [required] is the ID or full URL to the flavor that describes the server's specs.
+ FlavorRef string
+
+ // SecurityGroups [optional] lists the names of the security groups to which this server should belong.
+ SecurityGroups []string
+
+ // UserData [optional] contains configuration information or scripts to use upon launch.
+ // Create will base64-encode it for you.
+ UserData []byte
+
+ // AvailabilityZone [optional] in which to launch the server.
+ AvailabilityZone string
+
+ // Networks [optional] dictates how this server will be attached to available networks.
+ // By default, the server will be attached to all isolated networks for the tenant.
+ Networks []Network
+
+ // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
+ Metadata map[string]string
+
+ // Personality [optional] includes the path and contents of a file to inject into the server at launch.
+ // The maximum size of the file is 255 bytes (decoded).
+ Personality []byte
+
+ // ConfigDrive [optional] enables metadata injection through a configuration drive.
+ ConfigDrive bool
+}
+
+// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
+func (opts CreateOpts) ToServerCreateMap() map[string]interface{} {
+ server := make(map[string]interface{})
+
+ server["name"] = opts.Name
+ server["imageRef"] = opts.ImageRef
+ server["flavorRef"] = opts.FlavorRef
+
+ if opts.UserData != nil {
+ encoded := base64.StdEncoding.EncodeToString(opts.UserData)
+ server["user_data"] = &encoded
+ }
+ if opts.Personality != nil {
+ encoded := base64.StdEncoding.EncodeToString(opts.Personality)
+ server["personality"] = &encoded
+ }
+ if opts.ConfigDrive {
+ server["config_drive"] = "true"
+ }
+ if opts.AvailabilityZone != "" {
+ server["availability_zone"] = opts.AvailabilityZone
+ }
+ if opts.Metadata != nil {
+ server["metadata"] = opts.Metadata
+ }
+
+ if len(opts.SecurityGroups) > 0 {
+ securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
+ for i, groupName := range opts.SecurityGroups {
+ securityGroups[i] = map[string]interface{}{"name": groupName}
+ }
+ }
+ if len(opts.Networks) > 0 {
+ networks := make([]map[string]interface{}, len(opts.Networks))
+ for i, net := range opts.Networks {
+ networks[i] = make(map[string]interface{})
+ if net.UUID != "" {
+ networks[i]["uuid"] = net.UUID
+ }
+ if net.Port != "" {
+ networks[i]["port"] = net.Port
+ }
+ if net.FixedIP != "" {
+ networks[i]["fixed_ip"] = net.FixedIP
+ }
+ }
+ }
+
+ return map[string]interface{}{"server": server}
+}
+
// Create requests a server to be provisioned to the user in the current tenant.
-func Create(client *gophercloud.ServiceClient, opts map[string]interface{}) CreateResult {
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var result CreateResult
_, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
- Results: &result.Resp,
- ReqBody: map[string]interface{}{
- "server": opts,
- },
+ Results: &result.Resp,
+ ReqBody: opts.ToServerCreateMap(),
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
@@ -50,14 +157,46 @@
return result
}
+// UpdateOptsLike allows extentions to add additional attributes to the Update request.
+type UpdateOptsLike interface {
+ ToServerUpdateMap() map[string]interface{}
+}
+
+// UpdateOpts specifies the base attributes that may be updated on an existing server.
+type UpdateOpts struct {
+ // Name [optional] changes the displayed name of the server.
+ // The server host name will *not* change.
+ // Server names are not constrained to be unique, even within the same tenant.
+ Name string
+
+ // AccessIPv4 [optional] provides a new IPv4 address for the instance.
+ AccessIPv4 string
+
+ // AccessIPv6 [optional] provides a new IPv6 address for the instance.
+ AccessIPv6 string
+}
+
+// ToServerUpdateMap formats an UpdateOpts structure into a request body.
+func (opts UpdateOpts) ToServerUpdateMap() map[string]interface{} {
+ server := make(map[string]string)
+ if opts.Name != "" {
+ server["name"] = opts.Name
+ }
+ if opts.AccessIPv4 != "" {
+ server["accessIPv4"] = opts.AccessIPv4
+ }
+ if opts.AccessIPv6 != "" {
+ server["accessIPv6"] = opts.AccessIPv6
+ }
+ return map[string]interface{}{"server": server}
+}
+
// Update requests that various attributes of the indicated server be changed.
-func Update(client *gophercloud.ServiceClient, id string, opts map[string]interface{}) UpdateResult {
+func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsLike) UpdateResult {
var result UpdateResult
_, result.Err = perigee.Request("PUT", serverURL(client, id), perigee.Options{
- Results: &result.Resp,
- ReqBody: map[string]interface{}{
- "server": opts,
- },
+ Results: &result.Resp,
+ ReqBody: opts.ToServerUpdateMap(),
MoreHeaders: client.Provider.AuthenticatedHeaders(),
})
return result
diff --git a/openstack/compute/v2/servers/requests_test.go b/openstack/compute/v2/servers/requests_test.go
index fa1a2f5..01c4cdb 100644
--- a/openstack/compute/v2/servers/requests_test.go
+++ b/openstack/compute/v2/servers/requests_test.go
@@ -88,10 +88,10 @@
})
client := serviceClient()
- actual, err := Create(client, map[string]interface{}{
- "name": "derp",
- "imageRef": "f90f6034-2570-4974-8351-6b49732ef2eb",
- "flavorRef": "1",
+ actual, err := Create(client, CreateOpts{
+ Name: "derp",
+ ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb",
+ FlavorRef: "1",
}).Extract()
if err != nil {
t.Fatalf("Unexpected Create error: %v", err)
@@ -154,9 +154,7 @@
})
client := serviceClient()
- actual, err := Update(client, "1234asdf", map[string]interface{}{
- "name": "new-name",
- }).Extract()
+ actual, err := Update(client, "1234asdf", UpdateOpts{Name: "new-name"}).Extract()
if err != nil {
t.Fatalf("Unexpected Update error: %v", err)
}