Skip to content

Commit

Permalink
fix(utils): support auto fields (#465)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
samugi authored Aug 29, 2024
1 parent f96e181 commit 45107a3
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 10 deletions.
48 changes: 40 additions & 8 deletions kong/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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() {
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
Expand All @@ -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)
}
72 changes: 70 additions & 2 deletions kong/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 45107a3

Please sign in to comment.