diff --git a/PROJECT b/PROJECT index b46ee647..5ecd1643 100644 --- a/PROJECT +++ b/PROJECT @@ -103,4 +103,17 @@ resources: defaulting: true validation: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: openstack.org + group: network + kind: ServiceTransport + path: github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1 + version: v1beta1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 version: "3" diff --git a/apis/bases/memcached.openstack.org_servicetransports.yaml b/apis/bases/memcached.openstack.org_servicetransports.yaml new file mode 100644 index 00000000..8edccb51 --- /dev/null +++ b/apis/bases/memcached.openstack.org_servicetransports.yaml @@ -0,0 +1,108 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: servicetransports.memcached.openstack.org +spec: + group: memcached.openstack.org + names: + kind: ServiceTransport + listKind: ServiceTransportList + plural: servicetransports + singular: servicetransport + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Status + jsonPath: .status.conditions[0].status + name: Status + type: string + - description: Message + jsonPath: .status.conditions[0].message + name: Message + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: ServiceTransport is the Schema for the servicetransports 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: ServiceTransportSpec defines the desired state of ServiceTransport + properties: + serviceName: + description: Name of the service targeted by the transport + type: string + required: + - serviceName + type: object + status: + description: ServiceTransportStatus defines the observed state of ServiceTransport + properties: + conditions: + description: Conditions + items: + description: Condition defines an observation of a API resource + operational state. + properties: + lastTransitionTime: + description: 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: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. + type: string + severity: + description: Severity provides a classification of Reason code, + so the current situation is immediately understandable and + could act accordingly. It is meant for situations where Status=False + and it should be indicated if it is just informational, warning + (next reconciliation might fix it) or an error (e.g. DB create + issue and no actions to automatically resolve the issue can/should + be done). For conditions where Status=Unknown or Status=True + the Severity should be SeverityNone. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + secretName: + description: SecretName - name of the secret containing the service + transport URL + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/apis/memcached/v1beta1/conditions.go b/apis/memcached/v1beta1/conditions.go new file mode 100644 index 00000000..a4b8e1c4 --- /dev/null +++ b/apis/memcached/v1beta1/conditions.go @@ -0,0 +1,48 @@ +/* + +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 v1beta1 + +import ( + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" +) + +// ServiceTransport Condition Types used by API objects. +const ( + // ServiceTransportReadyCondition Status=True condition which indicates if ServiceTransport is configured and operational + ServiceTransportReadyCondition condition.Type = "ServiceTransportReady" +) + +// ServiceTransport Reasons used by API objects. +const () + +// Common Messages used by API objects. +const ( + // + // ServiceTransportReady condition messages + // + + // ServiceTransportReadyErrorMessage + ServiceTransportReadyErrorMessage = "ServiceTransport error occured %s" + + // ServiceTransportReadyInitMessage + ServiceTransportReadyInitMessage = "ServiceTransport not configured" + + // ServiceTransportReadyMessage + ServiceTransportReadyMessage = "ServiceTransport completed" + + // ServiceTransportInProgressMessage + ServiceTransportInProgressMessage = "ServiceTransport in progress" +) diff --git a/apis/memcached/v1beta1/servicetransport_types.go b/apis/memcached/v1beta1/servicetransport_types.go new file mode 100644 index 00000000..6439fd66 --- /dev/null +++ b/apis/memcached/v1beta1/servicetransport_types.go @@ -0,0 +1,71 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ServiceTransportSpec defines the desired state of ServiceTransport +type ServiceTransportSpec struct { + // +kubebuilder:validation:Required + // Name of the service targeted by the transport + ServiceName string `json:"serviceName"` +} + +// ServiceTransportStatus defines the observed state of ServiceTransport +type ServiceTransportStatus struct { + + // Conditions + Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"` + + // SecretName - name of the secret containing the service transport URL + SecretName string `json:"secretName,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[0].status",description="Status" +//+kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[0].message",description="Message" + +// ServiceTransport is the Schema for the servicetransports API +type ServiceTransport struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ServiceTransportSpec `json:"spec,omitempty"` + Status ServiceTransportStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// ServiceTransportList contains a list of ServiceTransport +type ServiceTransportList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ServiceTransport `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ServiceTransport{}, &ServiceTransportList{}) +} + +// IsReady - returns true if service is ready to serve requests +func (instance ServiceTransport) IsReady() bool { + return instance.Status.Conditions.IsTrue(ServiceTransportReadyCondition) +} diff --git a/apis/memcached/v1beta1/zz_generated.deepcopy.go b/apis/memcached/v1beta1/zz_generated.deepcopy.go index 366a8796..be430615 100644 --- a/apis/memcached/v1beta1/zz_generated.deepcopy.go +++ b/apis/memcached/v1beta1/zz_generated.deepcopy.go @@ -159,3 +159,99 @@ func (in *MemcachedStatus) DeepCopy() *MemcachedStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceTransport) DeepCopyInto(out *ServiceTransport) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceTransport. +func (in *ServiceTransport) DeepCopy() *ServiceTransport { + if in == nil { + return nil + } + out := new(ServiceTransport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceTransport) 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 *ServiceTransportList) DeepCopyInto(out *ServiceTransportList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ServiceTransport, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceTransportList. +func (in *ServiceTransportList) DeepCopy() *ServiceTransportList { + if in == nil { + return nil + } + out := new(ServiceTransportList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceTransportList) 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 *ServiceTransportSpec) DeepCopyInto(out *ServiceTransportSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceTransportSpec. +func (in *ServiceTransportSpec) DeepCopy() *ServiceTransportSpec { + if in == nil { + return nil + } + out := new(ServiceTransportSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceTransportStatus) DeepCopyInto(out *ServiceTransportStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(condition.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceTransportStatus. +func (in *ServiceTransportStatus) DeepCopy() *ServiceTransportStatus { + if in == nil { + return nil + } + out := new(ServiceTransportStatus) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/memcached.openstack.org_servicetransports.yaml b/config/crd/bases/memcached.openstack.org_servicetransports.yaml new file mode 100644 index 00000000..8edccb51 --- /dev/null +++ b/config/crd/bases/memcached.openstack.org_servicetransports.yaml @@ -0,0 +1,108 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: servicetransports.memcached.openstack.org +spec: + group: memcached.openstack.org + names: + kind: ServiceTransport + listKind: ServiceTransportList + plural: servicetransports + singular: servicetransport + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Status + jsonPath: .status.conditions[0].status + name: Status + type: string + - description: Message + jsonPath: .status.conditions[0].message + name: Message + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: ServiceTransport is the Schema for the servicetransports 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: ServiceTransportSpec defines the desired state of ServiceTransport + properties: + serviceName: + description: Name of the service targeted by the transport + type: string + required: + - serviceName + type: object + status: + description: ServiceTransportStatus defines the observed state of ServiceTransport + properties: + conditions: + description: Conditions + items: + description: Condition defines an observation of a API resource + operational state. + properties: + lastTransitionTime: + description: 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: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. + type: string + severity: + description: Severity provides a classification of Reason code, + so the current situation is immediately understandable and + could act accordingly. It is meant for situations where Status=False + and it should be indicated if it is just informational, warning + (next reconciliation might fix it) or an error (e.g. DB create + issue and no actions to automatically resolve the issue can/should + be done). For conditions where Status=Unknown or Status=True + the Severity should be SeverityNone. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + secretName: + description: SecretName - name of the secret containing the service + transport URL + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 67d20642..de5e2910 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,6 +4,7 @@ resources: - bases/rabbitmq.openstack.org_transporturls.yaml - bases/memcached.openstack.org_memcacheds.yaml +- bases/memcached.openstack.org_servicetransports.yaml - bases/redis.openstack.org_redises.yaml - bases/network.openstack.org_dnsmasqs.yaml - bases/network.openstack.org_dnsdata.yaml diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ed8e1fdc..0f49030b 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -94,6 +94,14 @@ rules: - patch - update - watch +- apiGroups: + - memcached.com + resources: + - memcachedclusters + verbs: + - get + - list + - watch - apiGroups: - memcached.openstack.org resources: @@ -120,6 +128,32 @@ rules: - get - patch - update +- apiGroups: + - memcached.openstack.org + resources: + - transporturls + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - memcached.openstack.org + resources: + - transporturls/finalizers + verbs: + - update +- apiGroups: + - memcached.openstack.org + resources: + - transporturls/status + verbs: + - get + - patch + - update - apiGroups: - network.openstack.org resources: diff --git a/controllers/memcached/servicetransport_controller.go b/controllers/memcached/servicetransport_controller.go new file mode 100644 index 00000000..093a86d7 --- /dev/null +++ b/controllers/memcached/servicetransport_controller.go @@ -0,0 +1,311 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package memcached + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + oko_secret "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// GetClient - +func (r *ServiceTransportReconciler) GetClient() client.Client { + return r.Client +} + +// GetKClient - +func (r *ServiceTransportReconciler) GetKClient() kubernetes.Interface { + return r.Kclient +} + +// GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields +func GetLog(ctx context.Context) logr.Logger { + return log.FromContext(ctx).WithName("Controllers").WithName("ServiceTransport") +} + +// GetScheme - +func (r *ServiceTransportReconciler) GetScheme() *runtime.Scheme { + return r.Scheme +} + +// ServiceTransportReconciler reconciles a ServiceTransport object +type ServiceTransportReconciler struct { + client.Client + Kclient kubernetes.Interface + Scheme *runtime.Scheme +} + +// GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields +func (r *ServiceTransportReconciler) GetLogger(ctx context.Context) logr.Logger { + return log.FromContext(ctx).WithName("Controllers").WithName("ServiceTransport") +} + +//+kubebuilder:rbac:groups=memcached.openstack.org,resources=transporturls,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=memcached.openstack.org,resources=transporturls/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=memcached.openstack.org,resources=transporturls/finalizers,verbs=update +//+kubebuilder:rbac:groups=memcached.com,resources=memcachedclusters,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete; + +// Reconcile - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile +func (r *ServiceTransportReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, _err error) { + Log := r.GetLogger(ctx) + // Fetch the ServiceTransport instance + instance := &memcachedv1.ServiceTransport{} + err := r.Client.Get(ctx, req.NamespacedName, instance) + if err != nil { + if k8s_errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + return ctrl.Result{}, err + } + + // + // initialize status + // + if instance.Status.Conditions == nil { + instance.Status.Conditions = condition.Conditions{} + + cl := condition.CreateList(condition.UnknownCondition(memcachedv1.ServiceTransportReadyCondition, condition.InitReason, memcachedv1.ServiceTransportReadyInitMessage)) + + instance.Status.Conditions.Init(&cl) + + // Register overall status immediately to have an early feedback e.g. in the cli + if err := r.Status().Update(ctx, instance); err != nil { + return ctrl.Result{}, err + } + } + + helper, err := helper.NewHelper( + instance, + r.Client, + r.Kclient, + r.Scheme, + Log, + ) + if err != nil { + return ctrl.Result{}, err + } + + // Always patch the instance status when exiting this function so we can persist any changes. + defer func() { + // update the Ready condition based on the sub conditions + if instance.Status.Conditions.AllSubConditionIsTrue() { + instance.Status.Conditions.MarkTrue( + condition.ReadyCondition, condition.ReadyMessage) + } else { + // something is not ready so reset the Ready condition + instance.Status.Conditions.MarkUnknown( + condition.ReadyCondition, condition.InitReason, condition.ReadyInitMessage) + // and recalculate it based on the state of the rest of the conditions + instance.Status.Conditions.Set( + instance.Status.Conditions.Mirror(condition.ReadyCondition)) + } + err := helper.PatchInstance(ctx, instance) + if err != nil { + _err = err + return + } + }() + + return r.reconcileNormal(ctx, instance, helper) +} + +func (r *ServiceTransportReconciler) reconcileNormal(ctx context.Context, instance *memcachedv1.ServiceTransport, helper *helper.Helper) (ctrl.Result, error) { + Log := r.GetLogger(ctx) + Log.Info("Reconciling Service") + + // TODO (implement a watch on the memcached cluster resources to update things if there are changes) + memcached, err := getMemcachedInstance(ctx, helper, instance) + if err != nil { + return ctrl.Result{}, err + } + + // Wait on memcached to be ready + if !memcached.IsReady() { + instance.Status.Conditions.Set(condition.FalseCondition( + memcachedv1.ServiceTransportReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + memcachedv1.ServiceTransportInProgressMessage)) + return ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil + } + + tlsEnabled := false + if memcached.Spec.TLS.SecretName != nil { + tlsEnabled = *memcached.Spec.TLS.SecretName != "" + } + Log.Info(fmt.Sprintf("memcached %s TLS enabled: %t", memcached.Name, tlsEnabled)) + + // Create a new secret with the transport URL for this CR + secret := r.createServiceTransportSecret(instance, memcached.Status.ServerList, false) + _, op, err := oko_secret.CreateOrPatchSecret(ctx, helper, instance, secret) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + memcachedv1.ServiceTransportReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + memcachedv1.ServiceTransportReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + if op != controllerutil.OperationResultNone { + instance.Status.Conditions.Set(condition.FalseCondition( + memcachedv1.ServiceTransportReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + memcachedv1.ServiceTransportReadyInitMessage)) + return ctrl.Result{RequeueAfter: time.Second * 5}, nil + } + + // Update the CR and return + instance.Status.SecretName = secret.Name + instance.Status.Conditions.MarkTrue(memcachedv1.ServiceTransportReadyCondition, memcachedv1.ServiceTransportReadyMessage) + + Log.Info("Reconciled Service successfully") + return ctrl.Result{}, nil +} + +// Create k8s secret with transport URL +func (r *ServiceTransportReconciler) createServiceTransportSecret( + instance *memcachedv1.ServiceTransport, + hosts []string, + tlsEnabled bool, +) *corev1.Secret { + hostList := strings.Join(hosts, ",") + // TLS only works with pymemcache + config := `[cache] +enabled=true +tls_enabled=%t +backend=dogpile.cache.pymemcache +memcache_servers=%s +` + // Create a new secret with the transport URL for this CR + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "memcached-service-transport-" + instance.Name, + Namespace: instance.Namespace, + }, + Data: map[string][]byte{ + "config_cache": []byte(fmt.Sprintf(config, tlsEnabled, hostList)), + "transport_url": []byte(hostList), + }, + } +} + +// fields to index to reconcile when change +const ( + memcachedNameField = ".spec.ServiceName" +) + +var allWatchFieldsTransport = []string{ + memcachedNameField, +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ServiceTransportReconciler) SetupWithManager(mgr ctrl.Manager) error { + // index caSecretName + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &memcachedv1.ServiceTransport{}, memcachedNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*memcachedv1.ServiceTransport) + if cr.Spec.ServiceName == "" { + return nil + } + return []string{cr.Spec.ServiceName} + }); err != nil { + return err + } + + return ctrl.NewControllerManagedBy(mgr). + For(&memcachedv1.ServiceTransport{}). + Owns(&corev1.Secret{}). + Watches( + &memcachedv1.Memcached{}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Complete(r) +} + +func (r *ServiceTransportReconciler) findObjectsForSrc(ctx context.Context, src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + for _, field := range allWatchFieldsTransport { + crList := &memcachedv1.ServiceTransportList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.List(ctx, crList, listOps) + if err != nil { + return []reconcile.Request{} + } + + for _, item := range crList.Items { + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + +// getMemcachedInstance - get Memcached object in namespace +func getMemcachedInstance( + ctx context.Context, + h *helper.Helper, + instance *memcachedv1.ServiceTransport, +) (*memcachedv1.Memcached, error) { + memcached := &memcachedv1.Memcached{} + + err := h.GetClient().Get(ctx, types.NamespacedName{Name: instance.Spec.ServiceName, Namespace: instance.Namespace}, memcached) + + return memcached, err +} diff --git a/main.go b/main.go index 998c0e57..73b0db18 100644 --- a/main.go +++ b/main.go @@ -151,6 +151,14 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Memcached") os.Exit(1) } + if err = (&memcachedcontrollers.ServiceTransportReconciler{ + Client: mgr.GetClient(), + Kclient: kclient, + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ServiceTransport") + os.Exit(1) + } if err = (&rediscontrollers.Reconciler{ Client: mgr.GetClient(), Kclient: kclient,