Skip to content

Commit 3c5e194

Browse files
authored
Add support for Security Groups and Security Group Rules (#54)
* Add support for Security Groups and Security Group Rules - Add security_groups resource with CRUD operations - Add security_group_rules resource with create/delete operations - Update ports resource to support security_groups field - Add comprehensive unit tests for all new resources * Fix code formatting issues * Add Security Groups and Security Group Rules support to v4
1 parent c70e43d commit 3c5e194

32 files changed

Lines changed: 2790 additions & 0 deletions

File tree

v3/ecl/network/v2/ports/requests.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ type CreateOpts struct {
8080
MACAddress string `json:"mac_address,omitempty"`
8181
Name string `json:"name,omitempty"`
8282
NetworkID string `json:"network_id"`
83+
SecurityGroups *[]string `json:"security_groups,omitempty"`
8384
SegmentationID int `json:"segmentation_id,omitempty"`
8485
SegmentationType string `json:"segmentation_type,omitempty"`
8586
Tags map[string]string `json:"tags,omitempty"`
@@ -118,6 +119,7 @@ type UpdateOpts struct {
118119
DeviceOwner *string `json:"device_owner,omitempty"`
119120
FixedIPs interface{} `json:"fixed_ips,omitempty"`
120121
Name *string `json:"name,omitempty"`
122+
SecurityGroups *[]string `json:"security_groups,omitempty"`
121123
SegmentationID *int `json:"segmentation_id,omitempty"`
122124
SegmentationType *string `json:"segmentation_type,omitempty"`
123125
Tags *map[string]string `json:"tags,omitempty"`

v3/ecl/network/v2/ports/results.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ type Port struct {
9494
// Network that this port is associated with.
9595
NetworkID string `json:"network_id"`
9696

97+
// SecurityGroups is the IDs of security groups applied to the port.
98+
SecurityGroups []string `json:"security_groups"`
99+
97100
// SegmentationID is the segmenation ID used for this port (i.e. for vlan type it is vlan tag)
98101
SegmentationID int `json:"segmentation_id"`
99102

v3/ecl/network/v2/ports/testing/fixtures.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,83 @@ var Port2 = ports.Port{
288288
}
289289

290290
var ExpectedPortSlice = []ports.Port{Port1, Port2}
291+
292+
const CreateWithSecurityGroupsRequest = `
293+
{
294+
"port":
295+
{
296+
"admin_state_up": true,
297+
"fixed_ips": [
298+
{
299+
"ip_address": "192.168.2.40",
300+
"subnet_id": "ab49eb24-667f-4a4e-9421-b4d915bff416"
301+
}
302+
],
303+
"name": "port_with_sg",
304+
"network_id": "8f36b88a-443f-4d97-9751-34d34af9e782",
305+
"security_groups": ["85cc3048-abc3-43cc-89b3-377341426ac5"],
306+
"tenant_id": "dcb2d589c0c646d0bad45c0cf9f90cf1"
307+
}
308+
}`
309+
310+
const CreateWithSecurityGroupsResponse = `
311+
{
312+
"port": {
313+
"admin_state_up": true,
314+
"allowed_address_pairs": [],
315+
"description": "",
316+
"device_id": "",
317+
"device_owner": "",
318+
"fixed_ips": [
319+
{
320+
"ip_address": "192.168.2.40",
321+
"subnet_id": "ab49eb24-667f-4a4e-9421-b4d915bff416"
322+
}
323+
],
324+
"id": "port-with-sg-id",
325+
"mac_address": "fa:16:3e:b0:ca:f2",
326+
"name": "port_with_sg",
327+
"network_id": "8f36b88a-443f-4d97-9751-34d34af9e782",
328+
"security_groups": ["85cc3048-abc3-43cc-89b3-377341426ac5"],
329+
"segmentation_id": null,
330+
"segmentation_type": null,
331+
"status": "PENDING_CREATE",
332+
"tags": {},
333+
"tenant_id": "dcb2d589c0c646d0bad45c0cf9f90cf1"
334+
}
335+
}`
336+
337+
const UpdateWithSecurityGroupsRequest = `
338+
{
339+
"port": {
340+
"name": "port_with_updated_sg",
341+
"security_groups": ["85cc3048-abc3-43cc-89b3-377341426ac5", "c0e1482e-2e3c-497e-8964-e4f818071700"]
342+
}
343+
}`
344+
345+
const UpdateWithSecurityGroupsResponse = `
346+
{
347+
"port": {
348+
"admin_state_up": true,
349+
"allowed_address_pairs": [],
350+
"description": "",
351+
"device_id": "",
352+
"device_owner": "",
353+
"fixed_ips": [
354+
{
355+
"ip_address": "192.168.2.40",
356+
"subnet_id": "ab49eb24-667f-4a4e-9421-b4d915bff416"
357+
}
358+
],
359+
"id": "port-with-sg-id",
360+
"mac_address": "fa:16:3e:b0:ca:f2",
361+
"name": "port_with_updated_sg",
362+
"network_id": "8f36b88a-443f-4d97-9751-34d34af9e782",
363+
"security_groups": ["85cc3048-abc3-43cc-89b3-377341426ac5", "c0e1482e-2e3c-497e-8964-e4f818071700"],
364+
"segmentation_id": null,
365+
"segmentation_type": null,
366+
"status": "PENDING_CREATE",
367+
"tags": {},
368+
"tenant_id": "dcb2d589c0c646d0bad45c0cf9f90cf1"
369+
}
370+
}`

v3/ecl/network/v2/ports/testing/request_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,71 @@ func TestDeletePort(t *testing.T) {
219219
res := ports.Delete(fake.ServiceClient(), "ac57c5c9-aaf4-4ffc-b8b8-f1ef84656730")
220220
th.AssertNoErr(t, res.Err)
221221
}
222+
223+
func TestCreatePortWithSecurityGroups(t *testing.T) {
224+
th.SetupHTTP()
225+
defer th.TeardownHTTP()
226+
227+
th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
228+
th.TestMethod(t, r, "POST")
229+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
230+
th.TestHeader(t, r, "Content-Type", "application/json")
231+
th.TestHeader(t, r, "Accept", "application/json")
232+
th.TestJSONRequest(t, r, CreateWithSecurityGroupsRequest)
233+
w.Header().Add("Content-Type", "application/json")
234+
w.WriteHeader(http.StatusCreated)
235+
236+
fmt.Fprintf(w, CreateWithSecurityGroupsResponse)
237+
})
238+
239+
asu := true
240+
securityGroups := []string{"85cc3048-abc3-43cc-89b3-377341426ac5"}
241+
242+
options := &ports.CreateOpts{
243+
AdminStateUp: &asu,
244+
FixedIPs: []ports.IP{{
245+
IPAddress: "192.168.2.40",
246+
SubnetID: "ab49eb24-667f-4a4e-9421-b4d915bff416",
247+
}},
248+
Name: "port_with_sg",
249+
NetworkID: "8f36b88a-443f-4d97-9751-34d34af9e782",
250+
SecurityGroups: &securityGroups,
251+
TenantID: "dcb2d589c0c646d0bad45c0cf9f90cf1",
252+
}
253+
p, err := ports.Create(fake.ServiceClient(), options).Extract()
254+
th.AssertNoErr(t, err)
255+
256+
th.CheckEquals(t, "port_with_sg", p.Name)
257+
th.CheckDeepEquals(t, securityGroups, p.SecurityGroups)
258+
}
259+
260+
func TestUpdatePortWithSecurityGroups(t *testing.T) {
261+
th.SetupHTTP()
262+
defer th.TeardownHTTP()
263+
264+
th.Mux.HandleFunc("/v2.0/ports/port-with-sg-id", func(w http.ResponseWriter, r *http.Request) {
265+
th.TestMethod(t, r, "PUT")
266+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
267+
th.TestHeader(t, r, "Content-Type", "application/json")
268+
th.TestHeader(t, r, "Accept", "application/json")
269+
th.TestJSONRequest(t, r, UpdateWithSecurityGroupsRequest)
270+
271+
w.Header().Add("Content-Type", "application/json")
272+
w.WriteHeader(http.StatusOK)
273+
274+
fmt.Fprintf(w, UpdateWithSecurityGroupsResponse)
275+
})
276+
277+
name := "port_with_updated_sg"
278+
securityGroups := []string{"85cc3048-abc3-43cc-89b3-377341426ac5", "c0e1482e-2e3c-497e-8964-e4f818071700"}
279+
280+
options := ports.UpdateOpts{
281+
Name: &name,
282+
SecurityGroups: &securityGroups,
283+
}
284+
p, err := ports.Update(fake.ServiceClient(), "port-with-sg-id", options).Extract()
285+
th.AssertNoErr(t, err)
286+
287+
th.CheckEquals(t, name, p.Name)
288+
th.CheckDeepEquals(t, securityGroups, p.SecurityGroups)
289+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Package security_group_rules contains functionality for working with ECL Security Group Rule resources.
3+
4+
Security Group Rules define specific ingress and egress traffic rules for Security Groups.
5+
6+
Example to List Security Group Rules
7+
8+
listOpts := security_group_rules.ListOpts{
9+
SecurityGroupID: "security-group-id",
10+
}
11+
12+
allPages, err := security_group_rules.List(networkClient, listOpts).AllPages()
13+
if err != nil {
14+
panic(err)
15+
}
16+
17+
allRules, err := security_group_rules.ExtractSecurityGroupRules(allPages)
18+
if err != nil {
19+
panic(err)
20+
}
21+
22+
for _, rule := range allRules {
23+
fmt.Printf("%+v\n", rule)
24+
}
25+
26+
Example to Create a Security Group Rule
27+
28+
createOpts := security_group_rules.CreateOpts{
29+
Direction: "ingress",
30+
SecurityGroupID: "security-group-id",
31+
Ethertype: "IPv4",
32+
Protocol: "tcp",
33+
PortRangeMin: &[]int{22}[0],
34+
PortRangeMax: &[]int{22}[0],
35+
RemoteIPPrefix: &[]string{"0.0.0.0/0"}[0],
36+
}
37+
38+
rule, err := security_group_rules.Create(networkClient, createOpts).Extract()
39+
if err != nil {
40+
panic(err)
41+
}
42+
43+
Example to Delete a Security Group Rule
44+
45+
ruleID := "rule-id"
46+
err := security_group_rules.Delete(networkClient, ruleID).ExtractErr()
47+
if err != nil {
48+
panic(err)
49+
}
50+
*/
51+
package security_group_rules
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package security_group_rules
2+
3+
import (
4+
"github.com/nttcom/eclcloud/v3"
5+
"github.com/nttcom/eclcloud/v3/pagination"
6+
)
7+
8+
// ListOptsBuilder allows extensions to add additional parameters to the
9+
// List request.
10+
type ListOptsBuilder interface {
11+
ToSecurityGroupRuleListQuery() (string, error)
12+
}
13+
14+
// ListOpts allows the filtering and sorting of paginated collections through
15+
// the API. Filtering is achieved by passing in struct field values that map to
16+
// the security group rule attributes you want to see returned.
17+
type ListOpts struct {
18+
Description string `q:"description"`
19+
Direction string `q:"direction"`
20+
Ethertype string `q:"ethertype"`
21+
ID string `q:"id"`
22+
PortRangeMax int `q:"port_range_max"`
23+
PortRangeMin int `q:"port_range_min"`
24+
Protocol string `q:"protocol"`
25+
RemoteGroupID string `q:"remote_group_id"`
26+
RemoteIPPrefix string `q:"remote_ip_prefix"`
27+
SecurityGroupID string `q:"security_group_id"`
28+
TenantID string `q:"tenant_id"`
29+
}
30+
31+
// ToSecurityGroupRuleListQuery formats a ListOpts into a query string.
32+
func (opts ListOpts) ToSecurityGroupRuleListQuery() (string, error) {
33+
q, err := eclcloud.BuildQueryString(opts)
34+
return q.String(), err
35+
}
36+
37+
// List returns a Pager which allows you to iterate over a collection of
38+
// security group rules. It accepts a ListOpts struct, which allows you to filter
39+
// and sort the returned collection for greater efficiency.
40+
func List(c *eclcloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
41+
url := listURL(c)
42+
if opts != nil {
43+
query, err := opts.ToSecurityGroupRuleListQuery()
44+
if err != nil {
45+
return pagination.Pager{Err: err}
46+
}
47+
url += query
48+
}
49+
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
50+
return SecurityGroupRulePage{pagination.LinkedPageBase{PageResult: r}}
51+
})
52+
}
53+
54+
// Get retrieves a specific security group rule based on its unique ID.
55+
func Get(c *eclcloud.ServiceClient, id string) (r GetResult) {
56+
_, r.Err = c.Get(getURL(c, id), &r.Body, nil)
57+
return
58+
}
59+
60+
// CreateOptsBuilder allows extensions to add additional parameters to the
61+
// Create request.
62+
type CreateOptsBuilder interface {
63+
ToSecurityGroupRuleCreateMap() (map[string]interface{}, error)
64+
}
65+
66+
// CreateOpts represents options used to create a security group rule.
67+
type CreateOpts struct {
68+
Description string `json:"description,omitempty"`
69+
Direction string `json:"direction" required:"true"`
70+
Ethertype string `json:"ethertype,omitempty"`
71+
PortRangeMax *int `json:"port_range_max,omitempty"`
72+
PortRangeMin *int `json:"port_range_min,omitempty"`
73+
Protocol string `json:"protocol,omitempty"`
74+
RemoteGroupID *string `json:"remote_group_id,omitempty"`
75+
RemoteIPPrefix *string `json:"remote_ip_prefix,omitempty"`
76+
SecurityGroupID string `json:"security_group_id" required:"true"`
77+
TenantID string `json:"tenant_id,omitempty"`
78+
}
79+
80+
// ToSecurityGroupRuleCreateMap builds a request body from CreateOpts.
81+
func (opts CreateOpts) ToSecurityGroupRuleCreateMap() (map[string]interface{}, error) {
82+
return eclcloud.BuildRequestBody(opts, "security_group_rule")
83+
}
84+
85+
// Create accepts a CreateOpts struct and creates a new security group rule.
86+
func Create(c *eclcloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
87+
b, err := opts.ToSecurityGroupRuleCreateMap()
88+
if err != nil {
89+
r.Err = err
90+
return
91+
}
92+
_, r.Err = c.Post(createURL(c), b, &r.Body, nil)
93+
return
94+
}
95+
96+
// Delete accepts a unique ID and deletes the security group rule associated with it.
97+
func Delete(c *eclcloud.ServiceClient, id string) (r DeleteResult) {
98+
_, r.Err = c.Delete(deleteURL(c, id), nil)
99+
return
100+
}

0 commit comments

Comments
 (0)