diff --git a/Makefile b/Makefile index 4b7d4f1eb..9cbe85839 100644 --- a/Makefile +++ b/Makefile @@ -71,8 +71,12 @@ test-clusterresources: test-kafkamanagement: KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./controllers/kafkamanagement -coverprofile cover.out +.PHONY: test-users +test-users: + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./controllers/tests + .PHONY: test -test: manifests generate fmt vet docker-build-server-stub run-server-stub envtest test-clusters test-clusterresources test-kafkamanagement stop-server-stub + test: manifests generate fmt vet docker-build-server-stub run-server-stub envtest test-clusters test-clusterresources test-kafkamanagement test-users stop-server-stub .PHONY: goimports goimports: diff --git a/controllers/clusterresources/cassandrauser_controller.go b/controllers/clusterresources/cassandrauser_controller.go index 5638dbd81..19c470288 100644 --- a/controllers/clusterresources/cassandrauser_controller.go +++ b/controllers/clusterresources/cassandrauser_controller.go @@ -123,6 +123,8 @@ func (r *CassandraUserReconciler) Reconcile(ctx context.Context, req ctrl.Reques for clusterID, event := range u.Status.ClustersEvents { if event == models.CreatingEvent { + l.Info("Creating user", "user", u, "cluster ID", clusterID) + err = r.API.CreateUser(u.ToInstAPI(username, password), clusterID, instaclustr.CassandraBundleUser) if err != nil { l.Error(err, "Cannot create a user for the Cassandra cluster", @@ -146,7 +148,7 @@ func (r *CassandraUserReconciler) Reconcile(ctx context.Context, req ctrl.Reques return models.ReconcileRequeue, nil } - l.Info("User has been created", "username", username) + l.Info("User has been created", "username", username, "cluster ID", clusterID) r.EventRecorder.Eventf(u, models.Normal, models.Created, "User has been created for a cluster. Cluster ID: %s, username: %s", clusterID, username) @@ -167,6 +169,8 @@ func (r *CassandraUserReconciler) Reconcile(ctx context.Context, req ctrl.Reques } if event == models.DeletingEvent { + l.Info("Deleting user", "user", u, "cluster ID", clusterID) + err = r.API.DeleteUser(username, clusterID, instaclustr.CassandraBundleUser) if err != nil { l.Error(err, "Cannot delete Cassandra user") diff --git a/controllers/clusters/cassandra_controller.go b/controllers/clusters/cassandra_controller.go index 0a0c65445..d58ae1024 100644 --- a/controllers/clusters/cassandra_controller.go +++ b/controllers/clusters/cassandra_controller.go @@ -639,6 +639,8 @@ func (r *CassandraReconciler) handleUsersCreate( return err } + l.Info("User has been added to the queue for creation", "username", u.Name) + return nil } @@ -692,6 +694,8 @@ func (r *CassandraReconciler) handleUsersDelete( return err } + l.Info("User has been added to the queue for deletion", "username", u.Name) + return nil } diff --git a/controllers/clusters/datatest/postgresql_v1beta1.yaml b/controllers/clusters/datatest/postgresql_v1beta1.yaml index 2a26e00dd..85edf43f3 100644 --- a/controllers/clusters/datatest/postgresql_v1beta1.yaml +++ b/controllers/clusters/datatest/postgresql_v1beta1.yaml @@ -6,8 +6,6 @@ metadata: testAnnotation: test spec: name: "testPostgre" - clusterConfigurations: - idle_in_transaction_session_timeout: "1" version: "14.5.0" dataCentres: - region: "US_EAST_1" diff --git a/controllers/tests/cassandra_plus_users_test.go b/controllers/tests/cassandra_plus_users_test.go new file mode 100644 index 000000000..00f03332b --- /dev/null +++ b/controllers/tests/cassandra_plus_users_test.go @@ -0,0 +1,191 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tests + +import ( + "context" + "os" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/utils/strings/slices" + + clusterresource "github.com/instaclustr/operator/apis/clusterresources/v1beta1" + "github.com/instaclustr/operator/apis/clusters/v1beta1" + openapi "github.com/instaclustr/operator/pkg/instaclustr/mock/server/go" + "github.com/instaclustr/operator/pkg/models" +) + +var _ = Describe("Basic Cassandra User controller + Basic Cassandra cluster controllers flow", func() { + var ( + ns = "default" + + user clusterresource.CassandraUser + userManifest clusterresource.CassandraUser + + secret v1.Secret + secretManifest v1.Secret + + cassandra v1beta1.Cassandra + cassandraManifest v1beta1.Cassandra + + timeout = time.Second * 5 + interval = time.Second * 2 + ) + + ctx := context.Background() + + cassandraUserYAML, err := os.ReadFile("datatest/clusterresources_v1beta1_cassandrauser.yaml") + Expect(err).NotTo(HaveOccurred()) + + err = yaml.Unmarshal(cassandraUserYAML, &userManifest) + Expect(err).NotTo(HaveOccurred()) + + cassandraUserNS := types.NamespacedName{Name: userManifest.ObjectMeta.Name, Namespace: ns} + + secretYAML, err := os.ReadFile("datatest/secret.yaml") + Expect(err).NotTo(HaveOccurred()) + + err = yaml.Unmarshal(secretYAML, &secretManifest) + Expect(err).NotTo(HaveOccurred()) + + secretNS := types.NamespacedName{Name: secretManifest.ObjectMeta.Name, Namespace: ns} + + cassandraYAML, err := os.ReadFile("datatest/clusters_v1beta1_cassandra.yaml") + Expect(err).NotTo(HaveOccurred()) + + err = yaml.Unmarshal(cassandraYAML, &cassandraManifest) + Expect(err).NotTo(HaveOccurred()) + + cassandraNS := types.NamespacedName{Name: cassandraManifest.ObjectMeta.Name, Namespace: ns} + + When("apply a secret and a cassandra user manifests", func() { + It("should create both resources and they've got to have a link with themselves through finalizer", func() { + Expect(k8sClient.Create(ctx, &secretManifest)).Should(Succeed()) + Expect(k8sClient.Create(ctx, &userManifest)).Should(Succeed()) + + Eventually(func() bool { + if err := k8sClient.Get(ctx, cassandraUserNS, &user); err != nil { + return false + } + + if err := k8sClient.Get(ctx, secretNS, &secret); err != nil { + return false + } + + if user.Finalizers == nil { + return false + } + + uniqFinalizer := user.GetDeletionFinalizer() + + return slices.Contains(user.Finalizers, uniqFinalizer) || slices.Contains(secret.Finalizers, uniqFinalizer) + }).Should(BeTrue()) + }) + }) + + When("apply a cassandra manifest", func() { + cassandraManifest.Annotations = map[string]string{models.ResourceStateAnnotation: models.CreatingEvent} + It("should create a cassandra resource", func() { + Expect(k8sClient.Create(ctx, &cassandraManifest)).Should(Succeed()) + + By("sending Cassandra specification to the Instaclustr API and get ID of a created cluster") + + Eventually(func() bool { + if err := k8sClient.Get(ctx, cassandraNS, &cassandra); err != nil { + return false + } + + return cassandra.Status.ID == openapi.CreatedID + }).Should(BeTrue()) + }) + }) + + When("add the user to a Cassandra UserReference", func() { + newUsers := []*v1beta1.UserReference{{ + Namespace: userManifest.Namespace, + Name: userManifest.Name, + }} + cassandraNS := types.NamespacedName{Name: cassandraManifest.ObjectMeta.Name, Namespace: ns} + userNS := types.NamespacedName{Name: userManifest.ObjectMeta.Name, Namespace: ns} + + It("should create the user for the cluster", func() { + + Expect(k8sClient.Get(ctx, cassandraNS, &cassandra)).Should(Succeed()) + + patch := cassandra.NewPatch() + cassandra.Spec.UserRefs = newUsers + + Expect(k8sClient.Patch(ctx, &cassandra, patch)).Should(Succeed()) + + By("going to Cassandra(cluster) controller predicate and put user entity to creation state. " + + "Finally creates the user for the corresponded cluster") + + clusterID := cassandra.Status.ID + Eventually(func() bool { + if err := k8sClient.Get(ctx, cassandraNS, &cassandra); err != nil { + return false + } + + if err := k8sClient.Get(ctx, userNS, &user); err != nil { + return false + } + + if state, exist := user.Status.ClustersEvents[clusterID]; exist && state != models.Created { + return false + } + + return true + }, timeout, interval).Should(BeTrue()) + }) + }) + + When("remove the user from the Cassandra UserReference", func() { + It("should delete the user for the cluster", func() { + Expect(k8sClient.Get(ctx, cassandraNS, &cassandra)).Should(Succeed()) + + patch := cassandra.NewPatch() + cassandra.Spec.UserRefs = []*v1beta1.UserReference{} + + Expect(k8sClient.Patch(ctx, &cassandra, patch)).Should(Succeed()) + + By("going to Cassandra(cluster) controller predicate and put user entity to deletion state. " + + "Finally deletes the user for the corresponded cluster") + + clusterID := cassandra.Status.ID + Eventually(func() bool { + if err := k8sClient.Get(ctx, cassandraNS, &cassandra); err != nil { + return false + } + + if err := k8sClient.Get(ctx, cassandraUserNS, &user); err != nil { + return false + } + + if _, exist := user.Status.ClustersEvents[clusterID]; exist { + return false + } + + return true + }, timeout, interval).Should(BeTrue()) + }) + }) +}) diff --git a/controllers/tests/datatest/clusterresources_v1beta1_cassandrauser.yaml b/controllers/tests/datatest/clusterresources_v1beta1_cassandrauser.yaml new file mode 100644 index 000000000..5e95ab7b7 --- /dev/null +++ b/controllers/tests/datatest/clusterresources_v1beta1_cassandrauser.yaml @@ -0,0 +1,9 @@ +apiVersion: clusterresources.instaclustr.com/v1beta1 +kind: CassandraUser +metadata: + name: cassandrauser-sample + namespace: default +spec: + secretRef: + name: "secret-sample" + namespace: "default" diff --git a/controllers/tests/datatest/clusters_v1beta1_cassandra.yaml b/controllers/tests/datatest/clusters_v1beta1_cassandra.yaml new file mode 100644 index 000000000..a86abd6a5 --- /dev/null +++ b/controllers/tests/datatest/clusters_v1beta1_cassandra.yaml @@ -0,0 +1,54 @@ +apiVersion: clusters.instaclustr.com/v1beta1 +kind: Cassandra +metadata: + name: cassandra-sample + namespace: default +spec: + name: "Cassandra" + version: "3.11.13" + bundledUseOnly: true + dataCentres: + - name: "AWS_cassandra" + region: "US_EAST_1" + cloudProvider: "AWS_VPC" + continuousBackup: true + nodesNumber: 2 + replicationFactor: 2 + privateIpBroadcastForDiscovery: true + network: "172.16.0.0/19" + tags: + "tag": "testTag" + clientToClusterEncryption: true + nodeSize: "CAS-DEV-t4g.small-5" +# accountName: "asdf" +# cloudProviderSettings: +# - customVirtualNetworkId: "vpc-12345678" +# diskEncryptionKey: "123e4567-e89b-12d3-a456-426614174000" +# resourceGroup: "asdfadfsdfas" +# - name: "Second Data Centre" +# region: "US_EAST_1" +# cloudProvider: "AWS_VPC" +# continuousBackup: true +# nodesNumber: 2 +# replicationFactor: 2 +# privateIpBroadcastForDiscovery: true +# nodeSize: "CAS-DEV-t4g.small-5" +# network: "172.16.0.0/19" +# tags: +# "tag": "testTag" +# clientToClusterEncryption: true +# accountName: "asdf" +# cloudProviderSettings: +# - customVirtualNetworkId: "vpc-12345678" +# diskEncryptionKey: "123e4567-e89b-12d3-a456-426614174000" +# resourceGroup: "asdfadfsdfas" + pciCompliance: true + luceneEnabled: true + passwordAndUserAuth: true + privateNetworkCluster: true + slaTier: "NON_PRODUCTION" +# twoFactorDelete: +# - email: "emailTest" +# phone: "phoneTest" +# spark: +# - version: "2.3.2" diff --git a/controllers/tests/datatest/secret.yaml b/controllers/tests/datatest/secret.yaml new file mode 100644 index 000000000..d1e40b125 --- /dev/null +++ b/controllers/tests/datatest/secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-sample + namespace: default +data: + password: NDgxMzU5ODM1NzlmMDU0ZTlhY2I4ZjcxMTMzMzQ1MjM3ZQo= + username: b2xvbG8K diff --git a/controllers/tests/suite_test.go b/controllers/tests/suite_test.go new file mode 100644 index 000000000..6b27caddb --- /dev/null +++ b/controllers/tests/suite_test.go @@ -0,0 +1,131 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tests + +import ( + "context" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + clusterresourcesv1beta1 "github.com/instaclustr/operator/apis/clusterresources/v1beta1" + "github.com/instaclustr/operator/apis/clusters/v1beta1" + "github.com/instaclustr/operator/controllers/clusterresources" + "github.com/instaclustr/operator/controllers/clusters" + "github.com/instaclustr/operator/pkg/instaclustr" + "github.com/instaclustr/operator/pkg/models" + "github.com/instaclustr/operator/pkg/scheduler" + //+kubebuilder:scaffold:imports +) + +var ( + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + instaClient := instaclustr.NewClient("test", "test", "http://localhost:8082", time.Second*10) + + err = v1beta1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + err = clusterresourcesv1beta1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) + Expect(err).ToNot(HaveOccurred()) + + eRecorder := k8sManager.GetEventRecorderFor("instaclustr-operator-tests") + + scheduler.ClusterStatusInterval = 1 * time.Second + scheduler.ClusterBackupsInterval = 30 * time.Second + models.ReconcileRequeue = reconcile.Result{RequeueAfter: time.Second * 3} + + err = (&clusters.CassandraReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + API: instaClient, + Scheduler: scheduler.NewScheduler(logf.Log), + EventRecorder: eRecorder, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&clusterresources.CassandraUserReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + API: instaClient, + EventRecorder: eRecorder, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + go func() { + defer GinkgoRecover() + err = k8sManager.Start(ctx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() + +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/pkg/instaclustr/mock/server/.openapi-generator/FILES b/pkg/instaclustr/mock/server/.openapi-generator/FILES index 7a0ee02d9..4ad5c2f99 100644 --- a/pkg/instaclustr/mock/server/.openapi-generator/FILES +++ b/pkg/instaclustr/mock/server/.openapi-generator/FILES @@ -15,10 +15,11 @@ go/api_awsvpc_peer_v2.go go/api_awsvpc_peer_v2_service.go go/api_azure_vnet_peer_v2.go go/api_azure_vnet_peer_v2_service.go +go/api_bundle_user.go +go/api_bundle_user_service.go go/api_cadence_cluster_v2.go go/api_cadence_cluster_v2_service.go go/api_cassandra_cluster_v2.go -go/api_cassandra_cluster_v2_service.go go/api_cluster_network_firewall_rule_v2.go go/api_cluster_network_firewall_rule_v2_service.go go/api_gcpvpc_peer_v2.go @@ -81,6 +82,8 @@ go/model_aws_vpc_peer_update_v2.go go/model_aws_vpc_peer_v2.go go/model_azure_vnet_peer_summary_v2.go go/model_azure_vnet_peer_v2.go +go/model_bundle_user_create_request.go +go/model_bundle_user_delete_request.go go/model_cadence_advanced_visibility_v2.go go/model_cadence_cluster_update_v2.go go/model_cadence_cluster_v2.go @@ -99,10 +102,12 @@ go/model_common_cluster_v2.go go/model_common_data_centre_v2.go go/model_current_cluster_operation_status_v2.go go/model_error_list_response_v2.go +go/model_error_message.go go/model_error_response_v2.go go/model_firewall_rule_types_v2.go go/model_gcp_vpc_peer_summary_v2.go go/model_gcp_vpc_peer_v2.go +go/model_generic_response.go go/model_iam_principal_arn_v2.go go/model_iam_principal_arns_v2.go go/model_kafka_acl_list_v2.go diff --git a/pkg/instaclustr/mock/server/.openapi-generator/VERSION b/pkg/instaclustr/mock/server/.openapi-generator/VERSION index e7e42a4b5..cd802a1ec 100644 --- a/pkg/instaclustr/mock/server/.openapi-generator/VERSION +++ b/pkg/instaclustr/mock/server/.openapi-generator/VERSION @@ -1 +1 @@ -6.3.0 \ No newline at end of file +6.6.0 \ No newline at end of file diff --git a/pkg/instaclustr/mock/server/README.md b/pkg/instaclustr/mock/server/README.md index 8d0709a7a..b41108af8 100644 --- a/pkg/instaclustr/mock/server/README.md +++ b/pkg/instaclustr/mock/server/README.md @@ -13,7 +13,7 @@ To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 2.0.0 -- Build date: 2023-02-14T18:44:54.991147+02:00[Europe/Kyiv] +- Build date: 2023-08-04T13:06:08.141718+03:00[Europe/Kyiv] ### Running the server diff --git a/pkg/instaclustr/mock/server/api/openapi.yaml b/pkg/instaclustr/mock/server/api/openapi.yaml index ff38d2017..b1aa4af24 100644 --- a/pkg/instaclustr/mock/server/api/openapi.yaml +++ b/pkg/instaclustr/mock/server/api/openapi.yaml @@ -71,6 +71,147 @@ tags: - description: "" name: Terraform Import V2 paths: + /provisioning/v1/{clusterId}/{bundle}/users: + delete: + deprecated: false + description: Delete bundle user for a given username + operationId: deleteUser + parameters: + - in: path + name: clusterId + required: true + schema: {} + - in: path + name: bundle + required: true + schema: {} + responses: + "200": + content: {} + description: Bundle user successfully deleted + "400": + content: {} + description: Bad Request + "401": + content: {} + description: Not Authorized + "403": + content: {} + description: Missing permissions + "404": + content: {} + description: Resource not found + "415": + content: {} + description: "Unsupported media type: returned when the payload is in an\ + \ unsupported format." + "429": + content: {} + description: "Too many requests: returned when more than 35 requests per\ + \ second are being received by your user." + "503": + content: {} + description: Service Unavailable + "504": + content: {} + description: Gateway Timeout + summary: Delete a bundle user + tags: + - Bundle User + get: + deprecated: false + description: Retrieve a list of bundle users currently enabled in the given + cluster. + operationId: listUsers + parameters: + - in: path + name: clusterId + required: true + schema: {} + - in: path + name: bundle + required: true + schema: {} + responses: + "200": + content: {} + description: Successfully returned a JSON list of bundle users + "400": + content: {} + description: Bad Request + "401": + content: {} + description: Not Authorized + "403": + content: {} + description: Missing permissions + "404": + content: {} + description: Resource not found + "415": + content: {} + description: "Unsupported media type: returned when the payload is in an\ + \ unsupported format." + "429": + content: {} + description: "Too many requests: returned when more than 35 requests per\ + \ second are being received by your user." + "503": + content: {} + description: Service Unavailable + "504": + content: {} + description: Gateway Timeout + summary: Get a list of bundle users + tags: + - Bundle User + post: + deprecated: false + description: Add a new bundle user to the given cluster with read write access + to all topics + operationId: createUser + parameters: + - in: path + name: clusterId + required: true + schema: {} + - in: path + name: bundle + required: true + schema: {} + responses: + "201": + content: {} + description: Bundle user successfully created + "400": + content: {} + description: Bad Request + "401": + content: {} + description: Not Authorized + "403": + content: {} + description: Missing permissions + "404": + content: {} + description: Resource not found + "415": + content: {} + description: "Unsupported media type: returned when the payload is in an\ + \ unsupported format." + "429": + content: {} + description: "Too many requests: returned when more than 35 requests per\ + \ second are being received by your user." + "503": + content: {} + description: Service Unavailable + "504": + content: {} + description: Gateway Timeout + summary: Add a bundle user + tags: + - Bundle User /cluster-management/v2/data-sources/kafka_connect_cluster/{clusterId}/mirrors/v2/: get: parameters: @@ -7407,6 +7548,75 @@ components: - region - replicationFactor type: object + BundleUserCreateRequest: + properties: + username: + example: yourUserName + maxLength: 255 + minLength: 1 + type: string + password: + description: Bundle user password + example: yourPassword + maxLength: 2147483647 + minLength: 8 + type: string + options: + description: |- + A collection of optional bundle-specific user options - the options available to be set differ depending on the bundle type. + + + +
BundleOptionAllowable values
Kafka"sasl-scram-mechanism""SCRAM-SHA-256" (Default), "SCRAM-SHA-512"
Kafka"override-existing-user"true (Default), false
+ example: + override-existing-user: true + sasl-scram-mechanism: SCRAM-SHA-512 + properties: {} + type: object + initial-permissions: + description: Permissions upon user creation + enum: + - standard + - read-only + - none + example: standard + type: string + required: + - password + - username + type: object + BundleUserDeleteRequest: + properties: + username: + example: yourUserName + maxLength: 255 + minLength: 1 + type: string + required: + - username + type: object + ErrorMessage: + properties: + status: + format: int32 + type: integer + message: + type: string + errorDetails: + properties: {} + type: object + code: + format: int32 + type: integer + link: + type: string + type: object + GenericResponse: + properties: + message: + description: message content + type: string + type: object KafkaConnectMirrorSummariesV2: description: A listable data source of all Mirrors within a Kafka Connect Cluster. example: diff --git a/pkg/instaclustr/mock/server/go/api.go b/pkg/instaclustr/mock/server/go/api.go index f72a2a1b5..96afff4ce 100644 --- a/pkg/instaclustr/mock/server/go/api.go +++ b/pkg/instaclustr/mock/server/go/api.go @@ -81,6 +81,14 @@ type AzureVnetPeerV2ApiRouter interface { ClusterManagementV2ResourcesProvidersAzureVnetPeersV2VpcPeerIdGet(http.ResponseWriter, *http.Request) } +// BundleUserApiRouter defines the required methods for binding the api requests to a responses for the BundleUserApi +// The BundleUserApiRouter implementation should parse necessary information from the http request, +// pass the data to a BundleUserApiServicer to perform the required actions, then write the service results to the http response. +type BundleUserApiRouter interface { + CreateUser(http.ResponseWriter, *http.Request) + DeleteUser(http.ResponseWriter, *http.Request) +} + // CadenceClusterV2ApiRouter defines the required methods for binding the api requests to a responses for the CadenceClusterV2Api // The CadenceClusterV2ApiRouter implementation should parse necessary information from the http request, // pass the data to a CadenceClusterV2ApiServicer to perform the required actions, then write the service results to the http response. @@ -374,6 +382,15 @@ type AzureVnetPeerV2ApiServicer interface { ClusterManagementV2ResourcesProvidersAzureVnetPeersV2VpcPeerIdGet(context.Context, string) (ImplResponse, error) } +// BundleUserApiServicer defines the api actions for the BundleUserApi service +// This interface intended to stay up to date with the openapi yaml used to generate it, +// while the service implementation can be ignored with the .openapi-generator-ignore file +// and updated with the logic required for the API. +type BundleUserApiServicer interface { + CreateUser(context.Context, interface{}, interface{}) (ImplResponse, error) + DeleteUser(context.Context, interface{}, interface{}) (ImplResponse, error) +} + // CadenceClusterV2ApiServicer defines the api actions for the CadenceClusterV2Api service // This interface intended to stay up to date with the openapi yaml used to generate it, // while the service implementation can be ignored with the .openapi-generator-ignore file diff --git a/pkg/instaclustr/mock/server/go/api_bundle_user.go b/pkg/instaclustr/mock/server/go/api_bundle_user.go new file mode 100644 index 000000000..a50641904 --- /dev/null +++ b/pkg/instaclustr/mock/server/go/api_bundle_user.go @@ -0,0 +1,97 @@ +/* + * Instaclustr Cluster Management API + * + * Instaclustr Cluster Management API + * + * API version: 2.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package openapi + +import ( + "net/http" + "strings" + + "github.com/gorilla/mux" +) + +// BundleUserApiController binds http requests to an api service and writes the service results to the http response +type BundleUserApiController struct { + service BundleUserApiServicer + errorHandler ErrorHandler +} + +// BundleUserApiOption for how the controller is set up. +type BundleUserApiOption func(*BundleUserApiController) + +// WithBundleUserApiErrorHandler inject ErrorHandler into controller +func WithBundleUserApiErrorHandler(h ErrorHandler) BundleUserApiOption { + return func(c *BundleUserApiController) { + c.errorHandler = h + } +} + +// NewBundleUserApiController creates a default api controller +func NewBundleUserApiController(s BundleUserApiServicer, opts ...BundleUserApiOption) Router { + controller := &BundleUserApiController{ + service: s, + errorHandler: DefaultErrorHandler, + } + + for _, opt := range opts { + opt(controller) + } + + return controller +} + +// Routes returns all the api routes for the BundleUserApiController +func (c *BundleUserApiController) Routes() Routes { + return Routes{ + { + "CreateUser", + strings.ToUpper("Post"), + "/provisioning/v1/{clusterId}/{bundle}/users", + c.CreateUser, + }, + { + "DeleteUser", + strings.ToUpper("Delete"), + "/provisioning/v1/{clusterId}/{bundle}/users", + c.DeleteUser, + }, + } +} + +// CreateUser - Add a bundle user +func (c *BundleUserApiController) CreateUser(w http.ResponseWriter, r *http.Request) { + params := mux.Vars(r) + clusterIdParam := params["clusterId"] + bundleParam := params["bundle"] + result, err := c.service.CreateUser(r.Context(), clusterIdParam, bundleParam) + // If an error occurred, encode the error with the status code + if err != nil { + c.errorHandler(w, r, err, &result) + return + } + // If no error, encode the body and the result code + EncodeJSONResponse(result.Body, &result.Code, w) + +} + +// DeleteUser - Delete a bundle user +func (c *BundleUserApiController) DeleteUser(w http.ResponseWriter, r *http.Request) { + params := mux.Vars(r) + clusterIdParam := params["clusterId"] + bundleParam := params["bundle"] + result, err := c.service.DeleteUser(r.Context(), clusterIdParam, bundleParam) + // If an error occurred, encode the error with the status code + if err != nil { + c.errorHandler(w, r, err, &result) + return + } + // If no error, encode the body and the result code + EncodeJSONResponse(result.Body, &result.Code, w) + +} diff --git a/pkg/instaclustr/mock/server/go/api_bundle_user_service.go b/pkg/instaclustr/mock/server/go/api_bundle_user_service.go new file mode 100644 index 000000000..6d8397d3e --- /dev/null +++ b/pkg/instaclustr/mock/server/go/api_bundle_user_service.go @@ -0,0 +1,58 @@ +/* + * Instaclustr Cluster Management API + * + * Instaclustr Cluster Management API + * + * API version: 2.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package openapi + +import ( + "context" +) + +// BundleUserApiService is a service that implements the logic for the BundleUserApiServicer +// This service should implement the business logic for every endpoint for the BundleUserApi API. +// Include any external packages or services that will be required by this service. +type BundleUserApiService struct { +} + +// NewBundleUserApiService creates a default api service +func NewBundleUserApiService() BundleUserApiServicer { + return &BundleUserApiService{} +} + +// CreateUser - Add a bundle user +func (s *BundleUserApiService) CreateUser(ctx context.Context, clusterId interface{}, bundle interface{}) (ImplResponse, error) { + // TODO - update CreateUser with the required logic for this service method. + // Add api_bundle_user_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation. + + //TODO: Uncomment the next line to return response Response(201, {}) or use other options such as http.Ok ... + return Response(201, nil), nil + + //TODO: Uncomment the next line to return response Response(400, {}) or use other options such as http.Ok ... + //return Response(400, nil),nil + + //TODO: Uncomment the next line to return response Response(404, {}) or use other options such as http.Ok ... + //return Response(404, nil),nil + + //return Response(http.StatusNotImplemented, nil), errors.New("CreateUser method not implemented") +} + +// DeleteUser - Delete a bundle user +func (s *BundleUserApiService) DeleteUser(ctx context.Context, clusterId interface{}, bundle interface{}) (ImplResponse, error) { + // TODO - update DeleteUser with the required logic for this service method. + // Add api_bundle_user_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation. + + return Response(200, nil), nil + + //TODO: Uncomment the next line to return response Response(400, {}) or use other options such as http.Ok ... + //return Response(400, nil),nil + + //TODO: Uncomment the next line to return response Response(404, {}) or use other options such as http.Ok ... + //return Response(404, nil),nil + + //return Response(http.StatusNotImplemented, nil), errors.New("DeleteUser method not implemented") +} diff --git a/pkg/instaclustr/mock/server/go/model_bundle_user_create_request.go b/pkg/instaclustr/mock/server/go/model_bundle_user_create_request.go new file mode 100644 index 000000000..90d32d727 --- /dev/null +++ b/pkg/instaclustr/mock/server/go/model_bundle_user_create_request.go @@ -0,0 +1,50 @@ +/* + * Instaclustr Cluster Management API + * + * Instaclustr Cluster Management API + * + * API version: 2.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package openapi + +type BundleUserCreateRequest struct { + Username string `json:"username"` + + // Bundle user password + Password string `json:"password"` + + // A collection of optional bundle-specific user options - the options available to be set differ depending on the bundle type.
BundleOptionAllowable values
Kafka\"sasl-scram-mechanism\"\"SCRAM-SHA-256\" (Default), \"SCRAM-SHA-512\"
Kafka\"override-existing-user\"true (Default), false
+ Options map[string]interface{} `json:"options,omitempty"` + + // Permissions upon user creation + InitialPermissions string `json:"initial-permissions,omitempty"` +} + +// AssertBundleUserCreateRequestRequired checks if the required fields are not zero-ed +func AssertBundleUserCreateRequestRequired(obj BundleUserCreateRequest) error { + elements := map[string]interface{}{ + "username": obj.Username, + "password": obj.Password, + } + for name, el := range elements { + if isZero := IsZeroValue(el); isZero { + return &RequiredError{Field: name} + } + } + + return nil +} + +// AssertRecurseBundleUserCreateRequestRequired recursively checks if required fields are not zero-ed in a nested slice. +// Accepts only nested slice of BundleUserCreateRequest (e.g. [][]BundleUserCreateRequest), otherwise ErrTypeAssertionError is thrown. +func AssertRecurseBundleUserCreateRequestRequired(objSlice interface{}) error { + return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error { + aBundleUserCreateRequest, ok := obj.(BundleUserCreateRequest) + if !ok { + return ErrTypeAssertionError + } + return AssertBundleUserCreateRequestRequired(aBundleUserCreateRequest) + }) +} diff --git a/pkg/instaclustr/mock/server/go/model_bundle_user_delete_request.go b/pkg/instaclustr/mock/server/go/model_bundle_user_delete_request.go new file mode 100644 index 000000000..62bdac8f5 --- /dev/null +++ b/pkg/instaclustr/mock/server/go/model_bundle_user_delete_request.go @@ -0,0 +1,40 @@ +/* + * Instaclustr Cluster Management API + * + * Instaclustr Cluster Management API + * + * API version: 2.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package openapi + +type BundleUserDeleteRequest struct { + Username string `json:"username"` +} + +// AssertBundleUserDeleteRequestRequired checks if the required fields are not zero-ed +func AssertBundleUserDeleteRequestRequired(obj BundleUserDeleteRequest) error { + elements := map[string]interface{}{ + "username": obj.Username, + } + for name, el := range elements { + if isZero := IsZeroValue(el); isZero { + return &RequiredError{Field: name} + } + } + + return nil +} + +// AssertRecurseBundleUserDeleteRequestRequired recursively checks if required fields are not zero-ed in a nested slice. +// Accepts only nested slice of BundleUserDeleteRequest (e.g. [][]BundleUserDeleteRequest), otherwise ErrTypeAssertionError is thrown. +func AssertRecurseBundleUserDeleteRequestRequired(objSlice interface{}) error { + return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error { + aBundleUserDeleteRequest, ok := obj.(BundleUserDeleteRequest) + if !ok { + return ErrTypeAssertionError + } + return AssertBundleUserDeleteRequestRequired(aBundleUserDeleteRequest) + }) +} diff --git a/pkg/instaclustr/mock/server/go/model_error_message.go b/pkg/instaclustr/mock/server/go/model_error_message.go new file mode 100644 index 000000000..f616e8efc --- /dev/null +++ b/pkg/instaclustr/mock/server/go/model_error_message.go @@ -0,0 +1,39 @@ +/* + * Instaclustr Cluster Management API + * + * Instaclustr Cluster Management API + * + * API version: 2.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package openapi + +type ErrorMessage struct { + Status int32 `json:"status,omitempty"` + + Message string `json:"message,omitempty"` + + ErrorDetails map[string]interface{} `json:"errorDetails,omitempty"` + + Code int32 `json:"code,omitempty"` + + Link string `json:"link,omitempty"` +} + +// AssertErrorMessageRequired checks if the required fields are not zero-ed +func AssertErrorMessageRequired(obj ErrorMessage) error { + return nil +} + +// AssertRecurseErrorMessageRequired recursively checks if required fields are not zero-ed in a nested slice. +// Accepts only nested slice of ErrorMessage (e.g. [][]ErrorMessage), otherwise ErrTypeAssertionError is thrown. +func AssertRecurseErrorMessageRequired(objSlice interface{}) error { + return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error { + aErrorMessage, ok := obj.(ErrorMessage) + if !ok { + return ErrTypeAssertionError + } + return AssertErrorMessageRequired(aErrorMessage) + }) +} diff --git a/pkg/instaclustr/mock/server/go/model_generic_response.go b/pkg/instaclustr/mock/server/go/model_generic_response.go new file mode 100644 index 000000000..8e7e679c3 --- /dev/null +++ b/pkg/instaclustr/mock/server/go/model_generic_response.go @@ -0,0 +1,33 @@ +/* + * Instaclustr Cluster Management API + * + * Instaclustr Cluster Management API + * + * API version: 2.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package openapi + +type GenericResponse struct { + + // message content + Message string `json:"message,omitempty"` +} + +// AssertGenericResponseRequired checks if the required fields are not zero-ed +func AssertGenericResponseRequired(obj GenericResponse) error { + return nil +} + +// AssertRecurseGenericResponseRequired recursively checks if required fields are not zero-ed in a nested slice. +// Accepts only nested slice of GenericResponse (e.g. [][]GenericResponse), otherwise ErrTypeAssertionError is thrown. +func AssertRecurseGenericResponseRequired(objSlice interface{}) error { + return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error { + aGenericResponse, ok := obj.(GenericResponse) + if !ok { + return ErrTypeAssertionError + } + return AssertGenericResponseRequired(aGenericResponse) + }) +} diff --git a/pkg/instaclustr/mock/server/go/routers.go b/pkg/instaclustr/mock/server/go/routers.go index 391b1a971..10104f43d 100644 --- a/pkg/instaclustr/mock/server/go/routers.go +++ b/pkg/instaclustr/mock/server/go/routers.go @@ -49,9 +49,9 @@ func NewRouter(routers ...Router) *mux.Router { handler = route.HandlerFunc handler = Logger(handler, route.Name) - if route.Method == "POST" { + if route.Method == "POST" && route.Name != "CreateUser" { route.Pattern = strings.TrimSuffix(route.Pattern, "v2") - route.Pattern += "{v2:v2\\/?}" + route.Pattern += "v2/" } router. diff --git a/pkg/instaclustr/mock/server/instaclustr_openapi_v2.yaml b/pkg/instaclustr/mock/server/instaclustr_openapi_v2.yaml index 92d3d5c19..5360ddeca 100644 --- a/pkg/instaclustr/mock/server/instaclustr_openapi_v2.yaml +++ b/pkg/instaclustr/mock/server/instaclustr_openapi_v2.yaml @@ -2,6 +2,137 @@ security: - terraformKey: [] - basicAuth: [] paths: + /provisioning/v1/{clusterId}/{bundle}/users: + post: + tags: + - "Bundle User" + summary: "Add a bundle user" + description: "Add a new bundle user to the given cluster with read write access\ + \ to all topics" + operationId: "createUser" + parameters: + - name: "clusterId" + in: "path" + required: true + style: "simple" + explode: false + schema: + type: "string" + format: "uuid" + example: "64223f17-7c9b-4986-8e2e-a44a91a26635" + - name: "bundle" + in: "path" + required: true + style: "simple" + explode: false + schema: + type: "string" + example: "kafka" + requestBody: + content: + application/json: + schema: + $ref: "#/definitions/BundleUserCreateRequest" + responses: + "201": + description: "Bundle user successfully created" + content: + application/json: + schema: + $ref: "#/definitions/GenericResponse" + example: + message: "User test1 created." + "400": + description: "Bad Request" + content: + application/json: + schema: + $ref: "#/definitions/ErrorMessage" + "404": + description: "Resource not found" + content: + application/json: + schema: + $ref: "#/definitions/ErrorMessage" + delete: + tags: + - "Bundle User" + summary: "Delete a bundle user" + description: "Delete bundle user for a given username" + operationId: "deleteUser" + parameters: + - name: "clusterId" + in: "path" + required: true + style: "simple" + explode: false + schema: + type: "string" + format: "uuid" + example: "64223f17-7c9b-4986-8e2e-a44a91a26635" + - name: "bundle" + in: "path" + required: true + style: "simple" + explode: false + schema: + type: "string" + example: "kafka" + requestBody: + content: + application/json: + schema: + $ref: "#/definitions/BundleUserDeleteRequest" + responses: + "200": + description: "Bundle user successfully deleted" + content: + application/json: + schema: + $ref: "#/definitions/GenericResponse" + example: + message: "User test1 has been deleted." + "400": + description: "Bad Request" + content: + application/json: + schema: + $ref: "#/definitions/ErrorMessage" + "401": + description: "Not Authorized" + content: + application/json: { } + "403": + description: "Missing permissions" + content: + application/json: + schema: + $ref: "#/definitions/ErrorMessage" + "404": + description: "Resource not found" + content: + application/json: + schema: + $ref: "#/definitions/ErrorMessage" + "415": + description: "Unsupported media type: returned when the payload is in an\ + \ unsupported format." + content: + application/json: + schema: + $ref: "#/definitions/ErrorMessage" + "429": + description: "Too many requests: returned when more than 35 requests per\ + \ second are being received by your user." + content: + application/json: + schema: + $ref: "#/definitions/ErrorMessage" + "503": + description: "Service Unavailable" + "504": + description: "Gateway Timeout" + deprecated: false /cluster-management/v2/data-sources/kafka_connect_cluster/{clusterId}/mirrors/v2/: x-terraform-resource-name: mirrors_v2 get: @@ -4608,6 +4739,74 @@ definitions: example: RUNNING} required: [nodeSize, replicationFactor, numberOfNodes, name, region, cloudProvider, network] + BundleUserCreateRequest: + required: + - "password" + - "username" + type: "object" + properties: + username: + maxLength: 255 + minLength: 1 + type: "string" + example: "yourUserName" + password: + maxLength: 2147483647 + minLength: 8 + type: "string" + description: "Bundle user password" + example: "yourPassword" + options: + type: "object" + description: "A collection of optional bundle-specific user options - the\ + \ options available to be set differ depending on the bundle type.\nBundleOptionAllowable\ + \ values\nKafka\"sasl-scram-mechanism\"\"\ + SCRAM-SHA-256\" (Default), \"SCRAM-SHA-512\"\nKafka\"\ + override-existing-user\"true (Default), false\n" + example: + override-existing-user: true + sasl-scram-mechanism: "SCRAM-SHA-512" + initial-permissions: + type: "string" + description: "Permissions upon user creation" + writeOnly: true + example: "standard" + enum: + - "standard" + - "read-only" + - "none" + BundleUserDeleteRequest: + required: + - "username" + type: "object" + properties: + username: + maxLength: 255 + minLength: 1 + type: "string" + example: "yourUserName" + ErrorMessage: + type: "object" + properties: + status: + type: "integer" + format: "int32" + message: + type: "string" + errorDetails: + type: "object" + code: + type: "integer" + format: "int32" + link: + type: "string" + GenericResponse: + type: "object" + properties: + message: + type: "string" + description: "message content" KafkaConnectMirrorSummariesV2: description: A listable data source of all Mirrors within a Kafka Connect Cluster. type: object diff --git a/pkg/instaclustr/mock/server/main.go b/pkg/instaclustr/mock/server/main.go index 162a17e4a..55ff20894 100644 --- a/pkg/instaclustr/mock/server/main.go +++ b/pkg/instaclustr/mock/server/main.go @@ -40,6 +40,9 @@ func main() { AzureVnetPeerV2ApiService := openapi.NewAzureVnetPeerV2ApiService() AzureVnetPeerV2ApiController := openapi.NewAzureVnetPeerV2ApiController(AzureVnetPeerV2ApiService) + BundleUserApiService := openapi.NewBundleUserApiService() + BundleUserApiController := openapi.NewBundleUserApiController(BundleUserApiService) + CadenceClusterV2ApiService := openapi.NewCadenceClusterV2ApiService() CadenceClusterV2ApiController := openapi.NewCadenceClusterV2ApiController(CadenceClusterV2ApiService) @@ -109,7 +112,7 @@ func main() { TerraformImportV2ApiService := openapi.NewTerraformImportV2ApiService() TerraformImportV2ApiController := openapi.NewTerraformImportV2ApiController(TerraformImportV2ApiService) - router := openapi.NewRouter(AWSEncryptionKeyV2ApiController, AWSEndpointServicePrincipalsV2ApiController, AWSSecurityGroupFirewallRuleV2ApiController, AWSVPCPeerV2ApiController, AccountClusterListV2ApiController, ApacheZookeeperClusterV2ApiController, AzureVnetPeerV2ApiController, CadenceClusterV2ApiController, CassandraClusterV2ApiController, ClusterNetworkFirewallRuleV2ApiController, GCPVPCPeerV2ApiController, KafkaACLV2ApiController, KafkaClusterV2ApiController, KafkaConnectClusterV2ApiController, KafkaConnectMirrorV2ApiController, KafkaTopicV2ApiController, KafkaUserV2ApiController, MongodbClusterV2ApiController, OpenAPISpecificationV2ApiController, OpenSearchClusterV2ApiController, OpenSearchEgressRulesV2ApiController, PostgresqlClusterV2ApiController, PostgresqlConfigurationV2ApiController, PostgresqlReloadOperationV2ApiController, PostgresqlUserV2ApiController, RedisClusterV2ApiController, RedisUserV2ApiController, SwaggerV2ApiController, TerraformDocumentationV2ApiController, TerraformImportV2ApiController) + router := openapi.NewRouter(AWSEncryptionKeyV2ApiController, AWSEndpointServicePrincipalsV2ApiController, AWSSecurityGroupFirewallRuleV2ApiController, AWSVPCPeerV2ApiController, AccountClusterListV2ApiController, ApacheZookeeperClusterV2ApiController, AzureVnetPeerV2ApiController, BundleUserApiController, CadenceClusterV2ApiController, CassandraClusterV2ApiController, ClusterNetworkFirewallRuleV2ApiController, GCPVPCPeerV2ApiController, KafkaACLV2ApiController, KafkaClusterV2ApiController, KafkaConnectClusterV2ApiController, KafkaConnectMirrorV2ApiController, KafkaTopicV2ApiController, KafkaUserV2ApiController, MongodbClusterV2ApiController, OpenAPISpecificationV2ApiController, OpenSearchClusterV2ApiController, OpenSearchEgressRulesV2ApiController, PostgresqlClusterV2ApiController, PostgresqlConfigurationV2ApiController, PostgresqlReloadOperationV2ApiController, PostgresqlUserV2ApiController, RedisClusterV2ApiController, RedisUserV2ApiController, SwaggerV2ApiController, TerraformDocumentationV2ApiController, TerraformImportV2ApiController) log.Fatal(http.ListenAndServe(":8082", router)) }