Skip to content
Open
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Adding a new version? You'll need three changes:
This is all the way at the bottom. It's the thing we always forget.
--->

- [3.5.2](#352)
- [3.5.1](#351)
- [3.5.0](#350)
- [3.4.8](#348)
Expand Down Expand Up @@ -116,6 +117,17 @@ Adding a new version? You'll need three changes:

### Fixed

- Do not cleanup `null`s in the configuration of plugins with Kong running in
DBLess mode in the translator of ingress-controller. This enables user to use
explicit `null`s in plugins.
[#7751](https://github.com/Kong/kubernetes-ingress-controller/pull/7751)

## [3.5.2]

> Release date: 2025-09-23

### Fixed

- Add `request-termination` plugin to return `500` if there are no available
`backendRef` only when the service is translated from `HTTPRoute` or
`GRPCRoute`.
Expand Down Expand Up @@ -4195,6 +4207,7 @@ Please read the changelog and test in your environment.
- The initial versions were rapildy iterated to deliver
a working ingress controller.

[3.5.2]: https://github.com/kong/kubernetes-ingress-controller/compare/v3.5.1...v3.5.2
[3.5.1]: https://github.com/kong/kubernetes-ingress-controller/compare/v3.5.0...v3.5.1
[3.5.0]: https://github.com/kong/kubernetes-ingress-controller/compare/v3.4.7...v3.5.0
[3.4.8]: https://github.com/kong/kubernetes-ingress-controller/compare/v3.4.7...v3.4.8
Expand Down
2 changes: 1 addition & 1 deletion internal/dataplane/sendconfig/inmemory.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (s UpdateStrategyInMemory) Update(ctx context.Context, targetState ContentW
}
}

configSize := mo.Some[int](len(config))
configSize := mo.Some(len(config))
if reloadConfigErr := s.configService.ReloadDeclarativeRawConfig(
ctx,
bytes.NewReader(config),
Expand Down
43 changes: 0 additions & 43 deletions internal/dataplane/sendconfig/inmemory_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,55 +28,12 @@ func (DefaultContentToDBLessConfigConverter) Convert(content *file.Content) DBLe
// DBLess schema does not support decK's Info section.
dblessConfig.Info = nil

// DBLess schema does not support nulls in plugin configs.
cleanUpNullsInPluginConfigs(&dblessConfig.Content)

// DBLess schema does not 1-1 match decK's schema for ConsumerGroups.
convertConsumerGroups(&dblessConfig)

return dblessConfig
}

// cleanUpNullsInPluginConfigs removes null values from plugins' configs.
func cleanUpNullsInPluginConfigs(state *file.Content) {
for _, s := range state.Services {
for _, p := range s.Plugins {
for k, v := range p.Config {
if v == nil {
delete(p.Config, k)
}
}
}
for _, r := range state.Routes {
for _, p := range r.Plugins {
for k, v := range p.Config {
if v == nil {
delete(p.Config, k)
}
}
}
}
}

for _, c := range state.Consumers {
for _, p := range c.Plugins {
for k, v := range p.Config {
if v == nil {
delete(p.Config, k)
}
}
}
}

for _, p := range state.Plugins {
for k, v := range p.Config {
if v == nil {
delete(p.Config, k)
}
}
}
}

// convertConsumerGroups drops consumer groups related fields that are not supported in DBLess schema:
// - Content.Consumers[].Groups,
// - Content.ConsumerGroups[].Plugins
Expand Down
4 changes: 4 additions & 0 deletions internal/dataplane/sendconfig/inmemory_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ func TestDefaultContentToDBLessConfigConverter(t *testing.T) {
Plugin: kong.Plugin{
Name: kong.String("p1"),
Config: kong.Configuration{
"config1": nil,
"config2": "value2",
},
},
Expand All @@ -294,6 +295,7 @@ func TestDefaultContentToDBLessConfigConverter(t *testing.T) {
Plugin: kong.Plugin{
Name: kong.String("p1"),
Config: kong.Configuration{
"config1": nil,
"config2": "value2",
},
},
Expand All @@ -311,6 +313,7 @@ func TestDefaultContentToDBLessConfigConverter(t *testing.T) {
Plugin: kong.Plugin{
Name: kong.String("p1"),
Config: kong.Configuration{
"config1": nil,
"config2": "value2",
},
},
Expand All @@ -328,6 +331,7 @@ func TestDefaultContentToDBLessConfigConverter(t *testing.T) {
Plugin: kong.Plugin{
Name: kong.String("p1"),
Config: kong.Configuration{
"config1": nil,
"config2": "value2",
},
},
Expand Down
98 changes: 98 additions & 0 deletions test/integration/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ import (
configurationv1 "github.com/kong/kubernetes-configuration/v2/api/configuration/v1"
"github.com/kong/kubernetes-configuration/v2/pkg/clientset"

"github.com/kong/kubernetes-ingress-controller/v3/internal/adminapi"
"github.com/kong/kubernetes-ingress-controller/v3/internal/annotations"
"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
"github.com/kong/kubernetes-ingress-controller/v3/internal/labels"
managercfg "github.com/kong/kubernetes-ingress-controller/v3/pkg/manager/config"
"github.com/kong/kubernetes-ingress-controller/v3/test"
"github.com/kong/kubernetes-ingress-controller/v3/test/consts"
"github.com/kong/kubernetes-ingress-controller/v3/test/internal/helpers"
Expand Down Expand Up @@ -712,3 +714,99 @@ func TestPluginCrossNamespaceReference(t *testing.T) {
assert.True(c, resp.StatusCode == http.StatusTeapot)
}, ingressWait, waitTick)
}

func TestPluginNullInConfig(t *testing.T) {
ctx := t.Context()

t.Parallel()
ns, cleaner := helpers.Setup(ctx, t, env)

t.Log("deploying a minimal HTTP container deployment to test Ingress routes")
container := generators.NewContainer("httpbin", test.HTTPBinImage, test.HTTPBinPort)
deployment := generators.NewDeploymentForContainer(container)
deployment, err := env.Cluster().Client().AppsV1().Deployments(ns.Name).Create(ctx, deployment, metav1.CreateOptions{})
require.NoError(t, err)
cleaner.Add(deployment)

t.Logf("exposing deployment %s via service", deployment.Name)
service := generators.NewServiceForDeployment(deployment, corev1.ServiceTypeLoadBalancer)
service, err = env.Cluster().Client().CoreV1().Services(ns.Name).Create(ctx, service, metav1.CreateOptions{})
require.NoError(t, err)
cleaner.Add(service)

t.Logf("creating an ingress for service %s with ingress.class %s", service.Name, consts.IngressClass)
ingress := generators.NewIngressForService("/test_plugin_essentials", map[string]string{
"konghq.com/strip-path": "true",
}, service)
ingress.Spec.IngressClassName = kong.String(consts.IngressClass)
ingress, err = env.Cluster().Client().NetworkingV1().Ingresses(ns.Name).Create(ctx, ingress, metav1.CreateOptions{})
require.NoError(t, err)
cleaner.Add(ingress)

t.Log("waiting for routes from Ingress to be operational")
assert.Eventually(t, func() bool {
resp, err := helpers.DefaultHTTPClient().Get(fmt.Sprintf("%s/test_plugin_essentials", proxyHTTPURL))
if err != nil {
t.Logf("WARNING: error while waiting for %s: %v", proxyHTTPURL, err)
return false
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
// now that the ingress backend is routable, make sure the contents we're getting back are what we expect
// Expected: "<title>httpbin.org</title>"
b := new(bytes.Buffer)
n, err := b.ReadFrom(resp.Body)
require.NoError(t, err)
require.True(t, n > 0)
return strings.Contains(b.String(), "<title>httpbin.org</title>")
}
return false
}, ingressWait, waitTick)

t.Log("Creating a plugin with `null` in its configuration")

kongplugin := &configurationv1.KongPlugin{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns.Name,
Name: "plugin-datadog",
},
InstanceName: "plugin-with-null",
PluginName: "datadog",
Config: apiextensionsv1.JSON{
Raw: []byte(`{"host":"localhost","port":8125,"prefix":null}`),
},
}
c, err := clientset.NewForConfig(env.Cluster().Config())
require.NoError(t, err)
kongplugin, err = c.ConfigurationV1().KongPlugins(ns.Name).Create(ctx, kongplugin, metav1.CreateOptions{})
require.NoError(t, err)
cleaner.Add(kongplugin)

t.Logf("Updating Ingress to use plugin %s", kongplugin.Name)
require.Eventually(t, func() bool {
ingress, err := env.Cluster().Client().NetworkingV1().Ingresses(ns.Name).Get(ctx, ingress.Name, metav1.GetOptions{})
if err != nil {
return false
}
ingress.Annotations[annotations.AnnotationPrefix+annotations.PluginsKey] = kongplugin.Name
_, err = env.Cluster().Client().NetworkingV1().Ingresses(ns.Name).Update(ctx, ingress, metav1.UpdateOptions{})
return err == nil
}, ingressWait, waitTick)

t.Logf("Checking the configuration of the plugin %s in Kong", kongplugin.Name)
require.Eventually(t, func() bool {
kc, err := adminapi.NewKongAPIClient(proxyAdminURL.String(), managercfg.AdminAPIClientConfig{}, consts.KongTestPassword)
require.NoError(t, err, "failed to create Kong client")
plugins, err := kc.Plugins.ListAll(ctx)
require.NoError(t, err, "failed to list plugins")
if len(plugins) != 1 {
return false
}
plugin := plugins[0]
if plugin.Name == nil || *plugin.Name != "datadog" {
return false
}
configPrefix, ok := plugin.Config["prefix"]
return ok && configPrefix == nil
}, ingressWait, waitTick, "failed to find 'datadog' plugin with null in config.prefix in Kong")
}
Loading