From 353570e70021b96bfd2e49340c2cd3ed08e7629b Mon Sep 17 00:00:00 2001 From: Murad Biashimov Date: Wed, 27 Mar 2024 12:38:14 +0100 Subject: [PATCH] feat(kafkaschemaregistryacl): add new kind --- CHANGELOG.md | 1 + PROJECT | 8 + api/v1alpha1/kafkaschemaregistryacl_types.go | 86 ++++++++ api/v1alpha1/zz_generated.deepcopy.go | 101 +++++++++ .../aiven.io_kafkaschemaregistryacls.yaml | 194 ++++++++++++++++++ .../templates/cluster_role.yaml | 26 +++ .../aiven.io_kafkaschemaregistryacls.yaml | 194 ++++++++++++++++++ config/crd/kustomization.yaml | 1 + ...ainjection_in_kafkaschemaregistryacls.yaml | 7 + .../webhook_in_kafkaschemaregistryacls.yaml | 16 ++ .../kafkaschemaregistryacl_editor_role.yaml | 31 +++ .../kafkaschemaregistryacl_viewer_role.yaml | 27 +++ config/rbac/role.yaml | 26 +++ .../_v1alpha1_kafkaschemaregistryacl.yaml | 12 ++ config/samples/kustomization.yaml | 1 + controllers/common.go | 4 + controllers/connectionpool_controller.go | 3 + controllers/kafkaacl_controller.go | 3 +- .../kafkaschemaregistryacl_controller.go | 164 +++++++++++++++ controllers/setup.go | 43 ++-- .../api-reference/kafkaschemaregistryacl.md | 43 ++++ main.go | 1 - tests/kafkschemaregistryaacl_test.go | 157 ++++++++++++++ tests/session.go | 10 +- tests/suite_test.go | 7 + 25 files changed, 1140 insertions(+), 26 deletions(-) create mode 100644 api/v1alpha1/kafkaschemaregistryacl_types.go create mode 100644 charts/aiven-operator-crds/templates/aiven.io_kafkaschemaregistryacls.yaml create mode 100644 config/crd/bases/aiven.io_kafkaschemaregistryacls.yaml create mode 100644 config/crd/patches/cainjection_in_kafkaschemaregistryacls.yaml create mode 100644 config/crd/patches/webhook_in_kafkaschemaregistryacls.yaml create mode 100644 config/rbac/kafkaschemaregistryacl_editor_role.yaml create mode 100644 config/rbac/kafkaschemaregistryacl_viewer_role.yaml create mode 100644 config/samples/_v1alpha1_kafkaschemaregistryacl.yaml create mode 100644 controllers/kafkaschemaregistryacl_controller.go create mode 100644 docs/docs/api-reference/kafkaschemaregistryacl.md create mode 100644 tests/kafkschemaregistryaacl_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d662acd..2aa69718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [MAJOR.MINOR.PATCH] - YYYY-MM-DD +- Add `KafkaSchemaRegistryACL` kind - Add `ClickhouseDatabase` kind - Fix secret creation for kinds with no secrets - Replace `Database` kind validations and default values with CRD validation rules diff --git a/PROJECT b/PROJECT index bd738021..65f86056 100644 --- a/PROJECT +++ b/PROJECT @@ -259,4 +259,12 @@ resources: kind: ClickhouseDatabase path: github.com/aiven/aiven-operator/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: aiven.io + kind: KafkaSchemaRegistryACL + path: github.com/aiven/aiven-operator/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/v1alpha1/kafkaschemaregistryacl_types.go b/api/v1alpha1/kafkaschemaregistryacl_types.go new file mode 100644 index 00000000..89960d5f --- /dev/null +++ b/api/v1alpha1/kafkaschemaregistryacl_types.go @@ -0,0 +1,86 @@ +// Copyright (c) 2024 Aiven, Helsinki, Finland. https://aiven.io/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// KafkaSchemaRegistryACLSpec defines the desired state of KafkaSchemaRegistryACL +type KafkaSchemaRegistryACLSpec struct { + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:Format="^[a-zA-Z0-9_-]*$" + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + // Identifies the project this resource belongs to + Project string `json:"project"` + + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + // Specifies the name of the service that this resource belongs to + ServiceName string `json:"serviceName"` + + // Authentication reference to Aiven token in a secret + AuthSecretRef *AuthSecretReference `json:"authSecretRef,omitempty"` + + // +kubebuilder:validation:Enum=schema_registry_read;schema_registry_write + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + Permission string `json:"permission"` + + // +kubebuilder:validation:MaxLength=249 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + // Resource name pattern for the Schema Registry ACL entry + Resource string `json:"resource"` + + // +kubebuilder:validation:MaxLength=64 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + // Username pattern for the ACL entry + Username string `json:"username"` +} + +// KafkaSchemaRegistryACLStatus defines the observed state of KafkaSchemaRegistryACL +type KafkaSchemaRegistryACLStatus struct { + // Conditions represent the latest available observations of an KafkaSchemaRegistryACL state + Conditions []metav1.Condition `json:"conditions"` + + // Kafka ACL ID + ACLId string `json:"acl_id"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// KafkaSchemaRegistryACL is the Schema for the kafkaschemaregistryacls API +type KafkaSchemaRegistryACL struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec KafkaSchemaRegistryACLSpec `json:"spec,omitempty"` + Status KafkaSchemaRegistryACLStatus `json:"status,omitempty"` +} + +func (in *KafkaSchemaRegistryACL) AuthSecretRef() *AuthSecretReference { + return in.Spec.AuthSecretRef +} + +func (in *KafkaSchemaRegistryACL) Conditions() *[]metav1.Condition { + return &in.Status.Conditions +} + +func (in *KafkaSchemaRegistryACL) NoSecret() bool { + return true +} + +var _ AivenManagedObject = &KafkaSchemaRegistryACL{} + +//+kubebuilder:object:root=true + +// KafkaSchemaRegistryACLList contains a list of KafkaSchemaRegistryACL +type KafkaSchemaRegistryACLList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []KafkaSchemaRegistryACL `json:"items"` +} + +func init() { + SchemeBuilder.Register(&KafkaSchemaRegistryACL{}, &KafkaSchemaRegistryACLList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 688992c8..bcc0b419 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1213,6 +1213,107 @@ func (in *KafkaSchemaList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KafkaSchemaRegistryACL) DeepCopyInto(out *KafkaSchemaRegistryACL) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KafkaSchemaRegistryACL. +func (in *KafkaSchemaRegistryACL) DeepCopy() *KafkaSchemaRegistryACL { + if in == nil { + return nil + } + out := new(KafkaSchemaRegistryACL) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KafkaSchemaRegistryACL) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KafkaSchemaRegistryACLList) DeepCopyInto(out *KafkaSchemaRegistryACLList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]KafkaSchemaRegistryACL, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KafkaSchemaRegistryACLList. +func (in *KafkaSchemaRegistryACLList) DeepCopy() *KafkaSchemaRegistryACLList { + if in == nil { + return nil + } + out := new(KafkaSchemaRegistryACLList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KafkaSchemaRegistryACLList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KafkaSchemaRegistryACLSpec) DeepCopyInto(out *KafkaSchemaRegistryACLSpec) { + *out = *in + if in.AuthSecretRef != nil { + in, out := &in.AuthSecretRef, &out.AuthSecretRef + *out = new(AuthSecretReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KafkaSchemaRegistryACLSpec. +func (in *KafkaSchemaRegistryACLSpec) DeepCopy() *KafkaSchemaRegistryACLSpec { + if in == nil { + return nil + } + out := new(KafkaSchemaRegistryACLSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KafkaSchemaRegistryACLStatus) DeepCopyInto(out *KafkaSchemaRegistryACLStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KafkaSchemaRegistryACLStatus. +func (in *KafkaSchemaRegistryACLStatus) DeepCopy() *KafkaSchemaRegistryACLStatus { + if in == nil { + return nil + } + out := new(KafkaSchemaRegistryACLStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KafkaSchemaSpec) DeepCopyInto(out *KafkaSchemaSpec) { *out = *in diff --git a/charts/aiven-operator-crds/templates/aiven.io_kafkaschemaregistryacls.yaml b/charts/aiven-operator-crds/templates/aiven.io_kafkaschemaregistryacls.yaml new file mode 100644 index 00000000..7eea9303 --- /dev/null +++ b/charts/aiven-operator-crds/templates/aiven.io_kafkaschemaregistryacls.yaml @@ -0,0 +1,194 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: kafkaschemaregistryacls.aiven.io +spec: + group: aiven.io + names: + kind: KafkaSchemaRegistryACL + listKind: KafkaSchemaRegistryACLList + plural: kafkaschemaregistryacls + singular: kafkaschemaregistryacl + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: + KafkaSchemaRegistryACL is the Schema for the kafkaschemaregistryacls + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: KafkaSchemaRegistryACLSpec defines the desired state of KafkaSchemaRegistryACL + properties: + authSecretRef: + description: Authentication reference to Aiven token in a secret + properties: + key: + minLength: 1 + type: string + name: + minLength: 1 + type: string + required: + - key + - name + type: object + permission: + enum: + - schema_registry_read + - schema_registry_write + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + project: + description: Identifies the project this resource belongs to + format: ^[a-zA-Z0-9_-]*$ + maxLength: 63 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + resource: + description: Resource name pattern for the Schema Registry ACL entry + maxLength: 249 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + serviceName: + description: + Specifies the name of the service that this resource + belongs to + maxLength: 63 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + username: + description: Username pattern for the ACL entry + maxLength: 64 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + required: + - permission + - project + - resource + - serviceName + - username + type: object + status: + description: + KafkaSchemaRegistryACLStatus defines the observed state of + KafkaSchemaRegistryACL + properties: + acl_id: + description: Kafka ACL ID + type: string + conditions: + description: + Conditions represent the latest available observations + of an KafkaSchemaRegistryACL state + items: + description: + "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: + lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: + message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: + observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: + reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: + type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + required: + - acl_id + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/aiven-operator/templates/cluster_role.yaml b/charts/aiven-operator/templates/cluster_role.yaml index 1d29f8ff..d4f67fbe 100644 --- a/charts/aiven-operator/templates/cluster_role.yaml +++ b/charts/aiven-operator/templates/cluster_role.yaml @@ -344,6 +344,32 @@ rules: - get - patch - update + - apiGroups: + - aiven.io + resources: + - kafkaschemaregistryacls + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - aiven.io + resources: + - kafkaschemaregistryacls/finalizers + verbs: + - update + - apiGroups: + - aiven.io + resources: + - kafkaschemaregistryacls/status + verbs: + - get + - patch + - update - apiGroups: - aiven.io resources: diff --git a/config/crd/bases/aiven.io_kafkaschemaregistryacls.yaml b/config/crd/bases/aiven.io_kafkaschemaregistryacls.yaml new file mode 100644 index 00000000..7eea9303 --- /dev/null +++ b/config/crd/bases/aiven.io_kafkaschemaregistryacls.yaml @@ -0,0 +1,194 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: kafkaschemaregistryacls.aiven.io +spec: + group: aiven.io + names: + kind: KafkaSchemaRegistryACL + listKind: KafkaSchemaRegistryACLList + plural: kafkaschemaregistryacls + singular: kafkaschemaregistryacl + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: + KafkaSchemaRegistryACL is the Schema for the kafkaschemaregistryacls + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: KafkaSchemaRegistryACLSpec defines the desired state of KafkaSchemaRegistryACL + properties: + authSecretRef: + description: Authentication reference to Aiven token in a secret + properties: + key: + minLength: 1 + type: string + name: + minLength: 1 + type: string + required: + - key + - name + type: object + permission: + enum: + - schema_registry_read + - schema_registry_write + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + project: + description: Identifies the project this resource belongs to + format: ^[a-zA-Z0-9_-]*$ + maxLength: 63 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + resource: + description: Resource name pattern for the Schema Registry ACL entry + maxLength: 249 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + serviceName: + description: + Specifies the name of the service that this resource + belongs to + maxLength: 63 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + username: + description: Username pattern for the ACL entry + maxLength: 64 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + required: + - permission + - project + - resource + - serviceName + - username + type: object + status: + description: + KafkaSchemaRegistryACLStatus defines the observed state of + KafkaSchemaRegistryACL + properties: + acl_id: + description: Kafka ACL ID + type: string + conditions: + description: + Conditions represent the latest available observations + of an KafkaSchemaRegistryACL state + items: + description: + "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: + lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: + message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: + observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: + reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: + type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + required: + - acl_id + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index f7025fb3..8640fb0c 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -23,6 +23,7 @@ resources: - bases/aiven.io_cassandras.yaml - bases/aiven.io_grafanas.yaml - bases/aiven.io_clickhousedatabases.yaml + - bases/aiven.io_kafkaschemaregistryacls.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/config/crd/patches/cainjection_in_kafkaschemaregistryacls.yaml b/config/crd/patches/cainjection_in_kafkaschemaregistryacls.yaml new file mode 100644 index 00000000..36e1615e --- /dev/null +++ b/config/crd/patches/cainjection_in_kafkaschemaregistryacls.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: kafkaschemaregistryacls.aiven.io diff --git a/config/crd/patches/webhook_in_kafkaschemaregistryacls.yaml b/config/crd/patches/webhook_in_kafkaschemaregistryacls.yaml new file mode 100644 index 00000000..1a1038be --- /dev/null +++ b/config/crd/patches/webhook_in_kafkaschemaregistryacls.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: kafkaschemaregistryacls.aiven.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/kafkaschemaregistryacl_editor_role.yaml b/config/rbac/kafkaschemaregistryacl_editor_role.yaml new file mode 100644 index 00000000..66cb2e79 --- /dev/null +++ b/config/rbac/kafkaschemaregistryacl_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit kafkaschemaregistryacls. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: kafkaschemaregistryacl-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: aiven-operator + app.kubernetes.io/part-of: aiven-operator + app.kubernetes.io/managed-by: kustomize + name: kafkaschemaregistryacl-editor-role +rules: + - apiGroups: + - aiven.io + resources: + - kafkaschemaregistryacls + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - aiven.io + resources: + - kafkaschemaregistryacls/status + verbs: + - get diff --git a/config/rbac/kafkaschemaregistryacl_viewer_role.yaml b/config/rbac/kafkaschemaregistryacl_viewer_role.yaml new file mode 100644 index 00000000..4fad14cb --- /dev/null +++ b/config/rbac/kafkaschemaregistryacl_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view kafkaschemaregistryacls. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: kafkaschemaregistryacl-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: aiven-operator + app.kubernetes.io/part-of: aiven-operator + app.kubernetes.io/managed-by: kustomize + name: kafkaschemaregistryacl-viewer-role +rules: + - apiGroups: + - aiven.io + resources: + - kafkaschemaregistryacls + verbs: + - get + - list + - watch + - apiGroups: + - aiven.io + resources: + - kafkaschemaregistryacls/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index fa177872..8ccf0d70 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -342,6 +342,32 @@ rules: - get - patch - update + - apiGroups: + - aiven.io + resources: + - kafkaschemaregistryacls + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - aiven.io + resources: + - kafkaschemaregistryacls/finalizers + verbs: + - update + - apiGroups: + - aiven.io + resources: + - kafkaschemaregistryacls/status + verbs: + - get + - patch + - update - apiGroups: - aiven.io resources: diff --git a/config/samples/_v1alpha1_kafkaschemaregistryacl.yaml b/config/samples/_v1alpha1_kafkaschemaregistryacl.yaml new file mode 100644 index 00000000..fb237b6a --- /dev/null +++ b/config/samples/_v1alpha1_kafkaschemaregistryacl.yaml @@ -0,0 +1,12 @@ +apiVersion: aiven.io/v1alpha1 +kind: KafkaSchemaRegistryACL +metadata: + labels: + app.kubernetes.io/name: kafkaschemaregistryacl + app.kubernetes.io/instance: kafkaschemaregistryacl-sample + app.kubernetes.io/part-of: aiven-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: aiven-operator + name: kafkaschemaregistryacl-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 498af1a2..7c4c3c04 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -21,4 +21,5 @@ resources: - _v1alpha1_cassandra.yaml - _v1alpha1_grafana.yaml - _v1alpha1_clickhousedatabase.yaml + - _v1alpha1_kafkaschemaregistryacl.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/controllers/common.go b/controllers/common.go index 04161c32..b9e2128e 100644 --- a/controllers/common.go +++ b/controllers/common.go @@ -238,3 +238,7 @@ func isNotFound(err error) bool { func isAlreadyExists(err error) bool { return aiven.IsAlreadyExists(err) || avngen.IsAlreadyExists(err) } + +func NewNotFound(msg string) error { + return aiven.Error{Status: http.StatusNotFound, Message: msg} +} diff --git a/controllers/connectionpool_controller.go b/controllers/connectionpool_controller.go index 6a217aca..6dc8c421 100644 --- a/controllers/connectionpool_controller.go +++ b/controllers/connectionpool_controller.go @@ -227,6 +227,9 @@ func (h ConnectionPoolHandler) checkPreconditions(ctx context.Context, avn *aive if check { db, err := avn.Databases.Get(ctx, cp.Spec.Project, cp.Spec.ServiceName, cp.Spec.DatabaseName) if err != nil { + if isNotFound(err) { + return false, nil + } return false, err } diff --git a/controllers/kafkaacl_controller.go b/controllers/kafkaacl_controller.go index fd7df66e..605cdd29 100644 --- a/controllers/kafkaacl_controller.go +++ b/controllers/kafkaacl_controller.go @@ -5,7 +5,6 @@ package controllers import ( "context" "fmt" - "net/http" "strconv" "github.com/aiven/aiven-go-client/v2" @@ -128,7 +127,7 @@ func (h KafkaACLHandler) getID(ctx context.Context, avn *aiven.Client, acl *v1al } // Error should mimic client error to play well with isNotFound(err) - return "", aiven.Error{Status: http.StatusNotFound, Message: fmt.Sprintf("Kafka ACL %q not found", acl.Name)} + return "", NewNotFound(fmt.Sprintf("Kafka ACL %q not found", acl.Name)) } func (h KafkaACLHandler) get(ctx context.Context, avn *aiven.Client, avnGen avngen.Client, obj client.Object) (*corev1.Secret, error) { diff --git a/controllers/kafkaschemaregistryacl_controller.go b/controllers/kafkaschemaregistryacl_controller.go new file mode 100644 index 00000000..5a62aeb2 --- /dev/null +++ b/controllers/kafkaschemaregistryacl_controller.go @@ -0,0 +1,164 @@ +// Copyright (c) 2024 Aiven, Helsinki, Finland. https://aiven.io/ + +package controllers + +import ( + "context" + "fmt" + "strconv" + + "github.com/aiven/aiven-go-client/v2" + avngen "github.com/aiven/go-client-codegen" + "github.com/aiven/go-client-codegen/handler/kafkaschemaregistry" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/aiven/aiven-operator/api/v1alpha1" +) + +// KafkaSchemaRegistryACLReconciler reconciles a KafkaSchemaRegistryACL object +type KafkaSchemaRegistryACLReconciler struct { + Controller +} + +func newKafkaSchemaRegistryACLReconciler(c Controller) reconcilerType { + return &KafkaSchemaRegistryACLReconciler{Controller: c} +} + +//+kubebuilder:rbac:groups=aiven.io,resources=kafkaschemaregistryacls,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=aiven.io,resources=kafkaschemaregistryacls/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=aiven.io,resources=kafkaschemaregistryacls/finalizers,verbs=update + +func (r *KafkaSchemaRegistryACLReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.reconcileInstance(ctx, req, KafkaSchemaRegistryACLHandler{}, &v1alpha1.KafkaSchemaRegistryACL{}) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *KafkaSchemaRegistryACLReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.KafkaSchemaRegistryACL{}). + Complete(r) +} + +type KafkaSchemaRegistryACLHandler struct{} + +func (h KafkaSchemaRegistryACLHandler) createOrUpdate(ctx context.Context, avn *aiven.Client, avnGen avngen.Client, obj client.Object, refs []client.Object) error { + acl, err := h.convert(obj) + if err != nil { + return err + } + + exists, err := h.exists(ctx, avnGen, acl) + if err != nil { + return err + } + + if !exists { + in := kafkaschemaregistry.ServiceSchemaRegistryAclAddIn{ + Permission: kafkaschemaregistry.PermissionType(acl.Spec.Permission), + Resource: acl.Spec.Resource, + Username: acl.Spec.Username, + } + + list, err := avnGen.ServiceSchemaRegistryAclAdd(ctx, acl.Spec.Project, acl.Spec.ServiceName, &in) + if err != nil { + return fmt.Errorf("cannot create KafkaSchemaRegistryAC on Aiven side: %w", err) + } + + for _, v := range list { + if in.Permission == v.Permission && in.Resource == v.Resource && in.Username == v.Username { + acl.Status.ACLId = v.Id + break + } + } + } + + meta.SetStatusCondition(&acl.Status.Conditions, + getInitializedCondition("Created", + "Instance was created or update on Aiven side")) + + meta.SetStatusCondition(&acl.Status.Conditions, + getRunningCondition(metav1.ConditionUnknown, "Created", + "Instance was created or update on Aiven side, status remains unknown")) + + metav1.SetMetaDataAnnotation(&acl.ObjectMeta, + processedGenerationAnnotation, strconv.FormatInt(acl.GetGeneration(), formatIntBaseDecimal)) + + return nil +} + +func (h KafkaSchemaRegistryACLHandler) delete(ctx context.Context, avn *aiven.Client, avnGen avngen.Client, obj client.Object) (bool, error) { + acl, err := h.convert(obj) + if err != nil { + return false, err + } + + _, err = avnGen.ServiceSchemaRegistryAclDelete(ctx, acl.Spec.Project, acl.Spec.ServiceName, acl.Status.ACLId) + if err != nil && !isNotFound(err) { + return false, err + } + + return true, nil +} + +func (h KafkaSchemaRegistryACLHandler) exists(ctx context.Context, avnGen avngen.Client, acl *v1alpha1.KafkaSchemaRegistryACL) (bool, error) { + list, err := avnGen.ServiceSchemaRegistryAclList(ctx, acl.Spec.Project, acl.Spec.ServiceName) + if err != nil { + return false, err + } + + for _, v := range list { + if v.Id == acl.Status.ACLId { + return true, nil + } + } + return false, nil +} + +func (h KafkaSchemaRegistryACLHandler) get(ctx context.Context, avn *aiven.Client, avnGen avngen.Client, obj client.Object) (*corev1.Secret, error) { + acl, err := h.convert(obj) + if err != nil { + return nil, err + } + + exists, err := h.exists(ctx, avnGen, acl) + if err != nil { + return nil, err + } + + if !exists { + return nil, NewNotFound(fmt.Sprintf("KafkaSchemaRegistryACL %q not found", acl.Name)) + } + + meta.SetStatusCondition(&acl.Status.Conditions, + getRunningCondition(metav1.ConditionTrue, "CheckRunning", + "Instance is running on Aiven side")) + + metav1.SetMetaDataAnnotation(&acl.ObjectMeta, instanceIsRunningAnnotation, "true") + + return nil, nil +} + +func (h KafkaSchemaRegistryACLHandler) checkPreconditions(ctx context.Context, avn *aiven.Client, avnGen avngen.Client, obj client.Object) (bool, error) { + acl, err := h.convert(obj) + if err != nil { + return false, err + } + + meta.SetStatusCondition(&acl.Status.Conditions, + getInitializedCondition("Preconditions", "Checking preconditions")) + + return checkServiceIsRunning(ctx, avn, avnGen, acl.Spec.Project, acl.Spec.ServiceName) +} + +func (h KafkaSchemaRegistryACLHandler) convert(i client.Object) (*v1alpha1.KafkaSchemaRegistryACL, error) { + db, ok := i.(*v1alpha1.KafkaSchemaRegistryACL) + if !ok { + return nil, fmt.Errorf("cannot convert object to KafkaSchemaRegistryACL") + } + + return db, nil +} diff --git a/controllers/setup.go b/controllers/setup.go index 144c89c0..d72dbe24 100644 --- a/controllers/setup.go +++ b/controllers/setup.go @@ -24,27 +24,28 @@ func SetupControllers(mgr ctrl.Manager, defaultToken, kubeVersion, operatorVersi } builders := map[string]reconcilerBuilder{ - "Cassandra": newCassandraReconciler, - "Clickhouse": newClickhouseReconciler, - "ClickhouseDatabase": newClickhouseDatabaseReconciler, - "ClickhouseUser": newClickhouseUserReconciler, - "ConnectionPool": newConnectionPoolReconciler, - "Database": newDatabaseReconciler, - "Grafana": newGrafanaReconciler, - "Kafka": newKafkaReconciler, - "KafkaACL": newKafkaACLReconciler, - "KafkaConnect": newKafkaConnectReconciler, - "KafkaConnector": newKafkaConnectorReconciler, - "KafkaSchema": newKafkaSchemaReconciler, - "KafkaTopic": newKafkaTopicReconciler, - "MySQL": newMySQLReconciler, - "OpenSearch": newOpenSearchReconciler, - "PostgreSQL": newPostgreSQLReconciler, - "Project": newProjectReconciler, - "ProjectVPC": newProjectVPCReconciler, - "Redis": newRedisReconciler, - "ServiceIntegration": newServiceIntegrationReconciler, - "ServiceUser": newServiceUserReconciler, + "Cassandra": newCassandraReconciler, + "Clickhouse": newClickhouseReconciler, + "ClickhouseDatabase": newClickhouseDatabaseReconciler, + "ClickhouseUser": newClickhouseUserReconciler, + "ConnectionPool": newConnectionPoolReconciler, + "Database": newDatabaseReconciler, + "Grafana": newGrafanaReconciler, + "Kafka": newKafkaReconciler, + "KafkaACL": newKafkaACLReconciler, + "KafkaConnect": newKafkaConnectReconciler, + "KafkaConnector": newKafkaConnectorReconciler, + "KafkaSchema": newKafkaSchemaReconciler, + "KafkaSchemaRegistryACLReconciler": newKafkaSchemaRegistryACLReconciler, + "KafkaTopic": newKafkaTopicReconciler, + "MySQL": newMySQLReconciler, + "OpenSearch": newOpenSearchReconciler, + "PostgreSQL": newPostgreSQLReconciler, + "Project": newProjectReconciler, + "ProjectVPC": newProjectVPCReconciler, + "Redis": newRedisReconciler, + "ServiceIntegration": newServiceIntegrationReconciler, + "ServiceUser": newServiceUserReconciler, } for k, v := range builders { diff --git a/docs/docs/api-reference/kafkaschemaregistryacl.md b/docs/docs/api-reference/kafkaschemaregistryacl.md new file mode 100644 index 00000000..d5f0b6e2 --- /dev/null +++ b/docs/docs/api-reference/kafkaschemaregistryacl.md @@ -0,0 +1,43 @@ +--- +title: "KafkaSchemaRegistryACL" +--- + +## KafkaSchemaRegistryACL {: #KafkaSchemaRegistryACL } + +KafkaSchemaRegistryACL is the Schema for the kafkaschemaregistryacls API. + +**Required** + +- [`apiVersion`](#apiVersion-property){: name='apiVersion-property'} (string). Value `aiven.io/v1alpha1`. +- [`kind`](#kind-property){: name='kind-property'} (string). Value `KafkaSchemaRegistryACL`. +- [`metadata`](#metadata-property){: name='metadata-property'} (object). Data that identifies the object, including a `name` string and optional `namespace`. +- [`spec`](#spec-property){: name='spec-property'} (object). KafkaSchemaRegistryACLSpec defines the desired state of KafkaSchemaRegistryACL. See below for [nested schema](#spec). + +## spec {: #spec } + +_Appears on [`KafkaSchemaRegistryACL`](#KafkaSchemaRegistryACL)._ + +KafkaSchemaRegistryACLSpec defines the desired state of KafkaSchemaRegistryACL. + +**Required** + +- [`permission`](#spec.permission-property){: name='spec.permission-property'} (string, Enum: `schema_registry_read`, `schema_registry_write`, Immutable). +- [`project`](#spec.project-property){: name='spec.project-property'} (string, Immutable, MaxLength: 63, Format: `^[a-zA-Z0-9_-]*$`). Identifies the project this resource belongs to. +- [`resource`](#spec.resource-property){: name='spec.resource-property'} (string, Immutable, MaxLength: 249). Resource name pattern for the Schema Registry ACL entry. +- [`serviceName`](#spec.serviceName-property){: name='spec.serviceName-property'} (string, Immutable, MaxLength: 63). Specifies the name of the service that this resource belongs to. +- [`username`](#spec.username-property){: name='spec.username-property'} (string, Immutable, MaxLength: 64). Username pattern for the ACL entry. + +**Optional** + +- [`authSecretRef`](#spec.authSecretRef-property){: name='spec.authSecretRef-property'} (object). Authentication reference to Aiven token in a secret. See below for [nested schema](#spec.authSecretRef). + +## authSecretRef {: #spec.authSecretRef } + +_Appears on [`spec`](#spec)._ + +Authentication reference to Aiven token in a secret. + +**Required** + +- [`key`](#spec.authSecretRef.key-property){: name='spec.authSecretRef.key-property'} (string, MinLength: 1). +- [`name`](#spec.authSecretRef.name-property){: name='spec.authSecretRef.name-property'} (string, MinLength: 1). diff --git a/main.go b/main.go index dd806ac6..391d0026 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,6 @@ const port = 9443 func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(v1alpha1.AddToScheme(scheme)) - //+kubebuilder:scaffold:scheme } func main() { diff --git a/tests/kafkschemaregistryaacl_test.go b/tests/kafkschemaregistryaacl_test.go new file mode 100644 index 00000000..5f638a94 --- /dev/null +++ b/tests/kafkschemaregistryaacl_test.go @@ -0,0 +1,157 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/aiven/go-client-codegen/handler/kafkaschemaregistry" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" + + "github.com/aiven/aiven-operator/api/v1alpha1" + "github.com/aiven/aiven-operator/controllers" +) + +func getKafkaSchemaRegistryACLYaml(project, cloudName, kafka, topic, acl string) string { + return fmt.Sprintf(` +apiVersion: aiven.io/v1alpha1 +kind: Kafka +metadata: + name: %[3]s +spec: + authSecretRef: + name: aiven-token + key: token + + project: %[1]s + cloudName: %[2]s + plan: startup-2 + +--- + +apiVersion: aiven.io/v1alpha1 +kind: KafkaTopic +metadata: + name: %[4]s +spec: + authSecretRef: + name: aiven-token + key: token + + project: %[1]s + serviceName: %[3]s + topicName: %[4]s + replication: 3 + partitions: 2 + +--- + +apiVersion: aiven.io/v1alpha1 +kind: KafkaSchemaRegistryACL +metadata: + name: %[5]s +spec: + authSecretRef: + name: aiven-token + key: token + + project: %[1]s + serviceName: %[3]s + resource: Subject:%[4]s + username: my-user + permission: schema_registry_read +`, project, cloudName, kafka, topic, acl) +} + +func TestKafkaSchemaRegistryACL(t *testing.T) { + t.Parallel() + defer recoverPanic(t) + + // GIVEN + ctx, cancel := testCtx() + defer cancel() + + kafkaName := randName("kafka-service") + topicName := randName("kafka-topic") + aclName := randName("kafka-schema-registry-acl") + yml := getKafkaSchemaRegistryACLYaml(cfg.Project, cfg.PrimaryCloudName, kafkaName, topicName, aclName) + s := NewSession(ctx, k8sClient, cfg.Project) + + // Cleans test afterward + defer s.Destroy() + + // WHEN + // Applies given manifest + require.NoError(t, s.Apply(yml)) + + // Waits kube objects + kafka := new(v1alpha1.Kafka) + require.NoError(t, s.GetRunning(kafka, kafkaName)) + + topic := new(v1alpha1.KafkaTopic) + require.NoError(t, s.GetRunning(topic, topicName)) + + acl := new(v1alpha1.KafkaSchemaRegistryACL) + require.NoError(t, s.GetRunning(acl, aclName)) + + // THEN + // Kafka + kafkaAvn, err := avnGen.ServiceGet(ctx, cfg.Project, kafkaName) + require.NoError(t, err) + assert.Equal(t, kafkaAvn.ServiceName, kafka.GetName()) + assert.Equal(t, "RUNNING", kafka.Status.State) + assert.EqualValues(t, kafkaAvn.State, kafka.Status.State) + assert.Equal(t, kafkaAvn.Plan, kafka.Spec.Plan) + assert.Equal(t, kafkaAvn.CloudName, kafka.Spec.CloudName) + + // KafkaTopic + // todo: replace with code-generated client, when the API schema is fixed: + // json: cannot unmarshal string into Go struct field SynonymOut.topic.config.cleanup_policy.synonyms.value of type bool + topicAvn, err := avnClient.KafkaTopics.Get(ctx, cfg.Project, kafkaName, topic.GetTopicName()) + require.NoError(t, err) + assert.Equal(t, topicName, topic.GetName()) + assert.Equal(t, topicName, topic.GetTopicName()) + assert.Equal(t, topicAvn.TopicName, topic.GetTopicName()) + assert.Equal(t, topicAvn.State, topic.Status.State) + assert.Equal(t, topicAvn.Replication, topic.Spec.Replication) + assert.Len(t, topicAvn.Partitions, topic.Spec.Partitions) + + // KafkaSchemaRegistryACL + aclListAvn, err := avnGen.ServiceSchemaRegistryAclList(ctx, cfg.Project, kafkaName) + require.NoError(t, err) + + var aclAvn *kafkaschemaregistry.AclOut + for _, v := range aclListAvn { + if v.Id == acl.Status.ACLId { + aclAvn = &v + break + } + } + + require.NotNil(t, aclAvn) + assert.True(t, meta.IsStatusConditionTrue(acl.Status.Conditions, "Running")) + assert.Equal(t, "schema_registry_read", acl.Spec.Permission) + assert.EqualValues(t, aclAvn.Permission, acl.Spec.Permission) + assert.Equal(t, "my-user", acl.Spec.Username) + assert.Equal(t, aclAvn.Username, acl.Spec.Username) + assert.Equal(t, acl.Spec.Resource, "Subject:"+topicName) + assert.Equal(t, aclAvn.Resource, "Subject:"+topicName) + + // Calls reconciler delete + assert.NoError(t, s.Delete(acl, func() error { + list, err := avnGen.ServiceSchemaRegistryAclList(ctx, cfg.Project, kafkaName) + if err != nil { + return err + } + + for _, v := range list { + if v.Id == acl.Status.ACLId { + return nil + } + } + + // There is no Get method for the ACL, so we emulate 404 for this + return controllers.NewNotFound("KafkaSchemaRegistryAcl not found with id " + acl.Status.ACLId) + })) +} diff --git a/tests/session.go b/tests/session.go index 6d93f55f..057f20d6 100644 --- a/tests/session.go +++ b/tests/session.go @@ -15,6 +15,7 @@ import ( "time" "github.com/aiven/aiven-go-client/v2" + avngen "github.com/aiven/go-client-codegen" "golang.org/x/sync/errgroup" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -183,7 +184,7 @@ func (s *session) Delete(o client.Object, exists func() error) error { return err } err = exists() - if aiven.IsNotFound(err) { + if aiven.IsNotFound(err) || avngen.IsNotFound(err) { return nil } return err @@ -252,6 +253,7 @@ const ( randIDSize = 7 // randIDChars Aiven allowed chars for "names" randIDChars = "0123456789abcdefghijklmnopqrstuvwxyz" + nameMaxSize = 255 ) // randID generates Aiven compatible random id @@ -265,7 +267,11 @@ func randID() string { } func randName(name string) string { - return fmt.Sprintf("test-%s-%s", randID(), name) + s := fmt.Sprintf("test-%s-%s", randID(), name) + if len(s) > nameMaxSize { + panic(fmt.Sprintf("invalid name, max length %d: %q", nameMaxSize, s)) + } + return s } func isNotFound(err error) bool { diff --git a/tests/suite_test.go b/tests/suite_test.go index 68a85e1f..a046d9e6 100644 --- a/tests/suite_test.go +++ b/tests/suite_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/aiven/aiven-go-client/v2" + avngen "github.com/aiven/go-client-codegen" "github.com/kelseyhightower/envconfig" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -28,6 +29,7 @@ var ( cfg *testConfig k8sClient client.Client avnClient *aiven.Client + avnGen avngen.Client ) const ( @@ -145,6 +147,11 @@ func setupSuite() (*envtest.Environment, error) { return nil, err } + avnGen, err = controllers.NewAivenGeneratedClient(cfg.Token, kubeVersion.String()+"-test", operatorVersion+"-test") + if err != nil { + return nil, err + } + err = k8sClient.Create(ctx, secret) if err != nil { return nil, err