Skip to content

Commit

Permalink
feat: add Konnect readiness validation (#1227)
Browse files Browse the repository at this point in the history
---------

Signed-off-by: John Harris <john@johnharris.io>
  • Loading branch information
johnharris85 committed Apr 10, 2024
1 parent 9d89a33 commit 40d355a
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 0 deletions.
9 changes: 9 additions & 0 deletions cmd/gateway_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var (
validateWorkspace string
validateParallelism int
validateJSONOutput bool
validateKonnectCompatibility bool
)

func executeValidate(cmd *cobra.Command, _ []string) error {
Expand Down Expand Up @@ -104,6 +105,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, targetContent.FormatVersion); len(errs) != 0 {
return validate.ErrorsWrapper{Errors: errs}
Expand Down Expand Up @@ -209,6 +216,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)
})
}
}

0 comments on commit 40d355a

Please sign in to comment.