diff --git a/README.md b/README.md index fb82b0cd..17fa1160 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ In this tutorial, a Kubernetes namespace called `test` is used for examples, whi - [StreamNative Cloud APIKey](docs/apikey.md) - [StreamNative Cloud ServiceAccount](docs/serviceaccount.md) - [StreamNative Cloud ServiceAccountBinding](docs/serviceaccountbinding.md) +- [StreamNative Cloud RBAC RoleBinding](docs/rolebinding.md) # Contributing diff --git a/api/v1alpha1/rolebinding_types.go b/api/v1alpha1/rolebinding_types.go new file mode 100644 index 00000000..e6ac59c3 --- /dev/null +++ b/api/v1alpha1/rolebinding_types.go @@ -0,0 +1,138 @@ +// Copyright 2025 StreamNative +// +// 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 v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// RoleBindingSpec defines the desired state of RoleBinding +type RoleBindingSpec struct { + // APIServerRef is the reference to the StreamNativeCloudConnection + // +required + APIServerRef corev1.LocalObjectReference `json:"apiServerRef"` + + // Users is a list of Users that will be granted the role + // +optional + Users []string `json:"users"` + + // IdentityPools is a list of IdentityPools that will be granted the role + // +optional + IdentityPools []string `json:"identityPools,omitempty"` + + // ServiceAccounts is a list of ServiceAccounts that will be granted the role + // +optional + ServiceAccounts []string `json:"serviceAccounts,omitempty"` + + // ClusterRole is the reference to the role that will be granted + // +required + ClusterRole string `json:"clusterRole"` + + // CEL is an optional CEL expression for the role binding + // +optional + CEL *string `json:"cel,omitempty"` + + // SRNOrganization is the organization of the SRN + // +optional + SRNOrganization []string `json:"srnOrganization,omitempty"` + + // SRNInstance is the pulsar instance of the SRN + // +optional + SRNInstance []string `json:"srnInstance,omitempty"` + + // SRNCluster is the cluster of the SRN + // +optional + SRNCluster []string `json:"srnCluster,omitempty"` + + // SRNTenant is the tenant of the SRN + // +optional + SRNTenant []string `json:"srnTenant,omitempty"` + + // SRNNamespace is the namespace of the SRN + // +optional + SRNNamespace []string `json:"srnNamespace,omitempty"` + + // SRNTopicDomain is the topic domain of the SRN + // +optional + SRNTopicDomain []string `json:"srnTopicDomain,omitempty"` + + // SRNTopicName is the topic of the SRN + // +optional + SRNTopicName []string `json:"srnTopicName,omitempty"` + + // SRNSubscription is the subscription of the SRN + // +optional + SRNSubscription []string `json:"srnSubscription,omitempty"` + + // SRNServiceAccount is the service account of the SRN + // +optional + SRNServiceAccount []string `json:"srnServiceAccount,omitempty"` + + // SRNSecret is the secret of the SRN + // +optional + SRNSecret []string `json:"srnSecret,omitempty"` +} + +// RoleBindingStatus defines the observed state of RoleBinding +type RoleBindingStatus struct { + // Conditions represent the latest available observations of an object's state + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` + + // ObservedGeneration is the last observed generation + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // FailedClusters is a list of clusters where the role binding failed + // +optional + FailedClusters []string `json:"failedClusters,omitempty"` + + // SyncedClusters is a map of clusters where the role binding is synced + // +optional + SyncedClusters map[string]string `json:"syncedClusters,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced,categories={streamnative,all} +//+kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +//+kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status" +//+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RoleBinding is the Schema for the RoleBindings API +type RoleBinding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RoleBindingSpec `json:"spec,omitempty"` + Status RoleBindingStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// RoleBindingList contains a list of RoleBinding +type RoleBindingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RoleBinding `json:"items"` +} + +func init() { + SchemeBuilder.Register(&RoleBinding{}, &RoleBindingList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index bb316f57..d7d611e0 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2779,6 +2779,185 @@ func (in *Resources) DeepCopy() *Resources { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleBinding) DeepCopyInto(out *RoleBinding) { + *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 RoleBinding. +func (in *RoleBinding) DeepCopy() *RoleBinding { + if in == nil { + return nil + } + out := new(RoleBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleBinding) 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 *RoleBindingList) DeepCopyInto(out *RoleBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RoleBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingList. +func (in *RoleBindingList) DeepCopy() *RoleBindingList { + if in == nil { + return nil + } + out := new(RoleBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleBindingList) 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 *RoleBindingSpec) DeepCopyInto(out *RoleBindingSpec) { + *out = *in + out.APIServerRef = in.APIServerRef + if in.Users != nil { + in, out := &in.Users, &out.Users + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.IdentityPools != nil { + in, out := &in.IdentityPools, &out.IdentityPools + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ServiceAccounts != nil { + in, out := &in.ServiceAccounts, &out.ServiceAccounts + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.CEL != nil { + in, out := &in.CEL, &out.CEL + *out = new(string) + **out = **in + } + if in.SRNOrganization != nil { + in, out := &in.SRNOrganization, &out.SRNOrganization + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SRNInstance != nil { + in, out := &in.SRNInstance, &out.SRNInstance + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SRNCluster != nil { + in, out := &in.SRNCluster, &out.SRNCluster + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SRNTenant != nil { + in, out := &in.SRNTenant, &out.SRNTenant + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SRNNamespace != nil { + in, out := &in.SRNNamespace, &out.SRNNamespace + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SRNTopicDomain != nil { + in, out := &in.SRNTopicDomain, &out.SRNTopicDomain + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SRNTopicName != nil { + in, out := &in.SRNTopicName, &out.SRNTopicName + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SRNSubscription != nil { + in, out := &in.SRNSubscription, &out.SRNSubscription + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SRNServiceAccount != nil { + in, out := &in.SRNServiceAccount, &out.SRNServiceAccount + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SRNSecret != nil { + in, out := &in.SRNSecret, &out.SRNSecret + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingSpec. +func (in *RoleBindingSpec) DeepCopy() *RoleBindingSpec { + if in == nil { + return nil + } + out := new(RoleBindingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleBindingStatus) DeepCopyInto(out *RoleBindingStatus) { + *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]) + } + } + if in.FailedClusters != nil { + in, out := &in.FailedClusters, &out.FailedClusters + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SyncedClusters != nil { + in, out := &in.SyncedClusters, &out.SyncedClusters + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingStatus. +func (in *RoleBindingStatus) DeepCopy() *RoleBindingStatus { + if in == nil { + return nil + } + out := new(RoleBindingStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SchemaInfo) DeepCopyInto(out *SchemaInfo) { *out = *in diff --git a/config/crd/bases/resource.streamnative.io_rolebindings.yaml b/config/crd/bases/resource.streamnative.io_rolebindings.yaml new file mode 100644 index 00000000..df7e4042 --- /dev/null +++ b/config/crd/bases/resource.streamnative.io_rolebindings.yaml @@ -0,0 +1,255 @@ +# Copyright 2025 StreamNative +# +# 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. + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: rolebindings.resource.streamnative.io +spec: + group: resource.streamnative.io + names: + categories: + - streamnative + - all + kind: RoleBinding + listKind: RoleBindingList + plural: rolebindings + singular: rolebinding + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: READY + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: RoleBinding is the Schema for the RoleBindings 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: RoleBindingSpec defines the desired state of RoleBinding + properties: + apiServerRef: + description: APIServerRef is the reference to the StreamNativeCloudConnection + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic + cel: + description: CEL is an optional CEL expression for the role binding + type: string + clusterRole: + description: ClusterRole is the reference to the role that will be + granted + type: string + identityPools: + description: IdentityPools is a list of IdentityPools that will be + granted the role + items: + type: string + type: array + serviceAccounts: + description: ServiceAccounts is a list of ServiceAccounts that will + be granted the role + items: + type: string + type: array + srnCluster: + description: SRNCluster is the cluster of the SRN + items: + type: string + type: array + srnInstance: + description: SRNInstance is the pulsar instance of the SRN + items: + type: string + type: array + srnNamespace: + description: SRNNamespace is the namespace of the SRN + items: + type: string + type: array + srnOrganization: + description: SRNOrganization is the organization of the SRN + items: + type: string + type: array + srnSecret: + description: SRNSecret is the secret of the SRN + items: + type: string + type: array + srnServiceAccount: + description: SRNServiceAccount is the service account of the SRN + items: + type: string + type: array + srnSubscription: + description: SRNSubscription is the subscription of the SRN + items: + type: string + type: array + srnTenant: + description: SRNTenant is the tenant of the SRN + items: + type: string + type: array + srnTopicDomain: + description: SRNTopicDomain is the topic domain of the SRN + items: + type: string + type: array + srnTopicName: + description: SRNTopicName is the topic of the SRN + items: + type: string + type: array + users: + description: Users is a list of Users that will be granted the role + items: + type: string + type: array + required: + - apiServerRef + - clusterRole + type: object + status: + description: RoleBindingStatus defines the observed state of RoleBinding + properties: + conditions: + description: Conditions represent the latest available observations + of an object's state + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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 + failedClusters: + description: FailedClusters is a list of clusters where the role binding + failed + items: + type: string + type: array + observedGeneration: + description: ObservedGeneration is the last observed generation + format: int64 + type: integer + syncedClusters: + additionalProperties: + type: string + description: SyncedClusters is a map of clusters where the role binding + is synced + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/controllers/rolebinding_controller.go b/controllers/rolebinding_controller.go new file mode 100644 index 00000000..2afe9690 --- /dev/null +++ b/controllers/rolebinding_controller.go @@ -0,0 +1,263 @@ +// Copyright 2025 StreamNative +// +// 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 controllers + +import ( + "context" + "fmt" + "time" + + resourcev1alpha1 "github.com/streamnative/pulsar-resources-operator/api/v1alpha1" + controllers2 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +const ( + roleBindingFinalizer = "rolebinding.resource.streamnative.io/finalizer" +) + +// RoleBindingReconciler reconciles a RoleBinding object +type RoleBindingReconciler struct { + client.Client + Scheme *runtime.Scheme + ConnectionManager *ConnectionManager +} + +//+kubebuilder:rbac:groups=resource.streamnative.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=resource.streamnative.io,resources=rolebindings/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=resource.streamnative.io,resources=rolebindings/finalizers,verbs=update +//+kubebuilder:rbac:groups=resource.streamnative.io,resources=streamnativecloudconnections,verbs=get;list;watch + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *RoleBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx) + logger.Info("Reconciling RoleBinding", "name", req.Name, "namespace", req.Namespace) + + // Fetch the RoleBinding instance + roleBinding := &resourcev1alpha1.RoleBinding{} + err := r.Get(ctx, req.NamespacedName, roleBinding) + if err != nil { + if apierrors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. + logger.Info("RoleBinding resource not found. Ignoring since object must be deleted") + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + logger.Error(err, "Failed to get RoleBinding") + return ctrl.Result{}, err + } + + // Get the APIServerConnection + connection := &resourcev1alpha1.StreamNativeCloudConnection{} + if err := r.Get(ctx, types.NamespacedName{ + Namespace: req.Namespace, + Name: roleBinding.Spec.APIServerRef.Name, + }, connection); err != nil { + r.updateRoleBindingStatus(ctx, roleBinding, err, "ConnectionNotFound", + fmt.Sprintf("Failed to get APIServerConnection: %v", err)) + return ctrl.Result{}, err + } + + // Get API connection + apiConn, err := r.ConnectionManager.GetOrCreateConnection(connection, nil) + if err != nil { + if _, ok := err.(*NotInitializedError); ok { + logger.Info("Connection not initialized, requeueing", "error", err.Error()) + return ctrl.Result{Requeue: true}, nil + } + r.updateRoleBindingStatus(ctx, roleBinding, err, "GetConnectionFailed", + fmt.Sprintf("Failed to get connection: %v", err)) + return ctrl.Result{}, err + } + + // Get organization from connection + organization := connection.Spec.Organization + if organization == "" { + err := fmt.Errorf("organization is required but not specified") + r.updateRoleBindingStatus(ctx, roleBinding, err, "ValidationFailed", err.Error()) + return ctrl.Result{}, err + } + + // Create RoleBinding client + rbClient, err := controllers2.NewRoleBindingClient(apiConn, organization) + if err != nil { + logger.Error(err, "Failed to create RoleBinding client") + r.updateRoleBindingStatus(ctx, roleBinding, err, "ClientError", "Failed to create RoleBinding client") + return ctrl.Result{}, err + } + + // Check if RoleBinding is being deleted + if roleBinding.DeletionTimestamp != nil { + return r.handleRoleBindingDeletion(ctx, rbClient, roleBinding) + } + + // Add finalizer if not present + if !controllerutil.ContainsFinalizer(roleBinding, roleBindingFinalizer) { + controllerutil.AddFinalizer(roleBinding, roleBindingFinalizer) + err := r.Update(ctx, roleBinding) + if err != nil { + logger.Error(err, "Failed to add finalizer") + return ctrl.Result{}, err + } + logger.Info("Added finalizer to RoleBinding") + return ctrl.Result{}, nil + } + + // Reconcile the RoleBinding + return r.reconcileRoleBinding(ctx, rbClient, roleBinding) +} + +func (r *RoleBindingReconciler) handleRoleBindingDeletion(ctx context.Context, rbClient *controllers2.RoleBindingClient, roleBinding *resourcev1alpha1.RoleBinding) (ctrl.Result, error) { + logger := log.FromContext(ctx) + logger.Info("Handling RoleBinding deletion", "name", roleBinding.Name) + + if controllerutil.ContainsFinalizer(roleBinding, roleBindingFinalizer) { + // Delete the RoleBinding from the API server + err := rbClient.DeleteRoleBinding(ctx, roleBinding) + if err != nil { + logger.Error(err, "Failed to delete RoleBinding from API server") + r.updateRoleBindingStatus(ctx, roleBinding, err, "DeletionError", "Failed to delete RoleBinding from API server") + return ctrl.Result{}, err + } + + // Remove the finalizer + controllerutil.RemoveFinalizer(roleBinding, roleBindingFinalizer) + err = r.Update(ctx, roleBinding) + if err != nil { + logger.Error(err, "Failed to remove finalizer") + return ctrl.Result{}, err + } + logger.Info("Successfully deleted RoleBinding and removed finalizer") + } + + return ctrl.Result{}, nil +} + +func (r *RoleBindingReconciler) reconcileRoleBinding(ctx context.Context, rbClient *controllers2.RoleBindingClient, roleBinding *resourcev1alpha1.RoleBinding) (ctrl.Result, error) { + logger := log.FromContext(ctx) + logger.Info("Reconciling RoleBinding", "name", roleBinding.Name) + + // Check if RoleBinding exists on the API server + _, err := rbClient.GetRoleBinding(ctx, roleBinding.Name) + if err != nil { + if apierrors.IsNotFound(err) { + // RoleBinding doesn't exist, create it + logger.Info("RoleBinding not found on API server, creating", "name", roleBinding.Name) + _, createErr := rbClient.CreateRoleBinding(ctx, roleBinding) + if createErr != nil { + logger.Error(createErr, "Failed to create RoleBinding on API server") + r.updateRoleBindingStatus(ctx, roleBinding, createErr, "CreationError", "Failed to create RoleBinding on API server") + return ctrl.Result{}, createErr + } + logger.Info("Successfully created RoleBinding on API server", "name", roleBinding.Name) + } else { + logger.Error(err, "Failed to get RoleBinding from API server") + r.updateRoleBindingStatus(ctx, roleBinding, err, "GetError", "Failed to get RoleBinding from API server") + return ctrl.Result{}, err + } + } else { + // RoleBinding exists, check if it needs to be updated + if r.needsUpdate(roleBinding) { + logger.Info("RoleBinding needs update", "name", roleBinding.Name) + _, updateErr := rbClient.UpdateRoleBinding(ctx, roleBinding) + if updateErr != nil { + logger.Error(updateErr, "Failed to update RoleBinding on API server") + r.updateRoleBindingStatus(ctx, roleBinding, updateErr, "UpdateError", "Failed to update RoleBinding on API server") + return ctrl.Result{}, updateErr + } + logger.Info("Successfully updated RoleBinding on API server", "name", roleBinding.Name) + } + } + + // Update status + r.updateRoleBindingStatus(ctx, roleBinding, nil, "Ready", "RoleBinding is ready") + + // Requeue for periodic sync + requeueInterval := 5 * time.Minute + logger.Info("Successfully reconciled RoleBinding", "roleBindingName", roleBinding.Name, "requeueAfter", requeueInterval) + return ctrl.Result{RequeueAfter: requeueInterval}, nil +} + +// needsUpdate returns true if the role binding needs to be updated +func (r *RoleBindingReconciler) needsUpdate(local *resourcev1alpha1.RoleBinding) bool { + return local.Generation != local.Status.ObservedGeneration +} + +func (r *RoleBindingReconciler) updateRoleBindingStatus( + ctx context.Context, + roleBinding *resourcev1alpha1.RoleBinding, + err error, + reason string, + message string, +) { + logger := log.FromContext(ctx) + roleBinding.Status.ObservedGeneration = roleBinding.Generation + + newCondition := metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: reason, + Message: message, + LastTransitionTime: metav1.Now(), + } + + if err != nil { + newCondition.Status = metav1.ConditionFalse + } + + // Update ready condition + found := false + for i, condition := range roleBinding.Status.Conditions { + if condition.Type == "Ready" { + // Only update LastTransitionTime if Status or Reason or Message changes + if condition.Status != newCondition.Status || condition.Reason != newCondition.Reason || condition.Message != newCondition.Message { + roleBinding.Status.Conditions[i] = newCondition + } else { + // If nothing changed, keep the old LastTransitionTime + newCondition.LastTransitionTime = condition.LastTransitionTime + roleBinding.Status.Conditions[i] = newCondition + } + found = true + break + } + } + if !found { + roleBinding.Status.Conditions = append(roleBinding.Status.Conditions, newCondition) + } + + // Persist status update + if statusUpdateErr := r.Status().Update(ctx, roleBinding); statusUpdateErr != nil { + logger.Error(statusUpdateErr, "Failed to update RoleBinding status") + } +} + +// SetupWithManager sets up the controller with the Manager. +func (r *RoleBindingReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&resourcev1alpha1.RoleBinding{}). + WithEventFilter(predicate.GenerationChangedPredicate{}). + Complete(r) +} diff --git a/docs/rolebinding.md b/docs/rolebinding.md new file mode 100644 index 00000000..321f8aaf --- /dev/null +++ b/docs/rolebinding.md @@ -0,0 +1,116 @@ +# RoleBinding + +The `RoleBinding` resource grants a [`ClusterRole`](https://docs.streamnative.io/cloud/security/access/rbac/manage-rbac-roles) to a set of subjects (users, identity pools, or service accounts). It connects a role's permissions with the entities that receive those permissions and scopes them to specific resources. + +This resource provides a Kubernetes-native way to manage [Role Bindings on StreamNative Cloud](https://docs.streamnative.io/cloud/security/access/rbac/manage-rbac-role-bindings). + +## Example + +This example grants the `org-admin` role to a user and a service account, scoped to a specific Pulsar instance. + +```yaml +apiVersion: resource.streamnative.io/v1alpha1 +kind: RoleBinding +metadata: + name: my-rolebinding + namespace: default +spec: + apiServerRef: + name: my-connection + clusterRole: org-admin + users: + - my-user@example.com + serviceAccounts: + - my-service-account + srnInstance: + - "my-pulsar-instance" +``` + +## Specification + +| Field | Type | Description | Required | +| --- | --- | --- | --- | +| `spec.apiServerRef` | [LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#localobjectreference-v1-core) | Reference to a StreamNativeCloudConnection in the same namespace. | Yes | +| `spec.clusterRole` | string | The name of the `ClusterRole` to be granted. See [Predefined RBAC Roles](https://docs.streamnative.io/cloud/security/access/rbac/predefined-rbac-roles). | Yes | +| `spec.users` | []string | A list of user emails that will be granted the role. | No | +| `spec.identityPools` | []string | A list of identity pools that will be granted the role. | No | +| `spec.serviceAccounts` | []string | A list of service accounts that will be granted the role. | No | +| `spec.cel` | string | An optional CEL (Common Expression Language) expression for conditional role binding. | No | +| `spec.srnOrganization` | []string | The organization scope for the SRN. | No | +| `spec.srnInstance` | []string | The Pulsar instance scope for the SRN. | No | +| `spec.srnCluster` | []string | The cluster scope for the SRN. | No | +| `spec.srnTenant` | []string | The tenant scope for the SRN. | No | +| `spec.srnNamespace` | []string | The namespace scope for the SRN. | No | +| `spec.srnTopicDomain` | []string | The topic domain scope for the SRN. | No | +| `spec.srnTopicName` | []string | The topic name scope for the SRN. | No | +| `spec.srnSubscription` | []string | The subscription scope for the SRN. | No | +| `spec.srnServiceAccount`| []string | The service account scope for the SRN. | No | +| `spec.srnSecret` | []string | The secret scope for the SRN. | No | + + +## Status + +| Field | Type | Description | +| --- | --- | --- | +| `status.conditions` | []Condition | Represents the latest available observations of the `RoleBinding`'s state. | +| `status.observedGeneration`| int64 | The last generation of the resource that was observed by the controller. | +| `status.failedClusters` | []string | A list of clusters where applying the role binding failed. | +| `status.syncedClusters` | map[string]string | A map of clusters where the role binding has been successfully synced. The key is the cluster name and the value is the sync status. | + + +## Conditional Role Bindings + +While basic role bindings associate a role with a subject, conditional role bindings provide more granular control by scoping permissions based on resource attributes. This operator supports two ways to define these conditions, mirroring the functionality available in StreamNative Cloud. + +### Using Resource Names (SRN Fields) + +You can scope permissions by specifying one or more `spec.srn*` fields. This is the simplest way to limit a role to specific resources like tenants, namespaces, or topics. + +The SRN fields are provided as arrays to allow granting the same role across multiple resources of the same type in a single `RoleBinding`. + +For example, to grant the `tenant-admin` role to a user for two specific tenants (`finance` and `marketing`) within an instance: + +```yaml +apiVersion: resource.streamnative.io/v1alpha1 +kind: RoleBinding +metadata: + name: tenant-admin-binding + namespace: default +spec: + apiServerRef: + name: my-connection + clusterRole: tenant-admin + users: + - "jane.doe@example.com" + # Define the scope of this binding + srnInstance: + - "my-cloud-instance" + srnTenant: + - "finance" + - "marketing" +``` +The controller will create a separate binding in the cloud for each combination of SRN values provided. + +### Using CEL Expressions + +For more complex conditions, you can use a [Common Expression Language (CEL)](https://github.com/google/cel-spec) expression in the `spec.cel` field. The expression has access to the `srn` variable, which contains the fields of the StreamNative Resource Name. + +This example grants the `topic-producer` role to a service account, but only for topics within the `finance` tenant on a specific cluster. + +```yaml +apiVersion: resource.streamnative.io/v1alpha1 +kind: RoleBinding +metadata: + name: producer-finance-binding +spec: + apiServerRef: + name: my-connection + clusterRole: topic-producer + serviceAccounts: + - "sa-producer-finance" + cel: "srn.instance == 'my-cloud-instance' && srn.cluster == 'us-west' && srn.tenant == 'finance'" +``` + +## Deleting a RoleBinding + +When you delete a `RoleBinding` resource from Kubernetes, the controller will automatically remove the corresponding binding from StreamNative Cloud, effectively revoking the granted permissions. \ No newline at end of file diff --git a/main.go b/main.go index f0d7cb5a..f1bc3c68 100644 --- a/main.go +++ b/main.go @@ -201,6 +201,16 @@ func main() { os.Exit(1) } + // Set up RoleBinding controller + if err = (&controllers.RoleBindingReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ConnectionManager: connectionManager, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "RoleBinding") + os.Exit(1) + } + //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/pkg/streamnativecloud/apis/cloud/v1alpha1/clusterrole_types.go b/pkg/streamnativecloud/apis/cloud/v1alpha1/clusterrole_types.go new file mode 100644 index 00000000..b97eed22 --- /dev/null +++ b/pkg/streamnativecloud/apis/cloud/v1alpha1/clusterrole_types.go @@ -0,0 +1,78 @@ +// Copyright 2025 StreamNative +// +// 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 v1alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient:nonNamespaced + +// ClusterRole is a resource that defines a set of permissions that can be applied to a set of subjects. +// +k8s:openapi-gen=true +// +resource:path=clusterroles,strategy=ClusterRoleStrategy +// +kubebuilder:categories=all +type ClusterRole struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Spec ClusterRoleSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Status ClusterRoleStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// ClusterRoleSpec defines the desired state of ClusterRole +type ClusterRoleSpec struct { + // +listType=atomic + // Permissions Designed for general permission format + // SERVICE.RESOURCE.VERB + Permissions []string `json:"permissions,omitempty" protobuf:"bytes,1,opt,name=permissions"` +} + +type FailedCluster struct { + // Name is the Cluster's name + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + + // Namespace is the Cluster's namespace + Namespace string `json:"namespace" protobuf:"bytes,2,opt,name=namespace"` + + // Reason is the failed reason + Reason string `json:"reason" protobuf:"bytes,3,opt,name=reason"` +} + +// ClusterRoleStatus defines the observed state of ClusterRole +type ClusterRoleStatus struct { + // Conditions is an array of current observed conditions. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + + // FailedClusters is an array of clusters which failed to apply the ClusterRole resources. + // +deprecated, shouldn't expose the failed clusters to the user + // +optional + // +listType=atomic + FailedClusters []FailedCluster `json:"failedClusters,omitempty" protobuf:"bytes,2,rep,name=failedClusters"` +} + +//+kubebuilder:object:root=true + +// ClusterRoleList contains a list of ClusterRole +type ClusterRoleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ClusterRole `json:"items"` +} diff --git a/pkg/streamnativecloud/apis/cloud/v1alpha1/rolebinding_types.go b/pkg/streamnativecloud/apis/cloud/v1alpha1/rolebinding_types.go new file mode 100644 index 00000000..a9927e60 --- /dev/null +++ b/pkg/streamnativecloud/apis/cloud/v1alpha1/rolebinding_types.go @@ -0,0 +1,142 @@ +// Copyright 2025 StreamNative +// +// 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RoleBinding is a resource that grants a set of permissions to a set of subjects. +// +k8s:openapi-gen=true +// +resource:path=rolebindings,strategy=RoleBindingStrategy +// +kubebuilder:categories=all +type RoleBinding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Spec RoleBindingSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Status RoleBindingStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// RoleBindingSpec defines the desired state of RoleBinding +type RoleBindingSpec struct { + // +listType=atomic + Subjects []Subject `json:"subjects" protobuf:"bytes,1,rep,name=subjects"` + RoleRef RoleRef `json:"roleRef" protobuf:"bytes,2,opt,name=roleRef"` + + // +optional + ConditionGroup *ConditionGroup `json:"conditionGroup,omitempty" protobuf:"bytes,3,opt,name=conditionGroup"` + // +optional + CEL *string `json:"cel,omitempty" protobuf:"bytes,4,opt,name=cel"` + + // +optional + // +listType=atomic + // ResourceNames indicate the StreamNative Resource Name + ResourceNames []ResourceName `json:"resourceNames,omitempty" protobuf:"bytes,5,opt,name=resourceNames"` +} + +type ResourceName struct { + Organization string `json:"organization,omitempty" protobuf:"bytes,1,opt,name=organization"` + Instance string `json:"instance,omitempty" protobuf:"bytes,2,opt,name=instance"` + Cluster string `json:"cluster,omitempty" protobuf:"bytes,3,opt,name=cluster"` + Tenant string `json:"tenant,omitempty" protobuf:"bytes,4,opt,name=tenant"` + Namespace string `json:"namespace,omitempty" protobuf:"bytes,5,opt,name=namespace"` + TopicDomain string `json:"topicDomain,omitempty" protobuf:"bytes,6,opt,name=topicDomain"` + TopicName string `json:"topicName,omitempty" protobuf:"bytes,7,opt,name=topicName"` + Subscription string `json:"subscription,omitempty" protobuf:"bytes,8,opt,name=subscription"` + ServiceAccount string `json:"serviceAccount,omitempty" protobuf:"bytes,9,opt,name=serviceAccount"` + APIKey string `json:"apiKey,omitempty" protobuf:"bytes,10,opt,name=apiKey"` + Secret string `json:"secret,omitempty" protobuf:"bytes,11,opt,name=secret"` +} + +// RoleBindingStatus defines the observed state of RoleBinding +type RoleBindingStatus struct { + // Conditions is an array of current observed conditions. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + + // FailedClusters is an array of clusters which failed to apply the RoleBinding resources. + // +optional + // +listType=atomic + FailedClusters []FailedCluster `json:"failedClusters,omitempty" protobuf:"bytes,2,rep,name=failedClusters"` + + // SyncedClusters is a map of clusters which applied the RoleBinding resources and the RoleBinding's generation. + // If the generation in this map is same with the current RoleBinding's generation, it will skip apply the + // RoleBinding to the cluster + // +optional + // +listType=atomic + SyncedClusters map[string]int64 `json:"syncedClusters,omitempty" protobuf:"bytes,3,rep,name=syncedClusters"` +} + +// Deprecated sections + +type ConditionGroupRelation int +type RoleBindingConditionType int +type RoleBindingConditionOperator int + +const ( + RelationOr ConditionGroupRelation = 0 + RelationAnd ConditionGroupRelation = 1 + TypeSrn RoleBindingConditionType = 0 + OperatorKeyMatch RoleBindingConditionOperator = 0 + OperatorRegexMatch RoleBindingConditionOperator = 1 +) + +// ConditionGroup is a group of conditions that are used to determine if a role binding should be applied. +// Deprecated +type ConditionGroup struct { + Relation ConditionGroupRelation `json:"relation,omitempty" protobuf:"bytes,1,opt,name=relation"` + Conditions []RoleBindingCondition `json:"conditions" protobuf:"bytes,2,req,name=conditions"` + ConditionGroups []ConditionGroup `json:"conditionGroups,omitempty" protobuf:"bytes,3,opt,name=conditionGroups"` +} + +// Srn is a StreamNative Resource Name. +// Deprecated +type Srn struct { + Schema string `json:"schema,omitempty" protobuf:"bytes,1,opt,name=schema"` + Version string `json:"version,omitempty" protobuf:"bytes,2,opt,name=version"` + Organization string `json:"organization,omitempty" protobuf:"bytes,3,opt,name=organization"` + Instance string `json:"instance,omitempty" protobuf:"bytes,4,opt,name=instance"` + Cluster string `json:"cluster,omitempty" protobuf:"bytes,5,opt,name=cluster"` + Tenant string `json:"tenant,omitempty" protobuf:"bytes,6,opt,name=tenant"` + Namespace string `json:"namespace,omitempty" protobuf:"bytes,7,opt,name=namespace"` + TopicDomain string `json:"topicDomain,omitempty" protobuf:"bytes,8,opt,name=topicDomain"` + TopicName string `json:"topicName,omitempty" protobuf:"bytes,9,opt,name=topicName"` + Subscription string `json:"subscription,omitempty" protobuf:"bytes,10,opt,name=subscription"` +} + +// RoleBindingCondition is a condition that is used to determine if a role binding should be applied. +// Deprecated +type RoleBindingCondition struct { + Type RoleBindingConditionType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type"` + Operator RoleBindingConditionOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator"` + Srn Srn `json:"srn,omitempty" protobuf:"bytes,3,opt,name=srn"` +} + +//+kubebuilder:object:root=true + +// RoleBindingList contains a list of RoleBinding +type RoleBindingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RoleBinding `json:"items"` +} diff --git a/pkg/streamnativecloud/apis/cloud/v1alpha1/zz_generated.api.register.go b/pkg/streamnativecloud/apis/cloud/v1alpha1/zz_generated.api.register.go index f8b1ea16..62d23a10 100644 --- a/pkg/streamnativecloud/apis/cloud/v1alpha1/zz_generated.api.register.go +++ b/pkg/streamnativecloud/apis/cloud/v1alpha1/zz_generated.api.register.go @@ -60,6 +60,10 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &APIKey{}, &APIKeyList{}, + &ClusterRole{}, + &ClusterRoleList{}, + &RoleBinding{}, + &RoleBindingList{}, &Secret{}, &SecretList{}, &ServiceAccount{}, diff --git a/pkg/streamnativecloud/apis/cloud/v1alpha1/zz_generated.deepcopy.go b/pkg/streamnativecloud/apis/cloud/v1alpha1/zz_generated.deepcopy.go index ce34d143..4c80ec5a 100644 --- a/pkg/streamnativecloud/apis/cloud/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/streamnativecloud/apis/cloud/v1alpha1/zz_generated.deepcopy.go @@ -236,6 +236,112 @@ func (in *BookKeeperSetReference) DeepCopy() *BookKeeperSetReference { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterRole) DeepCopyInto(out *ClusterRole) { + *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 ClusterRole. +func (in *ClusterRole) DeepCopy() *ClusterRole { + if in == nil { + return nil + } + out := new(ClusterRole) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterRole) 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 *ClusterRoleList) DeepCopyInto(out *ClusterRoleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterRole, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleList. +func (in *ClusterRoleList) DeepCopy() *ClusterRoleList { + if in == nil { + return nil + } + out := new(ClusterRoleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterRoleList) 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 *ClusterRoleSpec) DeepCopyInto(out *ClusterRoleSpec) { + *out = *in + if in.Permissions != nil { + in, out := &in.Permissions, &out.Permissions + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleSpec. +func (in *ClusterRoleSpec) DeepCopy() *ClusterRoleSpec { + if in == nil { + return nil + } + out := new(ClusterRoleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterRoleStatus) DeepCopyInto(out *ClusterRoleStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FailedClusters != nil { + in, out := &in.FailedClusters, &out.FailedClusters + *out = make([]FailedCluster, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleStatus. +func (in *ClusterRoleStatus) DeepCopy() *ClusterRoleStatus { + if in == nil { + return nil + } + out := new(ClusterRoleStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -252,6 +358,33 @@ func (in *Condition) DeepCopy() *Condition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConditionGroup) DeepCopyInto(out *ConditionGroup) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]RoleBindingCondition, len(*in)) + copy(*out, *in) + } + if in.ConditionGroups != nil { + in, out := &in.ConditionGroups, &out.ConditionGroups + *out = make([]ConditionGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConditionGroup. +func (in *ConditionGroup) DeepCopy() *ConditionGroup { + if in == nil { + return nil + } + out := new(ConditionGroup) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Config) DeepCopyInto(out *Config) { *out = *in @@ -374,6 +507,21 @@ func (in *EncryptionKey) DeepCopy() *EncryptionKey { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FailedCluster) DeepCopyInto(out *FailedCluster) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailedCluster. +func (in *FailedCluster) DeepCopy() *FailedCluster { + if in == nil { + return nil + } + out := new(FailedCluster) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GCPCloudConnection) DeepCopyInto(out *GCPCloudConnection) { *out = *in @@ -504,6 +652,166 @@ func (in *ProtocolsConfig) DeepCopy() *ProtocolsConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceName) DeepCopyInto(out *ResourceName) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceName. +func (in *ResourceName) DeepCopy() *ResourceName { + if in == nil { + return nil + } + out := new(ResourceName) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleBinding) DeepCopyInto(out *RoleBinding) { + *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 RoleBinding. +func (in *RoleBinding) DeepCopy() *RoleBinding { + if in == nil { + return nil + } + out := new(RoleBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleBinding) 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 *RoleBindingCondition) DeepCopyInto(out *RoleBindingCondition) { + *out = *in + out.Srn = in.Srn +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingCondition. +func (in *RoleBindingCondition) DeepCopy() *RoleBindingCondition { + if in == nil { + return nil + } + out := new(RoleBindingCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleBindingList) DeepCopyInto(out *RoleBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RoleBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingList. +func (in *RoleBindingList) DeepCopy() *RoleBindingList { + if in == nil { + return nil + } + out := new(RoleBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleBindingList) 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 *RoleBindingSpec) DeepCopyInto(out *RoleBindingSpec) { + *out = *in + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]Subject, len(*in)) + copy(*out, *in) + } + out.RoleRef = in.RoleRef + if in.ConditionGroup != nil { + in, out := &in.ConditionGroup, &out.ConditionGroup + *out = new(ConditionGroup) + (*in).DeepCopyInto(*out) + } + if in.CEL != nil { + in, out := &in.CEL, &out.CEL + *out = new(string) + **out = **in + } + if in.ResourceNames != nil { + in, out := &in.ResourceNames, &out.ResourceNames + *out = make([]ResourceName, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingSpec. +func (in *RoleBindingSpec) DeepCopy() *RoleBindingSpec { + if in == nil { + return nil + } + out := new(RoleBindingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleBindingStatus) DeepCopyInto(out *RoleBindingStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FailedClusters != nil { + in, out := &in.FailedClusters, &out.FailedClusters + *out = make([]FailedCluster, len(*in)) + copy(*out, *in) + } + if in.SyncedClusters != nil { + in, out := &in.SyncedClusters, &out.SyncedClusters + *out = make(map[string]int64, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingStatus. +func (in *RoleBindingStatus) DeepCopy() *RoleBindingStatus { + if in == nil { + return nil + } + out := new(RoleBindingStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RoleRef) DeepCopyInto(out *RoleRef) { *out = *in @@ -858,6 +1166,21 @@ func (in *SharingConfig) DeepCopy() *SharingConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Srn) DeepCopyInto(out *Srn) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Srn. +func (in *Srn) DeepCopy() *Srn { + if in == nil { + return nil + } + out := new(Srn) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Subject) DeepCopyInto(out *Subject) { *out = *in diff --git a/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/cloud_client.go b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/cloud_client.go index 45f785c8..fd6580c4 100644 --- a/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/cloud_client.go +++ b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/cloud_client.go @@ -26,6 +26,8 @@ import ( type CloudV1alpha1Interface interface { RESTClient() rest.Interface APIKeysGetter + ClusterRolesGetter + RoleBindingsGetter SecretsGetter ServiceAccountsGetter ServiceAccountBindingsGetter @@ -40,6 +42,14 @@ func (c *CloudV1alpha1Client) APIKeys(namespace string) APIKeyInterface { return newAPIKeys(c, namespace) } +func (c *CloudV1alpha1Client) ClusterRoles() ClusterRoleInterface { + return newClusterRoles(c) +} + +func (c *CloudV1alpha1Client) RoleBindings(namespace string) RoleBindingInterface { + return newRoleBindings(c, namespace) +} + func (c *CloudV1alpha1Client) Secrets(namespace string) SecretInterface { return newSecrets(c, namespace) } diff --git a/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/clusterrole.go b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/clusterrole.go new file mode 100644 index 00000000..6202ac14 --- /dev/null +++ b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/clusterrole.go @@ -0,0 +1,181 @@ +// Copyright 2025 StreamNative +// +// 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. +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/apis/cloud/v1alpha1" + scheme "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/client/clientset_generated/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// ClusterRolesGetter has a method to return a ClusterRoleInterface. +// A group's client should implement this interface. +type ClusterRolesGetter interface { + ClusterRoles() ClusterRoleInterface +} + +// ClusterRoleInterface has methods to work with ClusterRole resources. +type ClusterRoleInterface interface { + Create(ctx context.Context, clusterRole *v1alpha1.ClusterRole, opts v1.CreateOptions) (*v1alpha1.ClusterRole, error) + Update(ctx context.Context, clusterRole *v1alpha1.ClusterRole, opts v1.UpdateOptions) (*v1alpha1.ClusterRole, error) + UpdateStatus(ctx context.Context, clusterRole *v1alpha1.ClusterRole, opts v1.UpdateOptions) (*v1alpha1.ClusterRole, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.ClusterRole, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.ClusterRoleList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ClusterRole, err error) + ClusterRoleExpansion +} + +// clusterRoles implements ClusterRoleInterface +type clusterRoles struct { + client rest.Interface +} + +// newClusterRoles returns a ClusterRoles +func newClusterRoles(c *CloudV1alpha1Client) *clusterRoles { + return &clusterRoles{ + client: c.RESTClient(), + } +} + +// Get takes name of the clusterRole, and returns the corresponding clusterRole object, and an error if there is any. +func (c *clusterRoles) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ClusterRole, err error) { + result = &v1alpha1.ClusterRole{} + err = c.client.Get(). + Resource("clusterroles"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ClusterRoles that match those selectors. +func (c *clusterRoles) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ClusterRoleList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.ClusterRoleList{} + err = c.client.Get(). + Resource("clusterroles"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested clusterRoles. +func (c *clusterRoles) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Resource("clusterroles"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a clusterRole and creates it. Returns the server's representation of the clusterRole, and an error, if there is any. +func (c *clusterRoles) Create(ctx context.Context, clusterRole *v1alpha1.ClusterRole, opts v1.CreateOptions) (result *v1alpha1.ClusterRole, err error) { + result = &v1alpha1.ClusterRole{} + err = c.client.Post(). + Resource("clusterroles"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(clusterRole). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a clusterRole and updates it. Returns the server's representation of the clusterRole, and an error, if there is any. +func (c *clusterRoles) Update(ctx context.Context, clusterRole *v1alpha1.ClusterRole, opts v1.UpdateOptions) (result *v1alpha1.ClusterRole, err error) { + result = &v1alpha1.ClusterRole{} + err = c.client.Put(). + Resource("clusterroles"). + Name(clusterRole.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(clusterRole). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *clusterRoles) UpdateStatus(ctx context.Context, clusterRole *v1alpha1.ClusterRole, opts v1.UpdateOptions) (result *v1alpha1.ClusterRole, err error) { + result = &v1alpha1.ClusterRole{} + err = c.client.Put(). + Resource("clusterroles"). + Name(clusterRole.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(clusterRole). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the clusterRole and deletes it. Returns an error if one occurs. +func (c *clusterRoles) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Resource("clusterroles"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *clusterRoles) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Resource("clusterroles"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched clusterRole. +func (c *clusterRoles) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ClusterRole, err error) { + result = &v1alpha1.ClusterRole{} + err = c.client.Patch(pt). + Resource("clusterroles"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/fake/fake_cloud_client.go b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/fake/fake_cloud_client.go index 3a026341..c84968aa 100644 --- a/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/fake/fake_cloud_client.go +++ b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/fake/fake_cloud_client.go @@ -29,6 +29,14 @@ func (c *FakeCloudV1alpha1) APIKeys(namespace string) v1alpha1.APIKeyInterface { return &FakeAPIKeys{c, namespace} } +func (c *FakeCloudV1alpha1) ClusterRoles() v1alpha1.ClusterRoleInterface { + return &FakeClusterRoles{c} +} + +func (c *FakeCloudV1alpha1) RoleBindings(namespace string) v1alpha1.RoleBindingInterface { + return &FakeRoleBindings{c, namespace} +} + func (c *FakeCloudV1alpha1) Secrets(namespace string) v1alpha1.SecretInterface { return &FakeSecrets{c, namespace} } diff --git a/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/fake/fake_clusterrole.go b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/fake/fake_clusterrole.go new file mode 100644 index 00000000..f7e79023 --- /dev/null +++ b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/fake/fake_clusterrole.go @@ -0,0 +1,129 @@ +// Copyright 2025 StreamNative +// +// 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. +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/apis/cloud/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeClusterRoles implements ClusterRoleInterface +type FakeClusterRoles struct { + Fake *FakeCloudV1alpha1 +} + +var clusterrolesResource = v1alpha1.SchemeGroupVersion.WithResource("clusterroles") + +var clusterrolesKind = v1alpha1.SchemeGroupVersion.WithKind("ClusterRole") + +// Get takes name of the clusterRole, and returns the corresponding clusterRole object, and an error if there is any. +func (c *FakeClusterRoles) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ClusterRole, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(clusterrolesResource, name), &v1alpha1.ClusterRole{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ClusterRole), err +} + +// List takes label and field selectors, and returns the list of ClusterRoles that match those selectors. +func (c *FakeClusterRoles) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ClusterRoleList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(clusterrolesResource, clusterrolesKind, opts), &v1alpha1.ClusterRoleList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.ClusterRoleList{ListMeta: obj.(*v1alpha1.ClusterRoleList).ListMeta} + for _, item := range obj.(*v1alpha1.ClusterRoleList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested clusterRoles. +func (c *FakeClusterRoles) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(clusterrolesResource, opts)) +} + +// Create takes the representation of a clusterRole and creates it. Returns the server's representation of the clusterRole, and an error, if there is any. +func (c *FakeClusterRoles) Create(ctx context.Context, clusterRole *v1alpha1.ClusterRole, opts v1.CreateOptions) (result *v1alpha1.ClusterRole, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(clusterrolesResource, clusterRole), &v1alpha1.ClusterRole{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ClusterRole), err +} + +// Update takes the representation of a clusterRole and updates it. Returns the server's representation of the clusterRole, and an error, if there is any. +func (c *FakeClusterRoles) Update(ctx context.Context, clusterRole *v1alpha1.ClusterRole, opts v1.UpdateOptions) (result *v1alpha1.ClusterRole, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(clusterrolesResource, clusterRole), &v1alpha1.ClusterRole{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ClusterRole), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeClusterRoles) UpdateStatus(ctx context.Context, clusterRole *v1alpha1.ClusterRole, opts v1.UpdateOptions) (*v1alpha1.ClusterRole, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(clusterrolesResource, "status", clusterRole), &v1alpha1.ClusterRole{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ClusterRole), err +} + +// Delete takes name of the clusterRole and deletes it. Returns an error if one occurs. +func (c *FakeClusterRoles) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(clusterrolesResource, name, opts), &v1alpha1.ClusterRole{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeClusterRoles) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(clusterrolesResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.ClusterRoleList{}) + return err +} + +// Patch applies the patch and returns the patched clusterRole. +func (c *FakeClusterRoles) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ClusterRole, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(clusterrolesResource, name, pt, data, subresources...), &v1alpha1.ClusterRole{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ClusterRole), err +} diff --git a/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/fake/fake_rolebinding.go b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/fake/fake_rolebinding.go new file mode 100644 index 00000000..9057bace --- /dev/null +++ b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/fake/fake_rolebinding.go @@ -0,0 +1,138 @@ +// Copyright 2025 StreamNative +// +// 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. +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/apis/cloud/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeRoleBindings implements RoleBindingInterface +type FakeRoleBindings struct { + Fake *FakeCloudV1alpha1 + ns string +} + +var rolebindingsResource = v1alpha1.SchemeGroupVersion.WithResource("rolebindings") + +var rolebindingsKind = v1alpha1.SchemeGroupVersion.WithKind("RoleBinding") + +// Get takes name of the roleBinding, and returns the corresponding roleBinding object, and an error if there is any. +func (c *FakeRoleBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.RoleBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(rolebindingsResource, c.ns, name), &v1alpha1.RoleBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.RoleBinding), err +} + +// List takes label and field selectors, and returns the list of RoleBindings that match those selectors. +func (c *FakeRoleBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.RoleBindingList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(rolebindingsResource, rolebindingsKind, c.ns, opts), &v1alpha1.RoleBindingList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.RoleBindingList{ListMeta: obj.(*v1alpha1.RoleBindingList).ListMeta} + for _, item := range obj.(*v1alpha1.RoleBindingList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested roleBindings. +func (c *FakeRoleBindings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(rolebindingsResource, c.ns, opts)) + +} + +// Create takes the representation of a roleBinding and creates it. Returns the server's representation of the roleBinding, and an error, if there is any. +func (c *FakeRoleBindings) Create(ctx context.Context, roleBinding *v1alpha1.RoleBinding, opts v1.CreateOptions) (result *v1alpha1.RoleBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(rolebindingsResource, c.ns, roleBinding), &v1alpha1.RoleBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.RoleBinding), err +} + +// Update takes the representation of a roleBinding and updates it. Returns the server's representation of the roleBinding, and an error, if there is any. +func (c *FakeRoleBindings) Update(ctx context.Context, roleBinding *v1alpha1.RoleBinding, opts v1.UpdateOptions) (result *v1alpha1.RoleBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(rolebindingsResource, c.ns, roleBinding), &v1alpha1.RoleBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.RoleBinding), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeRoleBindings) UpdateStatus(ctx context.Context, roleBinding *v1alpha1.RoleBinding, opts v1.UpdateOptions) (*v1alpha1.RoleBinding, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(rolebindingsResource, "status", c.ns, roleBinding), &v1alpha1.RoleBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.RoleBinding), err +} + +// Delete takes name of the roleBinding and deletes it. Returns an error if one occurs. +func (c *FakeRoleBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(rolebindingsResource, c.ns, name, opts), &v1alpha1.RoleBinding{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeRoleBindings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(rolebindingsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.RoleBindingList{}) + return err +} + +// Patch applies the patch and returns the patched roleBinding. +func (c *FakeRoleBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.RoleBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(rolebindingsResource, c.ns, name, pt, data, subresources...), &v1alpha1.RoleBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.RoleBinding), err +} diff --git a/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/generated_expansion.go b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/generated_expansion.go index acaf4095..5e15d334 100644 --- a/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/generated_expansion.go +++ b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/generated_expansion.go @@ -17,6 +17,10 @@ package v1alpha1 type APIKeyExpansion interface{} +type ClusterRoleExpansion interface{} + +type RoleBindingExpansion interface{} + type SecretExpansion interface{} type ServiceAccountExpansion interface{} diff --git a/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/rolebinding.go b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/rolebinding.go new file mode 100644 index 00000000..628360dc --- /dev/null +++ b/pkg/streamnativecloud/client/clientset_generated/clientset/typed/cloud/v1alpha1/rolebinding.go @@ -0,0 +1,192 @@ +// Copyright 2025 StreamNative +// +// 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. +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/apis/cloud/v1alpha1" + scheme "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/client/clientset_generated/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// RoleBindingsGetter has a method to return a RoleBindingInterface. +// A group's client should implement this interface. +type RoleBindingsGetter interface { + RoleBindings(namespace string) RoleBindingInterface +} + +// RoleBindingInterface has methods to work with RoleBinding resources. +type RoleBindingInterface interface { + Create(ctx context.Context, roleBinding *v1alpha1.RoleBinding, opts v1.CreateOptions) (*v1alpha1.RoleBinding, error) + Update(ctx context.Context, roleBinding *v1alpha1.RoleBinding, opts v1.UpdateOptions) (*v1alpha1.RoleBinding, error) + UpdateStatus(ctx context.Context, roleBinding *v1alpha1.RoleBinding, opts v1.UpdateOptions) (*v1alpha1.RoleBinding, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.RoleBinding, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.RoleBindingList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.RoleBinding, err error) + RoleBindingExpansion +} + +// roleBindings implements RoleBindingInterface +type roleBindings struct { + client rest.Interface + ns string +} + +// newRoleBindings returns a RoleBindings +func newRoleBindings(c *CloudV1alpha1Client, namespace string) *roleBindings { + return &roleBindings{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the roleBinding, and returns the corresponding roleBinding object, and an error if there is any. +func (c *roleBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.RoleBinding, err error) { + result = &v1alpha1.RoleBinding{} + err = c.client.Get(). + Namespace(c.ns). + Resource("rolebindings"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of RoleBindings that match those selectors. +func (c *roleBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.RoleBindingList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.RoleBindingList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("rolebindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested roleBindings. +func (c *roleBindings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("rolebindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a roleBinding and creates it. Returns the server's representation of the roleBinding, and an error, if there is any. +func (c *roleBindings) Create(ctx context.Context, roleBinding *v1alpha1.RoleBinding, opts v1.CreateOptions) (result *v1alpha1.RoleBinding, err error) { + result = &v1alpha1.RoleBinding{} + err = c.client.Post(). + Namespace(c.ns). + Resource("rolebindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(roleBinding). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a roleBinding and updates it. Returns the server's representation of the roleBinding, and an error, if there is any. +func (c *roleBindings) Update(ctx context.Context, roleBinding *v1alpha1.RoleBinding, opts v1.UpdateOptions) (result *v1alpha1.RoleBinding, err error) { + result = &v1alpha1.RoleBinding{} + err = c.client.Put(). + Namespace(c.ns). + Resource("rolebindings"). + Name(roleBinding.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(roleBinding). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *roleBindings) UpdateStatus(ctx context.Context, roleBinding *v1alpha1.RoleBinding, opts v1.UpdateOptions) (result *v1alpha1.RoleBinding, err error) { + result = &v1alpha1.RoleBinding{} + err = c.client.Put(). + Namespace(c.ns). + Resource("rolebindings"). + Name(roleBinding.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(roleBinding). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the roleBinding and deletes it. Returns an error if one occurs. +func (c *roleBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("rolebindings"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *roleBindings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("rolebindings"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched roleBinding. +func (c *roleBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.RoleBinding, err error) { + result = &v1alpha1.RoleBinding{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("rolebindings"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/streamnativecloud/client/informers_generated/externalversions/cloud/v1alpha1/clusterrole.go b/pkg/streamnativecloud/client/informers_generated/externalversions/cloud/v1alpha1/clusterrole.go new file mode 100644 index 00000000..fa6dae4e --- /dev/null +++ b/pkg/streamnativecloud/client/informers_generated/externalversions/cloud/v1alpha1/clusterrole.go @@ -0,0 +1,86 @@ +// Copyright 2025 StreamNative +// +// 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. +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + cloudv1alpha1 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/apis/cloud/v1alpha1" + clientset "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/client/clientset_generated/clientset" + internalinterfaces "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/client/informers_generated/externalversions/internalinterfaces" + v1alpha1 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// ClusterRoleInformer provides access to a shared informer and lister for +// ClusterRoles. +type ClusterRoleInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.ClusterRoleLister +} + +type clusterRoleInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewClusterRoleInformer constructs a new informer for ClusterRole type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewClusterRoleInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredClusterRoleInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredClusterRoleInformer constructs a new informer for ClusterRole type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredClusterRoleInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CloudV1alpha1().ClusterRoles().List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CloudV1alpha1().ClusterRoles().Watch(context.TODO(), options) + }, + }, + &cloudv1alpha1.ClusterRole{}, + resyncPeriod, + indexers, + ) +} + +func (f *clusterRoleInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredClusterRoleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *clusterRoleInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&cloudv1alpha1.ClusterRole{}, f.defaultInformer) +} + +func (f *clusterRoleInformer) Lister() v1alpha1.ClusterRoleLister { + return v1alpha1.NewClusterRoleLister(f.Informer().GetIndexer()) +} diff --git a/pkg/streamnativecloud/client/informers_generated/externalversions/cloud/v1alpha1/interface.go b/pkg/streamnativecloud/client/informers_generated/externalversions/cloud/v1alpha1/interface.go index e574d9e6..2e9282fa 100644 --- a/pkg/streamnativecloud/client/informers_generated/externalversions/cloud/v1alpha1/interface.go +++ b/pkg/streamnativecloud/client/informers_generated/externalversions/cloud/v1alpha1/interface.go @@ -23,6 +23,10 @@ import ( type Interface interface { // APIKeys returns a APIKeyInformer. APIKeys() APIKeyInformer + // ClusterRoles returns a ClusterRoleInformer. + ClusterRoles() ClusterRoleInformer + // RoleBindings returns a RoleBindingInformer. + RoleBindings() RoleBindingInformer // Secrets returns a SecretInformer. Secrets() SecretInformer // ServiceAccounts returns a ServiceAccountInformer. @@ -47,6 +51,16 @@ func (v *version) APIKeys() APIKeyInformer { return &aPIKeyInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// ClusterRoles returns a ClusterRoleInformer. +func (v *version) ClusterRoles() ClusterRoleInformer { + return &clusterRoleInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + +// RoleBindings returns a RoleBindingInformer. +func (v *version) RoleBindings() RoleBindingInformer { + return &roleBindingInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Secrets returns a SecretInformer. func (v *version) Secrets() SecretInformer { return &secretInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/streamnativecloud/client/informers_generated/externalversions/cloud/v1alpha1/rolebinding.go b/pkg/streamnativecloud/client/informers_generated/externalversions/cloud/v1alpha1/rolebinding.go new file mode 100644 index 00000000..ea9ab231 --- /dev/null +++ b/pkg/streamnativecloud/client/informers_generated/externalversions/cloud/v1alpha1/rolebinding.go @@ -0,0 +1,87 @@ +// Copyright 2025 StreamNative +// +// 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. +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + cloudv1alpha1 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/apis/cloud/v1alpha1" + clientset "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/client/clientset_generated/clientset" + internalinterfaces "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/client/informers_generated/externalversions/internalinterfaces" + v1alpha1 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// RoleBindingInformer provides access to a shared informer and lister for +// RoleBindings. +type RoleBindingInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.RoleBindingLister +} + +type roleBindingInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewRoleBindingInformer constructs a new informer for RoleBinding type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRoleBindingInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredRoleBindingInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredRoleBindingInformer constructs a new informer for RoleBinding type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredRoleBindingInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CloudV1alpha1().RoleBindings(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CloudV1alpha1().RoleBindings(namespace).Watch(context.TODO(), options) + }, + }, + &cloudv1alpha1.RoleBinding{}, + resyncPeriod, + indexers, + ) +} + +func (f *roleBindingInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredRoleBindingInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *roleBindingInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&cloudv1alpha1.RoleBinding{}, f.defaultInformer) +} + +func (f *roleBindingInformer) Lister() v1alpha1.RoleBindingLister { + return v1alpha1.NewRoleBindingLister(f.Informer().GetIndexer()) +} diff --git a/pkg/streamnativecloud/client/informers_generated/externalversions/generic.go b/pkg/streamnativecloud/client/informers_generated/externalversions/generic.go index 2f2d23ee..ba4c3f3c 100644 --- a/pkg/streamnativecloud/client/informers_generated/externalversions/generic.go +++ b/pkg/streamnativecloud/client/informers_generated/externalversions/generic.go @@ -53,6 +53,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=cloud.streamnative.io, Version=v1alpha1 case v1alpha1.SchemeGroupVersion.WithResource("apikeys"): return &genericInformer{resource: resource.GroupResource(), informer: f.Cloud().V1alpha1().APIKeys().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("clusterroles"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Cloud().V1alpha1().ClusterRoles().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("rolebindings"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Cloud().V1alpha1().RoleBindings().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("secrets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Cloud().V1alpha1().Secrets().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("serviceaccounts"): diff --git a/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1/clusterrole.go b/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1/clusterrole.go new file mode 100644 index 00000000..d27223cb --- /dev/null +++ b/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1/clusterrole.go @@ -0,0 +1,65 @@ +// Copyright 2025 StreamNative +// +// 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. +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/apis/cloud/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// ClusterRoleLister helps list ClusterRoles. +// All objects returned here must be treated as read-only. +type ClusterRoleLister interface { + // List lists all ClusterRoles in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.ClusterRole, err error) + // Get retrieves the ClusterRole from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.ClusterRole, error) + ClusterRoleListerExpansion +} + +// clusterRoleLister implements the ClusterRoleLister interface. +type clusterRoleLister struct { + indexer cache.Indexer +} + +// NewClusterRoleLister returns a new ClusterRoleLister. +func NewClusterRoleLister(indexer cache.Indexer) ClusterRoleLister { + return &clusterRoleLister{indexer: indexer} +} + +// List lists all ClusterRoles in the indexer. +func (s *clusterRoleLister) List(selector labels.Selector) (ret []*v1alpha1.ClusterRole, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.ClusterRole)) + }) + return ret, err +} + +// Get retrieves the ClusterRole from the index for a given name. +func (s *clusterRoleLister) Get(name string) (*v1alpha1.ClusterRole, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("clusterrole"), name) + } + return obj.(*v1alpha1.ClusterRole), nil +} diff --git a/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1/expansion_generated.go b/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1/expansion_generated.go index 27d2fded..4f3f23da 100644 --- a/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1/expansion_generated.go +++ b/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1/expansion_generated.go @@ -23,6 +23,18 @@ type APIKeyListerExpansion interface{} // APIKeyNamespaceLister. type APIKeyNamespaceListerExpansion interface{} +// ClusterRoleListerExpansion allows custom methods to be added to +// ClusterRoleLister. +type ClusterRoleListerExpansion interface{} + +// RoleBindingListerExpansion allows custom methods to be added to +// RoleBindingLister. +type RoleBindingListerExpansion interface{} + +// RoleBindingNamespaceListerExpansion allows custom methods to be added to +// RoleBindingNamespaceLister. +type RoleBindingNamespaceListerExpansion interface{} + // SecretListerExpansion allows custom methods to be added to // SecretLister. type SecretListerExpansion interface{} diff --git a/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1/rolebinding.go b/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1/rolebinding.go new file mode 100644 index 00000000..9a659e8c --- /dev/null +++ b/pkg/streamnativecloud/client/listers_generated/cloud/v1alpha1/rolebinding.go @@ -0,0 +1,96 @@ +// Copyright 2025 StreamNative +// +// 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. +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/apis/cloud/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// RoleBindingLister helps list RoleBindings. +// All objects returned here must be treated as read-only. +type RoleBindingLister interface { + // List lists all RoleBindings in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.RoleBinding, err error) + // RoleBindings returns an object that can list and get RoleBindings. + RoleBindings(namespace string) RoleBindingNamespaceLister + RoleBindingListerExpansion +} + +// roleBindingLister implements the RoleBindingLister interface. +type roleBindingLister struct { + indexer cache.Indexer +} + +// NewRoleBindingLister returns a new RoleBindingLister. +func NewRoleBindingLister(indexer cache.Indexer) RoleBindingLister { + return &roleBindingLister{indexer: indexer} +} + +// List lists all RoleBindings in the indexer. +func (s *roleBindingLister) List(selector labels.Selector) (ret []*v1alpha1.RoleBinding, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.RoleBinding)) + }) + return ret, err +} + +// RoleBindings returns an object that can list and get RoleBindings. +func (s *roleBindingLister) RoleBindings(namespace string) RoleBindingNamespaceLister { + return roleBindingNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// RoleBindingNamespaceLister helps list and get RoleBindings. +// All objects returned here must be treated as read-only. +type RoleBindingNamespaceLister interface { + // List lists all RoleBindings in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.RoleBinding, err error) + // Get retrieves the RoleBinding from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.RoleBinding, error) + RoleBindingNamespaceListerExpansion +} + +// roleBindingNamespaceLister implements the RoleBindingNamespaceLister +// interface. +type roleBindingNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all RoleBindings in the indexer for a given namespace. +func (s roleBindingNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.RoleBinding, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.RoleBinding)) + }) + return ret, err +} + +// Get retrieves the RoleBinding from the indexer for a given namespace and name. +func (s roleBindingNamespaceLister) Get(name string) (*v1alpha1.RoleBinding, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("rolebinding"), name) + } + return obj.(*v1alpha1.RoleBinding), nil +} diff --git a/pkg/streamnativecloud/rolebinding_client.go b/pkg/streamnativecloud/rolebinding_client.go new file mode 100644 index 00000000..3a1719ed --- /dev/null +++ b/pkg/streamnativecloud/rolebinding_client.go @@ -0,0 +1,217 @@ +// Copyright 2025 StreamNative +// +// 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 streamnativecloud + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/rest" + + resourcev1alpha1 "github.com/streamnative/pulsar-resources-operator/api/v1alpha1" + cloudapi "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/apis/cloud/v1alpha1" + cloudclient "github.com/streamnative/pulsar-resources-operator/pkg/streamnativecloud/client/clientset_generated/clientset" +) + +// RoleBindingClient handles RoleBinding operations with the API server +type RoleBindingClient struct { + client cloudclient.Interface + // organization is the organization to use for API calls + organization string +} + +// NewRoleBindingClient creates a new RoleBinding client +func NewRoleBindingClient(apiConn *APIConnection, organization string) (*RoleBindingClient, error) { + if apiConn == nil || apiConn.config == nil || apiConn.client == nil { + return nil, fmt.Errorf("api connection is nil") + } + if !apiConn.IsInitialized() { + return nil, fmt.Errorf("api connection is not initialized") + } + // Create REST config + config := &rest.Config{ + Host: apiConn.config.Spec.Server, + Transport: apiConn.client.Transport, + } + + // Create client + client, err := cloudclient.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("failed to create client: %w", err) + } + + return &RoleBindingClient{ + client: client, + organization: organization, + }, nil +} + +// WatchRoleBinding watches a RoleBinding by name +func (c *RoleBindingClient) WatchRoleBinding(ctx context.Context, name string) (watch.Interface, error) { + // Use ListOptions to watch a single object + opts := metav1.ListOptions{ + FieldSelector: fmt.Sprintf("metadata.name=%s", name), + Watch: true, + } + return c.client.CloudV1alpha1().RoleBindings(c.organization).Watch(ctx, opts) +} + +// GetRoleBinding gets a RoleBinding by name +func (c *RoleBindingClient) GetRoleBinding(ctx context.Context, name string) (*cloudapi.RoleBinding, error) { + return c.client.CloudV1alpha1().RoleBindings(c.organization).Get(ctx, name, metav1.GetOptions{}) +} + +// convertToCloudRoleBinding converts a local RoleBinding to a cloud RoleBinding +func convertToCloudRoleBinding(roleBinding *resourcev1alpha1.RoleBinding, organization string) *cloudapi.RoleBinding { + // Convert to cloud API type + cloudRoleBinding := &cloudapi.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleBinding.Name, + }, + Spec: cloudapi.RoleBindingSpec{ + Subjects: makeSubjects(roleBinding.Spec.Users, roleBinding.Spec.IdentityPools, roleBinding.Spec.ServiceAccounts), + RoleRef: cloudapi.RoleRef{ + Kind: "ClusterRole", + Name: roleBinding.Spec.ClusterRole, + APIGroup: "cloud.streamnative.io", + }, + ResourceNames: makeResourceNames(roleBinding.Spec, organization), + }, + } + + // Handle optional fields + if roleBinding.Spec.CEL != nil { + cloudRoleBinding.Spec.CEL = roleBinding.Spec.CEL + } + + return cloudRoleBinding +} + +// makeSubjects creates Subject array from Users, IdentityPools, and ServiceAccounts +func makeSubjects(users []string, identityPools []string, serviceAccounts []string) []cloudapi.Subject { + subjects := make([]cloudapi.Subject, 0, len(users)+len(identityPools)+len(serviceAccounts)) + + // Add users + for _, user := range users { + subjects = append(subjects, cloudapi.Subject{ + Kind: "User", + Name: user, + APIGroup: "cloud.streamnative.io", + }) + } + + // Add identity pools + for _, pool := range identityPools { + subjects = append(subjects, cloudapi.Subject{ + Kind: "IdentityPool", + Name: pool, + APIGroup: "cloud.streamnative.io", + }) + } + + // Add service accounts + for _, sa := range serviceAccounts { + subjects = append(subjects, cloudapi.Subject{ + Kind: "ServiceAccount", + Name: sa, + APIGroup: "cloud.streamnative.io", + }) + } + + return subjects +} + +// makeResourceNames creates ResourceName array from SRN fields +func makeResourceNames(spec resourcev1alpha1.RoleBindingSpec, organization string) []cloudapi.ResourceName { + longest := findLongestStrArray(spec.SRNOrganization, spec.SRNInstance, spec.SRNCluster, spec.SRNTenant, spec.SRNNamespace, spec.SRNTopicDomain, spec.SRNTopicName, spec.SRNSubscription, spec.SRNServiceAccount, spec.SRNSecret) + resourceNames := make([]cloudapi.ResourceName, 0, len(longest)) + + for idx := range longest { + resourceName := cloudapi.ResourceName{} + resourceName.Organization = organization + if len(spec.SRNInstance) > idx { + resourceName.Instance = spec.SRNInstance[idx] + } + if len(spec.SRNCluster) > idx { + resourceName.Cluster = spec.SRNCluster[idx] + } + if len(spec.SRNTenant) > idx { + resourceName.Tenant = spec.SRNTenant[idx] + } + if len(spec.SRNNamespace) > idx { + resourceName.Namespace = spec.SRNNamespace[idx] + } + if len(spec.SRNTopicDomain) > idx { + resourceName.TopicDomain = spec.SRNTopicDomain[idx] + } + if len(spec.SRNTopicName) > idx { + resourceName.TopicName = spec.SRNTopicName[idx] + } + if len(spec.SRNSubscription) > idx { + resourceName.Subscription = spec.SRNSubscription[idx] + } + if len(spec.SRNServiceAccount) > idx { + resourceName.ServiceAccount = spec.SRNServiceAccount[idx] + } + if len(spec.SRNSecret) > idx { + resourceName.Secret = spec.SRNSecret[idx] + } + resourceNames = append(resourceNames, resourceName) + } + + return resourceNames +} + +func findLongestStrArray(arrays ...[]string) []string { + var longest []string + for _, arr := range arrays { + if len(arr) > len(longest) { + longest = arr + } + } + return longest +} + +// CreateRoleBinding creates a new RoleBinding +func (c *RoleBindingClient) CreateRoleBinding(ctx context.Context, roleBinding *resourcev1alpha1.RoleBinding) (*cloudapi.RoleBinding, error) { + cloudRoleBinding := convertToCloudRoleBinding(roleBinding, c.organization) + + // Create RoleBinding + return c.client.CloudV1alpha1().RoleBindings(c.organization).Create(ctx, cloudRoleBinding, metav1.CreateOptions{}) +} + +// UpdateRoleBinding updates an existing RoleBinding +func (c *RoleBindingClient) UpdateRoleBinding(ctx context.Context, roleBinding *resourcev1alpha1.RoleBinding) (*cloudapi.RoleBinding, error) { + // Get existing RoleBinding + existing, err := c.GetRoleBinding(ctx, roleBinding.Name) + if err != nil { + return nil, err + } + + // Create updated version + updated := convertToCloudRoleBinding(roleBinding, c.organization) + // Preserve ResourceVersion for optimistic concurrency + updated.ResourceVersion = existing.ResourceVersion + + // Update RoleBinding + return c.client.CloudV1alpha1().RoleBindings(c.organization).Update(ctx, updated, metav1.UpdateOptions{}) +} + +// DeleteRoleBinding deletes a RoleBinding by name +func (c *RoleBindingClient) DeleteRoleBinding(ctx context.Context, roleBinding *resourcev1alpha1.RoleBinding) error { + return c.client.CloudV1alpha1().RoleBindings(c.organization).Delete(ctx, roleBinding.Name, metav1.DeleteOptions{}) +}