diff --git a/internal/dsf/ip.go b/internal/dsf/ip.go new file mode 100644 index 0000000000..f5f349c0b7 --- /dev/null +++ b/internal/dsf/ip.go @@ -0,0 +1,22 @@ +package dsf + +import ( + "net" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DiffSuppressFuncStandaloneIPandCIDR(_, oldValue, newValue string, _ *schema.ResourceData) bool { + parseIPOrCIDR := func(s string) net.IP { + if ip, _, err := net.ParseCIDR(s); err == nil { + return ip + } + + return net.ParseIP(s) + } + + oldIP := parseIPOrCIDR(oldValue) + newIP := parseIPOrCIDR(newValue) + + return oldIP != nil && newIP != nil && oldIP.Equal(newIP) +} diff --git a/internal/dsf/ip_test.go b/internal/dsf/ip_test.go new file mode 100644 index 0000000000..6d22adc17c --- /dev/null +++ b/internal/dsf/ip_test.go @@ -0,0 +1,86 @@ +package dsf_test + +import ( + "testing" + + "github.com/scaleway/terraform-provider-scaleway/v2/internal/dsf" +) + +func TestDiffSuppress_IPAMIP(t *testing.T) { + tests := []struct { + name string + oldValue string + newValue string + want bool + }{ + { + name: "IP == IP", + oldValue: "172.16.32.7", + newValue: "172.16.32.7", + want: true, + }, + { + name: "IP == IP/CIDR same host", + oldValue: "172.16.32.7/22", + newValue: "172.16.32.7", + want: true, + }, + { + name: "IP/CIDR == IP same host (reversed)", + oldValue: "172.16.32.7", + newValue: "172.16.32.7/22", + want: true, + }, + { + name: "Different host within same CIDR is NOT suppressed", + oldValue: "172.16.32.7/22", + newValue: "172.16.32.8", + want: false, + }, + { + name: "Different host (plain IPs) is NOT suppressed", + oldValue: "172.16.32.7", + newValue: "172.16.32.8", + want: false, + }, + { + name: "Equal but with /32 single-host CIDR", + oldValue: "10.0.0.1/32", + newValue: "10.0.0.1", + want: true, + }, + { + name: "Broader CIDR vs network address should NOT suppress", + oldValue: "10.0.0.1/24", + newValue: "10.0.0.0", + want: false, + }, + { + name: "Invalid old value -> not suppressed", + oldValue: "not-an-ip", + newValue: "10.0.0.1", + want: false, + }, + { + name: "Invalid new value -> not suppressed", + oldValue: "10.0.0.1", + newValue: "bad/32", + want: false, + }, + { + name: "Both invalid -> not suppressed", + oldValue: "nope", + newValue: "nope2", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := dsf.DiffSuppressFuncStandaloneIPandCIDR("", tt.oldValue, tt.newValue, nil) + if got != tt.want { + t.Fatalf("diffSuppress(%q, %q) = %v, want %v", tt.oldValue, tt.newValue, got, tt.want) + } + }) + } +} diff --git a/internal/services/ipam/helpers.go b/internal/services/ipam/helpers.go index 8708acbb8b..b7c10c5061 100644 --- a/internal/services/ipam/helpers.go +++ b/internal/services/ipam/helpers.go @@ -3,7 +3,6 @@ package ipam import ( "context" "fmt" - "net" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -43,32 +42,6 @@ func NewAPIWithRegionAndID(m any, id string) (*ipam.API, scw.Region, string, err return ipamAPI, region, ID, err } -func diffSuppressFuncStandaloneIPandCIDR(_, oldValue, newValue string, _ *schema.ResourceData) bool { - oldIP, oldNet, errOld := net.ParseCIDR(oldValue) - if errOld != nil { - oldIP = net.ParseIP(oldValue) - } - - newIP, newNet, errNew := net.ParseCIDR(newValue) - if errNew != nil { - newIP = net.ParseIP(newValue) - } - - if oldIP != nil && newIP != nil && oldIP.Equal(newIP) { - return true - } - - if oldNet != nil && newIP != nil && oldNet.Contains(newIP) { - return true - } - - if newNet != nil && oldIP != nil && newNet.Contains(oldIP) { - return true - } - - return false -} - type GetResourcePrivateIPsOptions struct { ResourceType *ipam.ResourceType ResourceID *string diff --git a/internal/services/ipam/ip.go b/internal/services/ipam/ip.go index af19925ada..b04f457cc0 100644 --- a/internal/services/ipam/ip.go +++ b/internal/services/ipam/ip.go @@ -38,7 +38,7 @@ func ResourceIP() *schema.Resource { ForceNew: true, Description: "Request a specific IP in the requested source pool", ValidateFunc: validation.IsIPAddress, - DiffSuppressFunc: diffSuppressFuncStandaloneIPandCIDR, + DiffSuppressFunc: dsf.DiffSuppressFuncStandaloneIPandCIDR, }, "source": { Type: schema.TypeList,