Skip to content

Commit

Permalink
fix: introduce deck content to dbless config converter (#4453)
Browse files Browse the repository at this point in the history
Introduces `ContentToDBLessConfigConverter` used by `UpdateStrategyInMemory` to adapt decK's `file.Content` to match DBLess schema constraints.

It extracts the already existing conversions from `UpdateStrategyInMemory` (dropping `Info` field, cleaning up nulls in plugins' configs), and also adds cleanups for `ConsumerGroups` related fields (dropping `Plugins[i].ConsumerGroup`, `Consumers[i].Group`, `ConsumerGroups[i].Plugins`, `ConsumerGroups[i].Consumers`, filling relationships).

Also adds an example config for `ConsumerGroup`.
  • Loading branch information
czeslavo committed Aug 4, 2023
1 parent bceedda commit 9dfe212
Show file tree
Hide file tree
Showing 7 changed files with 517 additions and 60 deletions.
16 changes: 16 additions & 0 deletions examples/consumer-groups.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: configuration.konghq.com/v1beta1
kind: KongConsumerGroup
metadata:
name: cg1
annotations:
kubernetes.io/ingress.class: kong
---
apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
name: c1
annotations:
kubernetes.io/ingress.class: kong
username: c1
consumerGroups:
- cg1
40 changes: 0 additions & 40 deletions internal/dataplane/deckgen/deckgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,46 +22,6 @@ func GenerateSHA(targetContent *file.Content) ([]byte, error) {
return shaSum[:], nil
}

// CleanUpNullsInPluginConfigs modifies `state` by deleting plugin config map keys that have nil as their value.
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)
}
}
}
}

// GetFCertificateFromKongCert converts a kong.Certificate to a file.FCertificate.
func GetFCertificateFromKongCert(kongCert kong.Certificate) file.FCertificate {
var res file.FCertificate
Expand Down
32 changes: 18 additions & 14 deletions internal/dataplane/sendconfig/inmemory.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
"io"

"github.com/blang/semver/v4"
"github.com/kong/deck/file"
"github.com/sirupsen/logrus"

"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/deckgen"
"github.com/kong/kubernetes-ingress-controller/v2/internal/metrics"
"github.com/kong/kubernetes-ingress-controller/v2/internal/versions"
)
Expand All @@ -24,23 +24,32 @@ type ConfigService interface {
) ([]byte, error)
}

type ContentToDBLessConfigConverter interface {
// Convert converts a decK's file.Content to a DBLessConfig.
// Implementations are allowed to modify the input *file.Content. Make sure it's copied beforehand if needed.
Convert(content *file.Content) DBLessConfig
}

// UpdateStrategyInMemory implements the UpdateStrategy interface. It updates Kong's data-plane
// configuration using its `POST /config` endpoint that is used by ConfigService.ReloadDeclarativeRawConfig.
type UpdateStrategyInMemory struct {
configService ConfigService
log logrus.FieldLogger
version semver.Version
configService ConfigService
configConverter ContentToDBLessConfigConverter
log logrus.FieldLogger
version semver.Version
}

func NewUpdateStrategyInMemory(
configService ConfigService,
configConverter ContentToDBLessConfigConverter,
log logrus.FieldLogger,
version semver.Version,
) UpdateStrategyInMemory {
return UpdateStrategyInMemory{
configService: configService,
log: log,
version: version,
configService: configService,
configConverter: configConverter,
log: log,
version: version,
}
}

Expand All @@ -49,13 +58,8 @@ func (s UpdateStrategyInMemory) Update(ctx context.Context, targetState ContentW
resourceErrors []ResourceError,
resourceErrorsParseErr error,
) {
// Kong will error out if this is set
targetState.Content.Info = nil

// Kong errors out if `null`s are present in `config` of plugins
deckgen.CleanUpNullsInPluginConfigs(targetState.Content)

config, err := json.Marshal(targetState.Content)
dblessConfig := s.configConverter.Convert(targetState.Content)
config, err := json.Marshal(dblessConfig)
if err != nil {
return fmt.Errorf("constructing kong configuration: %w", err), nil, nil
}
Expand Down
129 changes: 129 additions & 0 deletions internal/dataplane/sendconfig/inmemory_schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package sendconfig

import (
"github.com/kong/deck/file"
)

// DBLessConfig is the configuration that is sent to Kong's data-plane via its `POST /config` endpoint after being
// marshalled to JSON.
// It uses file.Content as its base schema, but it also includes additional fields that are not part of decK's schema.
type DBLessConfig struct {
file.Content
ConsumerGroupConsumerRelationships []ConsumerGroupConsumerRelationship `json:"consumer_group_consumers,omitempty"`
ConsumerGroupPluginRelationships []ConsumerGroupPluginRelationship `json:"consumer_group_plugins,omitempty"`
}

// ConsumerGroupConsumerRelationship is a relationship between a ConsumerGroup and a Consumer.
type ConsumerGroupConsumerRelationship struct {
ConsumerGroup string `json:"consumer_group"`
Consumer string `json:"consumer"`
}

// ConsumerGroupPluginRelationship is a relationship between a ConsumerGroup and a Plugin.
type ConsumerGroupPluginRelationship struct {
ConsumerGroup string `json:"consumer_group"`
Plugin string `json:"plugin"`
}

type DefaultContentToDBLessConfigConverter struct{}

func (DefaultContentToDBLessConfigConverter) Convert(content *file.Content) DBLessConfig {
dblessConfig := DBLessConfig{
Content: *content,
}

// DBLess schema does not support decK's Info section.
dblessConfig.Content.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.Plugins[].ConsumerGroup
// - Content.Consumers[].Group,
// - Content.ConsumerGroups[].Plugins
// - Content.ConsumerGroups[].Consumers
//
// In their place it creates relationships slices:
// - ConsumerGroupConsumerRelationships
// - ConsumerGroupPluginRelationships
func convertConsumerGroups(dblessConfig *DBLessConfig) {
// DBLess schema does not support Consumer.Groups field...
for i, c := range dblessConfig.Content.Consumers {
// ... therefore we need to convert them to relationships...
for _, cg := range dblessConfig.Content.Consumers[i].Groups {
dblessConfig.ConsumerGroupConsumerRelationships = append(dblessConfig.ConsumerGroupConsumerRelationships, ConsumerGroupConsumerRelationship{
ConsumerGroup: *cg.Name,
Consumer: *c.Username,
})
}
// ... and remove them from the Consumer struct.
dblessConfig.Content.Consumers[i].Groups = nil
}

// DBLess schema does not support Consumer.ConsumerGroup field...
for i, p := range dblessConfig.Content.Plugins {
// ... therefore we need to convert it to relationships...
if p.ConsumerGroup != nil {
dblessConfig.ConsumerGroupPluginRelationships = append(dblessConfig.ConsumerGroupPluginRelationships, ConsumerGroupPluginRelationship{
ConsumerGroup: *p.ConsumerGroup.Name,
Plugin: *p.Name,
})
}
// ... and remove it from the Plugin struct.
dblessConfig.Content.Plugins[i].ConsumerGroup = nil
}

// DBLess schema does not support ConsumerGroups.Consumers and ConsumerGroups.Plugins fields so we need to remove
// them.
for i := range dblessConfig.Content.ConsumerGroups {
dblessConfig.Content.ConsumerGroups[i].Consumers = nil
dblessConfig.Content.ConsumerGroups[i].Plugins = nil
}
}
Loading

0 comments on commit 9dfe212

Please sign in to comment.