in cluster-autoscaler/cloudprovider/exoscale/internal/github.com/exoscale/egoscale/v2/security_group_rule.go [91:229]
func (c *Client) CreateSecurityGroupRule(
ctx context.Context,
zone string,
securityGroup *SecurityGroup,
rule *SecurityGroupRule,
) (*SecurityGroupRule, error) {
if err := validateOperationParams(securityGroup, "update"); err != nil {
return nil, err
}
if err := validateOperationParams(rule, "create"); err != nil {
return nil, err
}
var icmp *struct {
Code *int64 `json:"code,omitempty"`
Type *int64 `json:"type,omitempty"`
}
if strings.HasPrefix(*rule.Protocol, "icmp") {
icmp = &struct {
Code *int64 `json:"code,omitempty"`
Type *int64 `json:"type,omitempty"`
}{
Code: rule.ICMPCode,
Type: rule.ICMPType,
}
}
// The API doesn't return the Security Group rule created directly, so in order to
// return a *SecurityGroupRule corresponding to the new rule we have to manually
// compare the list of rules in the SG before and after the rule creation, and
// identify the rule that wasn't there before.
// Note: in case of multiple rules creation in parallel this technique is subject
// to race condition as we could return an unrelated rule. To prevent this, we
// also compare the properties of the new rule to the ones specified in the input
// rule parameter.
sgCurrent, err := c.GetSecurityGroup(ctx, zone, *securityGroup.ID)
if err != nil {
return nil, err
}
currentRules := make(map[string]struct{})
for _, r := range sgCurrent.Rules {
currentRules[*r.ID] = struct{}{}
}
resp, err := c.AddRuleToSecurityGroupWithResponse(
apiv2.WithZone(ctx, zone),
*securityGroup.ID,
oapi.AddRuleToSecurityGroupJSONRequestBody{
Description: rule.Description,
EndPort: func() (v *int64) {
if rule.EndPort != nil {
port := int64(*rule.EndPort)
v = &port
}
return
}(),
FlowDirection: oapi.AddRuleToSecurityGroupJSONBodyFlowDirection(*rule.FlowDirection),
Icmp: icmp,
Network: func() (v *string) {
if rule.Network != nil {
ip := rule.Network.String()
v = &ip
}
return
}(),
Protocol: oapi.AddRuleToSecurityGroupJSONBodyProtocol(*rule.Protocol),
SecurityGroup: func() (v *oapi.SecurityGroupResource) {
if rule.SecurityGroupID != nil {
v = &oapi.SecurityGroupResource{Id: *rule.SecurityGroupID}
}
return
}(),
StartPort: func() (v *int64) {
if rule.StartPort != nil {
port := int64(*rule.StartPort)
v = &port
}
return
}(),
})
if err != nil {
return nil, err
}
res, err := oapi.NewPoller().
WithTimeout(c.timeout).
WithInterval(c.pollInterval).
Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id))
if err != nil {
return nil, err
}
sgUpdated, err := c.GetSecurityGroup(ctx, zone, *res.(*oapi.Reference).Id)
if err != nil {
return nil, err
}
// Look for an unknown rule which properties match the one we've just created.
for _, r := range sgUpdated.Rules {
if _, ok := currentRules[*r.ID]; !ok {
if *r.FlowDirection == *rule.FlowDirection && *r.Protocol == *rule.Protocol {
if rule.Description != nil && r.Description != nil && *r.Description != *rule.Description {
continue
}
if rule.StartPort != nil && r.StartPort != nil && *r.StartPort != *rule.StartPort {
continue
}
if rule.EndPort != nil && r.EndPort != nil && *r.EndPort != *rule.EndPort {
continue
}
if rule.Network != nil && r.Network != nil && r.Network.String() != rule.Network.String() {
continue
}
if rule.SecurityGroupID != nil && r.SecurityGroupID != nil &&
*r.SecurityGroupID != *rule.SecurityGroupID {
continue
}
if rule.ICMPType != nil && r.ICMPType != nil && *r.ICMPType != *rule.ICMPType {
continue
}
if rule.ICMPCode != nil && r.ICMPCode != nil && *r.ICMPCode != *rule.ICMPCode {
continue
}
return r, nil
}
}
}
return nil, errors.New("unable to identify the rule created")
}