diff --git a/internal/meta/extractors.go b/internal/meta/extractors.go index 59734f77e..f32370554 100644 --- a/internal/meta/extractors.go +++ b/internal/meta/extractors.go @@ -2,7 +2,11 @@ package meta import ( "net/http" + "strconv" + "strings" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/scaleway/scaleway-sdk-go/scw" "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional" "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/zonal" @@ -96,3 +100,63 @@ func ExtractScwClient(m interface{}) *scw.Client { func ExtractHTTPClient(m interface{}) *http.Client { return m.(*Meta).HTTPClient() } + +func getKeyInRawConfigMap(rawConfig map[string]cty.Value, key string, ty cty.Type) (interface{}, bool) { + if key == "" { + return rawConfig, false + } + // We split the key into its elements + keys := strings.Split(key, ".") + + // We look at the first element's type + if value, ok := rawConfig[keys[0]]; ok { + switch { + case value.Type().IsListType(): + // If it's a list and the second element of the key is an index, we look for the value in the list at the given index + if index, err := strconv.Atoi(keys[1]); err == nil { + return getKeyInRawConfigMap(value.AsValueSlice()[index].AsValueMap(), strings.Join(keys[2:], ""), ty) + } + // If it's a list and the second element of the key is '#', we look for the value in the list's first element + return getKeyInRawConfigMap(value.AsValueSlice()[0].AsValueMap(), strings.Join(keys[2:], ""), ty) + + case value.Type().IsMapType(): + // If it's a map, we look for the value in the map + return getKeyInRawConfigMap(value.AsValueMap(), strings.Join(keys[1:], ""), ty) + + case value.Type().IsPrimitiveType(): + // If it's a primitive type (bool, string, number), we convert the value to the expected type given as parameter before returning it + switch ty { + case cty.String: + if value.IsNull() { + return nil, false + } + return value.AsString(), true + case cty.Bool: + if value.IsNull() { + return false, false + } + if value.True() { + return true, true + } + return false, true + case cty.Number: + if value.IsNull() { + return nil, false + } + valueInt, _ := value.AsBigFloat().Int64() + return valueInt, true + } + } + } + return nil, false +} + +// GetRawConfigForKey returns the value for a specific key in the user's raw configuration, which can be useful on resources' update +// The value for the key to look for must be a primitive type (bool, string, number) and the expected type of the value should be passed as the ty parameter +func GetRawConfigForKey(d *schema.ResourceData, key string, ty cty.Type) (interface{}, bool) { + rawConfig := d.GetRawConfig() + if rawConfig.IsNull() { + return nil, false + } + return getKeyInRawConfigMap(rawConfig.AsValueMap(), key, ty) +} diff --git a/internal/services/lb/backend.go b/internal/services/lb/backend.go index 79623d22f..aeeb9942f 100644 --- a/internal/services/lb/backend.go +++ b/internal/services/lb/backend.go @@ -14,6 +14,7 @@ import ( "github.com/scaleway/terraform-provider-scaleway/v2/internal/dsf" "github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors" "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/zonal" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/meta" "github.com/scaleway/terraform-provider-scaleway/v2/internal/types" "github.com/scaleway/terraform-provider-scaleway/v2/internal/verify" ) @@ -619,7 +620,7 @@ func resourceLbBackendUpdate(ctx context.Context, d *schema.ResourceData, m inte updateHCRequest.TCPConfig = expandLbHCTCP(d.Get("health_check_tcp")) } - _, healthCheckPortSetByUser := getRawConfigForKey(d, "health_check_port", cty.Number) + _, healthCheckPortSetByUser := meta.GetRawConfigForKey(d, "health_check_port", cty.Number) if d.HasChange("forward_port") && !healthCheckPortSetByUser { updateHCRequest.Port = int32(d.Get("forward_port").(int)) } diff --git a/internal/services/lb/helpers_lb.go b/internal/services/lb/helpers_lb.go index 795f9c3fe..222ad2562 100644 --- a/internal/services/lb/helpers_lb.go +++ b/internal/services/lb/helpers_lb.go @@ -7,7 +7,6 @@ import ( "fmt" "net" "reflect" - "strconv" "strings" "time" @@ -178,66 +177,6 @@ func normalizeIPSubnet(ip string) string { return ip } -// getRawConfigForKey returns the value for a specific key in the user's raw configuration, which can be useful on resources' update -// The value for the key to look for must be a primitive type (bool, string, number) and the expected type of the value should be passed as the ty parameter -func getRawConfigForKey(d *schema.ResourceData, key string, ty cty.Type) (interface{}, bool) { - rawConfig := d.GetRawConfig() - if rawConfig.IsNull() { - return nil, false - } - return GetKeyInRawConfigMap(rawConfig.AsValueMap(), key, ty) -} - -func GetKeyInRawConfigMap(rawConfig map[string]cty.Value, key string, ty cty.Type) (interface{}, bool) { - if key == "" { - return rawConfig, false - } - // We split the key into its elements - keys := strings.Split(key, ".") - - // We look at the first element's type - if value, ok := rawConfig[keys[0]]; ok { - switch { - case value.Type().IsListType(): - // If it's a list and the second element of the key is an index, we look for the value in the list at the given index - if index, err := strconv.Atoi(keys[1]); err == nil { - return GetKeyInRawConfigMap(value.AsValueSlice()[index].AsValueMap(), strings.Join(keys[2:], ""), ty) - } - // If it's a list and the second element of the key is '#', we look for the value in the list's first element - return GetKeyInRawConfigMap(value.AsValueSlice()[0].AsValueMap(), strings.Join(keys[2:], ""), ty) - - case value.Type().IsMapType(): - // If it's a map, we look for the value in the map - return GetKeyInRawConfigMap(value.AsValueMap(), strings.Join(keys[1:], ""), ty) - - case value.Type().IsPrimitiveType(): - // If it's a primitive type (bool, string, number), we convert the value to the expected type given as parameter before returning it - switch ty { - case cty.String: - if value.IsNull() { - return nil, false - } - return value.AsString(), true - case cty.Bool: - if value.IsNull() { - return false, false - } - if value.True() { - return true, true - } - return false, true - case cty.Number: - if value.IsNull() { - return nil, false - } - valueInt, _ := value.AsBigFloat().Int64() - return valueInt, true - } - } - } - return nil, false -} - func customizeDiffLBIPIDs(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { oldIPIDs, newIPIDs := diff.GetChange("ip_ids") oldIPIDsSet := make(map[string]struct{}) diff --git a/internal/services/rdb/helpers.go b/internal/services/rdb/helpers.go index d29fceb9e..3eddf8411 100644 --- a/internal/services/rdb/helpers.go +++ b/internal/services/rdb/helpers.go @@ -94,19 +94,11 @@ func getIPConfigCreate(d *schema.ResourceData, ipFieldName string) (ipamConfig * // getIPConfigUpdate forces the provider to read the user's config instead of checking the state, because "enable_ipam" is not readable from the API func getIPConfigUpdate(d *schema.ResourceData, ipFieldName string) (ipamConfig *bool, staticConfig *string) { - if rawConfig := d.GetRawConfig(); !rawConfig.IsNull() { - pnRawConfig := rawConfig.AsValueMap()["private_network"].AsValueSlice()[0].AsValueMap() - if !pnRawConfig["enable_ipam"].IsNull() { - if pnRawConfig["enable_ipam"].False() { - ipamConfig = scw.BoolPtr(false) - } else { - ipamConfig = scw.BoolPtr(true) - } - } - if !pnRawConfig[ipFieldName].IsNull() { - value := pnRawConfig[ipFieldName].AsString() - staticConfig = &value - } + if ipamConfigI, _ := meta.GetRawConfigForKey(d, "private_network.#.enable_ipam", cty.Bool); ipamConfigI != nil { + ipamConfig = types.ExpandBoolPtr(ipamConfigI) + } + if staticConfigI, _ := meta.GetRawConfigForKey(d, "private_network.#."+ipFieldName, cty.String); staticConfigI != nil { + staticConfig = types.ExpandStringPtr(staticConfigI) } return ipamConfig, staticConfig }