From 45107a3f3968ff1dc098cfa7a01d6a990eab9242 Mon Sep 17 00:00:00 2001 From: Samuele Date: Thu, 29 Aug 2024 10:31:04 +0200 Subject: [PATCH] fix(utils): support auto fields (#465) When fields marked as `auto` are passed to kong without a value they are auto generated by the gateway. In that case, comparing with "defaults" doesn't work, so in order to compute a correct diff they must be copied from Kong's configuration. This commit addS a new FillPluginsDefaultsAutoFields utils function that in addition to default fields, adds auto fields to the configuration. It takes an additional parameter: oldConfig, which represents the config from Kong: auto fields are just copied from it so that they don't show up as differences in the diff. --- kong/utils.go | 48 +++++++++++++++++++++++++------ kong/utils_test.go | 72 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 10 deletions(-) diff --git a/kong/utils.go b/kong/utils.go index 8fe73f46..40f591f2 100644 --- a/kong/utils.go +++ b/kong/utils.go @@ -278,7 +278,9 @@ func backfillResultConfigMap(res Configuration, path []string, configValue inter return nil } -func fillConfigRecord(schema gjson.Result, config Configuration) Configuration { +// fills the config record with default values +// if oldConfig is provided, copies auto fields from it, into config +func fillConfigRecord(schema gjson.Result, config Configuration, oldConfig Configuration) Configuration { res := config.DeepCopy() configFields := schema.Get("fields") // Fetch deprecated fields @@ -295,7 +297,7 @@ func fillConfigRecord(schema gjson.Result, config Configuration) Configuration { } if fname == "config" { - newConfig := fillConfigRecord(value.Get(fname), config) + newConfig := fillConfigRecord(value.Get(fname), config, oldConfig) res = newConfig return true } @@ -348,7 +350,7 @@ func fillConfigRecord(schema gjson.Result, config Configuration) Configuration { default: fieldConfig = subConfig.(map[string]interface{}) } - newSubConfig := fillConfigRecord(value.Get(fname), fieldConfig) + newSubConfig := fillConfigRecord(value.Get(fname), fieldConfig, oldConfig) res[fname] = map[string]interface{}(newSubConfig) return true } @@ -368,7 +370,7 @@ func fillConfigRecord(schema gjson.Result, config Configuration) Configuration { for i, configRecord := range subConfigArray { // Check if element is of type record, if it is, set default values by recursively calling `fillConfigRecord` if configRecordMap, ok := configRecord.(map[string]interface{}); ok { - processedConfigRecord := fillConfigRecord(value.Get(fname).Get("elements"), configRecordMap) + processedConfigRecord := fillConfigRecord(value.Get(fname).Get("elements"), configRecordMap, oldConfig) processedSubConfigArray[i] = processedConfigRecord continue } @@ -382,6 +384,19 @@ func fillConfigRecord(schema gjson.Result, config Configuration) Configuration { } } + // handle `auto` fields by copying them from oldConfig if they don't have + // a value. When fields marked as `auto` are passed to kong without a value + // they are auto generated by the gateway. In that case, comparing with + // "defaults" doesn't work, so in order to compute a correct diff they must + // be copied from the oldConfig. + auto := value.Get(fname + ".auto") + if auto.Exists() && auto.Bool() && oldConfig != nil { + if v, ok := oldConfig[fname]; ok { + res[fname] = v + return true + } + } + // Check if the record has a default value for the specified field. // If so, use it. If not, fall back to the default value of the field itself. if defaultRecordValue.Exists() && defaultRecordValue.Get(fname).Exists() { @@ -665,9 +680,7 @@ func FillEntityDefaults(entity interface{}, schema Schema) error { return nil } -// FillPluginsDefaults ingests plugin's defaults from its schema. -// Takes in a plugin struct and mutate it in place. -func FillPluginsDefaults(plugin *Plugin, schema Schema) error { +func fillConfigRecordDefaultsAutoFields(plugin *Plugin, schema map[string]interface{}, oldPlugin *Plugin) error { jsonb, err := json.Marshal(&schema) if err != nil { return err @@ -680,7 +693,12 @@ func FillPluginsDefaults(plugin *Plugin, schema Schema) error { if plugin.Config == nil { plugin.Config = make(Configuration) } - plugin.Config = fillConfigRecord(configSchema, plugin.Config) + + var oldConfig Configuration + if oldPlugin != nil { + oldConfig = oldPlugin.Config + } + plugin.Config = fillConfigRecord(configSchema, plugin.Config, oldConfig) if plugin.Protocols == nil { plugin.Protocols = getDefaultProtocols(gjsonSchema) } @@ -689,3 +707,17 @@ func FillPluginsDefaults(plugin *Plugin, schema Schema) error { } return nil } + +// FillPluginsDefaults ingests plugin's defaults from its schema. +// Takes in a plugin struct and mutate it in place. +func FillPluginsDefaults(plugin *Plugin, schema Schema) error { + return fillConfigRecordDefaultsAutoFields(plugin, schema, nil) +} + +// same as FillPluginsDefaults but also fills auto fields. +// `oldPlugin` (which repsesents the plugin from the old/existing Kong config +// is used to copy auto fields from it when they are not set in `plugin“. +// keeping both for compatibility: these utils are public +func FillPluginsDefaultsAutoFields(plugin *Plugin, schema map[string]interface{}, oldPlugin *Plugin) error { + return fillConfigRecordDefaultsAutoFields(plugin, schema, oldPlugin) +} diff --git a/kong/utils_test.go b/kong/utils_test.go index fd7a4fda..1cf7f80e 100644 --- a/kong/utils_test.go +++ b/kong/utils_test.go @@ -1836,7 +1836,7 @@ func Test_fillConfigRecord(t *testing.T) { t.Run(tc.name, func(t *testing.T) { configSchema, err := getConfigSchema(tc.schema) require.NoError(t, err) - config := fillConfigRecord(configSchema, tc.config) + config := fillConfigRecord(configSchema, tc.config, nil) require.NotNil(t, config) if diff := cmp.Diff(config, tc.expected); diff != "" { t.Errorf("unexpected diff:\n%s", diff) @@ -1933,6 +1933,35 @@ const fillConfigRecordTestSchemaWithShorthandFields = `{ } ` +const fillConfigRecordTestSchemaWithAutoFields = `{ + "fields": { + "config": { + "type": "record", + "fields": [ + { + "foo_string": { + "type": "string", + "auto": true + } + }, + { + "bar_string": { + "type": "string", + "auto": true + } + }, + { + "baz_string": { + "type": "string", + "auto": true + } + } + ] + } + } +} +` + func Test_fillConfigRecord_shorthand_fields(t *testing.T) { tests := []struct { name string @@ -1992,7 +2021,46 @@ func Test_fillConfigRecord_shorthand_fields(t *testing.T) { t.Run(tc.name, func(t *testing.T) { configSchema, err := getConfigSchema(tc.schema) require.NoError(t, err) - config := fillConfigRecord(configSchema, tc.config) + config := fillConfigRecord(configSchema, tc.config, nil) + require.NotNil(t, config) + if diff := cmp.Diff(config, tc.expected); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + } +} + +func Test_fillConfigRecord_auto_fields(t *testing.T) { + tests := []struct { + name string + schema gjson.Result + config Configuration + expected Configuration + }{ + { + name: "fills auto fields with values from oldConfig", + schema: gjson.Parse(fillConfigRecordTestSchemaWithAutoFields), + config: Configuration{ + "baz_string": "789", + }, + expected: Configuration{ + "foo_string": "123", + "bar_string": "456", + "baz_string": "789", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + configSchema, err := getConfigSchema(tc.schema) + require.NoError(t, err) + oldConfig := Configuration{ + "foo_string": "123", + "bar_string": "456", + "baz_string": "000", + } + config := fillConfigRecord(configSchema, tc.config, oldConfig) require.NotNil(t, config) if diff := cmp.Diff(config, tc.expected); diff != "" { t.Errorf("unexpected diff:\n%s", diff)