From a18140f5b6140faca11fc463316d9033cae8c6ba Mon Sep 17 00:00:00 2001 From: Gabriele Date: Thu, 15 Feb 2024 10:56:58 +0100 Subject: [PATCH] feat: auto-generate rla's namespace on 'convert' subcommand (#1206) * feat: auto-generate rla's namespace on 'convert' subcommand * test: correct integration test --- convert/convert.go | 84 ++++++++++++++++++++++++++++++++++ convert/convert_test.go | 75 ++++++++++++++++++++++++++++++ tests/integration/sync_test.go | 30 +++++++++--- 3 files changed, 183 insertions(+), 6 deletions(-) diff --git a/convert/convert.go b/convert/convert.go index f40e1f10a..e9baac76b 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -2,7 +2,9 @@ package convert import ( "context" + "crypto/rand" "fmt" + "math/big" "strings" "github.com/blang/semver/v4" @@ -27,6 +29,9 @@ const ( FormatKongGateway2x Format = "kong-gateway-2.x" // FormatKongGateway3x represents the Kong gateway 3.x format. FormatKongGateway3x Format = "kong-gateway-3.x" + + rateLimitingAdvancedPluginName = "rate-limiting-advanced" + rlaNamespaceDefaultLength = 32 ) // AllFormats contains all available formats. @@ -100,6 +105,22 @@ func Convert( return err } +func randomString(n int) (string, error) { + const charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzj" + charsetLength := big.NewInt(int64(len(charset))) + + ret := make([]byte, n) + for i := 0; i < n; i++ { + num, err := rand.Int(rand.Reader, charsetLength) + if err != nil { + return "", fmt.Errorf("error generating random string: %w", err) + } + ret[i] = charset[num.Int64()] + } + + return string(ret), nil +} + func convertKongGateway2xTo3x(input *file.Content, filename string) (*file.Content, error) { if input == nil { return nil, fmt.Errorf("input content is nil") @@ -140,6 +161,11 @@ func convertKongGateway2xTo3x(input *file.Content, filename string) (*file.Conte filename, changedRoutesLen, strings.Join(changedRoutes, "\n")) } + // generate any missing required auto fields. + if err := generateAutoFields(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"+ @@ -153,6 +179,64 @@ func convertKongGateway2xTo3x(input *file.Content, filename string) (*file.Conte return outputContent, nil } +func generateAutoFields(content *file.Content) error { + for _, plugin := range content.Plugins { + if *plugin.Name == rateLimitingAdvancedPluginName { + plugin := plugin + if err := autoGenerateNamespaceForRLAPlugin(&plugin); err != nil { + return err + } + } + } + + for _, service := range content.Services { + for _, plugin := range service.Plugins { + if *plugin.Name == rateLimitingAdvancedPluginName { + if err := autoGenerateNamespaceForRLAPlugin(plugin); err != nil { + return err + } + } + } + } + + for _, route := range content.Routes { + for _, plugin := range route.Plugins { + if *plugin.Name == rateLimitingAdvancedPluginName { + if err := autoGenerateNamespaceForRLAPlugin(plugin); err != nil { + return err + } + } + } + } + + for _, consumer := range content.Consumers { + for _, plugin := range consumer.Plugins { + if *plugin.Name == rateLimitingAdvancedPluginName { + if err := autoGenerateNamespaceForRLAPlugin(plugin); err != nil { + return err + } + } + } + } + + return nil +} + +func autoGenerateNamespaceForRLAPlugin(plugin *file.FPlugin) error { + if plugin.Config != nil { + ns, ok := plugin.Config["namespace"] + if !ok || ns == nil { + // namespace is not set, generate one. + randomNamespace, err := randomString(rlaNamespaceDefaultLength) + if err != nil { + return fmt.Errorf("error generating random namespace: %w", err) + } + plugin.Config["namespace"] = randomNamespace + } + } + return nil +} + func migrateRoutesPathFieldPre300(route *file.FRoute) (*file.FRoute, bool) { var hasChanged bool for _, path := range route.Paths { diff --git a/convert/convert_test.go b/convert/convert_test.go index 7d71ca922..bb4fb6a52 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -469,3 +469,78 @@ func Test_convertKongGatewayToKonnect(t *testing.T) { }) } } + +func Test_convertAutoFields(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("rate-limiting-advanced"), + Config: kong.Configuration{}, + }, + }, + }, + }, + }, + Routes: []file.FRoute{ + { + Route: kong.Route{ + Name: kong.String("r1"), + Paths: []*string{kong.String("/r1")}, + }, + Plugins: []*file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("rate-limiting-advanced"), + Config: kong.Configuration{}, + }, + }, + }, + }, + }, + Consumers: []file.FConsumer{ + { + Consumer: kong.Consumer{ + Username: kong.String("foo"), + }, + Plugins: []*file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("rate-limiting-advanced"), + Config: kong.Configuration{}, + }, + }, + }, + }, + }, + Plugins: []file.FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("rate-limiting-advanced"), + Config: kong.Configuration{}, + }, + }, + }, + } + + got, err := convertKongGateway2xTo3x(content, "-") + assert.NoError(t, err) + + globalPluginConfig := got.Plugins[0].Config + assert.NotEmpty(t, globalPluginConfig["namespace"]) + + servicePluginConfig := got.Services[0].Plugins[0].Config + assert.NotEmpty(t, servicePluginConfig["namespace"]) + + routePluginConfig := got.Routes[0].Plugins[0].Config + assert.NotEmpty(t, routePluginConfig["namespace"]) + + consumerPluginConfig := got.Consumers[0].Plugins[0].Config + assert.NotEmpty(t, consumerPluginConfig["namespace"]) +} diff --git a/tests/integration/sync_test.go b/tests/integration/sync_test.go index 684cec6f6..523c1d53c 100644 --- a/tests/integration/sync_test.go +++ b/tests/integration/sync_test.go @@ -195,6 +195,24 @@ var ( }, } + plugin36 = []*kong.Plugin{ + { + Name: kong.String("basic-auth"), + Protocols: []*string{ + kong.String("grpc"), + kong.String("grpcs"), + kong.String("http"), + kong.String("https"), + }, + Enabled: kong.Bool(true), + Config: kong.Configuration{ + "anonymous": "58076db2-28b6-423b-ba39-a797193017f7", + "hide_credentials": false, + "realm": string("service"), + }, + }, + } + plugin_on_entities = []*kong.Plugin{ //nolint:revive,stylecheck { Name: kong.String("prometheus"), @@ -1536,7 +1554,7 @@ func Test_Sync_BasicAuth_Plugin_From_2_0_5_Till_2_8_0(t *testing.T) { } // test scope: -// - 3.x +// - >=3.0 <3.6.0 func Test_Sync_BasicAuth_Plugin_From_3x(t *testing.T) { // setup stage client, err := getTestClient() @@ -1560,7 +1578,7 @@ func Test_Sync_BasicAuth_Plugin_From_3x(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhenKongOrKonnect(t, ">=3.0.0") + runWhen(t, "kong", ">=3.0.0 <3.6.0") setup(t) sync(tc.kongFile) @@ -1570,8 +1588,8 @@ func Test_Sync_BasicAuth_Plugin_From_3x(t *testing.T) { } // test scope: -// - konnect -func Test_Sync_BasicAuth_Plugin_Konnect(t *testing.T) { +// - 3.6+ +func Test_Sync_BasicAuth_Plugin_From_36(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1588,13 +1606,13 @@ func Test_Sync_BasicAuth_Plugin_Konnect(t *testing.T) { name: "create a plugin", kongFile: "testdata/sync/003-create-a-plugin/kong3x.yaml", expectedState: utils.KongRawState{ - Plugins: plugin, + Plugins: plugin36, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "konnect", "") + runWhenKongOrKonnect(t, ">=3.6.0") setup(t) sync(tc.kongFile)