From 65ac18af164f72dcd6ffb143cf9baeb6d682ef3d Mon Sep 17 00:00:00 2001 From: Prashansa Kulshrestha Date: Tue, 11 Feb 2025 14:17:37 +0530 Subject: [PATCH 1/4] feat: added client apis for partials entity --- kong/client.go | 2 + kong/configuration.go | 2 +- kong/partial.go | 12 ++ kong/partial_service.go | 216 +++++++++++++++++++++ kong/partial_service_test.go | 348 ++++++++++++++++++++++++++++++++++ kong/zz_generated.deepcopy.go | 42 ++++ 6 files changed, 621 insertions(+), 1 deletion(-) create mode 100644 kong/partial.go create mode 100644 kong/partial_service.go create mode 100644 kong/partial_service_test.go diff --git a/kong/client.go b/kong/client.go index 6bde3fa2c..2f4bc650d 100644 --- a/kong/client.go +++ b/kong/client.go @@ -70,6 +70,7 @@ type Client struct { KonnectApplication AbstractKonnectApplicationService Licenses AbstractLicenseService FilterChains AbstractFilterChainService + Partials AbstractPartialService credentials abstractCredentialService KeyAuths AbstractKeyAuthService @@ -169,6 +170,7 @@ func NewClient(baseURL *string, client *http.Client) (*Client, error) { kong.KonnectApplication = (*KonnectApplicationService)(&kong.common) kong.Licenses = (*LicenseService)(&kong.common) kong.FilterChains = (*FilterChainService)(&kong.common) + kong.Partials = (*PartialService)(&kong.common) kong.credentials = (*credentialService)(&kong.common) kong.KeyAuths = (*KeyAuthService)(&kong.common) diff --git a/kong/configuration.go b/kong/configuration.go index a8757597e..902b37487 100644 --- a/kong/configuration.go +++ b/kong/configuration.go @@ -4,7 +4,7 @@ import ( "encoding/json" ) -// Configuration represents a config of a plugin in Kong. +// Configuration represents a config of a plugin/partial in Kong. type Configuration map[string]interface{} // DeepCopyInto copies the receiver, writing into out. in must be non-nil. diff --git a/kong/partial.go b/kong/partial.go new file mode 100644 index 000000000..2e3f8d837 --- /dev/null +++ b/kong/partial.go @@ -0,0 +1,12 @@ +package kong + +// Partial represents a Partial in Kong. +// +k8s:deepcopy-gen=true +type Partial struct { + ID *string `json:"id,omitempty" yaml:"id,omitempty"` + Name *string `json:"name,omitempty" yaml:"name,omitempty"` + Type *string `json:"type,omitempty" yaml:"type,omitempty"` + Config Configuration `json:"config,omitempty" yaml:"config,omitempty"` + CreatedAt *int `json:"created_at,omitempty" yaml:"created_at,omitempty"` + UpdatedAt *int `json:"updated_at,omitempty" yaml:"updated_at,omitempty"` +} diff --git a/kong/partial_service.go b/kong/partial_service.go new file mode 100644 index 000000000..6ab181669 --- /dev/null +++ b/kong/partial_service.go @@ -0,0 +1,216 @@ +package kong + +import ( + "context" + "encoding/json" + "fmt" +) + +// AbstractPartialService handles Partials in Kong. +type AbstractPartialService interface { + // Create creates a Partial in Kong. + Create(ctx context.Context, partial *Partial) (*Partial, error) + // Get fetches a Partial in Kong. + Get(ctx context.Context, nameOrID *string) (*Partial, error) + // Update updates a Partial in Kong + Update(ctx context.Context, partial *Partial) (*Partial, error) + // Delete deletes a Partial in Kong + Delete(ctx context.Context, nameOrID *string) error + // List fetches a list of Partials in Kong. + List(ctx context.Context, opt *ListOpt) ([]*Partial, *ListOpt, error) + // ListAll fetches all Partials in Kong. + ListAll(ctx context.Context) ([]*Partial, error) +} + +// PartialService handles Partials in Kong. +type PartialService service + +// Create creates a Partial in Kong. +// If an ID is specified, it will be used to +// create a Partial in Kong, otherwise an ID +// is auto-generated. +func (s *PartialService) Create(ctx context.Context, + partial *Partial, +) (*Partial, error) { + if partial == nil { + return nil, fmt.Errorf("cannot create a nil partial") + } + + if partial.Type == nil { + return nil, fmt.Errorf("partial type cannot be nil") + } + + queryPath := "/partials" + method := "POST" + if partial.ID != nil { + queryPath = queryPath + "/" + *partial.ID + method = "PUT" + } + + req, err := s.client.NewRequest(method, queryPath, nil, partial) + if err != nil { + return nil, err + } + + var createdPartial Partial + _, err = s.client.Do(ctx, req, &createdPartial) + if err != nil { + return nil, err + } + return &createdPartial, nil +} + +// Get fetches a Partial in Kong. +func (s *PartialService) Get(ctx context.Context, + nameOrID *string, +) (*Partial, error) { + if isEmptyString(nameOrID) { + return nil, fmt.Errorf("nameOrID cannot be nil for Get operation") + } + + endpoint := fmt.Sprintf("/partials/%v", *nameOrID) + req, err := s.client.NewRequest("GET", endpoint, nil, nil) + if err != nil { + return nil, err + } + + var partial Partial + _, err = s.client.Do(ctx, req, &partial) + if err != nil { + return nil, err + } + return &partial, nil +} + +// Update updates a Partial in Kong +func (s *PartialService) Update(ctx context.Context, + partial *Partial, +) (*Partial, error) { + if partial == nil { + return nil, fmt.Errorf("cannot update a nil partial") + } + + if isEmptyString(partial.ID) { + return nil, fmt.Errorf("ID cannot be nil for Update operation") + } + + endpoint := fmt.Sprintf("/partials/%v", *partial.ID) + req, err := s.client.NewRequest("PATCH", endpoint, nil, partial) + if err != nil { + return nil, err + } + + var updatedPartial Partial + _, err = s.client.Do(ctx, req, &updatedPartial) + if err != nil { + return nil, err + } + return &updatedPartial, nil +} + +// Delete deletes a Partial in Kong +func (s *PartialService) Delete(ctx context.Context, + nameOrID *string, +) error { + if isEmptyString(nameOrID) { + return fmt.Errorf("nameOrID cannot be nil for Delete operation") + } + + // TODO: Uncomment when /partials/:id/links endpoint is enabled + // plugins, _, err := s.GetLinkedPlugins(ctx, nameOrID, nil) + // if err != nil { + // return err + // } + + // if len(plugins) != 0 { + // return fmt.Errorf("cannot delete partial %v, it is still linked to plugins: %v", + // *nameOrID, plugins) + // } + + endpoint := fmt.Sprintf("/partials/%v", *nameOrID) + req, err := s.client.NewRequest("DELETE", endpoint, nil, nil) + if err != nil { + return err + } + + _, err = s.client.Do(ctx, req, nil) + return err +} + +// List fetches a list of Partials in Kong. +// opt can be used to control pagination. +func (s *PartialService) List(ctx context.Context, + opt *ListOpt, +) ([]*Partial, *ListOpt, error) { + data, next, err := s.client.list(ctx, "/partials", opt) + if err != nil { + return nil, nil, err + } + var partials []*Partial + + for _, object := range data { + b, err := object.MarshalJSON() + if err != nil { + return nil, nil, err + } + var p Partial + err = json.Unmarshal(b, &p) + if err != nil { + return nil, nil, err + } + partials = append(partials, &p) + } + + return partials, next, nil +} + +// ListAll fetches all Partials in Kong. +func (s *PartialService) ListAll(ctx context.Context) ([]*Partial, error) { + var partials, data []*Partial + var err error + opt := &ListOpt{Size: pageSize} + + for opt != nil { + data, opt, err = s.List(ctx, opt) + if err != nil { + return nil, err + } + partials = append(partials, data...) + } + return partials, nil +} + +// TODO: Uncomment when /partials/:id/links endpoint is enabled +// GetLinkedPlugins fetches a list of Plugins in Kong, +// linked with the Partial. +// opt can be used to control pagination. +// func (s *PartialService) GetLinkedPlugins(ctx context.Context, +// nameOrID *string, opt *ListOpt, +// ) ([]*Plugin, *ListOpt, error) { +// if isEmptyString(nameOrID) { +// return nil, nil, fmt.Errorf("nameOrID cannot be nil for GetLinkedPlugins operation") +// } + +// endpoint := fmt.Sprintf("/partials/%v/links", *nameOrID) +// data, next, err := s.client.list(ctx, endpoint, opt) +// if err != nil { +// return nil, nil, err +// } + +// var plugins []*Plugin + +// for _, object := range data { +// b, err := object.MarshalJSON() +// if err != nil { +// return nil, nil, err +// } +// var p Plugin +// err = json.Unmarshal(b, &p) +// if err != nil { +// return nil, nil, err +// } +// plugins = append(plugins, &p) +// } + +// return plugins, next, nil +// } diff --git a/kong/partial_service_test.go b/kong/partial_service_test.go new file mode 100644 index 000000000..024467e61 --- /dev/null +++ b/kong/partial_service_test.go @@ -0,0 +1,348 @@ +package kong + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var defaultConfigRedisEEPartial = Configuration{ + "cluster_max_redirections": float64(5), + "cluster_nodes": nil, + "connect_timeout": float64(2000), + "connection_is_proxied": bool(false), + "database": float64(0), + "host": "127.0.0.1", + "keepalive_backlog": nil, + "keepalive_pool_size": float64(256), + "password": nil, + "port": float64(6379), + "read_timeout": float64(2000), + "send_timeout": float64(2000), + "sentinel_master": nil, + "sentinel_nodes": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": bool(false), + "ssl_verify": bool(false), + "username": nil, +} + +func TestPartialServiceCreateEndpoint(t *testing.T) { + RunWhenEnterprise(t, ">=3.10.0", RequiredFeatures{}) + + require := require.New(t) + assert := assert.New(t) + + client, err := NewTestClient(nil, nil) + require.NoError(err) + require.NotNil(client) + + t.Run("invalid partial creation - nil partial", func(_ *testing.T) { + notCreatedPartial, err := client.Partials.Create(defaultCtx, nil) + + assert.Error(err) + assert.ErrorContains(err, "cannot create a nil partial") + assert.Nil(notCreatedPartial) + }) + + t.Run("invalid partial creation - empty partial", func(_ *testing.T) { + notCreatedPartial, err := client.Partials.Create(defaultCtx, &Partial{}) + + assert.Error(err) + assert.ErrorContains(err, "partial type cannot be nil") + assert.Nil(notCreatedPartial) + }) + + t.Run("invalid partial creation - nil type", func(_ *testing.T) { + notCreatedPartial, err := client.Partials.Create(defaultCtx, &Partial{ + Name: String("my-test-partial"), + }) + + assert.Error(err) + assert.ErrorContains(err, "partial type cannot be nil") + assert.Nil(notCreatedPartial) + }) + + t.Run("create valid partial", func(_ *testing.T) { + validPartial := &Partial{ + Name: String("my-test-partial"), + Type: String("redis-ee"), + } + + createdPartial, err := client.Partials.Create(defaultCtx, validPartial) + require.NoError(err) + require.NotNil(createdPartial) + assert.Equal("my-test-partial", *createdPartial.Name) + assert.Equal("redis-ee", *createdPartial.Type) + assert.Equal(defaultConfigRedisEEPartial, createdPartial.Config) + + t.Cleanup(func() { + if createdPartial != nil { + assert.NoError(client.Partials.Delete(defaultCtx, createdPartial.Name)) + } + }) + }) +} + +func TestPartialServiceGetEndpoint(t *testing.T) { + RunWhenEnterprise(t, ">=3.10.0", RequiredFeatures{}) + + require := require.New(t) + assert := assert.New(t) + + client, err := NewTestClient(nil, nil) + require.NoError(err) + require.NotNil(client) + + t.Run("invalid get - empty name/id", func(_ *testing.T) { + noPartial, err := client.Partials.Get(defaultCtx, String("")) + + assert.Error(err) + assert.ErrorContains(err, "nameOrID cannot be nil for Get operation") + assert.Nil(noPartial) + }) + + t.Run("get by id", func(_ *testing.T) { + createdPartial, err := client.Partials.Create(defaultCtx, &Partial{ + Name: String("my-test-partial"), + Type: String("redis-ee"), + }) + require.NoError(err) + + t.Cleanup(func() { + if createdPartial != nil { + assert.NoError(client.Partials.Delete(defaultCtx, createdPartial.Name)) + } + }) + + fetchedPartial, err := client.Partials.Get(defaultCtx, createdPartial.ID) + assert.NoError(err) + assert.NotNil(fetchedPartial) + assert.Equal("my-test-partial", *fetchedPartial.Name) + assert.Equal("redis-ee", *fetchedPartial.Type) + }) + + t.Run("get by name", func(_ *testing.T) { + createdPartial, err := client.Partials.Create(defaultCtx, &Partial{ + Name: String("my-demo-partial"), + Type: String("redis-ee"), + }) + require.NoError(err) + + t.Cleanup(func() { + if createdPartial != nil { + assert.NoError(client.Partials.Delete(defaultCtx, createdPartial.Name)) + } + }) + + fetchedPartial, err := client.Partials.Get(defaultCtx, String("my-demo-partial")) + assert.NoError(err) + assert.NotNil(fetchedPartial) + assert.Equal("my-demo-partial", *fetchedPartial.Name) + assert.Equal("redis-ee", *fetchedPartial.Type) + }) +} + +func TestPartialServiceUpdateEndpoint(t *testing.T) { + RunWhenEnterprise(t, ">=3.10.0", RequiredFeatures{}) + + require := require.New(t) + assert := assert.New(t) + + client, err := NewTestClient(nil, nil) + require.NoError(err) + require.NotNil(client) + + t.Run("invalid update - nil partial", func(_ *testing.T) { + notUpdatedPartial, err := client.Partials.Update(defaultCtx, nil) + + assert.Error(err) + assert.ErrorContains(err, "cannot update a nil partial") + assert.Nil(notUpdatedPartial) + }) + + t.Run("valid update", func(_ *testing.T) { + createdPartial, err := client.Partials.Create(defaultCtx, &Partial{ + Name: String("my-test-partial"), + Type: String("redis-ee"), + }) + require.NoError(err) + + t.Cleanup(func() { + if createdPartial != nil { + assert.NoError(client.Partials.Delete(defaultCtx, createdPartial.Name)) + } + }) + + // initially created with default config + assert.Equal(defaultConfigRedisEEPartial, createdPartial.Config) + + // config to update + createdPartial.Config = Configuration{ + "send_timeout": 2001, + "read_timeout": 3001, + "connect_timeout": 4001, + } + + // update partial + updatedPartial, err := client.Partials.Update(defaultCtx, createdPartial) + require.NoError(err) + require.NotNil(updatedPartial) + + assert.Equal(float64(2001), updatedPartial.Config["send_timeout"]) + assert.Equal(float64(3001), updatedPartial.Config["read_timeout"]) + assert.Equal(float64(4001), updatedPartial.Config["connect_timeout"]) + }) +} + +func TestPartialServiceDeleteEndpoint(t *testing.T) { + RunWhenEnterprise(t, ">=3.10.0", RequiredFeatures{}) + + require := require.New(t) + assert := assert.New(t) + + client, err := NewTestClient(nil, nil) + require.NoError(err) + require.NotNil(client) + + t.Run("invalid delete - empty name/id", func(_ *testing.T) { + err := client.Partials.Delete(defaultCtx, String("")) + + assert.Error(err) + assert.ErrorContains(err, "nameOrID cannot be nil for Delete operation") + }) + + t.Run("valid delete - by id", func(_ *testing.T) { + createdPartial, err := client.Partials.Create(defaultCtx, &Partial{ + Name: String("my-test-partial"), + Type: String("redis-ee"), + }) + require.NoError(err) + + t.Cleanup(func() { + if createdPartial != nil { + assert.NoError(client.Partials.Delete(defaultCtx, createdPartial.Name)) + } + }) + + err = client.Partials.Delete(defaultCtx, createdPartial.ID) + assert.NoError(err) + }) + + t.Run("valid delete - by name", func(_ *testing.T) { + createdPartial, err := client.Partials.Create(defaultCtx, &Partial{ + Name: String("my-test-partial"), + Type: String("redis-ee"), + }) + require.NoError(err) + + t.Cleanup(func() { + if createdPartial != nil { + assert.NoError(client.Partials.Delete(defaultCtx, createdPartial.Name)) + } + }) + + err = client.Partials.Delete(defaultCtx, String("my-test-partial")) + assert.NoError(err) + }) +} + +func TestPartialServiceListEndpoint(t *testing.T) { + RunWhenEnterprise(t, ">=3.10.0", RequiredFeatures{}) + + require := require.New(t) + assert := assert.New(t) + + client, err := NewTestClient(nil, nil) + require.NoError(err) + require.NotNil(client) + + populatePartials(t, client) + + // Testing pagination + partialsFromKong := []*Partial{} + + // first page + page1, next, err := client.Partials.List(defaultCtx, &ListOpt{Size: 1}) + assert.NoError(err) + require.NotNil(next) + assert.NotNil(page1) + assert.Len(page1, 1) + partialsFromKong = append(partialsFromKong, page1...) + + // last page + next.Size = 3 + page2, next, err := client.Partials.List(defaultCtx, next) + assert.NoError(err) + assert.Nil(next) + assert.NotNil(page2) + assert.Len(page2, 3) + partialsFromKong = append(partialsFromKong, page2...) + + assert.Len(partialsFromKong, 4) + for _, p := range partialsFromKong { + assert.Equal("redis-ee", *p.Type) + assert.Contains(*p.Name, "test-partial-") + } + + t.Cleanup(func() { + for _, p := range partialsFromKong { + assert.NoError(client.Partials.Delete(defaultCtx, p.Name)) + } + }) +} + +func TestPartialServiceListAllEndpoint(t *testing.T) { + RunWhenEnterprise(t, ">=3.10.0", RequiredFeatures{}) + + require := require.New(t) + assert := assert.New(t) + + client, err := NewTestClient(nil, nil) + require.NoError(err) + require.NotNil(client) + populatePartials(t, client) + + partials, err := client.Partials.ListAll(defaultCtx) + require.NoError(err) + require.NotNil(partials) + assert.Len(partials, 4) + + t.Cleanup(func() { + for _, p := range partials { + assert.NoError(client.Partials.Delete(defaultCtx, p.Name)) + } + }) +} + +func populatePartials(t *testing.T, client *Client) { + require := require.New(t) + partials := []*Partial{ + { + Name: String("test-partial-1"), + Type: String("redis-ee"), + }, + { + Name: String("test-partial-2"), + Type: String("redis-ee"), + }, + { + Name: String("test-partial-3"), + Type: String("redis-ee"), + }, + { + Name: String("test-partial-4"), + Type: String("redis-ee"), + }, + } + + for _, p := range partials { + createdPartial, err := client.Partials.Create(defaultCtx, p) + require.NoError(err) + require.NotNil(createdPartial) + } +} diff --git a/kong/zz_generated.deepcopy.go b/kong/zz_generated.deepcopy.go index 5aff0f4f6..b351b7b11 100644 --- a/kong/zz_generated.deepcopy.go +++ b/kong/zz_generated.deepcopy.go @@ -1705,6 +1705,48 @@ func (in *PEM) DeepCopy() *PEM { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Partial) DeepCopyInto(out *Partial) { + *out = *in + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.Type != nil { + in, out := &in.Type, &out.Type + *out = new(string) + **out = **in + } + out.Config = in.Config.DeepCopy() + if in.CreatedAt != nil { + in, out := &in.CreatedAt, &out.CreatedAt + *out = new(int) + **out = **in + } + if in.UpdatedAt != nil { + in, out := &in.UpdatedAt, &out.UpdatedAt + *out = new(int) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Partial. +func (in *Partial) DeepCopy() *Partial { + if in == nil { + return nil + } + out := new(Partial) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PassiveHealthcheck) DeepCopyInto(out *PassiveHealthcheck) { *out = *in From 9f7d91abf8a642be0cff4ebf3e72ead7206ae373 Mon Sep 17 00:00:00 2001 From: Prashansa Kulshrestha Date: Tue, 11 Feb 2025 14:59:12 +0530 Subject: [PATCH 2/4] feat: added partial link to plugin --- kong/plugin.go | 7 +++++++ kong/zz_generated.deepcopy.go | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/kong/plugin.go b/kong/plugin.go index 646169a51..934748132 100644 --- a/kong/plugin.go +++ b/kong/plugin.go @@ -18,6 +18,13 @@ type Plugin struct { Ordering *PluginOrdering `json:"ordering,omitempty" yaml:"ordering,omitempty"` Protocols []*string `json:"protocols,omitempty" yaml:"protocols,omitempty"` Tags []*string `json:"tags,omitempty" yaml:"tags,omitempty"` + Partial []*PartialLink `json:"partials,omitempty" yaml:"partials,omitempty"` +} + +// +k8s:deepcopy-gen=true +type PartialLink struct { + *Partial + Path *string `json:"path,omitempty" yaml:"path,omitempty"` } // PluginOrdering contains before or after instructions for plugin execution order diff --git a/kong/zz_generated.deepcopy.go b/kong/zz_generated.deepcopy.go index b351b7b11..6f2573886 100644 --- a/kong/zz_generated.deepcopy.go +++ b/kong/zz_generated.deepcopy.go @@ -1747,6 +1747,32 @@ func (in *Partial) DeepCopy() *Partial { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PartialLink) DeepCopyInto(out *PartialLink) { + *out = *in + if in.Partial != nil { + in, out := &in.Partial, &out.Partial + *out = new(Partial) + (*in).DeepCopyInto(*out) + } + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartialLink. +func (in *PartialLink) DeepCopy() *PartialLink { + if in == nil { + return nil + } + out := new(PartialLink) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PassiveHealthcheck) DeepCopyInto(out *PassiveHealthcheck) { *out = *in @@ -1859,6 +1885,17 @@ func (in *Plugin) DeepCopyInto(out *Plugin) { } } } + if in.Partial != nil { + in, out := &in.Partial, &out.Partial + *out = make([]*PartialLink, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(PartialLink) + (*in).DeepCopyInto(*out) + } + } + } return } From 72320235bf3cf8a3ce2f05749b77251ce4bfbfd5 Mon Sep 17 00:00:00 2001 From: Prashansa Kulshrestha Date: Tue, 11 Feb 2025 15:22:44 +0530 Subject: [PATCH 3/4] fix: fixed partial linking with plugin --- kong/plugin.go | 2 +- kong/plugin_service_test.go | 60 +++++++++++++++++++++++++++++++++++ kong/zz_generated.deepcopy.go | 8 ++--- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/kong/plugin.go b/kong/plugin.go index 934748132..42289ceee 100644 --- a/kong/plugin.go +++ b/kong/plugin.go @@ -23,7 +23,7 @@ type Plugin struct { // +k8s:deepcopy-gen=true type PartialLink struct { - *Partial + ID *string `json:"id,omitempty" yaml:"id,omitempty"` Path *string `json:"path,omitempty" yaml:"path,omitempty"` } diff --git a/kong/plugin_service_test.go b/kong/plugin_service_test.go index e91b2eccb..5f14a7b57 100644 --- a/kong/plugin_service_test.go +++ b/kong/plugin_service_test.go @@ -924,6 +924,66 @@ func TestPluginsWithConsumerGroup(T *testing.T) { } } +func TestPluginsWithPartialLinks(T *testing.T) { + RunWhenEnterprise(T, ">=3.10.0", RequiredFeatures{}) + require := require.New(T) + assert := assert.New(T) + + client, err := NewTestClient(nil, nil) + require.NoError(err) + require.NotNil(client) + + // create a partial + newPartial, err := client.Partials.Create(defaultCtx, &Partial{ + Name: String("my-test-partial"), + Type: String("redis-ee"), + Config: Configuration{ + "send_timeout": 2001, + "read_timeout": 3001, + "connect_timeout": 4001, + }, + }) + require.NoError(err) + T.Cleanup(func() { + if newPartial != nil { + assert.NoError(client.Partials.Delete(defaultCtx, newPartial.Name)) + } + }) + + newPartial.CreatedAt = nil + newPartial.UpdatedAt = nil + + plugin := &Plugin{ + Name: String("rate-limiting-advanced"), + Config: Configuration{ + "limit": []interface{}{50}, + "window_size": []interface{}{30}, + }, + Partial: []*PartialLink{ + { + ID: newPartial.ID, + }, + }, + } + + createdPlugin, err := client.Plugins.Create(defaultCtx, plugin) + require.NoError(err) + require.NotNil(createdPlugin) + assert.Equal(createdPlugin.Partial[0].ID, newPartial.ID) + assert.Equal(String("config.redis"), createdPlugin.Partial[0].Path) + redisConfig, ok := createdPlugin.Config["redis"].(map[string]interface{}) + assert.True(ok) + assert.Equal(float64(2001), redisConfig["send_timeout"]) + assert.Equal(float64(3001), redisConfig["read_timeout"]) + assert.Equal(float64(4001), redisConfig["connect_timeout"]) + + T.Cleanup(func() { + if createdPlugin != nil { + assert.NoError(client.Plugins.Delete(defaultCtx, createdPlugin.ID)) + } + }) +} + func comparePlugins(T *testing.T, expected, actual []*Plugin) bool { var expectedNames, actualNames []string for _, plugin := range expected { diff --git a/kong/zz_generated.deepcopy.go b/kong/zz_generated.deepcopy.go index 6f2573886..8e53a04a2 100644 --- a/kong/zz_generated.deepcopy.go +++ b/kong/zz_generated.deepcopy.go @@ -1750,10 +1750,10 @@ func (in *Partial) DeepCopy() *Partial { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PartialLink) DeepCopyInto(out *PartialLink) { *out = *in - if in.Partial != nil { - in, out := &in.Partial, &out.Partial - *out = new(Partial) - (*in).DeepCopyInto(*out) + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in } if in.Path != nil { in, out := &in.Path, &out.Path From 9f1242468be4416c99f2410882fc48f4adcfdce4 Mon Sep 17 00:00:00 2001 From: Prashansa Kulshrestha Date: Tue, 18 Feb 2025 11:59:49 +0530 Subject: [PATCH 4/4] feat: added friendly name func --- kong/partial.go | 11 +++++++ kong/partial_service.go | 62 +++++++++++++++++------------------- kong/partial_service_test.go | 14 +------- 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/kong/partial.go b/kong/partial.go index 2e3f8d837..c82d7afe0 100644 --- a/kong/partial.go +++ b/kong/partial.go @@ -10,3 +10,14 @@ type Partial struct { CreatedAt *int `json:"created_at,omitempty" yaml:"created_at,omitempty"` UpdatedAt *int `json:"updated_at,omitempty" yaml:"updated_at,omitempty"` } + +// FriendlyName returns the endpoint key name or ID. +func (p *Partial) FriendlyName() string { + if p.Name != nil { + return *p.Name + } + if p.ID != nil { + return *p.ID + } + return "" +} diff --git a/kong/partial_service.go b/kong/partial_service.go index 6ab181669..0bcbdd7c8 100644 --- a/kong/partial_service.go +++ b/kong/partial_service.go @@ -116,7 +116,6 @@ func (s *PartialService) Delete(ctx context.Context, return fmt.Errorf("nameOrID cannot be nil for Delete operation") } - // TODO: Uncomment when /partials/:id/links endpoint is enabled // plugins, _, err := s.GetLinkedPlugins(ctx, nameOrID, nil) // if err != nil { // return err @@ -180,37 +179,36 @@ func (s *PartialService) ListAll(ctx context.Context) ([]*Partial, error) { return partials, nil } -// TODO: Uncomment when /partials/:id/links endpoint is enabled // GetLinkedPlugins fetches a list of Plugins in Kong, // linked with the Partial. // opt can be used to control pagination. -// func (s *PartialService) GetLinkedPlugins(ctx context.Context, -// nameOrID *string, opt *ListOpt, -// ) ([]*Plugin, *ListOpt, error) { -// if isEmptyString(nameOrID) { -// return nil, nil, fmt.Errorf("nameOrID cannot be nil for GetLinkedPlugins operation") -// } - -// endpoint := fmt.Sprintf("/partials/%v/links", *nameOrID) -// data, next, err := s.client.list(ctx, endpoint, opt) -// if err != nil { -// return nil, nil, err -// } - -// var plugins []*Plugin - -// for _, object := range data { -// b, err := object.MarshalJSON() -// if err != nil { -// return nil, nil, err -// } -// var p Plugin -// err = json.Unmarshal(b, &p) -// if err != nil { -// return nil, nil, err -// } -// plugins = append(plugins, &p) -// } - -// return plugins, next, nil -// } +func (s *PartialService) GetLinkedPlugins(ctx context.Context, + nameOrID *string, opt *ListOpt, +) ([]*Plugin, *ListOpt, error) { + if isEmptyString(nameOrID) { + return nil, nil, fmt.Errorf("nameOrID cannot be nil for GetLinkedPlugins operation") + } + + endpoint := fmt.Sprintf("/partials/%v/links", *nameOrID) + data, next, err := s.client.list(ctx, endpoint, opt) + if err != nil { + return nil, nil, err + } + + var plugins []*Plugin + + for _, object := range data { + b, err := object.MarshalJSON() + if err != nil { + return nil, nil, err + } + var p Plugin + err = json.Unmarshal(b, &p) + if err != nil { + return nil, nil, err + } + plugins = append(plugins, &p) + } + + return plugins, next, nil +} diff --git a/kong/partial_service_test.go b/kong/partial_service_test.go index 024467e61..ffa4281ba 100644 --- a/kong/partial_service_test.go +++ b/kong/partial_service_test.go @@ -223,29 +223,17 @@ func TestPartialServiceDeleteEndpoint(t *testing.T) { }) require.NoError(err) - t.Cleanup(func() { - if createdPartial != nil { - assert.NoError(client.Partials.Delete(defaultCtx, createdPartial.Name)) - } - }) - err = client.Partials.Delete(defaultCtx, createdPartial.ID) assert.NoError(err) }) t.Run("valid delete - by name", func(_ *testing.T) { - createdPartial, err := client.Partials.Create(defaultCtx, &Partial{ + _, err := client.Partials.Create(defaultCtx, &Partial{ Name: String("my-test-partial"), Type: String("redis-ee"), }) require.NoError(err) - t.Cleanup(func() { - if createdPartial != nil { - assert.NoError(client.Partials.Delete(defaultCtx, createdPartial.Name)) - } - }) - err = client.Partials.Delete(defaultCtx, String("my-test-partial")) assert.NoError(err) })