Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Konnect readiness validation #1227

Merged
merged 2 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/gateway_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var (
validateWorkspace string
validateParallelism int
validateJSONOutput bool
validateKonnectCompatibility bool
)

func executeValidate(cmd *cobra.Command, _ []string) error {
Expand Down Expand Up @@ -102,6 +103,12 @@ func executeValidate(cmd *cobra.Command, _ []string) error {
return err
}

if validateKonnectCompatibility {
if errs := validate.KonnectCompatibility(targetContent); len(errs) != 0 {
return validate.ErrorsWrapper{Errors: errs}
}
}

if validateOnline {
if errs := validateWithKong(ctx, kongClient, ks); len(errs) != 0 {
return validate.ErrorsWrapper{Errors: errs}
Expand Down Expand Up @@ -207,6 +214,8 @@ this command unless --online flag is used.
10, "Maximum number of concurrent requests to Kong.")
validateCmd.Flags().BoolVar(&validateJSONOutput, "json-output",
false, "generate command execution report in a JSON format")
validateCmd.Flags().BoolVar(&validateKonnectCompatibility, "konnect-compatibility",
false, "validate that the state file(s) are ready to be deployed to Konnect")

if err := ensureGetAllMethods(); err != nil {
panic(err.Error())
Expand Down
127 changes: 127 additions & 0 deletions validate/konnect_compatibility.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package validate

import (
"errors"
"fmt"
"strconv"

"github.com/kong/go-database-reconciler/pkg/file"
"github.com/kong/go-kong/kong"
)

var (
errKonnect = "[konnect] section not specified - ensure details are set via cli flags"
errWorkspace = "[workspaces] not supported by Konnect - use control planes instead"
errNoVersion = "[version] unable to determine decK file version"
errBadVersion = fmt.Sprintf("[version] decK file version must be '%.1f' or greater", supportedVersion)
errPluginIncompatible = "[%s] plugin is not compatible with Konnect"
errPluginNoCluster = "[%s] plugin can't be used with cluster strategy"
)

var supportedVersion = 3.0

func checkPlugin(name *string, config kong.Configuration) error {
switch *name {
case "jwt-signer", "vault-auth", "oauth2":
return fmt.Errorf(errPluginIncompatible, *name)
case "application-registration":
return fmt.Errorf("[%s] available in Konnect, but doesn't require this plugin", *name)
case "key-auth-enc":
return fmt.Errorf("[%s] keys are automatically encrypted in Konnect, use the key auth plugin instead", *name)
case "openwhisk":
return fmt.Errorf("[%s] plugin not bundled with Kong Gateway - installed as a LuaRocks package", *name)
case "rate-limiting", "rate-limiting-advanced", "response-ratelimiting", "graphql-rate-limiting-advanced":
if config["strategy"] == "cluster" {
return fmt.Errorf(errPluginNoCluster, *name)
}
default:
}
return nil
}

func KonnectCompatibility(targetContent *file.Content) []error {
var errs []error

if targetContent.Workspace != "" {
errs = append(errs, errors.New(errWorkspace))
}

if targetContent.Konnect == nil {
errs = append(errs, errors.New(errKonnect))
}

versionNumber, err := strconv.ParseFloat(targetContent.FormatVersion, 32)
if err != nil {
errs = append(errs, errors.New(errNoVersion))
} else {
if versionNumber < supportedVersion {
errs = append(errs, errors.New(errBadVersion))
}
}

for _, plugin := range targetContent.Plugins {
if plugin.Enabled != nil && *plugin.Enabled && plugin.Config != nil {
err := checkPlugin(plugin.Name, plugin.Config)
if err != nil {
errs = append(errs, err)
}
}
}

for _, consumer := range targetContent.Consumers {
for _, plugin := range consumer.Plugins {
if plugin.Enabled != nil && *plugin.Enabled && plugin.Config != nil {
err := checkPlugin(plugin.Name, plugin.Config)
if err != nil {
errs = append(errs, err)
}
}
}
}

for _, consumerGroup := range targetContent.ConsumerGroups {
for _, plugin := range consumerGroup.Plugins {
err := checkPlugin(plugin.Name, plugin.Config)
if err != nil {
errs = append(errs, err)
}
}
}

for _, service := range targetContent.Services {
for _, plugin := range service.Plugins {
if plugin.Enabled != nil && *plugin.Enabled && plugin.Config != nil {
err := checkPlugin(plugin.Name, plugin.Config)
if err != nil {
errs = append(errs, err)
}
}
}
}

for _, service := range targetContent.Services {
for _, route := range service.Routes {
for _, plugin := range route.Plugins {
if plugin.Enabled != nil && *plugin.Enabled && plugin.Config != nil {
err := checkPlugin(plugin.Name, plugin.Config)
if err != nil {
errs = append(errs, err)
}
}
}
}
}

for _, route := range targetContent.Routes {
for _, plugin := range route.Plugins {
if plugin.Enabled != nil && *plugin.Enabled && plugin.Config != nil {
err := checkPlugin(plugin.Name, plugin.Config)
if err != nil {
errs = append(errs, err)
}
}
}
}

return errs
}
143 changes: 143 additions & 0 deletions validate/konnect_compatibility_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package validate

import (
"errors"
"fmt"
"testing"

"github.com/kong/go-database-reconciler/pkg/file"
"github.com/kong/go-kong/kong"
"github.com/stretchr/testify/assert"
)

func Test_KonnectCompatibility(t *testing.T) {
tests := []struct {
name string
content *file.Content
expected []error
}{
{
name: "version invalid",
content: &file.Content{
FormatVersion: "2.9",
Workspace: "test",
Konnect: &file.Konnect{
RuntimeGroupName: "s",
ControlPlaneName: "s",
},
},
expected: []error{
errors.New(errWorkspace),
errors.New(errBadVersion),
},
},
{
name: "no konnect",
content: &file.Content{
FormatVersion: "3.1",
},
expected: []error{
errors.New(errKonnect),
},
},
{
name: "incompatible service plugin",
content: &file.Content{
FormatVersion: "3.1",
Konnect: &file.Konnect{
RuntimeGroupName: "s",
ControlPlaneName: "s",
},
Services: []file.FService{
{Plugins: []*file.FPlugin{
{
Plugin: kong.Plugin{
Name: kong.String("oauth2"),
Enabled: kong.Bool(true),
Config: kong.Configuration{"config": "config"},
},
},
}},
},
},
expected: []error{
fmt.Errorf(errPluginIncompatible, "oauth2"),
},
},
{
name: "incompatible service route plugins",
content: &file.Content{
FormatVersion: "3.1",
Konnect: &file.Konnect{
RuntimeGroupName: "s",
ControlPlaneName: "s",
},
Services: []file.FService{
{Routes: []*file.FRoute{
{
Plugins: []*file.FPlugin{
{
Plugin: kong.Plugin{
Name: kong.String("oauth2"),
Enabled: kong.Bool(true),
Config: kong.Configuration{"config": "config"},
},
},
{
Plugin: kong.Plugin{
Name: kong.String("key-auth-enc"),
Enabled: kong.Bool(true),
Config: kong.Configuration{"config": "config"},
},
},
},
},
}},
},
},
expected: []error{
fmt.Errorf(errPluginIncompatible, "oauth2"),
fmt.Errorf("[%s] keys are automatically encrypted in Konnect, use the key auth plugin instead", "key-auth-enc"),
},
},
{
name: "incompatible top-level and consumer-group plugins",
content: &file.Content{
FormatVersion: "3.1",
Konnect: &file.Konnect{
RuntimeGroupName: "s",
ControlPlaneName: "s",
},
Plugins: []file.FPlugin{
{
Plugin: kong.Plugin{
Name: kong.String("response-ratelimiting"),
Enabled: kong.Bool(true),
Config: kong.Configuration{"strategy": "cluster"},
},
},
},
ConsumerGroups: []file.FConsumerGroupObject{
{
Plugins: []*kong.ConsumerGroupPlugin{
{
Name: kong.String("key-auth-enc"),
Config: kong.Configuration{"config": "config"},
},
},
},
},
},
expected: []error{
fmt.Errorf(errPluginNoCluster, "response-ratelimiting"),
fmt.Errorf("[%s] keys are automatically encrypted in Konnect, use the key auth plugin instead", "key-auth-enc"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
errs := KonnectCompatibility(tt.content)
assert.Equal(t, tt.expected, errs)
})
}
}