diff --git a/kong/client.go b/kong/client.go index 2fc6570b4..f4c63e954 100644 --- a/kong/client.go +++ b/kong/client.go @@ -64,6 +64,7 @@ type Client struct { Keys AbstractKeyService KeySets AbstractKeySetService Licenses AbstractLicenseService + FilterChains AbstractFilterChainService credentials abstractCredentialService KeyAuths AbstractKeyAuthService @@ -160,6 +161,7 @@ func NewClient(baseURL *string, client *http.Client) (*Client, error) { kong.Keys = (*KeyService)(&kong.common) kong.KeySets = (*KeySetService)(&kong.common) kong.Licenses = (*LicenseService)(&kong.common) + kong.FilterChains = (*FilterChainService)(&kong.common) kong.credentials = (*credentialService)(&kong.common) kong.KeyAuths = (*KeyAuthService)(&kong.common) diff --git a/kong/filter_chain.go b/kong/filter_chain.go new file mode 100644 index 000000000..c3974a757 --- /dev/null +++ b/kong/filter_chain.go @@ -0,0 +1,35 @@ +package kong + +// FilterChain represents a FilterChain in Kong. +// Read https://docs.konghq.com/gateway/latest/admin-api/#filter-chain-object +// +k8s:deepcopy-gen=true +type FilterChain struct { + ID *string `json:"id,omitempty" yaml:"id,omitempty"` + Name *string `json:"name,omitempty" yaml:"name,omitempty"` + Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + Route *Route `json:"route,omitempty" yaml:"route,omitempty"` + Service *Service `json:"service,omitempty" yaml:"service,omitempty"` + Filters []*Filter `json:"filters,omitempty" yaml:"filters,omitempty"` + CreatedAt *int `json:"created_at,omitempty" yaml:"created_at,omitempty"` + UpdatedAt *int `json:"updated_at,omitempty" yaml:"updated_at,omitempty"` + Tags []*string `json:"tags,omitempty" yaml:"tags,omitempty"` +} + +// Filter contains information about each filter in the chain +// +k8s:deepcopy-gen=true +type Filter struct { + Name *string `json:"name,omitempty" yaml:"name,omitempty"` + Config *string `json:"config,omitempty" yaml:"config,omitempty"` + Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` +} + +// FriendlyName returns the endpoint key name or ID. +func (f *FilterChain) FriendlyName() string { + if f.Name != nil { + return *f.Name + } + if f.ID != nil { + return *f.ID + } + return "" +} diff --git a/kong/filter_chain_service.go b/kong/filter_chain_service.go new file mode 100644 index 000000000..77e8ee97c --- /dev/null +++ b/kong/filter_chain_service.go @@ -0,0 +1,333 @@ +package kong + +import ( + "context" + "encoding/json" + "fmt" + "net/http" +) + +// AbstractFilterChainService handles FilterChains in Kong. +type AbstractFilterChainService interface { + // Create creates a FilterChain in Kong. + Create(ctx context.Context, filterChain *FilterChain) (*FilterChain, error) + // CreateForService creates a FilterChain in Kong. + CreateForService(ctx context.Context, serviceIDorName *string, filterChain *FilterChain) (*FilterChain, error) + // CreateForRoute creates a FilterChain in Kong. + CreateForRoute(ctx context.Context, routeIDorName *string, filterChain *FilterChain) (*FilterChain, error) + // Get fetches a FilterChain in Kong. + Get(ctx context.Context, nameOrID *string) (*FilterChain, error) + // Update updates a FilterChain in Kong + Update(ctx context.Context, filterChain *FilterChain) (*FilterChain, error) + // UpdateForService updates a FilterChain in Kong for a service + UpdateForService(ctx context.Context, serviceIDorName *string, filterChain *FilterChain) (*FilterChain, error) + // UpdateForRoute updates a FilterChain in Kong for a service + UpdateForRoute(ctx context.Context, routeIDorName *string, filterChain *FilterChain) (*FilterChain, error) + // Delete deletes a FilterChain in Kong + Delete(ctx context.Context, nameOrID *string) error + // DeleteForService deletes a FilterChain in Kong + DeleteForService(ctx context.Context, serviceIDorName *string, filterChainID *string) error + // DeleteForRoute deletes a FilterChain in Kong + DeleteForRoute(ctx context.Context, routeIDorName *string, filterChainID *string) error + // List fetches a list of FilterChains in Kong. + List(ctx context.Context, opt *ListOpt) ([]*FilterChain, *ListOpt, error) + // ListAll fetches all FilterChains in Kong. + ListAll(ctx context.Context) ([]*FilterChain, error) + // ListAllForService fetches all FilterChains in Kong enabled for a service. + ListAllForService(ctx context.Context, serviceIDorName *string) ([]*FilterChain, error) + // ListAllForRoute fetches all FilterChains in Kong enabled for a service. + ListAllForRoute(ctx context.Context, routeID *string) ([]*FilterChain, error) +} + +// FilterChainService handles FilterChains in Kong. +type FilterChainService service + +// Create creates a FilterChain in Kong. +// If an ID is specified, it will be used to +// create a filter chain in Kong, otherwise an ID +// is auto-generated. +func (s *FilterChainService) Create(ctx context.Context, + filterChain *FilterChain, +) (*FilterChain, error) { + queryPath := "/filter-chains" + method := "POST" + if filterChain.ID != nil { + queryPath = queryPath + "/" + *filterChain.ID + method = "PUT" + } + return s.sendRequest(ctx, filterChain, queryPath, method) +} + +// CreateForService creates a FilterChain in Kong at Service level. +// If an ID is specified, it will be used to +// create a filter chain in Kong, otherwise an ID +// is auto-generated. +func (s *FilterChainService) CreateForService(ctx context.Context, + serviceIDorName *string, filterChain *FilterChain, +) (*FilterChain, error) { + queryPath := "/filter-chains" + method := "POST" + if filterChain.ID != nil { + queryPath = queryPath + "/" + *filterChain.ID + method = "PUT" + } + if isEmptyString(serviceIDorName) { + return nil, fmt.Errorf("serviceIDorName cannot be nil") + } + + return s.sendRequest(ctx, filterChain, fmt.Sprintf("/services/%v"+queryPath, *serviceIDorName), method) +} + +// CreateForRoute creates a FilterChain in Kong at Route level. +// If an ID is specified, it will be used to +// create a filter chain in Kong, otherwise an ID +// is auto-generated. +func (s *FilterChainService) CreateForRoute(ctx context.Context, + routeIDorName *string, filterChain *FilterChain, +) (*FilterChain, error) { + queryPath := "/filter-chains" + method := "POST" + + if filterChain.ID != nil { + queryPath = queryPath + "/" + *filterChain.ID + method = "PUT" + } + if isEmptyString(routeIDorName) { + return nil, fmt.Errorf("routeIDorName cannot be nil") + } + + return s.sendRequest(ctx, filterChain, fmt.Sprintf("/routes/%v"+queryPath, *routeIDorName), method) +} + +// Get fetches a FilterChain in Kong. +func (s *FilterChainService) Get(ctx context.Context, + nameOrID *string, +) (*FilterChain, error) { + if isEmptyString(nameOrID) { + return nil, fmt.Errorf("nameOrID cannot be nil for Get operation") + } + + endpoint := fmt.Sprintf("/filter-chains/%v", *nameOrID) + req, err := s.client.NewRequest("GET", endpoint, nil, nil) + if err != nil { + return nil, err + } + + var filterChain FilterChain + _, err = s.client.Do(ctx, req, &filterChain) + if err != nil { + return nil, err + } + return &filterChain, nil +} + +// Update updates a FilterChain in Kong +func (s *FilterChainService) Update(ctx context.Context, + filterChain *FilterChain, +) (*FilterChain, error) { + if isEmptyString(filterChain.ID) { + return nil, fmt.Errorf("ID cannot be nil for Update operation") + } + + endpoint := fmt.Sprintf("/filter-chains/%v", *filterChain.ID) + return s.sendRequest(ctx, filterChain, endpoint, "PATCH") +} + +// UpdateForService updates a FilterChain in Kong at Service level. +func (s *FilterChainService) UpdateForService(ctx context.Context, + serviceIDorName *string, filterChain *FilterChain, +) (*FilterChain, error) { + if isEmptyString(filterChain.ID) { + return nil, fmt.Errorf("ID cannot be nil for Update operation") + } + if isEmptyString(serviceIDorName) { + return nil, fmt.Errorf("serviceIDorName cannot be nil") + } + + endpoint := fmt.Sprintf("/services/%v/filter-chains/%v", *serviceIDorName, *filterChain.ID) + return s.sendRequest(ctx, filterChain, endpoint, "PATCH") +} + +// UpdateForRoute updates a FilterChain in Kong at Route level. +func (s *FilterChainService) UpdateForRoute(ctx context.Context, + routeIDorName *string, filterChain *FilterChain, +) (*FilterChain, error) { + if isEmptyString(filterChain.ID) { + return nil, fmt.Errorf("ID cannot be nil for Update operation") + } + if isEmptyString(routeIDorName) { + return nil, fmt.Errorf("routeIDorName cannot be nil") + } + + endpoint := fmt.Sprintf("/routes/%v/filter-chains/%v", *routeIDorName, *filterChain.ID) + return s.sendRequest(ctx, filterChain, endpoint, "PATCH") +} + +// Delete deletes a FilterChain in Kong +func (s *FilterChainService) Delete(ctx context.Context, + filterChainID *string, +) error { + if isEmptyString(filterChainID) { + return fmt.Errorf("filterChainID cannot be nil for Delete operation") + } + + endpoint := fmt.Sprintf("/filter-chains/%v", *filterChainID) + _, err := s.sendRequest(ctx, nil, endpoint, "DELETE") + if err != nil { + return err + } + return err +} + +// DeleteForService deletes a FilterChain in Kong at Service level. +func (s *FilterChainService) DeleteForService(ctx context.Context, + serviceIDorName *string, filterChainID *string, +) error { + if isEmptyString(filterChainID) { + return fmt.Errorf("filterChain ID cannot be nil for Delete operation") + } + if isEmptyString(serviceIDorName) { + return fmt.Errorf("serviceIDorName cannot be nil") + } + + endpoint := fmt.Sprintf("/services/%v/filter-chains/%v", *serviceIDorName, *filterChainID) + _, err := s.sendRequest(ctx, nil, endpoint, "DELETE") + if err != nil { + return err + } + return err +} + +// DeleteForRoute deletes a FilterChain in Kong at Route level. +func (s *FilterChainService) DeleteForRoute(ctx context.Context, + routeIDorName *string, filterChainID *string, +) error { + if isEmptyString(filterChainID) { + return fmt.Errorf("filterChain ID cannot be nil for Delete operation") + } + if isEmptyString(routeIDorName) { + return fmt.Errorf("routeIDorName cannot be nil") + } + + endpoint := fmt.Sprintf("/routes/%v/filter-chains/%v", *routeIDorName, *filterChainID) + _, err := s.sendRequest(ctx, nil, endpoint, "DELETE") + if err != nil { + return err + } + return nil +} + +// listByPath fetches a list of FilterChains in Kong +// on a specific path. +// This is a helper method for listing all filter chains +// or filter chains for specific entities. +func (s *FilterChainService) listByPath(ctx context.Context, + path string, opt *ListOpt, +) ([]*FilterChain, *ListOpt, error) { + data, next, err := s.client.list(ctx, path, opt) + if err != nil { + return nil, nil, err + } + var filterChains []*FilterChain + + for _, object := range data { + b, err := object.MarshalJSON() + if err != nil { + return nil, nil, err + } + var filterChain FilterChain + err = json.Unmarshal(b, &filterChain) + if err != nil { + return nil, nil, err + } + filterChains = append(filterChains, &filterChain) + } + + return filterChains, next, nil +} + +// ListAll fetches all FilterChains in Kong. +// This method can take a while if there +// a lot of FilterChains present. +func (s *FilterChainService) listAllByPath(ctx context.Context, + path string, +) ([]*FilterChain, error) { + var filterChains, data []*FilterChain + var err error + opt := &ListOpt{Size: pageSize} + + for opt != nil { + data, opt, err = s.listByPath(ctx, path, opt) + if err != nil { + return nil, err + } + filterChains = append(filterChains, data...) + } + return filterChains, nil +} + +// List fetches a list of FilterChains in Kong. +// opt can be used to control pagination. +func (s *FilterChainService) List(ctx context.Context, + opt *ListOpt, +) ([]*FilterChain, *ListOpt, error) { + return s.listByPath(ctx, "/filter-chains", opt) +} + +// ListAll fetches all FilterChains in Kong. +// This method can take a while if there +// a lot of FilterChains present. +func (s *FilterChainService) ListAll(ctx context.Context) ([]*FilterChain, error) { + return s.listAllByPath(ctx, "/filter-chains") +} + +// ListAllForService fetches all FilterChains in Kong enabled for a service. +func (s *FilterChainService) ListAllForService(ctx context.Context, + serviceIDorName *string, +) ([]*FilterChain, error) { + if isEmptyString(serviceIDorName) { + return nil, fmt.Errorf("serviceIDorName cannot be nil") + } + return s.listAllByPath(ctx, "/services/"+*serviceIDorName+"/filter-chains") +} + +// ListAllForRoute fetches all FilterChains in Kong enabled for a service. +func (s *FilterChainService) ListAllForRoute(ctx context.Context, + routeID *string, +) ([]*FilterChain, error) { + if isEmptyString(routeID) { + return nil, fmt.Errorf("routeID cannot be nil") + } + return s.listAllByPath(ctx, "/routes/"+*routeID+"/filter-chains") +} + +func (s *FilterChainService) sendRequest(ctx context.Context, + filterChain *FilterChain, endpoint, method string, +) (*FilterChain, error) { + var req *http.Request + var err error + if method == "DELETE" { + req, err = s.client.NewRequest(method, endpoint, nil, nil) + if err != nil { + return nil, err + } + } else { + req, err = s.client.NewRequest(method, endpoint, nil, filterChain) + if err != nil { + return nil, err + } + } + var createdFilterChain FilterChain + if method == "DELETE" { + _, err = s.client.Do(ctx, req, nil) + if err != nil { + return nil, err + } + } else { + _, err = s.client.Do(ctx, req, &createdFilterChain) + if err != nil { + return nil, err + } + } + return &createdFilterChain, nil +} diff --git a/kong/filter_chain_service_test.go b/kong/filter_chain_service_test.go new file mode 100644 index 000000000..7585e7b4a --- /dev/null +++ b/kong/filter_chain_service_test.go @@ -0,0 +1,506 @@ +package kong + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFilterChainsService(T *testing.T) { + RunWhenDBMode(T, "postgres") + RunWhenKong(T, ">=3.4.0") + + assert := assert.New(T) + require := require.New(T) + + client, err := NewTestClient(nil, nil) + assert.NoError(err) + assert.NotNil(client) + + service := &Service{ + Name: String("fooWithFilterChain1"), + Host: String("example.com"), + Port: Int(42), + Path: String("/"), + } + err = client.Services.Delete(defaultCtx, service.Name) + assert.NoError(err) + + _, err = client.Services.Create(defaultCtx, service) + assert.NoError(err) + + filterChain := &FilterChain{ + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Howdy\" }"), + }, + }, + Service: service, + } + assert.NotNil(filterChain) + + createdFilterChain, err := client.FilterChains.Create(defaultCtx, filterChain) + assert.NoError(err) + require.NotNil(createdFilterChain) + require.Nil(createdFilterChain.Name) + + filterChain, err = client.FilterChains.Get(defaultCtx, createdFilterChain.ID) + assert.NoError(err) + assert.NotNil(filterChain) + + filterChain.Name = String("my-chain") + filterChain, err = client.FilterChains.Update(defaultCtx, filterChain) + assert.NoError(err) + assert.NotNil(filterChain) + assert.Equal(String("my-chain"), filterChain.Name) + + err = client.FilterChains.Delete(defaultCtx, createdFilterChain.ID) + assert.NoError(err) + + // ID can be specified + id := uuid.NewString() + filterChain = &FilterChain{ + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Howdy\" }"), + }, + }, + Service: service, + ID: String(id), + } + + createdFilterChain, err = client.FilterChains.Create(defaultCtx, filterChain) + assert.NoError(err) + assert.NotNil(createdFilterChain) + assert.Equal(id, *createdFilterChain.ID) + + err = client.FilterChains.Delete(defaultCtx, createdFilterChain.ID) + assert.NoError(err) + + service = &Service{ + Name: String("fooWithFilterChain2"), + Host: String("upstream"), + Port: Int(42), + Path: String("/path"), + } + // Clean Data + err = client.Services.Delete(defaultCtx, service.Name) + assert.NoError(err) + // Test to create filter chain from service endpoint + createdService, err := client.Services.Create(defaultCtx, service) + assert.NoError(err) + + id = uuid.NewString() + FilterChainForService := &FilterChain{ + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Howdy\" }"), + Enabled: Bool(true), + }, + }, + ID: String(id), + } + + createdFilterChain, err = client.FilterChains.CreateForService(defaultCtx, createdService.Name, FilterChainForService) + assert.NoError(err) + assert.NotNil(createdFilterChain) + assert.Equal(id, *createdFilterChain.ID) + assert.Equal(Bool(true), createdFilterChain.Filters[0].Enabled) + + createdFilterChain.Filters[0].Enabled = Bool(false) + updatedFilterChain, err := client.FilterChains.UpdateForService(defaultCtx, createdService.Name, createdFilterChain) + assert.NoError(err) + assert.NotNil(updatedFilterChain) + assert.Equal(id, *updatedFilterChain.ID) + assert.Equal(Bool(false), createdFilterChain.Filters[0].Enabled) + + err = client.FilterChains.DeleteForService(defaultCtx, createdService.Name, updatedFilterChain.ID) + assert.NoError(err) + + // Create filter chain without ID + createdFilterChain, err = client.FilterChains.CreateForService(defaultCtx, createdService.Name, &FilterChain{ + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Howdy\" }"), + Enabled: Bool(true), + }, + }, + }) + assert.NoError(err) + assert.NotNil(createdFilterChain) + assert.NotNil(createdFilterChain.ID) + + assert.NoError(client.Services.Delete(defaultCtx, createdService.ID)) + + route := &Route{ + Name: String("route_filter_chain"), + Paths: []*string{String("/route_filter_chain")}, + } + // Clean Data + err = client.Routes.Delete(defaultCtx, route.Name) + assert.NoError(err) + // Test to create filter chain from route endpoint + createdRoute, err := client.Routes.Create(defaultCtx, route) + assert.NoError(err) + + id = uuid.NewString() + FilterChainForRoute := &FilterChain{ + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Howdy\" }"), + Enabled: Bool(true), + }, + }, + ID: String(id), + } + + createdFilterChain, err = client.FilterChains.CreateForRoute(defaultCtx, createdRoute.Name, FilterChainForRoute) + assert.NoError(err) + assert.NotNil(createdFilterChain) + assert.Equal(id, *createdFilterChain.ID) + assert.Equal(Bool(true), createdFilterChain.Filters[0].Enabled) + + createdFilterChain.Filters[0].Enabled = Bool(false) + updatedFilterChain, err = client.FilterChains.UpdateForRoute(defaultCtx, createdRoute.Name, createdFilterChain) + assert.NoError(err) + assert.NotNil(updatedFilterChain) + assert.Equal(id, *updatedFilterChain.ID) + assert.Equal(Bool(false), createdFilterChain.Filters[0].Enabled) + + err = client.FilterChains.DeleteForRoute(defaultCtx, createdRoute.Name, updatedFilterChain.ID) + assert.NoError(err) + + // Create filter chain without ID + createdFilterChain, err = client.FilterChains.CreateForRoute(defaultCtx, createdRoute.Name, &FilterChain{ + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Howdy\" }"), + Enabled: Bool(true), + }, + }, + }) + assert.NoError(err) + assert.NotNil(createdFilterChain) + assert.NotNil(createdFilterChain.ID) + + assert.NoError(client.Routes.Delete(defaultCtx, createdRoute.ID)) +} + +func TestFilterChainWithTags(T *testing.T) { + RunWhenDBMode(T, "postgres") + RunWhenKong(T, ">=3.4.0") + + assert := assert.New(T) + require := require.New(T) + + client, err := NewTestClient(nil, nil) + require.NoError(err) + require.NotNil(client) + + service := &Service{ + Name: String("fooWithFilterChain1"), + Host: String("example.com"), + Port: Int(42), + Path: String("/"), + } + err = client.Services.Delete(defaultCtx, service.Name) + assert.NoError(err) + + createdService, err := client.Services.Create(defaultCtx, service) + assert.NoError(err) + + filterChain := &FilterChain{ + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Howdy\" }"), + }, + }, + Service: createdService, + Tags: StringSlice("tag1", "tag2"), + } + + createdFilterChain, err := client.FilterChains.Create(defaultCtx, filterChain) + assert.NoError(err) + require.NotNil(createdFilterChain) + require.Equal(StringSlice("tag1", "tag2"), createdFilterChain.Tags) + + err = client.FilterChains.Delete(defaultCtx, createdFilterChain.ID) + require.NoError(err) + + err = client.Services.Delete(defaultCtx, createdService.ID) + require.NoError(err) +} + +func TestUnknownFilterChain(T *testing.T) { + RunWhenDBMode(T, "postgres") + RunWhenKong(T, ">=3.4.0") + + assert := assert.New(T) + + client, err := NewTestClient(nil, nil) + assert.NoError(err) + assert.NotNil(client) + + service := &Service{ + Name: String("fooWithFilterChain1"), + Host: String("example.com"), + Port: Int(42), + Path: String("/"), + } + err = client.Services.Delete(defaultCtx, service.Name) + assert.NoError(err) + + createdService, err := client.Services.Create(defaultCtx, service) + assert.NoError(err) + + filterChain := &FilterChain{ + Filters: []*Filter{ + { + Name: String("filter-chain-not-present"), + Config: String("{ \"option\": true }"), + }, + }, + Service: createdService, + Tags: StringSlice("tag1", "tag2"), + } + + createdFilterChain, err := client.FilterChains.Create(defaultCtx, filterChain) + require.Error(T, err) + require.Nil(T, createdFilterChain) + + err = client.Services.Delete(defaultCtx, createdService.ID) + assert.NoError(err) +} + +func TestFilterChainListEndpoint(T *testing.T) { + RunWhenDBMode(T, "postgres") + RunWhenKong(T, ">=3.4.0") + + assert := assert.New(T) + + client, err := NewTestClient(nil, nil) + assert.NoError(err) + assert.NotNil(client) + + // fixtures + filterChains := []*FilterChain{ + { + Name: String("chain-1"), + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Hi\" }"), + }, + }, + }, + { + Name: String("chain-2"), + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Hey\" }"), + }, + }, + }, + { + Name: String("chain-3"), + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Howdy\" }"), + }, + }, + }, + } + + // create fixtures + for i := 0; i < len(filterChains); i++ { + service, err := client.Services.Create(defaultCtx, &Service{ + Name: String("service-for-" + *filterChains[i].Name), + Host: String("example.com"), + Port: Int(42), + Path: String("/"), + }) + + assert.NoError(err) + assert.NotNil(service) + filterChain, err := client.FilterChains.CreateForService(defaultCtx, service.Name, filterChains[i]) + assert.NoError(err) + assert.NotNil(filterChain) + filterChains[i] = filterChain + } + + filterChainsFromKong, next, err := client.FilterChains.List(defaultCtx, nil) + assert.NoError(err) + assert.Nil(next) + assert.NotNil(filterChainsFromKong) + assert.Equal(3, len(filterChainsFromKong)) + + // check if we see all filterChains + assert.True(compareFilterChains(T, filterChains, filterChainsFromKong)) + + // Test pagination + filterChainsFromKong = []*FilterChain{} + + // first page + page1, next, err := client.FilterChains.List(defaultCtx, &ListOpt{Size: 1}) + assert.NoError(err) + assert.NotNil(next) + assert.NotNil(page1) + assert.Equal(1, len(page1)) + filterChainsFromKong = append(filterChainsFromKong, page1...) + + // second page + page2, next, err := client.FilterChains.List(defaultCtx, next) + assert.NoError(err) + assert.NotNil(next) + assert.NotNil(page2) + assert.Equal(1, len(page2)) + filterChainsFromKong = append(filterChainsFromKong, page2...) + + // last page + page3, next, err := client.FilterChains.List(defaultCtx, next) + assert.NoError(err) + assert.Nil(next) + assert.NotNil(page3) + assert.Equal(1, len(page3)) + filterChainsFromKong = append(filterChainsFromKong, page3...) + + assert.True(compareFilterChains(T, filterChains, filterChainsFromKong)) + + filterChains, err = client.FilterChains.ListAll(defaultCtx) + assert.NoError(err) + assert.NotNil(filterChains) + assert.Equal(3, len(filterChains)) + + for i := 0; i < len(filterChains); i++ { + assert.NoError(client.Services.Delete(defaultCtx, filterChains[i].Service.ID)) + } +} + +func TestFilterChainListAllForEntityEndpoint(T *testing.T) { + RunWhenDBMode(T, "postgres") + RunWhenKong(T, ">=3.4.0") + + assert := assert.New(T) + + client, err := NewTestClient(nil, nil) + assert.NoError(err) + assert.NotNil(client) + + // fixtures + + createdService, err := client.Services.Create(defaultCtx, &Service{ + Name: String("foo"), + Host: String("upstream"), + Port: Int(42), + Path: String("/path"), + }) + assert.NoError(err) + assert.NotNil(createdService) + + createdRoute, err := client.Routes.Create(defaultCtx, &Route{ + Hosts: StringSlice("example.com", "example.test"), + Service: createdService, + }) + assert.NoError(err) + assert.NotNil(createdRoute) + + filterChains := []*FilterChain{ + // specific to route + { + Name: String("route-chain"), + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Hello, route\" }"), + }, + { + Name: String("example-filter"), + Config: String("{ \"option\": false }"), + }, + }, + Route: createdRoute, + }, + // specific to service + { + Name: String("service-chain"), + Filters: []*Filter{ + { + Name: String("example-filter"), + Config: String("{ \"option\": false }"), + }, + { + Name: String("example-filter"), + Config: String("{ \"my_greeting\": \"Hello, service\" }"), + }, + }, + Service: createdService, + }, + } + + // create fixtures + for i := 0; i < len(filterChains); i++ { + filterChain, err := client.FilterChains.Create(defaultCtx, filterChains[i]) + assert.NoError(err) + assert.NotNil(filterChain) + filterChains[i] = filterChain + } + + filterChainsFromKong, err := client.FilterChains.ListAll(defaultCtx) + assert.NoError(err) + assert.NotNil(filterChainsFromKong) + assert.Equal(len(filterChains), len(filterChainsFromKong)) + + // check if we see all filterChains + assert.True(compareFilterChains(T, filterChains, filterChainsFromKong)) + + filterChainsFromKong, err = client.FilterChains.ListAll(defaultCtx) + assert.NoError(err) + assert.NotNil(filterChainsFromKong) + assert.Equal(2, len(filterChainsFromKong)) + + filterChainsFromKong, err = client.FilterChains.ListAllForService(defaultCtx, + createdService.ID) + assert.NoError(err) + assert.NotNil(filterChainsFromKong) + assert.Equal(1, len(filterChainsFromKong)) + + filterChainsFromKong, err = client.FilterChains.ListAllForRoute(defaultCtx, + createdRoute.ID) + assert.NoError(err) + assert.NotNil(filterChainsFromKong) + assert.Equal(1, len(filterChainsFromKong)) + + for i := 0; i < len(filterChains); i++ { + assert.NoError(client.FilterChains.Delete(defaultCtx, filterChains[i].ID)) + } + + assert.NoError(client.Routes.Delete(defaultCtx, createdRoute.ID)) + assert.NoError(client.Services.Delete(defaultCtx, createdService.ID)) +} + +func compareFilterChains(T *testing.T, expected, actual []*FilterChain) bool { + var expectedNames, actualNames []string + for _, filterChain := range expected { + if !assert.NotNil(T, filterChain) { + continue + } + expectedNames = append(expectedNames, *filterChain.Name) + } + + for _, filterChain := range actual { + actualNames = append(actualNames, *filterChain.Name) + } + + return (compareSlices(expectedNames, actualNames)) +} diff --git a/kong/zz_generated.deepcopy.go b/kong/zz_generated.deepcopy.go index da6d642d6..4f5dac63d 100644 --- a/kong/zz_generated.deepcopy.go +++ b/kong/zz_generated.deepcopy.go @@ -775,6 +775,110 @@ func (in *DeveloperRole) DeepCopy() *DeveloperRole { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Filter) DeepCopyInto(out *Filter) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = new(string) + **out = **in + } + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Filter. +func (in *Filter) DeepCopy() *Filter { + if in == nil { + return nil + } + out := new(Filter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FilterChain) DeepCopyInto(out *FilterChain) { + *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.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } + if in.Route != nil { + in, out := &in.Route, &out.Route + *out = new(Route) + (*in).DeepCopyInto(*out) + } + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = new(Service) + (*in).DeepCopyInto(*out) + } + if in.Filters != nil { + in, out := &in.Filters, &out.Filters + *out = make([]*Filter, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Filter) + (*in).DeepCopyInto(*out) + } + } + } + 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 + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]*string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(string) + **out = **in + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FilterChain. +func (in *FilterChain) DeepCopy() *FilterChain { + if in == nil { + return nil + } + out := new(FilterChain) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GraphqlRateLimitingCostDecoration) DeepCopyInto(out *GraphqlRateLimitingCostDecoration) { *out = *in