Skip to content

Commit 22b6e99

Browse files
committed
Fourth batch of XML-to-JSON conversions (#10)
* Fourth batch of XML-to-JSON conversions * Fix IPAddress unmarshalling * Fix IPAddress unmarshalling from ServerDetails and refresh fixtures
1 parent 7d6633e commit 22b6e99

35 files changed

+3122
-3302
lines changed

upcloud/firewall.go

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package upcloud
22

3+
import "encoding/json"
4+
35
// Constants
46
const (
57
FirewallRuleActionAccept = "accept"
@@ -16,24 +18,65 @@ const (
1618

1719
// FirewallRules represents a list of firewall rules
1820
type FirewallRules struct {
19-
FirewallRules []FirewallRule `xml:"firewall_rule"`
21+
FirewallRules []FirewallRule `xml:"firewall_rule" json:"firewall_rules"`
22+
}
23+
24+
// UnmarshalJSON is a custom unmarshaller that deals with
25+
// deeply embedded values.
26+
func (s *FirewallRules) UnmarshalJSON(b []byte) error {
27+
type localFirewallRule FirewallRule
28+
type firewallRuleWrapper struct {
29+
FirewallRules []localFirewallRule `json:"firewall_rule"`
30+
}
31+
32+
v := struct {
33+
FirewallRules firewallRuleWrapper `json:"firewall_rules"`
34+
}{}
35+
err := json.Unmarshal(b, &v)
36+
if err != nil {
37+
return err
38+
}
39+
40+
for _, f := range v.FirewallRules.FirewallRules {
41+
s.FirewallRules = append(s.FirewallRules, FirewallRule(f))
42+
}
43+
44+
return nil
2045
}
2146

2247
// FirewallRule represents a single firewall rule. Note that most integer values are represented as strings
2348
type FirewallRule struct {
24-
Action string `xml:"action"`
25-
Comment string `xml:"comment,omitempty"`
26-
DestinationAddressStart string `xml:"destination_address_start,omitempty"`
27-
DestinationAddressEnd string `xml:"destination_address_end,omitempty"`
28-
DestinationPortStart string `xml:"destination_port_start,omitempty"`
29-
DestinationPortEnd string `xml:"destination_port_end,omitempty"`
30-
Direction string `xml:"direction"`
31-
Family string `xml:"family"`
32-
ICMPType string `xml:"icmp_type,omitempty"`
33-
Position int `xml:"position"`
34-
Protocol string `xml:"protocol,omitempty"`
35-
SourceAddressStart string `xml:"source_address_start,omitempty"`
36-
SourceAddressEnd string `xml:"source_address_end,omitempty"`
37-
SourcePortStart string `xml:"source_port_start,omitempty"`
38-
SourcePortEnd string `xml:"source_port_end,omitempty"`
49+
Action string `xml:"action" json:"action"`
50+
Comment string `xml:"comment,omitempty" json:"comment,omitempty"`
51+
DestinationAddressStart string `xml:"destination_address_start,omitempty" json:"destination_address_start,omitempty"`
52+
DestinationAddressEnd string `xml:"destination_address_end,omitempty" json:"destination_address_end,omitempty"`
53+
DestinationPortStart string `xml:"destination_port_start,omitempty" json:"destination_port_start,omitempty"`
54+
DestinationPortEnd string `xml:"destination_port_end,omitempty" json:"destination_port_end,omitempty"`
55+
Direction string `xml:"direction" json:"direction"`
56+
Family string `xml:"family" json:"family"`
57+
ICMPType string `xml:"icmp_type,omitempty" json:"icmp_type,omitempty"`
58+
Position int `xml:"position" json:"position,string"`
59+
Protocol string `xml:"protocol,omitempty" json:"protocol,omitempty"`
60+
SourceAddressStart string `xml:"source_address_start,omitempty" json:"source_address_start,omitempty"`
61+
SourceAddressEnd string `xml:"source_address_end,omitempty" json:"source_address_end,omitempty"`
62+
SourcePortStart string `xml:"source_port_start,omitempty" json:"source_port_start,omitempty"`
63+
SourcePortEnd string `xml:"source_port_end,omitempty" json:"source_port_end,omitempty"`
64+
}
65+
66+
// UnmarshalJSON is a custom unmarshaller that deals with
67+
// deeply embedded values.
68+
func (s *FirewallRule) UnmarshalJSON(b []byte) error {
69+
type localFirewallRule FirewallRule
70+
71+
v := struct {
72+
FirewallRule localFirewallRule `json:"firewall_rule"`
73+
}{}
74+
err := json.Unmarshal(b, &v)
75+
if err != nil {
76+
return err
77+
}
78+
79+
(*s) = FirewallRule(v.FirewallRule)
80+
81+
return nil
3982
}

upcloud/firewall_test.go

Lines changed: 191 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,204 @@
11
package upcloud
22

33
import (
4-
"encoding/xml"
4+
"encoding/json"
55
"testing"
66

77
"github.com/stretchr/testify/assert"
88
)
99

1010
// TestUnmarshalFirewallRules tests the FirewallRules and FirewallRule are unmarshaled correctly
1111
func TestUnmarshalFirewallRules(t *testing.T) {
12-
originalXML := `<?xml version="1.0" encoding="utf-8"?>
13-
<firewall_rules>
14-
<firewall_rule>
15-
<action>accept</action>
16-
<comment>HTTP 80</comment>
17-
<destination_address_end></destination_address_end>
18-
<destination_address_start></destination_address_start>
19-
<destination_port_end>80</destination_port_end>
20-
<destination_port_start>80</destination_port_start>
21-
<direction>in</direction>
22-
<family>IPv4</family>
23-
<icmp_type></icmp_type>
24-
<position>1</position>
25-
<protocol>tcp</protocol>
26-
<source_address_end></source_address_end>
27-
<source_address_start></source_address_start>
28-
<source_port_end></source_port_end>
29-
<source_port_start></source_port_start>
30-
</firewall_rule>
31-
<firewall_rule>
32-
<action>reject</action>
33-
<comment>ICMP</comment>
34-
<destination_address_end></destination_address_end>
35-
<destination_address_start></destination_address_start>
36-
<destination_port_end></destination_port_end>
37-
<destination_port_start></destination_port_start>
38-
<direction>in</direction>
39-
<family>IPv6</family>
40-
<icmp_type>1</icmp_type>
41-
<position>2</position>
42-
<protocol>icmp</protocol>
43-
<source_address_end></source_address_end>
44-
<source_address_start></source_address_start>
45-
<source_port_end></source_port_end>
46-
<source_port_start></source_port_start>
47-
</firewall_rule>
48-
</firewall_rules>`
49-
12+
originalJSON := `
13+
{
14+
"firewall_rules": {
15+
"firewall_rule": [
16+
{
17+
"action": "accept",
18+
"comment": "Allow HTTP from anywhere",
19+
"destination_address_end": "",
20+
"destination_address_start": "",
21+
"destination_port_end": "80",
22+
"destination_port_start": "80",
23+
"direction": "in",
24+
"family": "IPv4",
25+
"icmp_type": "",
26+
"position": "1",
27+
"protocol": "",
28+
"source_address_end": "",
29+
"source_address_start": "",
30+
"source_port_end": "",
31+
"source_port_start": ""
32+
},
33+
{
34+
"action": "accept",
35+
"comment": "Allow SSH from a specific network only",
36+
"destination_address_end": "",
37+
"destination_address_start": "",
38+
"destination_port_end": "22",
39+
"destination_port_start": "22",
40+
"direction": "in",
41+
"family": "IPv4",
42+
"icmp_type": "",
43+
"position": "2",
44+
"protocol": "tcp",
45+
"source_address_end": "192.168.1.255",
46+
"source_address_start": "192.168.1.1",
47+
"source_port_end": "",
48+
"source_port_start": ""
49+
},
50+
{
51+
"action": "accept",
52+
"comment": "Allow SSH over IPv6 from this range",
53+
"destination_address_end": "",
54+
"destination_address_start": "",
55+
"destination_port_end": "22",
56+
"destination_port_start": "22",
57+
"direction": "in",
58+
"family": "IPv6",
59+
"icmp_type": "",
60+
"position": "3",
61+
"protocol": "tcp",
62+
"source_address_end": "2a04:3540:1000:aaaa:bbbb:cccc:d001",
63+
"source_address_start": "2a04:3540:1000:aaaa:bbbb:cccc:d001",
64+
"source_port_end": "",
65+
"source_port_start": ""
66+
},
67+
{
68+
"action": "accept",
69+
"comment": "Allow ICMP echo request (ping)",
70+
"destination_address_end": "",
71+
"destination_address_start": "",
72+
"destination_port_end": "",
73+
"destination_port_start": "",
74+
"direction": "in",
75+
"family": "IPv4",
76+
"icmp_type": "8",
77+
"position": "4",
78+
"protocol": "icmp",
79+
"source_address_end": "",
80+
"source_address_start": "",
81+
"source_port_end": "",
82+
"source_port_start": ""
83+
},
84+
{
85+
"action": "drop",
86+
"comment": "",
87+
"destination_address_end": "",
88+
"destination_address_start": "",
89+
"destination_port_end": "",
90+
"destination_port_start": "",
91+
"direction": "in",
92+
"family": "",
93+
"icmp_type": "",
94+
"position": "5",
95+
"protocol": "",
96+
"source_address_end": "",
97+
"source_address_start": "",
98+
"source_port_end": "",
99+
"source_port_start": ""
100+
}
101+
]
102+
}
103+
}
104+
`
50105
firewallRules := FirewallRules{}
51-
err := xml.Unmarshal([]byte(originalXML), &firewallRules)
52-
assert.Nil(t, err)
53-
assert.Len(t, firewallRules.FirewallRules, 2)
106+
err := json.Unmarshal([]byte(originalJSON), &firewallRules)
107+
assert.NoError(t, err)
108+
assert.Len(t, firewallRules.FirewallRules, 5)
109+
110+
testData := []FirewallRule{
111+
{
112+
Action: FirewallRuleActionAccept,
113+
Comment: "Allow HTTP from anywhere",
114+
DestinationPortStart: "80",
115+
DestinationPortEnd: "80",
116+
Direction: FirewallRuleDirectionIn,
117+
Family: IPAddressFamilyIPv4,
118+
Position: 1,
119+
},
120+
{
121+
Action: FirewallRuleActionAccept,
122+
Comment: "Allow SSH from a specific network only",
123+
DestinationPortStart: "22",
124+
DestinationPortEnd: "22",
125+
Direction: FirewallRuleDirectionIn,
126+
Family: IPAddressFamilyIPv4,
127+
Position: 2,
128+
Protocol: FirewallRuleProtocolTCP,
129+
SourceAddressStart: "192.168.1.1",
130+
SourceAddressEnd: "192.168.1.255",
131+
},
132+
{
133+
Action: FirewallRuleActionAccept,
134+
Comment: "Allow SSH over IPv6 from this range",
135+
DestinationPortStart: "22",
136+
DestinationPortEnd: "22",
137+
Direction: FirewallRuleDirectionIn,
138+
Family: IPAddressFamilyIPv6,
139+
Position: 3,
140+
Protocol: FirewallRuleProtocolTCP,
141+
SourceAddressStart: "2a04:3540:1000:aaaa:bbbb:cccc:d001",
142+
SourceAddressEnd: "2a04:3540:1000:aaaa:bbbb:cccc:d001",
143+
},
144+
{
145+
Action: FirewallRuleActionAccept,
146+
Comment: "Allow ICMP echo request (ping)",
147+
Direction: FirewallRuleDirectionIn,
148+
Family: IPAddressFamilyIPv4,
149+
ICMPType: "8",
150+
Position: 4,
151+
Protocol: FirewallRuleProtocolICMP,
152+
},
153+
{
154+
Action: FirewallRuleActionDrop,
155+
Direction: FirewallRuleDirectionIn,
156+
Position: 5,
157+
},
158+
}
159+
160+
for i, expectedRule := range testData {
161+
actualRule := firewallRules.FirewallRules[i]
162+
assert.Equal(t, expectedRule, actualRule)
163+
}
164+
}
165+
166+
// TestUnmarshalFirewallRule tests that FirewallRule is unmarshaled correctly on its own
167+
func TestUnmarshalFirewallRule(t *testing.T) {
168+
originalJSON := `
169+
{
170+
"firewall_rule": {
171+
"action": "accept",
172+
"comment": "Allow HTTP from anywhere",
173+
"destination_address_end": "",
174+
"destination_address_start": "",
175+
"destination_port_end": "80",
176+
"destination_port_start": "80",
177+
"direction": "in",
178+
"family": "IPv4",
179+
"icmp_type": "",
180+
"position": "1",
181+
"protocol": "",
182+
"source_address_end": "",
183+
"source_address_start": "",
184+
"source_port_end": "",
185+
"source_port_start": ""
186+
}
187+
}
188+
`
189+
actualRule := FirewallRule{}
190+
err := json.Unmarshal([]byte(originalJSON), &actualRule)
191+
assert.NoError(t, err)
192+
193+
expectedRule := FirewallRule{
194+
Action: FirewallRuleActionAccept,
195+
Comment: "Allow HTTP from anywhere",
196+
DestinationPortStart: "80",
197+
DestinationPortEnd: "80",
198+
Direction: FirewallRuleDirectionIn,
199+
Family: IPAddressFamilyIPv4,
200+
Position: 1,
201+
}
54202

55-
firstRule := firewallRules.FirewallRules[0]
56-
assert.Equal(t, FirewallRuleActionAccept, firstRule.Action)
57-
assert.Equal(t, "HTTP 80", firstRule.Comment)
58-
assert.Empty(t, firstRule.DestinationAddressEnd)
59-
assert.Empty(t, firstRule.DestinationAddressStart)
60-
assert.Equal(t, "80", firstRule.DestinationPortEnd)
61-
assert.Equal(t, "80", firstRule.DestinationPortStart)
62-
assert.Equal(t, FirewallRuleDirectionIn, firstRule.Direction)
63-
assert.Equal(t, IPAddressFamilyIPv4, firstRule.Family)
64-
assert.Empty(t, firstRule.ICMPType)
65-
assert.Equal(t, 1, firstRule.Position)
66-
assert.Equal(t, FirewallRuleProtocolTCP, firstRule.Protocol)
67-
assert.Empty(t, firstRule.SourceAddressEnd)
68-
assert.Empty(t, firstRule.SourceAddressStart)
69-
assert.Empty(t, firstRule.SourcePortEnd)
70-
assert.Empty(t, firstRule.SourcePortStart)
203+
assert.Equal(t, expectedRule, actualRule)
71204
}

0 commit comments

Comments
 (0)