implement json.Unmarshaler for Server
diff --git a/openstack/compute/v2/servers/results.go b/openstack/compute/v2/servers/results.go
index 4237481..7659c75 100644
--- a/openstack/compute/v2/servers/results.go
+++ b/openstack/compute/v2/servers/results.go
@@ -3,6 +3,7 @@
import (
"crypto/rsa"
"encoding/base64"
+ "encoding/json"
"fmt"
"net/url"
"path"
@@ -129,58 +130,68 @@
type Server struct {
// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
ID string `json:"id"`
-
// TenantID identifies the tenant owning this server resource.
TenantID string `json:"tenant_id"`
-
// UserID uniquely identifies the user account owning the tenant.
UserID string `json:"user_id"`
-
// Name contains the human-readable name for the server.
Name string `json:"name"`
-
// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
Updated string
Created string
-
- HostID string
-
+ HostID string
// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
Status string
-
// Progress ranges from 0..100.
// A request made against the server completes only once Progress reaches 100.
Progress int
-
// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
AccessIPv4, AccessIPv6 string
-
// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
Image map[string]interface{}
-
// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
Flavor map[string]interface{}
-
// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
Addresses map[string]interface{}
-
// Metadata includes a list of all user-specified key-value pairs attached to the server.
Metadata map[string]interface{}
-
// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
Links []interface{}
-
// KeyName indicates which public key was injected into the server on launch.
KeyName string `json:"key_name"`
-
// 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.
// Note that this is the ONLY time this field will be valid.
AdminPass string `json:"adminPass"`
-
// SecurityGroups includes the security groups that this instance has applied to it
SecurityGroups []map[string]interface{} `json:"security_groups"`
}
+func (s *Server) UnmarshalJSON(b []byte) error {
+ type tmp Server
+ var server *struct {
+ tmp
+ Image interface{}
+ }
+ err := json.Unmarshal(b, &server)
+ if err != nil {
+ return err
+ }
+
+ *s = Server(server.tmp)
+
+ switch t := server.Image.(type) {
+ case map[string]interface{}:
+ s.Image = t
+ case string:
+ switch t {
+ case "":
+ s.Image = nil
+ }
+ }
+
+ return nil
+}
+
// ServerPage abstracts the raw results of making a List() request against the API.
// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
// data provided through the ExtractServers call.
diff --git a/openstack/compute/v2/servers/testing/fixtures.go b/openstack/compute/v2/servers/testing/fixtures.go
index b4fb7ff..613dc1e 100644
--- a/openstack/compute/v2/servers/testing/fixtures.go
+++ b/openstack/compute/v2/servers/testing/fixtures.go
@@ -154,7 +154,69 @@
"OS-EXT-STS:power_state": 1,
"config_drive": "",
"metadata": {}
- }
+ },
+ {
+ "status": "ACTIVE",
+ "updated": "2014-09-25T13:04:49Z",
+ "hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+ "OS-EXT-SRV-ATTR:host": "devstack",
+ "addresses": {
+ "private": [
+ {
+ "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
+ "version": 4,
+ "addr": "10.0.0.31",
+ "OS-EXT-IPS:type": "fixed"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+ "rel": "self"
+ },
+ {
+ "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+ "rel": "bookmark"
+ }
+ ],
+ "key_name": null,
+ "image": "",
+ "OS-EXT-STS:task_state": null,
+ "OS-EXT-STS:vm_state": "active",
+ "OS-EXT-SRV-ATTR:instance_name": "instance-0000001d",
+ "OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000",
+ "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "id": "9e5476bd-a4ec-4653-93d6-72c93aa682bb",
+ "security_groups": [
+ {
+ "name": "default"
+ }
+ ],
+ "OS-SRV-USG:terminated_at": null,
+ "OS-EXT-AZ:availability_zone": "nova",
+ "user_id": "9349aff8be7545ac9d2f1d00999a23cd",
+ "name": "merp",
+ "created": "2014-09-25T13:04:41Z",
+ "tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
+ "OS-DCF:diskConfig": "MANUAL",
+ "os-extended-volumes:volumes_attached": [],
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "progress": 0,
+ "OS-EXT-STS:power_state": 1,
+ "config_drive": "",
+ "metadata": {}
+ }
]
}
`
@@ -353,6 +415,54 @@
},
},
}
+
+ // ServerMerp is a Server struct that should correspond to the second server in ServerListBody.
+ ServerMerp = servers.Server{
+ Status: "ACTIVE",
+ Updated: "2014-09-25T13:04:49Z",
+ HostID: "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+ Addresses: map[string]interface{}{
+ "private": []interface{}{
+ map[string]interface{}{
+ "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
+ "version": float64(4),
+ "addr": "10.0.0.31",
+ "OS-EXT-IPS:type": "fixed",
+ },
+ },
+ },
+ Links: []interface{}{
+ map[string]interface{}{
+ "href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+ "rel": "self",
+ },
+ map[string]interface{}{
+ "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+ "rel": "bookmark",
+ },
+ },
+ Image: nil,
+ Flavor: map[string]interface{}{
+ "id": "1",
+ "links": []interface{}{
+ map[string]interface{}{
+ "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+ "rel": "bookmark",
+ },
+ },
+ },
+ ID: "9e5476bd-a4ec-4653-93d6-72c93aa682bb",
+ UserID: "9349aff8be7545ac9d2f1d00999a23cd",
+ Name: "merp",
+ Created: "2014-09-25T13:04:41Z",
+ TenantID: "fcad67a6189847c4aecfa3c81a05783b",
+ Metadata: map[string]interface{}{},
+ SecurityGroups: []map[string]interface{}{
+ map[string]interface{}{
+ "name": "default",
+ },
+ },
+ }
)
type CreateOptsWithCustomField struct {
diff --git a/openstack/compute/v2/servers/testing/requests_test.go b/openstack/compute/v2/servers/testing/requests_test.go
index 7db6b93..6d792d5 100644
--- a/openstack/compute/v2/servers/testing/requests_test.go
+++ b/openstack/compute/v2/servers/testing/requests_test.go
@@ -26,11 +26,12 @@
return false, err
}
- if len(actual) != 2 {
- t.Fatalf("Expected 2 servers, got %d", len(actual))
+ if len(actual) != 3 {
+ t.Fatalf("Expected 3 servers, got %d", len(actual))
}
th.CheckDeepEquals(t, ServerHerp, actual[0])
th.CheckDeepEquals(t, ServerDerp, actual[1])
+ th.CheckDeepEquals(t, ServerMerp, actual[2])
return true, nil
})