Handle Unmarshaling Compute Security Group IDs (#192)
* Handling integer secgroup IDs
* Handling integer secgroup rule IDs
* Updating unit tests for integer secgroup IDs and rule IDs
* Style updates
* Style updates
* Test formatting fix
diff --git a/openstack/compute/v2/extensions/secgroups/results.go b/openstack/compute/v2/extensions/secgroups/results.go
index 764f580..f49338a 100644
--- a/openstack/compute/v2/extensions/secgroups/results.go
+++ b/openstack/compute/v2/extensions/secgroups/results.go
@@ -1,6 +1,9 @@
package secgroups
import (
+ "encoding/json"
+ "strconv"
+
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@@ -10,28 +13,51 @@
// The unique ID of the group. If Neutron is installed, this ID will be
// represented as a string UUID; if Neutron is not installed, it will be a
// numeric ID. For the sake of consistency, we always cast it to a string.
- ID string
+ ID string `json:"-"`
// The human-readable name of the group, which needs to be unique.
- Name string
+ Name string `json:"name"`
// The human-readable description of the group.
- Description string
+ Description string `json:"description"`
// The rules which determine how this security group operates.
- Rules []Rule
+ Rules []Rule `json:"rules"`
// The ID of the tenant to which this security group belongs.
TenantID string `json:"tenant_id"`
}
+func (r *SecurityGroup) UnmarshalJSON(b []byte) error {
+ type tmp SecurityGroup
+ var s struct {
+ tmp
+ ID interface{} `json:"id"`
+ }
+ err := json.Unmarshal(b, &s)
+ if err != nil {
+ return err
+ }
+
+ *r = SecurityGroup(s.tmp)
+
+ switch t := s.ID.(type) {
+ case float64:
+ r.ID = strconv.FormatFloat(t, 'f', -1, 64)
+ case string:
+ r.ID = t
+ }
+
+ return err
+}
+
// Rule represents a security group rule, a policy which determines how a
// security group operates and what inbound traffic it allows in.
type Rule struct {
// The unique ID. If Neutron is installed, this ID will be
// represented as a string UUID; if Neutron is not installed, it will be a
// numeric ID. For the sake of consistency, we always cast it to a string.
- ID string
+ ID string `json:"-"`
// The lower bound of the port range which this security group should open up
FromPort int `json:"from_port"`
@@ -52,6 +78,37 @@
Group Group
}
+func (r *Rule) UnmarshalJSON(b []byte) error {
+ type tmp Rule
+ var s struct {
+ tmp
+ ID interface{} `json:"id"`
+ ParentGroupID interface{} `json:"parent_group_id"`
+ }
+ err := json.Unmarshal(b, &s)
+ if err != nil {
+ return err
+ }
+
+ *r = Rule(s.tmp)
+
+ switch t := s.ID.(type) {
+ case float64:
+ r.ID = strconv.FormatFloat(t, 'f', -1, 64)
+ case string:
+ r.ID = t
+ }
+
+ switch t := s.ParentGroupID.(type) {
+ case float64:
+ r.ParentGroupID = strconv.FormatFloat(t, 'f', -1, 64)
+ case string:
+ r.ParentGroupID = t
+ }
+
+ return err
+}
+
// IPRange represents the IP range whose traffic will be accepted by the
// security group.
type IPRange struct {
diff --git a/openstack/compute/v2/extensions/secgroups/testing/fixtures.go b/openstack/compute/v2/extensions/secgroups/testing/fixtures.go
index 8a83ca8..536e7f8 100644
--- a/openstack/compute/v2/extensions/secgroups/testing/fixtures.go
+++ b/openstack/compute/v2/extensions/secgroups/testing/fixtures.go
@@ -163,10 +163,35 @@
fmt.Fprintf(w, `
{
"security_group": {
- "id": "12345"
+ "id": %d
}
}
- `)
+ `, groupID)
+ })
+}
+
+func mockGetNumericIDGroupRuleResponse(t *testing.T, groupID int) {
+ url := fmt.Sprintf("%s/%d", rootPath, groupID)
+ th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "security_group": {
+ "id": %d,
+ "rules": [
+ {
+ "parent_group_id": %d,
+ "id": %d
+ }
+ ]
+ }
+}
+ `, groupID, groupID, groupID)
})
}
diff --git a/openstack/compute/v2/extensions/secgroups/testing/requests_test.go b/openstack/compute/v2/extensions/secgroups/testing/requests_test.go
index b7ffa20..b520764 100644
--- a/openstack/compute/v2/extensions/secgroups/testing/requests_test.go
+++ b/openstack/compute/v2/extensions/secgroups/testing/requests_test.go
@@ -178,6 +178,29 @@
th.AssertDeepEquals(t, expected, group)
}
+func TestGetNumericRuleID(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ numericGroupID := 12345
+
+ mockGetNumericIDGroupRuleResponse(t, numericGroupID)
+
+ group, err := secgroups.Get(client.ServiceClient(), "12345").Extract()
+ th.AssertNoErr(t, err)
+
+ expected := &secgroups.SecurityGroup{
+ ID: "12345",
+ Rules: []secgroups.Rule{
+ {
+ ParentGroupID: "12345",
+ ID: "12345",
+ },
+ },
+ }
+ th.AssertDeepEquals(t, expected, group)
+}
+
func TestDelete(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()