Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 77 additions & 35 deletions internal/services/instance/security_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
instanceSDK "github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/identity"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/zonal"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
Expand All @@ -31,6 +32,7 @@ func ResourceSecurityGroup() *schema.Resource {
Default: schema.DefaultTimeout(defaultInstanceSecurityGroupTimeout),
},
SchemaFunc: securityGroupSchema,
Identity: identity.DefaultZonal(),
}
}

Expand Down Expand Up @@ -135,15 +137,43 @@ func ResourceInstanceSecurityGroupCreate(ctx context.Context, d *schema.Resource
return diag.FromErr(err)
}

d.SetId(zonal.NewIDString(zone, res.SecurityGroup.ID))
err = identity.SetZonalIdentity(d, res.SecurityGroup.Zone, res.SecurityGroup.ID)
if err != nil {
return diag.FromErr(err)
}

if d.Get("external_rules").(bool) {
return ResourceInstanceSecurityGroupRead(ctx, d, m)
return setSecurityGroupState(ctx, instanceAPI, d, res.SecurityGroup)
}
// We call update instead of read as it will take care of creating rules.
return ResourceInstanceSecurityGroupUpdate(ctx, d, m)
}

func setSecurityGroupState(ctx context.Context, instanceAPI *instanceSDK.API, d *schema.ResourceData, sg *instanceSDK.SecurityGroup) diag.Diagnostics {
_ = d.Set("zone", sg.Zone)
_ = d.Set("organization_id", sg.Organization)
_ = d.Set("project_id", sg.Project)
_ = d.Set("name", sg.Name)
_ = d.Set("stateful", sg.Stateful)
_ = d.Set("description", sg.Description)
_ = d.Set("inbound_default_policy", sg.InboundDefaultPolicy.String())
_ = d.Set("outbound_default_policy", sg.OutboundDefaultPolicy.String())
_ = d.Set("enable_default_security", sg.EnableDefaultSecurity)
_ = d.Set("tags", sg.Tags)

if !d.Get("external_rules").(bool) {
inboundRules, outboundRules, err := getSecurityGroupRules(ctx, instanceAPI, sg.Zone, sg.ID, d)
if err != nil {
return diag.FromErr(err)
}

_ = d.Set("inbound_rule", inboundRules)
_ = d.Set("outbound_rule", outboundRules)
}

return nil
}

func ResourceInstanceSecurityGroupRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
instanceAPI, zone, ID, err := NewAPIWithZoneAndID(m, d.Id())
if err != nil {
Expand All @@ -164,28 +194,12 @@ func ResourceInstanceSecurityGroupRead(ctx context.Context, d *schema.ResourceDa
return diag.FromErr(err)
}

_ = d.Set("zone", zone)
_ = d.Set("organization_id", res.SecurityGroup.Organization)
_ = d.Set("project_id", res.SecurityGroup.Project)
_ = d.Set("name", res.SecurityGroup.Name)
_ = d.Set("stateful", res.SecurityGroup.Stateful)
_ = d.Set("description", res.SecurityGroup.Description)
_ = d.Set("inbound_default_policy", res.SecurityGroup.InboundDefaultPolicy.String())
_ = d.Set("outbound_default_policy", res.SecurityGroup.OutboundDefaultPolicy.String())
_ = d.Set("enable_default_security", res.SecurityGroup.EnableDefaultSecurity)
_ = d.Set("tags", res.SecurityGroup.Tags)

if !d.Get("external_rules").(bool) {
inboundRules, outboundRules, err := getSecurityGroupRules(ctx, instanceAPI, zone, ID, d)
if err != nil {
return diag.FromErr(err)
}

_ = d.Set("inbound_rule", inboundRules)
_ = d.Set("outbound_rule", outboundRules)
err = identity.SetZonalIdentity(d, res.SecurityGroup.Zone, res.SecurityGroup.ID)
if err != nil {
return diag.FromErr(err)
}

return nil
return setSecurityGroupState(ctx, instanceAPI, d, res.SecurityGroup)
}

func getSecurityGroupRules(ctx context.Context, instanceAPI *instanceSDK.API, zone scw.Zone, securityGroupID string, d *schema.ResourceData) ([]any, []any, error) {
Expand Down Expand Up @@ -223,19 +237,28 @@ func getSecurityGroupRules(ctx context.Context, instanceAPI *instanceSDK.API, zo
for direction := range apiRules {
for index, apiRule := range apiRules[direction] {
if index < len(stateRules[direction]) {
stateRuleRaw := stateRules[direction][index].(map[string]any)

var ipFieldToSet string
if _, ok := stateRuleRaw["ip"]; ok {
ipFieldToSet = "ip"
} else if _, ok := stateRuleRaw["ip_range"]; ok {
ipFieldToSet = "ip_range"
}

stateRule, errGroup := securityGroupRuleExpand(stateRules[direction][index])
if errGroup != nil {
return nil, nil, errGroup
}

if ok, _ := SecurityGroupRuleEquals(stateRule, apiRule); !ok {
stateRules[direction][index], err = securityGroupRuleFlatten(apiRule)
stateRules[direction][index], err = securityGroupRuleFlatten(apiRule, ipFieldToSet)
if err != nil {
return nil, nil, err
}
}
} else {
rulesGroup, err := securityGroupRuleFlatten(apiRule)
rulesGroup, err := securityGroupRuleFlatten(apiRule, "")
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -303,7 +326,7 @@ func ResourceInstanceSecurityGroupUpdate(ctx context.Context, d *schema.Resource
updateReq.Name = types.ExpandStringPtr(d.Get("name"))
}

_, err = instanceAPI.UpdateSecurityGroup(updateReq, scw.WithContext(ctx))
res, err := instanceAPI.UpdateSecurityGroup(updateReq, scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}
Expand All @@ -315,7 +338,7 @@ func ResourceInstanceSecurityGroupUpdate(ctx context.Context, d *schema.Resource
}
}

return ResourceInstanceSecurityGroupRead(ctx, d, m)
return setSecurityGroupState(ctx, instanceAPI, d, res.SecurityGroup)
}

// updateSecurityGroupeRules handles updating SecurityGroupRules
Expand Down Expand Up @@ -479,7 +502,13 @@ func securityGroupRuleExpand(i any) (*instanceSDK.SecurityGroupRule, error) {
}

// securityGroupRuleFlatten transform an api rule to a state one.
func securityGroupRuleFlatten(rule *instanceSDK.SecurityGroupRule) (map[string]any, error) {
func securityGroupRuleFlatten(rule *instanceSDK.SecurityGroupRule, ipFieldToSet string) (map[string]any, error) {
res := map[string]any{
"protocol": rule.Protocol.String(),
"action": rule.Action.String(),
}

// Set port or port_range
portFrom, portTo := uint32(0), uint32(0)

if rule.DestPortFrom != nil {
Expand All @@ -490,16 +519,29 @@ func securityGroupRuleFlatten(rule *instanceSDK.SecurityGroupRule) (map[string]a
portTo = *rule.DestPortTo
}

ipnetRange, err := types.FlattenIPNet(rule.IPRange)
if err != nil {
return nil, err
if portFrom != 0 {
if portTo != 0 {
res["port_range"] = fmt.Sprintf("%d-%d", portFrom, portTo)
} else {
res["port"] = portFrom
}
}

res := map[string]any{
"protocol": rule.Protocol.String(),
"ip_range": ipnetRange,
"port_range": fmt.Sprintf("%d-%d", portFrom, portTo),
"action": rule.Action.String(),
// Set ip or ip_range
switch ipFieldToSet {
case "ip":
res["ip"] = rule.IPRange.IP.String()
case "ip_range":
res["ip_range"] = rule.IPRange.String()
case "":
// If we don't have access to the IP field that was set in the config (most likely in an import context),
// we always set 'ip_range' with the value from the API, and 'ip' if the value is not a range.
res["ip_range"] = rule.IPRange.String()
if one, _ := rule.IPRange.Mask.Size(); one == 32 {
res["ip"] = rule.IPRange.IP.String()
}
default:
return nil, fmt.Errorf("unknown IP field to set: %q", ipFieldToSet)
}

return res, nil
Expand Down
21 changes: 20 additions & 1 deletion internal/services/instance/security_group_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/datasource"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/verify"
)
Expand Down Expand Up @@ -41,6 +42,8 @@ func DataSourceInstanceSecurityGroupRead(ctx context.Context, d *schema.Resource
return diag.FromErr(err)
}

var securityGroup *instance.SecurityGroup

securityGroupID, ok := d.GetOk("security_group_id")
if !ok {
sgName := d.Get("name").(string)
Expand All @@ -63,12 +66,28 @@ func DataSourceInstanceSecurityGroupRead(ctx context.Context, d *schema.Resource
return diag.FromErr(err)
}

securityGroup = foundSG
securityGroupID = foundSG.ID
} else {
id, err := locality.ExtractUUID(securityGroupID.(string))
if err != nil {
return diag.FromErr(err)
}

res, err := instanceAPI.GetSecurityGroup(&instance.GetSecurityGroupRequest{
Zone: zone,
SecurityGroupID: id,
}, scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}

securityGroup = res.SecurityGroup
}

zonedID := datasource.NewZonedID(securityGroupID, zone)
d.SetId(zonedID)
_ = d.Set("security_group_id", zonedID)

return ResourceInstanceSecurityGroupRead(ctx, d, m)
return setSecurityGroupState(ctx, instanceAPI, d, securityGroup)
}
Loading
Loading