-
Notifications
You must be signed in to change notification settings - Fork 610
Add support for default security group rules #3978
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| //go:build acceptance || networking || security | ||
|
|
||
| package extensions | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
|
|
||
| "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" | ||
| networking "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" | ||
| "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" | ||
| "github.com/gophercloud/gophercloud/v2/internal/ptr" | ||
| "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/defaultrules" | ||
| "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/rules" | ||
| th "github.com/gophercloud/gophercloud/v2/testhelper" | ||
| ) | ||
|
|
||
| func TestDefaultSecurityGroupRulesCreateGetListDelete(t *testing.T) { | ||
| clients.RequireAdmin(t) | ||
|
|
||
| client, err := clients.NewNetworkV2Client() | ||
| th.AssertNoErr(t, err) | ||
|
|
||
| networking.RequireNeutronExtension(t, client, "security-groups-default-rules") | ||
|
|
||
| description := "Default rule description" | ||
| fromPort := tools.RandomInt(10080, 10089) | ||
| toPort := tools.RandomInt(10090, 10099) | ||
|
|
||
| createOpts := defaultrules.CreateOpts{ | ||
| Description: description, | ||
| Direction: rules.DirIngress, | ||
| EtherType: rules.EtherType4, | ||
| PortRangeMin: ptr.To(fromPort), | ||
| PortRangeMax: ptr.To(toPort), | ||
| Protocol: rules.ProtocolTCP, | ||
| RemoteGroupID: "PARENT", | ||
| UsedInDefaultSG: ptr.To(true), | ||
| UsedInNonDefaultSG: ptr.To(false), | ||
| } | ||
|
|
||
| rule, err := defaultrules.Create(context.TODO(), client, createOpts).Extract() | ||
| th.AssertNoErr(t, err) | ||
| defer func() { | ||
| err := defaultrules.Delete(context.TODO(), client, rule.ID).ExtractErr() | ||
| th.AssertNoErr(t, err) | ||
| }() | ||
|
|
||
| tools.PrintResource(t, rule) | ||
|
|
||
| th.AssertEquals(t, description, rule.Description) | ||
| th.AssertEquals(t, "ingress", rule.Direction) | ||
| th.AssertEquals(t, "IPv4", rule.EtherType) | ||
| th.AssertEquals(t, fromPort, rule.PortRangeMin) | ||
| th.AssertEquals(t, toPort, rule.PortRangeMax) | ||
| th.AssertEquals(t, string(rules.ProtocolTCP), rule.Protocol) | ||
| th.AssertEquals(t, "PARENT", rule.RemoteGroupID) | ||
| th.AssertEquals(t, true, rule.UsedInDefaultSG) | ||
| th.AssertEquals(t, false, rule.UsedInNonDefaultSG) | ||
|
|
||
| getRule, err := defaultrules.Get(context.TODO(), client, rule.ID).Extract() | ||
| th.AssertNoErr(t, err) | ||
| th.AssertEquals(t, rule.ID, getRule.ID) | ||
| th.AssertEquals(t, description, getRule.Description) | ||
|
|
||
| listOpts := defaultrules.ListOpts{ | ||
| Protocol: string(rules.ProtocolTCP), | ||
| } | ||
| allPages, err := defaultrules.List(client, listOpts).AllPages(context.TODO()) | ||
| th.AssertNoErr(t, err) | ||
|
|
||
| allDefaultRules, err := defaultrules.ExtractDefaultRules(allPages) | ||
| th.AssertNoErr(t, err) | ||
|
|
||
| var found bool | ||
| for _, r := range allDefaultRules { | ||
| if r.ID == rule.ID { | ||
| found = true | ||
| break | ||
| } | ||
| } | ||
| th.AssertTrue(t, found) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| /* | ||
| Package defaultrules provides information and interaction with Security Group | ||
| Default Rules for the OpenStack Networking service. These rules are templates | ||
| used by Neutron to populate the rules of newly created security groups; they | ||
| require the "security-groups-default-rules" API extension. | ||
|
|
||
| Example to List Default Security Group Rules | ||
|
|
||
| listOpts := defaultrules.ListOpts{ | ||
| Protocol: "tcp", | ||
| } | ||
|
|
||
| allPages, err := defaultrules.List(networkClient, listOpts).AllPages(context.TODO()) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| allDefaultRules, err := defaultrules.ExtractDefaultRules(allPages) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| for _, rule := range allDefaultRules { | ||
| fmt.Printf("%+v\n", rule) | ||
| } | ||
|
|
||
| Example to Create a Default Security Group Rule | ||
|
|
||
| createOpts := defaultrules.CreateOpts{ | ||
| Direction: rules.DirIngress, | ||
| EtherType: rules.EtherType4, | ||
| Protocol: rules.ProtocolTCP, | ||
| PortRangeMin: gophercloud.IntToPointer(443), | ||
| PortRangeMax: gophercloud.IntToPointer(443), | ||
| RemoteGroupID: "PARENT", | ||
| } | ||
|
|
||
| rule, err := defaultrules.Create(context.TODO(), networkClient, createOpts).Extract() | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| Example to Delete a Default Security Group Rule | ||
|
|
||
| ruleID := "37d94f8a-d136-465c-ae46-144f0d8ef141" | ||
| err := defaultrules.Delete(context.TODO(), networkClient, ruleID).ExtractErr() | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| */ | ||
| package defaultrules |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| package defaultrules | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| "github.com/gophercloud/gophercloud/v2" | ||
| "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/rules" | ||
| "github.com/gophercloud/gophercloud/v2/pagination" | ||
| ) | ||
|
|
||
| // ListOptsBuilder allows extensions to add additional parameters to the List | ||
| // request. | ||
| type ListOptsBuilder interface { | ||
| ToDefaultSecGroupRuleListQuery() (string, error) | ||
| } | ||
|
|
||
| // ListOpts allows the filtering and sorting of paginated collections through | ||
| // the API. Filtering is achieved by passing in struct field values that map to | ||
| // the default security group rule attributes you want to see returned. SortKey | ||
|
Comment on lines
+18
to
+19
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sentence about filtering confused me a little. Wouldn't be the idea of this something like: we pass field values for the attributes that we would like to use as filters.? |
||
| // allows you to sort by a particular network attribute. SortDir sets the | ||
| // direction, and is either `asc' or `desc'. Marker and Limit are used for | ||
| // pagination. | ||
| type ListOpts struct { | ||
| ID string `q:"id"` | ||
| Description string `q:"description"` | ||
| Direction string `q:"direction"` | ||
| EtherType string `q:"ethertype"` | ||
| PortRangeMax int `q:"port_range_max"` | ||
| PortRangeMin int `q:"port_range_min"` | ||
| Protocol string `q:"protocol"` | ||
| RemoteAddressGroupID string `q:"remote_address_group_id"` | ||
| RemoteGroupID string `q:"remote_group_id"` | ||
| RemoteIPPrefix string `q:"remote_ip_prefix"` | ||
| UsedInDefaultSG *bool `q:"used_in_default_sg"` | ||
| UsedInNonDefaultSG *bool `q:"used_in_non_default_sg"` | ||
|
Comment on lines
+34
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| Limit int `q:"limit"` | ||
| Marker string `q:"marker"` | ||
| SortKey string `q:"sort_key"` | ||
| SortDir string `q:"sort_dir"` | ||
| } | ||
|
|
||
| // ToDefaultSecGroupRuleListQuery formats a ListOpts into a query string. | ||
| func (opts ListOpts) ToDefaultSecGroupRuleListQuery() (string, error) { | ||
| q, err := gophercloud.BuildQueryString(&opts) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| return q.String(), nil | ||
| } | ||
|
|
||
| // List returns a Pager which allows you to iterate over a collection of | ||
| // default security group rules. It accepts a ListOpts struct, which allows you | ||
| // to filter and sort the returned collection for greater efficiency. | ||
| func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { | ||
| url := rootURL(c) | ||
| if opts != nil { | ||
| query, err := opts.ToDefaultSecGroupRuleListQuery() | ||
| if err != nil { | ||
| return pagination.Pager{Err: err} | ||
| } | ||
| url += query | ||
| } | ||
| return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { | ||
| return DefaultSecGroupRulePage{pagination.LinkedPageBase{PageResult: r}} | ||
| }) | ||
| } | ||
|
|
||
| // CreateOptsBuilder allows extensions to add additional parameters to the | ||
| // Create request. | ||
| type CreateOptsBuilder interface { | ||
| ToDefaultSecGroupRuleCreateMap() (map[string]any, error) | ||
| } | ||
|
|
||
| // CreateOpts contains all the values needed to create a new default security | ||
| // group rule. Rules created through this API are templates used by Neutron to | ||
| // populate the rules of newly created security groups; they are not applied to | ||
| // any existing security group. | ||
| type CreateOpts struct { | ||
| // Must be either "ingress" or "egress": the direction in which the | ||
| // security group rule will be applied. | ||
| Direction rules.RuleDirection `json:"direction" required:"true"` | ||
|
|
||
| // String description of each rule, optional. | ||
| Description string `json:"description,omitempty"` | ||
|
|
||
| // Must be "IPv4" or "IPv6". Defaults to "IPv4" when not set. | ||
| EtherType rules.RuleEtherType `json:"ethertype,omitempty"` | ||
|
|
||
| // The maximum port number in the range that will be matched by the | ||
| // security group rule. The PortRangeMin attribute constrains the | ||
| // PortRangeMax attribute. If the protocol is ICMP, this value must be an | ||
| // ICMP code. | ||
| PortRangeMax *int `json:"port_range_max,omitempty"` | ||
|
|
||
| // The minimum port number in the range that will be matched by the | ||
| // security group rule. If the protocol is TCP or UDP, this value must be | ||
| // less than or equal to the value of the PortRangeMax attribute. If the | ||
| // protocol is ICMP, this value must be an ICMP type. | ||
| PortRangeMin *int `json:"port_range_min,omitempty"` | ||
|
|
||
| // The protocol that will be matched by the security group rule. Valid | ||
| // values are "tcp", "udp", "icmp" or an empty string. | ||
| Protocol rules.RuleProtocol `json:"protocol,omitempty"` | ||
|
|
||
| // The remote address group ID to be associated with this security group | ||
| // rule. You can specify either RemoteAddressGroupID, RemoteGroupID, or | ||
| // RemoteIPPrefix. | ||
| RemoteAddressGroupID string `json:"remote_address_group_id,omitempty"` | ||
|
|
||
| // The remote group ID to be associated with this security group rule. You | ||
| // can specify either RemoteAddressGroupID, RemoteGroupID or | ||
| // RemoteIPPrefix. The special value "PARENT" can be used to reference the | ||
| // security group the rule will belong to once created from this template. | ||
| RemoteGroupID string `json:"remote_group_id,omitempty"` | ||
|
|
||
| // The remote IP prefix to be associated with this security group rule. | ||
| // You can specify either RemoteAddressGroupID, RemoteGroupID or | ||
| // RemoteIPPrefix. This attribute matches the specified IP prefix as the | ||
| // source IP address of the IP packet. | ||
| RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"` | ||
|
|
||
| // Whether this rule template should be used in the default security group | ||
| // created automatically for each new project. Defaults to false. | ||
| UsedInDefaultSG *bool `json:"used_in_default_sg,omitempty"` | ||
|
|
||
| // Whether this rule template should be used in security groups created by | ||
| // users, other than the project default security group. Defaults to true. | ||
| UsedInNonDefaultSG *bool `json:"used_in_non_default_sg,omitempty"` | ||
| } | ||
|
|
||
| // ToDefaultSecGroupRuleCreateMap builds a request body from CreateOpts. | ||
| func (opts CreateOpts) ToDefaultSecGroupRuleCreateMap() (map[string]any, error) { | ||
| return gophercloud.BuildRequestBody(opts, "default_security_group_rule") | ||
| } | ||
|
|
||
| // Create is an operation which adds a new default security group rule | ||
| // template that will be used by Neutron when creating the rules of new | ||
| // security groups. | ||
| func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { | ||
| b, err := opts.ToDefaultSecGroupRuleCreateMap() | ||
| if err != nil { | ||
| r.Err = err | ||
| return | ||
| } | ||
| resp, err := c.Post(ctx, rootURL(c), b, &r.Body, nil) | ||
| _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) | ||
| return | ||
| } | ||
|
|
||
| // Get retrieves a particular default security group rule based on its unique | ||
| // ID. | ||
| func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetResult) { | ||
| resp, err := c.Get(ctx, resourceURL(c, id), &r.Body, nil) | ||
| _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) | ||
| return | ||
| } | ||
|
|
||
| // Delete will permanently delete a particular default security group rule | ||
| // based on its unique ID. Existing security groups are not affected. | ||
| func Delete(ctx context.Context, c *gophercloud.ServiceClient, id string) (r DeleteResult) { | ||
| resp, err := c.Delete(ctx, resourceURL(c, id), nil) | ||
| _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) | ||
| return | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We usually do a GET for the created resource and then compare the returned value with the expected, instead of comparing with the result of a creation operation. Some APIs return different values for these operations and comparing with the GET result shows that in a N + 1 operation the resource state is as expected.
So IMO, you might want to move these checks after the
defaultrules.Getand then compare with thegetRuleSG.