From b9644e16c835b98dd4945a770c30259119d95527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Sch=C3=A4fer?= Date: Tue, 23 Apr 2024 10:44:39 +0200 Subject: [PATCH] SYSENG-1357: generic client default options --- CHANGELOG.md | 6 + pkg/api/api_implementation.go | 67 +++++++++-- pkg/api/api_test.go | 107 ++++++++++++++++-- pkg/api/internal/options.go | 12 +- pkg/api/mock/mock_api_implementation.go | 12 +- pkg/api/object_example_test.go | 18 +++ pkg/api/options.go | 23 ++++ pkg/api/types/options.go | 73 +++++++++++- pkg/apis/kubernetes/v1/cluster_genclient.go | 2 +- pkg/apis/kubernetes/v1/common.go | 7 +- pkg/apis/kubernetes/v1/crud_test.go | 47 +++++++- pkg/apis/kubernetes/v1/node_pool_genclient.go | 2 +- 12 files changed, 334 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1193a9c7..998f4eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,12 @@ Some examples, more below in the actual changelog (newer entries are more likely --> +### Added +* `WithRequestOptions` API option to configure default request options (#361, @anx-mschaefer) + +### Changed +* (internal) add "error-return" to request option interfaces (#361, @anx-mschaefer) + ## [0.6.4] - 2024-03-15 ### Fixed diff --git a/pkg/api/api_implementation.go b/pkg/api/api_implementation.go index 4b2f2416..da5902f5 100644 --- a/pkg/api/api_implementation.go +++ b/pkg/api/api_implementation.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "mime" @@ -30,7 +31,8 @@ type defaultAPI struct { client client.Client logger *logr.Logger - clientOptions []client.Option + clientOptions []client.Option + requestOptions []types.Option } // NewAPIOption is the type for giving options to the NewAPI function. @@ -43,6 +45,13 @@ func WithClientOptions(o ...client.Option) NewAPIOption { } } +// WithRequestOptions configures default options applied to requests +func WithRequestOptions(opts ...types.Option) NewAPIOption { + return func(a *defaultAPI) { + a.requestOptions = opts + } +} + // WithLogger configures the API to use the given logger. It is recommended to pass a named logger. // If you don't pass an existing client, the logger you give here will given to the client (with // added name "client"). @@ -79,8 +88,12 @@ func NewAPI(opts ...NewAPIOption) (API, error) { // Get the identified object from the engine. func (a defaultAPI) Get(ctx context.Context, o types.IdentifiedObject, opts ...types.GetOption) error { options := types.GetOptions{} - for _, opt := range opts { - opt.ApplyToGet(&options) + var err error + for _, opt := range resolveRequestOptions(a.requestOptions, opts) { + err = errors.Join(err, opt.ApplyToGet(&options)) + } + if err != nil { + return fmt.Errorf("apply request options: %w", err) } return a.do(ctx, o, o, &options, types.OperationGet) @@ -89,8 +102,12 @@ func (a defaultAPI) Get(ctx context.Context, o types.IdentifiedObject, opts ...t // Create the given object on the engine. func (a defaultAPI) Create(ctx context.Context, o types.Object, opts ...types.CreateOption) error { options := types.CreateOptions{} - for _, opt := range opts { - opt.ApplyToCreate(&options) + var err error + for _, opt := range resolveRequestOptions(a.requestOptions, opts) { + err = errors.Join(err, opt.ApplyToCreate(&options)) + } + if err != nil { + return fmt.Errorf("apply request options: %w", err) } if err := a.do(ctx, o, o, &options, types.OperationCreate); err != nil { @@ -115,8 +132,12 @@ func (a defaultAPI) handlePostCreateOptions(ctx context.Context, o types.Identif // Update the object on the engine. func (a defaultAPI) Update(ctx context.Context, o types.IdentifiedObject, opts ...types.UpdateOption) error { options := types.UpdateOptions{} - for _, opt := range opts { - opt.ApplyToUpdate(&options) + var err error + for _, opt := range resolveRequestOptions(a.requestOptions, opts) { + err = errors.Join(err, opt.ApplyToUpdate(&options)) + } + if err != nil { + return fmt.Errorf("apply request options: %w", err) } return a.do(ctx, o, o, &options, types.OperationUpdate) @@ -125,8 +146,12 @@ func (a defaultAPI) Update(ctx context.Context, o types.IdentifiedObject, opts . // Destroy the identified object. func (a defaultAPI) Destroy(ctx context.Context, o types.IdentifiedObject, opts ...types.DestroyOption) error { options := types.DestroyOptions{} - for _, opt := range opts { - opt.ApplyToDestroy(&options) + var err error + for _, opt := range resolveRequestOptions(a.requestOptions, opts) { + err = errors.Join(err, opt.ApplyToDestroy(&options)) + } + if err != nil { + return fmt.Errorf("apply request options: %w", err) } return a.do(ctx, o, o, &options, types.OperationDestroy) @@ -135,11 +160,14 @@ func (a defaultAPI) Destroy(ctx context.Context, o types.IdentifiedObject, opts // List objects matching the info given in the object. func (a defaultAPI) List(ctx context.Context, o types.FilterObject, opts ...types.ListOption) error { options := types.ListOptions{} - for _, opt := range opts { - opt.ApplyToList(&options) + var err error + for _, opt := range resolveRequestOptions(a.requestOptions, opts) { + err = errors.Join(err, opt.ApplyToList(&options)) + } + if err != nil { + return fmt.Errorf("apply request options: %w", err) } - var err error ctx, err = a.contextPrepare(ctx, o, types.OperationList, &options) if err != nil { @@ -515,3 +543,18 @@ func decodeResponse(ctx context.Context, mediaType string, data io.Reader, res i return fmt.Errorf("%w: no idea how to handle media type %v", ErrUnsupportedResponseFormat, mediaType) } + +func resolveRequestOptions[T any](commonOptions []types.Option, requestOptions []T) []T { + return append(filterOptions[T](commonOptions), requestOptions...) +} + +func filterOptions[T any](opts []types.Option) []T { + ret := make([]T, 0, len(opts)) + for _, v := range opts { + if v, ok := v.(T); ok { + ret = append(ret, v) + } + } + + return ret +} diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index ff015d7c..157aafaf 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -74,24 +74,30 @@ var _ = Describe("getResponseType function", func() { type apiTestAnyopOption string -func (o apiTestAnyopOption) ApplyToGet(opts *types.GetOptions) { - _ = opts.Set("api_test_option", o, false) +func (o apiTestAnyopOption) ApplyToGet(opts *types.GetOptions) error { + return opts.Set("api_test_option", o, false) } -func (o apiTestAnyopOption) ApplyToList(opts *types.ListOptions) { - _ = opts.Set("api_test_option", o, false) +func (o apiTestAnyopOption) ApplyToList(opts *types.ListOptions) error { + return opts.Set("api_test_option", o, false) } -func (o apiTestAnyopOption) ApplyToCreate(opts *types.CreateOptions) { - _ = opts.Set("api_test_option", o, false) +func (o apiTestAnyopOption) ApplyToCreate(opts *types.CreateOptions) error { + return opts.Set("api_test_option", o, false) } -func (o apiTestAnyopOption) ApplyToUpdate(opts *types.UpdateOptions) { - _ = opts.Set("api_test_option", o, false) +func (o apiTestAnyopOption) ApplyToUpdate(opts *types.UpdateOptions) error { + return opts.Set("api_test_option", o, false) } -func (o apiTestAnyopOption) ApplyToDestroy(opts *types.DestroyOptions) { - _ = opts.Set("api_test_option", o, false) +func (o apiTestAnyopOption) ApplyToDestroy(opts *types.DestroyOptions) error { + return opts.Set("api_test_option", o, false) +} + +func errorOption(err error) types.AnyOption { + return func(o types.Options) error { + return err + } } type apiTestObject struct { @@ -1025,6 +1031,87 @@ var _ = Describe("using an API object", func() { err = api.Destroy(ctx, &o, opt) Expect(err).NotTo(HaveOccurred()) }) + + It("consumes the default options for all operations", func() { + opt := apiTestAnyopOption("hello world") + ctx := context.WithValue(context.TODO(), errAPITest, opt) + + server.AppendHandlers( + ghttp.RespondWithJSONEncoded(200, map[string]string{"value": "option-check"}), + ghttp.RespondWithJSONEncoded(200, map[string]string{"value": "option-check"}), + ghttp.RespondWithJSONEncoded(200, []map[string]string{{"value": "option-check"}}), + ghttp.RespondWithJSONEncoded(200, map[string]string{"value": "option-check"}), + ghttp.RespondWithJSONEncoded(200, map[string]string{}), + ) + + api, err := NewAPI( + WithLogger(logger), + WithClientOptions( + client.BaseURL(server.URL()), + client.IgnoreMissingToken(), + ), + WithRequestOptions(opt), + ) + Expect(err).NotTo(HaveOccurred()) + + o := apiTestObject{"option-check"} + + err = api.Create(ctx, &o) + Expect(err).NotTo(HaveOccurred()) + + err = api.Get(ctx, &o) + Expect(err).NotTo(HaveOccurred()) + + err = api.List(ctx, &o) + Expect(err).NotTo(HaveOccurred()) + + err = api.Update(ctx, &o) + Expect(err).NotTo(HaveOccurred()) + + err = api.Destroy(ctx, &o) + Expect(err).NotTo(HaveOccurred()) + }) + + It("returns an error when applying configured options return errors", func() { + mockErr := errors.New("foo") + api, err := NewAPI( + WithLogger(logger), + WithClientOptions( + client.BaseURL(server.URL()), + client.IgnoreMissingToken(), + ), + ) + Expect(err).NotTo(HaveOccurred()) + + o := apiTestObject{"foo"} + + Expect(api.Create(context.TODO(), &o, errorOption(mockErr))).Error().To(MatchError(mockErr)) + Expect(api.Get(context.TODO(), &o, errorOption(mockErr))).Error().To(MatchError(mockErr)) + Expect(api.List(context.TODO(), &o, errorOption(mockErr))).Error().To(MatchError(mockErr)) + Expect(api.Update(context.TODO(), &o, errorOption(mockErr))).Error().To(MatchError(mockErr)) + Expect(api.Destroy(context.TODO(), &o, errorOption(mockErr))).Error().To(MatchError(mockErr)) + }) + + It("returns an error when applying configured default options return errors", func() { + mockErr := errors.New("foo") + api, err := NewAPI( + WithLogger(logger), + WithClientOptions( + client.BaseURL(server.URL()), + client.IgnoreMissingToken(), + ), + WithRequestOptions(errorOption(mockErr)), + ) + Expect(err).NotTo(HaveOccurred()) + + o := apiTestObject{"foo"} + + Expect(api.Create(context.TODO(), &o)).Error().To(MatchError(mockErr)) + Expect(api.Get(context.TODO(), &o)).Error().To(MatchError(mockErr)) + Expect(api.List(context.TODO(), &o)).Error().To(MatchError(mockErr)) + Expect(api.Update(context.TODO(), &o)).Error().To(MatchError(mockErr)) + Expect(api.Destroy(context.TODO(), &o)).Error().To(MatchError(mockErr)) + }) }) const contextTestObjectBaseurl = "/v1/context_test_object" diff --git a/pkg/api/internal/options.go b/pkg/api/internal/options.go index d1397ab7..c4653a68 100644 --- a/pkg/api/internal/options.go +++ b/pkg/api/internal/options.go @@ -17,11 +17,12 @@ type PagedOption struct { } // ApplyToList applies the Paged option to all the ListOptions. -func (p PagedOption) ApplyToList(o *types.ListOptions) { +func (p PagedOption) ApplyToList(o *types.ListOptions) error { o.Paged = true o.Page = p.Page o.EntriesPerPage = p.Limit o.PageInfo = p.Info + return nil } // ObjectChannelOption configures the List operation to return the objects via the given channel. @@ -30,8 +31,9 @@ type ObjectChannelOption struct { } // ApplyToList applies the AsObjectChannel option to all the ListOptions. -func (aoc ObjectChannelOption) ApplyToList(o *types.ListOptions) { +func (aoc ObjectChannelOption) ApplyToList(o *types.ListOptions) error { o.ObjectChannel = aoc.Channel + return nil } // FullObjectsOption configures if the List operation shall make a Get operation for each object before @@ -39,14 +41,16 @@ func (aoc ObjectChannelOption) ApplyToList(o *types.ListOptions) { type FullObjectsOption bool // ApplyToList applies the FullObjectsOption option to all the ListOptions. -func (foo FullObjectsOption) ApplyToList(o *types.ListOptions) { +func (foo FullObjectsOption) ApplyToList(o *types.ListOptions) error { o.FullObjects = bool(foo) + return nil } // AutoTagOption configures the Create operation to automatically tag objects after creation type AutoTagOption []string // ApplyToCreate applies the AutoTagOption to the ListOptions -func (ato AutoTagOption) ApplyToCreate(o *types.CreateOptions) { +func (ato AutoTagOption) ApplyToCreate(o *types.CreateOptions) error { o.AutoTags = ato + return nil } diff --git a/pkg/api/mock/mock_api_implementation.go b/pkg/api/mock/mock_api_implementation.go index 781bf480..0cebc519 100644 --- a/pkg/api/mock/mock_api_implementation.go +++ b/pkg/api/mock/mock_api_implementation.go @@ -102,8 +102,12 @@ func listDataAggregation(o types.Object, data mockDataView) ([]types.Object, err func listOutput(ctx context.Context, objects []types.Object, opts []types.ListOption) error { options := types.ListOptions{} + var err error for _, opt := range opts { - opt.ApplyToList(&options) + err = errors.Join(err, opt.ApplyToList(&options)) + } + if err != nil { + return fmt.Errorf("apply request options: %w", err) } var channelPageIterator types.PageInfo @@ -183,8 +187,12 @@ func (a *mockAPI) Create(ctx context.Context, o types.Object, opts ...types.Crea } options := types.CreateOptions{} + var err error for _, opt := range opts { - opt.ApplyToCreate(&options) + err = errors.Join(err, opt.ApplyToCreate(&options)) + } + if err != nil { + return fmt.Errorf("apply request options: %w", err) } a.mu.Lock() diff --git a/pkg/api/object_example_test.go b/pkg/api/object_example_test.go index 16ed7778..113dd3e5 100644 --- a/pkg/api/object_example_test.go +++ b/pkg/api/object_example_test.go @@ -229,3 +229,21 @@ func Example_implementObject() { // Retrieved object with mode 'tcp' named 'hello TCP 1' // Retrieved object with mode 'tcp' named 'hello TCP 2' } + +func ExampleWithRequestOptions() { + api, err := NewAPI( + WithRequestOptions( + // automatically assign tags to newly created resources + AutoTag("foo", "bar"), + ), + ) + + if err != nil { + panic(fmt.Errorf("Error creating API instance: %v\n", err)) + } + + // create resource and automatically apply 'foo' & 'bar' tags + if err := api.Create(context.TODO(), &ExampleObject{Name: "foo"}); err != nil { + panic(err) + } +} diff --git a/pkg/api/options.go b/pkg/api/options.go index 7706f21f..292209c0 100644 --- a/pkg/api/options.go +++ b/pkg/api/options.go @@ -1,6 +1,9 @@ package api import ( + "context" + "fmt" + "go.anx.io/go-anxcloud/pkg/api/internal" "go.anx.io/go-anxcloud/pkg/api/types" ) @@ -34,3 +37,23 @@ func FullObjects(fullObjects bool) ListOption { func AutoTag(tags ...string) CreateOption { return internal.AutoTagOption(tags) } + +// EnvironmentOption can be used to configure an alternative environment path +// segment for a given API group +func EnvironmentOption(apiGroup, envPathSegment string, override bool) types.AnyOption { + return func(o types.Options) error { + return o.SetEnvironment(fmt.Sprintf("environment/%s", apiGroup), envPathSegment, override) + } +} + +// GetEnvironmentPathSegment retrieves the environment path segment of a given API group +// or the provided defaultValue if no environment override is set +func GetEnvironmentPathSegment(ctx context.Context, apiGroup, defaultValue string) string { + if options, err := types.OptionsFromContext(ctx); err != nil { + return defaultValue + } else if env, err := options.GetEnvironment(fmt.Sprintf("environment/%s", apiGroup)); err != nil { + return defaultValue + } else { + return env + } +} diff --git a/pkg/api/types/options.go b/pkg/api/types/options.go index d4ba4b8e..03e6d100 100644 --- a/pkg/api/types/options.go +++ b/pkg/api/types/options.go @@ -11,7 +11,8 @@ var ( ) type commonOptions struct { - additional map[string]interface{} + additional map[string]interface{} + environments map[string]string } // Options is the interface all operation-specific options implement, making it possible to pass all the specific options to the same functions. @@ -19,38 +20,72 @@ type commonOptions struct { type Options interface { Get(key string) (interface{}, error) Set(key string, value interface{}, overwrite bool) error + GetEnvironment(key string) (string, error) + SetEnvironment(key, value string, overwrite bool) error } // GetOption is the interface options have to implement to be usable with Get operation. type GetOption interface { // Apply this option to the set of all options - ApplyToGet(*GetOptions) + ApplyToGet(*GetOptions) error } // ListOption is the interface options have to implement to be usable with List operation. type ListOption interface { // Apply this option to the set of all options - ApplyToList(*ListOptions) + ApplyToList(*ListOptions) error } // CreateOption is the interface options have to implement to be usable with Create operation. type CreateOption interface { // Apply this option to the set of all options - ApplyToCreate(*CreateOptions) + ApplyToCreate(*CreateOptions) error } // UpdateOption is the interface options have to implement to be usable with Update operation. type UpdateOption interface { // Apply this option to the set of all options - ApplyToUpdate(*UpdateOptions) + ApplyToUpdate(*UpdateOptions) error } // DestroyOption is the interface options have to implement to be usable with Destroy operation. type DestroyOption interface { // Apply this option to the set of all options - ApplyToDestroy(*DestroyOptions) + ApplyToDestroy(*DestroyOptions) error } +// AnyOption is the interface options have to implement to be usable with any operation. +type AnyOption func(Options) error + +// ApplyToGet applies the AnyOption to the GetOptions +func (ao AnyOption) ApplyToGet(opts *GetOptions) error { + return ao(opts) +} + +// ApplyToList applies the AnyOption to the ListOptions +func (ao AnyOption) ApplyToList(opts *ListOptions) error { + return ao(opts) +} + +// ApplyToCreate applies the AnyOption to the CreateOptions +func (ao AnyOption) ApplyToCreate(opts *CreateOptions) error { + return ao(opts) +} + +// ApplyToUpdate applies the AnyOption to the UpdateOptions +func (ao AnyOption) ApplyToUpdate(opts *UpdateOptions) error { + return ao(opts) +} + +// ApplyToDestroy applies the AnyOption to the DestroyOptions +func (ao AnyOption) ApplyToDestroy(opts *DestroyOptions) error { + return ao(opts) +} + +// Option is a dummy interface used for any type of request Options +type Option interface{} + +// Get retrieves a custom value from request options func (o commonOptions) Get(key string) (interface{}, error) { if o.additional != nil { if v, ok := o.additional[key]; ok { @@ -61,6 +96,7 @@ func (o commonOptions) Get(key string) (interface{}, error) { return nil, ErrKeyNotSet } +// Set stores a custom value in request options func (o *commonOptions) Set(key string, val interface{}, overwrite bool) error { if o.additional == nil { o.additional = make(map[string]interface{}, 1) @@ -73,3 +109,28 @@ func (o *commonOptions) Set(key string, val interface{}, overwrite bool) error { o.additional[key] = val return nil } + +// GetEnvironment retrieves an environment value from request options +func (o commonOptions) GetEnvironment(key string) (string, error) { + if o.environments != nil { + if v, ok := o.environments[key]; ok { + return v, nil + } + } + + return "", ErrKeyNotSet +} + +// SetEnvironment stores an environment value in request options +func (o *commonOptions) SetEnvironment(key, val string, overwrite bool) error { + if o.environments == nil { + o.environments = make(map[string]string, 1) + } + + if _, alreadySet := o.environments[key]; alreadySet && !overwrite { + return ErrKeyAlreadySet + } + + o.environments[key] = val + return nil +} diff --git a/pkg/apis/kubernetes/v1/cluster_genclient.go b/pkg/apis/kubernetes/v1/cluster_genclient.go index 1dd1d5c0..35bfd7b0 100644 --- a/pkg/apis/kubernetes/v1/cluster_genclient.go +++ b/pkg/apis/kubernetes/v1/cluster_genclient.go @@ -15,7 +15,7 @@ var ErrManagedPrefixSet = errors.New("managed prefixes cannot be set on create") // EndpointURL returns the common URL for operations on Cluster resource func (c *Cluster) EndpointURL(ctx context.Context) (*url.URL, error) { - return endpointURL(ctx, c, "/api/kubernetes/v1/cluster.json") + return endpointURL(ctx, c, "cluster") } // explicitlyFalse returns true if the value of the provided diff --git a/pkg/apis/kubernetes/v1/common.go b/pkg/apis/kubernetes/v1/common.go index 584b9947..66125be8 100644 --- a/pkg/apis/kubernetes/v1/common.go +++ b/pkg/apis/kubernetes/v1/common.go @@ -2,6 +2,7 @@ package v1 import ( "context" + "fmt" "net/url" "go.anx.io/go-anxcloud/pkg/api" @@ -9,7 +10,7 @@ import ( "go.anx.io/go-anxcloud/pkg/utils/object/filter" ) -func endpointURL(ctx context.Context, o types.Object, apiPath string) (*url.URL, error) { +func endpointURL(ctx context.Context, o types.Object, resourcePathName string) (*url.URL, error) { op, err := types.OperationFromContext(ctx) if err != nil { return nil, err @@ -19,8 +20,10 @@ func endpointURL(ctx context.Context, o types.Object, apiPath string) (*url.URL, return nil, api.ErrOperationNotSupported } + env := api.GetEnvironmentPathSegment(ctx, "kubernetes/v1", "kubernetes") + // we can ignore the error since the URL is hard-coded known as valid - u, _ := url.Parse(apiPath) + u, _ := url.Parse(fmt.Sprintf("/api/%s/v1/%s.json", env, resourcePathName)) if op == types.OperationList { helper, err := filter.NewHelper(o) diff --git a/pkg/apis/kubernetes/v1/crud_test.go b/pkg/apis/kubernetes/v1/crud_test.go index dade105b..a80e7c0e 100644 --- a/pkg/apis/kubernetes/v1/crud_test.go +++ b/pkg/apis/kubernetes/v1/crud_test.go @@ -24,11 +24,17 @@ const ( var mockStateOK = map[string]interface{}{"type": gs.StateTypeOK} +func EnvironmentTest() types.AnyOption { + return api.EnvironmentOption("kubernetes/v1", "kubernetes-test", true) +} + var _ = Describe("CRUD", Ordered, func() { var ( a api.API srv *ghttp.Server + requestOptions []types.Option + clusterIdentifier string nodePoolIdentifier string @@ -38,6 +44,10 @@ var _ = Describe("CRUD", Ordered, func() { ) BeforeEach(func() { + requestOptions = []types.Option{} + }) + + JustBeforeEach(func() { var err error if isIntegrationTest { @@ -49,14 +59,43 @@ var _ = Describe("CRUD", Ordered, func() { srv.Close() }) - a, err = api.NewAPI(api.WithClientOptions( - client.BaseURL(srv.URL()), - client.IgnoreMissingToken(), - )) + a, err = api.NewAPI( + api.WithClientOptions( + client.BaseURL(srv.URL()), + client.IgnoreMissingToken(), + ), + api.WithRequestOptions(requestOptions...), + ) Expect(err).ToNot(HaveOccurred()) } }) + Context("with environment option", func() { + BeforeEach(func() { + if isIntegrationTest { + Skip("don't integration-test environment path overrides") + } + + requestOptions = []types.Option{EnvironmentTest()} + }) + + It("correctly applies the environment to the cluster resource path", func() { + srv.AppendHandlers(ghttp.CombineHandlers( + ghttp.VerifyRequest("POST", "/api/kubernetes-test/v1/cluster.json"), + ghttp.RespondWithJSONEncoded(http.StatusOK, map[string]any{}), + )) + Expect(a.Create(context.TODO(), &Cluster{})).To(Succeed()) + }) + + It("correctly applies the environment to the node pool resource path", func() { + srv.AppendHandlers(ghttp.CombineHandlers( + ghttp.VerifyRequest("POST", "/api/kubernetes-test/v1/node_pool.json"), + ghttp.RespondWithJSONEncoded(http.StatusOK, map[string]any{}), + )) + Expect(a.Create(context.TODO(), &NodePool{})).To(Succeed()) + }) + }) + Context("Cluster object", Ordered, func() { Context("Create operation", Ordered, func() { It("Create new cluster", func() { diff --git a/pkg/apis/kubernetes/v1/node_pool_genclient.go b/pkg/apis/kubernetes/v1/node_pool_genclient.go index 4575db56..1a177d30 100644 --- a/pkg/apis/kubernetes/v1/node_pool_genclient.go +++ b/pkg/apis/kubernetes/v1/node_pool_genclient.go @@ -7,7 +7,7 @@ import ( // EndpointURL returns the common URL for operations on NodePool resource func (np *NodePool) EndpointURL(ctx context.Context) (*url.URL, error) { - return endpointURL(ctx, np, "/api/kubernetes/v1/node_pool.json") + return endpointURL(ctx, np, "node_pool") } // FilterAPIRequestBody adds the CommonRequestBody