From 5e0de31a17c045f00b3a7fab1147aa45157587ba Mon Sep 17 00:00:00 2001 From: Michael Heap Date: Fri, 3 May 2024 13:03:20 +0100 Subject: [PATCH] feat(convert): Convert pre-function 'functions' to 'access' in 2.x to 3.x --- convert/convert.go | 82 +++++++++++++++++ convert/convert_test.go | 192 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+) diff --git a/convert/convert.go b/convert/convert.go index 350ee8d45..97ff9b4ce 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -3,6 +3,7 @@ package convert import ( "context" "crypto/rand" + "errors" "fmt" "math/big" "strings" @@ -31,6 +32,7 @@ const ( FormatKongGateway3x Format = "kong-gateway-3.x" rateLimitingAdvancedPluginName = "rate-limiting-advanced" + preFunctionPluginName = "pre-function" rlaNamespaceDefaultLength = 32 ) @@ -166,6 +168,11 @@ func convertKongGateway2xTo3x(input *file.Content, filename string) (*file.Conte return nil, err } + // update the pre-function + if err := convertPreFunctions2xTo3x(outputContent); err != nil { + return nil, err + } + cprint.UpdatePrintf( "From the '%s' config file,\n"+ "the _format_version field has been migrated from '%s' to '%s'.\n"+ @@ -179,6 +186,81 @@ func convertKongGateway2xTo3x(input *file.Content, filename string) (*file.Conte return outputContent, nil } +func convertPreFunctions2xTo3x(content *file.Content) error { + err := applyForEachPluginInstance(content, preFunctionPluginName, func(plugin *file.FPlugin) error { + if plugin.Config["functions"] == nil { + return errors.New("functions field is missing in pre-function plugin") + } + + plugin.Config["access"] = plugin.Config["functions"] + delete(plugin.Config, "functions") + + return nil + }) + + // Fix Consumer Group plugins too + for _, consumerGroup := range content.ConsumerGroups { + for _, plugin := range consumerGroup.Plugins { + if *plugin.Name == preFunctionPluginName { + if plugin.Config["functions"] == nil { + return errors.New("functions field is missing in pre-function plugin") + } + plugin.Config["access"] = plugin.Config["functions"] + delete(plugin.Config, "functions") + } + } + } + + if err != nil { + return err + } + + return nil +} + +func applyForEachPluginInstance(content *file.Content, pluginName string, f func(plugin *file.FPlugin) error) error { + for _, plugin := range content.Plugins { + if *plugin.Name == pluginName { + plugin := plugin + if err := f(&plugin); err != nil { + return err + } + } + } + + for _, service := range content.Services { + for _, plugin := range service.Plugins { + if *plugin.Name == pluginName { + if err := f(plugin); err != nil { + return err + } + } + } + } + + for _, route := range content.Routes { + for _, plugin := range route.Plugins { + if *plugin.Name == pluginName { + if err := f(plugin); err != nil { + return err + } + } + } + } + + for _, consumer := range content.Consumers { + for _, plugin := range consumer.Plugins { + if *plugin.Name == pluginName { + if err := f(plugin); err != nil { + return err + } + } + } + } + + return nil +} + func generateAutoFields(content *file.Content) error { for _, plugin := range content.Plugins { if *plugin.Name == rateLimitingAdvancedPluginName { diff --git a/convert/convert_test.go b/convert/convert_test.go index 93a95a693..480eaeb96 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -579,3 +579,195 @@ func Test_convertAutoFields(t *testing.T) { consumerGroupPluginConfig := got.ConsumerGroups[0].Plugins[0].Config assert.NotEmpty(t, consumerGroupPluginConfig["namespace"]) } + +func Test_convertPreFunction(t *testing.T) { + content := &file.Content{ + Services: []file.FService{ + { + Service: kong.Service{ + Name: kong.String("s1"), + Host: kong.String("httpbin.org"), + }, + Plugins: []*file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("pre-function"), + Config: kong.Configuration{ + "functions": "kong.log.err(\"service\")", + }, + }, + }, + }, + }, + }, + Routes: []file.FRoute{ + { + Route: kong.Route{ + Name: kong.String("r1"), + Paths: []*string{kong.String("/r1")}, + }, + Plugins: []*file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("pre-function"), + Config: kong.Configuration{ + "functions": "kong.log.err(\"route\")", + }, + }, + }, + }, + }, + }, + Consumers: []file.FConsumer{ + { + Consumer: kong.Consumer{ + Username: kong.String("foo"), + }, + Plugins: []*file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("pre-function"), + Config: kong.Configuration{ + "functions": "kong.log.err(\"consumer\")", + }, + }, + }, + }, + }, + }, + ConsumerGroups: []file.FConsumerGroupObject{ + { + ConsumerGroup: kong.ConsumerGroup{ + Name: kong.String("my_consumer_group"), + }, + Plugins: []*kong.ConsumerGroupPlugin{ + { + Name: kong.String("pre-function"), + Config: kong.Configuration{ + "functions": "kong.log.err(\"consumer_group\")", + }, + }, + }, + }, + }, + Plugins: []file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("pre-function"), + Config: kong.Configuration{ + "functions": "kong.log.err(\"global\")", + }, + }, + }, + }, + } + + got, err := convertKongGateway2xTo3x(content, "-") + assert.NoError(t, err) + + globalPluginConfig := got.Plugins[0].Config + assert.Contains(t, globalPluginConfig["access"], "global") + assert.Empty(t, globalPluginConfig["functions"]) + + servicePluginConfig := got.Services[0].Plugins[0].Config + assert.Contains(t, servicePluginConfig["access"], "service") + assert.Empty(t, servicePluginConfig["functions"]) + + routePluginConfig := got.Routes[0].Plugins[0].Config + assert.Contains(t, routePluginConfig["access"], "route") + assert.Empty(t, routePluginConfig["functions"]) + + consumerPluginConfig := got.Consumers[0].Plugins[0].Config + assert.Contains(t, consumerPluginConfig["access"], "consumer") + assert.Empty(t, consumerPluginConfig["functions"]) + + consumerGroupPluginConfig := got.ConsumerGroups[0].Plugins[0].Config + assert.Contains(t, consumerGroupPluginConfig["access"], "consumer_group") + assert.Empty(t, consumerGroupPluginConfig["functions"]) +} + +func Test_convertPreFunctionWithError(t *testing.T) { + content := &file.Content{ + Services: []file.FService{ + { + Service: kong.Service{ + Name: kong.String("s1"), + Host: kong.String("httpbin.org"), + }, + Plugins: []*file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("pre-function"), + Config: kong.Configuration{ + "should_be_functions": "kong.log.err(\"service\")", + }, + }, + }, + }, + }, + }, + Routes: []file.FRoute{ + { + Route: kong.Route{ + Name: kong.String("r1"), + Paths: []*string{kong.String("/r1")}, + }, + Plugins: []*file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("pre-function"), + Config: kong.Configuration{ + "should_be_functions": "kong.log.err(\"route\")", + }, + }, + }, + }, + }, + }, + Consumers: []file.FConsumer{ + { + Consumer: kong.Consumer{ + Username: kong.String("foo"), + }, + Plugins: []*file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("pre-function"), + Config: kong.Configuration{ + "should_be_functions": "kong.log.err(\"consumer\")", + }, + }, + }, + }, + }, + }, + ConsumerGroups: []file.FConsumerGroupObject{ + { + ConsumerGroup: kong.ConsumerGroup{ + Name: kong.String("my_consumer_group"), + }, + Plugins: []*kong.ConsumerGroupPlugin{ + { + Name: kong.String("pre-function"), + Config: kong.Configuration{ + "should_be_functions": "kong.log.err(\"consumer_group\")", + }, + }, + }, + }, + }, + Plugins: []file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("pre-function"), + Config: kong.Configuration{ + "should_be_functions": "kong.log.err(\"global\")", + }, + }, + }, + }, + } + + _, err := convertKongGateway2xTo3x(content, "-") + assert.Error(t, err) +}