From bf29ca89d99fe3b9a1ad411750230f769ab10b2e Mon Sep 17 00:00:00 2001 From: Murad Biashimov Date: Mon, 13 May 2024 09:32:31 +0200 Subject: [PATCH] fix: convert ip_filter values into the target type (#1721) --- CHANGELOG.md | 2 + .../userconfig/converters/converters.go | 5 +- .../userconfig/converters/utils.go | 68 ++++++++++++++++++- .../userconfig/converters/utils_test.go | 58 +++++++++++++++- 4 files changed, 129 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c18178e48..59ab9de16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ nav_order: 1 ## [MAJOR.MINOR.PATCH] - YYYY-MM-DD +- Fix `ip_filter`, `ip_filter_string`, and `ip_filter_object` crash when receive an unexpected type + ## [4.17.0] - 2024-05-07 - Fix `aiven_kafka_connector` fails to create resource with 201 error diff --git a/internal/sdkprovider/userconfig/converters/converters.go b/internal/sdkprovider/userconfig/converters/converters.go index 96c07bfde..f5eaa0470 100644 --- a/internal/sdkprovider/userconfig/converters/converters.go +++ b/internal/sdkprovider/userconfig/converters/converters.go @@ -363,7 +363,10 @@ func flatten(kind userConfigType, name string, d *schema.ResourceData, dto map[s prefix := fmt.Sprintf("%s.0.", key) // Renames ip_filter to ip_filter_object - renameAliasesToTfo(kind, name, dto, d) + err := renameAliasesToTfo(kind, name, dto, d) + if err != nil { + return err + } // Copies "create only" fields from the original config. // Like admin_password, that is received only on POST request when service is created. diff --git a/internal/sdkprovider/userconfig/converters/utils.go b/internal/sdkprovider/userconfig/converters/utils.go index 416505a9c..876cad42d 100644 --- a/internal/sdkprovider/userconfig/converters/utils.go +++ b/internal/sdkprovider/userconfig/converters/utils.go @@ -1,6 +1,7 @@ package converters import ( + "encoding/json" "fmt" "reflect" "strings" @@ -37,15 +38,17 @@ type resourceData interface { // renameAliasesToTfo renames aliases to TF object // Must sort keys to rename from bottom to top. // Otherwise, might not find the deepest key if parent key is renamed -func renameAliasesToTfo(kind userConfigType, name string, dto map[string]any, d resourceData) { +func renameAliasesToTfo(kind userConfigType, name string, dto map[string]any, d resourceData) error { + prefix := userConfigKey(kind, name) + ".0." m := getFieldMapping(kind, name) + for _, to := range sortKeys(m) { from := m[to] if strings.HasSuffix(to, "_string") || strings.HasSuffix(to, "_object") { // If resource doesn't have this field, then ignores (uses original) path := strings.ReplaceAll(to, "/", ".0.") - _, ok := d.GetOk(fmt.Sprintf("%s.0.%s", userConfigKey(kind, name), path)) + _, ok := d.GetOk(prefix + path) if !ok { continue } @@ -53,6 +56,67 @@ func renameAliasesToTfo(kind userConfigType, name string, dto map[string]any, d renameAlias(dto, from, to) } + + // Converts ip_filter list into an expected by the config type + return convertIPFilter(dto) +} + +// ipFilterMistyped reverse types: string to map, map to string +// Unmarshalled with no errors when ip_filter has type missmatch +type ipFilterMistyped struct { + IPFilter []map[string]string `json:"ip_filter"` + IPFilterString []map[string]string `json:"ip_filter_string"` + IPFilterObject []string `json:"ip_filter_object"` +} + +// convertIPFilter converts a list of ip_filter objects into a list of strings and vice versa +func convertIPFilter(dto map[string]any) error { + b, err := json.Marshal(dto) + if err != nil { + return err + } + + var r ipFilterMistyped + err = json.Unmarshal(b, &r) + if err != nil { + // nolint: nilerr + // Marshaling went wrong, nothing to fix + return nil + } + + // Converting went smooth. + // Which means either there is no ip_filter at all, or it has an invalid type + + // Converts strings into objects + if len(r.IPFilterObject) > 0 { + mapList := make([]map[string]string, 0) + for _, v := range r.IPFilterObject { + mapList = append(mapList, map[string]string{"network": v}) + } + + dto["ip_filter_object"] = mapList + return nil + } + + // Converts objects into strings + strList := make([]string, 0) + for _, v := range append(r.IPFilter, r.IPFilterString...) { + strList = append(strList, v["network"]) + } + + if len(strList) == 0 { + // Nothing to do here + return nil + } + + // Chooses which key to set values to + strKey := "ip_filter" + if len(r.IPFilterString) > 0 { + strKey = "ip_filter_string" + } + + dto[strKey] = strList + return nil } // renameAlias renames ip_filter_string to ip_filter diff --git a/internal/sdkprovider/userconfig/converters/utils_test.go b/internal/sdkprovider/userconfig/converters/utils_test.go index 673feef6f..e8466052d 100644 --- a/internal/sdkprovider/userconfig/converters/utils_test.go +++ b/internal/sdkprovider/userconfig/converters/utils_test.go @@ -172,6 +172,60 @@ func TestRenameAliasesToTfo(t *testing.T) { dto: `{"pg": {"pg_stat_statements.track": 0}}`, tfo: newResourceDataMock(), }, + { + serviceType: "thanos", + name: "ip_filter gets a list of objects", + expected: `{"ip_filter": ["0.0.0.0/0"]}`, + dto: `{"ip_filter": [{"network": "0.0.0.0/0"}]}`, + tfo: newResourceDataMock( + newResourceDataKV("thanos_user_config.0.ip_filter", []string{"0.0.0.0/0"}), + ), + }, + { + serviceType: "thanos", + name: "ip_filter_string gets a list of objects", + expected: `{"ip_filter_string": ["0.0.0.0/0"]}`, + dto: `{"ip_filter": [{"network": "0.0.0.0/0"}]}`, + tfo: newResourceDataMock( + newResourceDataKV("thanos_user_config.0.ip_filter_string", []string{"0.0.0.0/0"}), + ), + }, + { + serviceType: "thanos", + name: "ip_filter_object gets a list of strings", + expected: `{"ip_filter_object": [{"network": "0.0.0.0/0"}]}`, + dto: `{"ip_filter": ["0.0.0.0/0"]}`, + tfo: newResourceDataMock( + newResourceDataKV("thanos_user_config.0.ip_filter_object", []map[string]string{{"network": "0.0.0.0/0"}}), + ), + }, + { + serviceType: "thanos", + name: "ip_filter_object empty list", + expected: `{"ip_filter_object": []}`, + dto: `{"ip_filter": []}`, + tfo: newResourceDataMock( + newResourceDataKV("thanos_user_config.0.ip_filter_object", []map[string]string{}), + ), + }, + { + serviceType: "thanos", + name: "ip_filter_string empty list", + expected: `{"ip_filter_string": []}`, + dto: `{"ip_filter": []}`, + tfo: newResourceDataMock( + newResourceDataKV("thanos_user_config.0.ip_filter_string", []string{}), + ), + }, + { + serviceType: "thanos", + name: "ip_filter empty list", + expected: `{"ip_filter": []}`, + dto: `{"ip_filter": []}`, + tfo: newResourceDataMock( + newResourceDataKV("thanos_user_config.0.ip_filter", []string{}), + ), + }, } reSpaces := regexp.MustCompile(`\s+`) @@ -181,7 +235,9 @@ func TestRenameAliasesToTfo(t *testing.T) { err := json.Unmarshal([]byte(opt.dto), &m) require.NoError(t, err) - renameAliasesToTfo(ServiceUserConfig, opt.serviceType, m, opt.tfo) + err = renameAliasesToTfo(ServiceUserConfig, opt.serviceType, m, opt.tfo) + require.NoError(t, err) + b, err := json.Marshal(&m) require.NoError(t, err) assert.Equal(t, reSpaces.ReplaceAllString(opt.expected, ""), string(b))