Skip to content

Commit

Permalink
Add support for foreign key references in partial sync
Browse files Browse the repository at this point in the history
The b.intermediate collection is populated when entities
are discovered in targetState, allowing for in-file foreign
key references.

The new `deck gateway apply` command allows you to sync
partial configuration, and so must populate b.intermediate
from the currentState.

This results in duplicate entity errors, and so an
AddIgnoringDuplicates method has been added to entities
that may be a foreign key
  • Loading branch information
mheap committed Jan 7, 2025
1 parent 84694fd commit 61a30c0
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 6 deletions.
2 changes: 2 additions & 0 deletions pkg/dump/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type Config struct {

// IsConsumerGroupScopedPluginSupported
IsConsumerGroupScopedPluginSupported bool

IsPartialApply bool
}

func deduplicate(stringSlice []string) []string {
Expand Down
118 changes: 112 additions & 6 deletions pkg/file/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ type stateBuilder struct {

removePathHandlingFromExpressionRoute bool

isPartialApply bool

err error
}

Expand Down Expand Up @@ -167,6 +169,23 @@ func (b *stateBuilder) consumerGroups() {
return
}

// Load all existing consumer groups in to the immediate state for
// foreign key lookups if we're doing a partial apply
if b.isPartialApply {
consumerGroups, err := b.currentState.ConsumerGroups.GetAll()
if err != nil {
b.err = err
return
}
for _, cg := range consumerGroups {
err = b.intermediate.ConsumerGroups.Add(*cg)
if err != nil {
b.err = err
return
}
}
}

for _, cg := range b.targetContent.ConsumerGroups {
current, err := b.currentState.ConsumerGroups.Get(*cg.Name)
if utils.Empty(cg.ID) {
Expand Down Expand Up @@ -199,7 +218,7 @@ func (b *stateBuilder) consumerGroups() {
ConsumerGroup: &cg.ConsumerGroup,
}

err = b.intermediate.ConsumerGroups.Add(state.ConsumerGroup{ConsumerGroup: cg.ConsumerGroup})
err = b.intermediate.ConsumerGroups.AddIgnoringDuplicates(state.ConsumerGroup{ConsumerGroup: cg.ConsumerGroup})
if err != nil {
b.err = err
return
Expand Down Expand Up @@ -248,6 +267,23 @@ func (b *stateBuilder) certificates() {
return
}

// Load all existing certificates in to the immediate state for
// foreign key lookups if we're doing a partial apply
if b.isPartialApply {
certs, err := b.currentState.Certificates.GetAll()
if err != nil {
b.err = err
return
}
for _, c := range certs {
err = b.intermediate.Certificates.Add(*c)
if err != nil {
b.err = err
return
}
}
}

for i := range b.targetContent.Certificates {
c := b.targetContent.Certificates[i]
if utils.Empty(c.ID) {
Expand Down Expand Up @@ -314,6 +350,23 @@ func (b *stateBuilder) caCertificates() {
return
}

// Load all existing CA certificates in to the immediate state for
// foreign key lookups if we're doing a partial apply
if b.isPartialApply {
certs, err := b.currentState.CACertificates.GetAll()
if err != nil {
b.err = err
return
}
for _, c := range certs {
err = b.intermediate.CACertificates.Add(*c)
if err != nil {
b.err = err
return
}
}
}

for _, c := range b.targetContent.CACertificates {
cert, err := b.currentState.CACertificates.Get(*c.Cert)
if utils.Empty(c.ID) {
Expand Down Expand Up @@ -388,11 +441,11 @@ func (b *stateBuilder) ingestConsumerGroupConsumer(cgID *string, c *FConsumer) (
}

b.rawState.Consumers = append(b.rawState.Consumers, &c.Consumer)
err = b.intermediate.Consumers.Add(state.Consumer{Consumer: c.Consumer})
err = b.intermediate.Consumers.AddIgnoringDuplicates(state.Consumer{Consumer: c.Consumer})
if err != nil {
return nil, err
}
err = b.intermediate.ConsumerGroupConsumers.Add(state.ConsumerGroupConsumer{
err = b.intermediate.ConsumerGroupConsumers.AddIgnoringDuplicates(state.ConsumerGroupConsumer{
ConsumerGroupConsumer: kong.ConsumerGroupConsumer{
ConsumerGroup: &kong.ConsumerGroup{ID: cgID},
Consumer: &c.Consumer,
Expand All @@ -409,6 +462,24 @@ func (b *stateBuilder) consumers() {
return
}

// Load all existing consumers in to the immediate state for
// foreign key lookups if we're doing a partial apply
if b.isPartialApply {
consumers, err := b.currentState.Consumers.GetAll()
if err != nil {
b.err = err
return
}

for _, c := range consumers {
err = b.intermediate.Consumers.Add(*c)
if err != nil {
b.err = err
return
}
}
}

for _, c := range b.targetContent.Consumers {

var (
Expand Down Expand Up @@ -473,7 +544,7 @@ func (b *stateBuilder) consumers() {
}
if !consumerAlreadyAdded {
b.rawState.Consumers = append(b.rawState.Consumers, &c.Consumer)
err = b.intermediate.Consumers.Add(state.Consumer{Consumer: c.Consumer})
err = b.intermediate.Consumers.AddIgnoringDuplicates(state.Consumer{Consumer: c.Consumer})
if err != nil {
b.err = err
return
Expand Down Expand Up @@ -865,6 +936,23 @@ func (b *stateBuilder) services() {
return
}

// Load all existing services in to the immediate state for
// foreign key lookups if we're doing a partial apply
if b.isPartialApply {
services, err := b.currentState.Services.GetAll()
if err != nil {
b.err = err
return
}
for _, s := range services {
err = b.intermediate.Services.Add(*s)
if err != nil {
b.err = err
return
}
}
}

for _, s := range b.targetContent.Services {
err := b.ingestService(&s)
if err != nil {
Expand Down Expand Up @@ -912,7 +1000,7 @@ func (b *stateBuilder) ingestService(s *FService) error {
s.Service.CreatedAt = svc.CreatedAt
}
b.rawState.Services = append(b.rawState.Services, &s.Service)
err = b.intermediate.Services.Add(state.Service{Service: s.Service})
err = b.intermediate.Services.AddIgnoringDuplicates(state.Service{Service: s.Service})
if err != nil {
return err
}
Expand Down Expand Up @@ -952,6 +1040,24 @@ func (b *stateBuilder) routes() {
return
}

// Load all existing routes in to the immediate state for
// foreign key lookups if we're doing a partial apply
if b.isPartialApply {
routes, err := b.currentState.Routes.GetAll()
if err != nil {
b.err = err
return
}

for _, r := range routes {
err = b.intermediate.Routes.Add(*r)
if err != nil {
b.err = err
return
}
}
}

for _, r := range b.targetContent.Routes {
if err := b.ingestRoute(r); err != nil {
b.err = err
Expand Down Expand Up @@ -1418,7 +1524,7 @@ func (b *stateBuilder) ingestRoute(r FRoute) error {
}

b.rawState.Routes = append(b.rawState.Routes, &r.Route)
err = b.intermediate.Routes.Add(state.Route{Route: r.Route})
err = b.intermediate.Routes.AddIgnoringDuplicates(state.Route{Route: r.Route})
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/file/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func Get(ctx context.Context, fileContent *Content, opt RenderConfig, dumpConfig
builder.skipCACerts = dumpConfig.SkipCACerts
builder.isKonnect = dumpConfig.KonnectControlPlane != ""
builder.includeLicenses = dumpConfig.IncludeLicenses
builder.isPartialApply = dumpConfig.IsPartialApply

if len(dumpConfig.SelectorTags) > 0 {
builder.selectTags = dumpConfig.SelectorTags
Expand Down
38 changes: 38 additions & 0 deletions pkg/state/consumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,44 @@ var consumerTableSchema = &memdb.TableSchema{
// ConsumersCollection stores and indexes Kong Consumers.
type ConsumersCollection collection

func (k *ConsumersCollection) AddIgnoringDuplicates(consumer Consumer) error {
// Detect duplicates
if !utils.Empty(consumer.ID) {
c, err := k.GetByIDOrUsername(*consumer.ID)
if c != nil {
return nil
}

if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
}

if !utils.Empty(consumer.Username) {
c, err := k.GetByIDOrUsername(*consumer.Username)
if c != nil {
return nil
}

if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
}

// Check for custom ID
if !utils.Empty(consumer.CustomID) {
c, err := k.GetByCustomID(*consumer.CustomID)
if c != nil {
return nil
}
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
}

return k.Add(consumer)
}

// Add adds a consumer to the collection
// An error is thrown if consumer.ID is empty.
func (k *ConsumersCollection) Add(consumer Consumer) error {
Expand Down
25 changes: 25 additions & 0 deletions pkg/state/consumer_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,31 @@ var consumerGroupTableSchema = &memdb.TableSchema{
// consumerGroupsCollection stores and indexes Kong consumerGroups.
type ConsumerGroupsCollection collection

func (k *ConsumerGroupsCollection) AddIgnoringDuplicates(consumerGroup ConsumerGroup) error {
// Detect duplicates
if !utils.Empty(consumerGroup.ID) {
cg, err := k.Get(*consumerGroup.ID)
if cg != nil {
return nil
}
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
}

if !utils.Empty(consumerGroup.Name) {
cg, err := k.Get(*consumerGroup.Name)
if cg != nil {
return nil
}
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
}

return k.Add(consumerGroup)
}

// Add adds an consumerGroup to the collection.
// consumerGroup.ID should not be nil else an error is thrown.
func (k *ConsumerGroupsCollection) Add(consumerGroup ConsumerGroup) error {
Expand Down
25 changes: 25 additions & 0 deletions pkg/state/consumer_group_consumers.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,31 @@ func validateConsumerGroup(consumer *ConsumerGroupConsumer) error {
// ConsumerGroupConsumersCollection stores and indexes Kong consumerGroupConsumers.
type ConsumerGroupConsumersCollection collection

func (k *ConsumerGroupConsumersCollection) AddIgnoringDuplicates(consumer ConsumerGroupConsumer) error {
// Detect duplicates
if !utils.Empty(consumer.Consumer.ID) {
cgc, err := k.Get(*consumer.Consumer.ID, *consumer.ConsumerGroup.ID)
if cgc != nil {
return nil
}
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
}

if !utils.Empty(consumer.Consumer.Username) {
cgc, err := k.Get(*consumer.Consumer.Username, *consumer.ConsumerGroup.ID)
if cgc != nil {
return nil
}
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
}

return k.Add(consumer)
}

// Add adds a consumerGroupConsumer to the collection.
func (k *ConsumerGroupConsumersCollection) Add(consumer ConsumerGroupConsumer) error {
if utils.Empty(consumer.Consumer.ID) {
Expand Down
41 changes: 41 additions & 0 deletions pkg/state/consumer_group_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package state

import (
"testing"

"github.com/kong/go-kong/kong"
"github.com/stretchr/testify/assert"
)

func consumerGroupsCollection() *ConsumerGroupsCollection {
return state().ConsumerGroups
}

func TestConsumerGroupInsert(t *testing.T) {
assert := assert.New(t)
collection := consumerGroupsCollection()

var cg ConsumerGroup

assert.NotNil(collection.Add(cg))

cg.ID = kong.String("my-id")
cg.Name = kong.String("first")
assert.Nil(collection.Add(cg))

// re-insert
assert.NotNil(collection.Add(cg))
}

func TestConsumerGroupInsertIgnoreDuplicate(t *testing.T) {
assert := assert.New(t)
collection := consumerGroupsCollection()

var cg ConsumerGroup
cg.ID = kong.String("my-id")
cg.Name = kong.String("first")
err := collection.Add(cg)
assert.Nil(err)
err = collection.AddIgnoringDuplicates(cg)
assert.Nil(err)
}
Loading

0 comments on commit 61a30c0

Please sign in to comment.