managedknowledgebase_controller.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /*
  2. Copyright 2026 LocoStack.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package controller
  14. import (
  15. "context"
  16. "fmt"
  17. "maps"
  18. corev1 "k8s.io/api/core/v1"
  19. apierrors "k8s.io/apimachinery/pkg/api/errors"
  20. apimeta "k8s.io/apimachinery/pkg/api/meta"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/runtime"
  23. "k8s.io/apimachinery/pkg/types"
  24. ctrl "sigs.k8s.io/controller-runtime"
  25. "sigs.k8s.io/controller-runtime/pkg/client"
  26. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  27. "sigs.k8s.io/controller-runtime/pkg/handler"
  28. logf "sigs.k8s.io/controller-runtime/pkg/log"
  29. "sigs.k8s.io/controller-runtime/pkg/reconcile"
  30. "github.com/LocoStack/loco-operator/api/v1alpha1"
  31. "github.com/LocoStack/loco-operator/internal/reconciler"
  32. "github.com/LocoStack/loco-operator/pkg/templates"
  33. )
  34. // ManagedKnowledgeBaseReconciler reconciles a ManagedKnowledgeBase object
  35. type ManagedKnowledgeBaseReconciler struct {
  36. client.Client
  37. Scheme *runtime.Scheme
  38. }
  39. // +kubebuilder:rbac:groups=locostack.com,resources=managedknowledgebases,verbs=get;list;watch;create;update;patch;delete
  40. // +kubebuilder:rbac:groups=locostack.com,resources=managedknowledgebases/status,verbs=get;update;patch
  41. // +kubebuilder:rbac:groups=locostack.com,resources=managedknowledgebases/finalizers,verbs=update
  42. // +kubebuilder:rbac:groups=locostack.com,resources=components,verbs=get;list;watch;create;update;patch;delete
  43. // +kubebuilder:rbac:groups=locostack.com,resources=stacks,verbs=get;list;watch
  44. func (r *ManagedKnowledgeBaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  45. log := logf.FromContext(ctx)
  46. mkb := &v1alpha1.ManagedKnowledgeBase{}
  47. if err := r.Get(ctx, req.NamespacedName, mkb); err != nil {
  48. if apierrors.IsNotFound(err) {
  49. return ctrl.Result{}, nil
  50. }
  51. log.Error(err, "Failed to get ManagedKnowledgeBase", "namespace", req.NamespacedName.Namespace, "name", req.NamespacedName.Name)
  52. return ctrl.Result{}, err
  53. }
  54. patch := client.MergeFrom(mkb.DeepCopy())
  55. componentAvailable, err := r.reconcileComponent(ctx, mkb)
  56. if err != nil {
  57. return ctrl.Result{}, err
  58. }
  59. if !componentAvailable {
  60. apimeta.SetStatusCondition(&mkb.Status.Conditions, metav1.Condition{
  61. Type: "Available",
  62. Status: metav1.ConditionFalse,
  63. Reason: "KnowledgeBaseUnavailable",
  64. Message: "ManagedKnowledgeBase is not available",
  65. ObservedGeneration: mkb.Generation,
  66. })
  67. } else {
  68. apimeta.SetStatusCondition(&mkb.Status.Conditions, metav1.Condition{
  69. Type: "Available",
  70. Status: metav1.ConditionTrue,
  71. Reason: "KnowledgeBaseAvailable",
  72. Message: "ManagedKnowledgeBase is available",
  73. ObservedGeneration: mkb.Generation,
  74. })
  75. }
  76. mkb.Status.ObservedGeneration = mkb.Generation
  77. if err := r.Status().Patch(ctx, mkb, patch); err != nil {
  78. return ctrl.Result{}, client.IgnoreNotFound(err)
  79. }
  80. log.Info("Reconciled ManagedKnowledgeBase", "namespace", req.NamespacedName.Namespace, "name", req.NamespacedName.Name)
  81. return ctrl.Result{}, nil
  82. }
  83. func (r *ManagedKnowledgeBaseReconciler) reconcileComponent(ctx context.Context, mkb *v1alpha1.ManagedKnowledgeBase) (bool, error) {
  84. compName := fmt.Sprintf("kb-%s", mkb.Name)
  85. comp := &v1alpha1.Component{
  86. ObjectMeta: metav1.ObjectMeta{
  87. Name: compName,
  88. Namespace: mkb.Namespace,
  89. },
  90. }
  91. err := r.Get(ctx, client.ObjectKey{Namespace: mkb.Namespace, Name: compName}, comp)
  92. tmpl, err := templates.Manager.ResolveTemplate(mkb.Spec.Template, "")
  93. if err != nil {
  94. apimeta.SetStatusCondition(&mkb.Status.Conditions, metav1.Condition{
  95. Type: "KnowledgeBaseRuntimeResolved",
  96. Status: metav1.ConditionFalse,
  97. Reason: "TemplateNotResolved",
  98. Message: fmt.Sprintf("Failed to reconcile template for ManagedKnowledgeBase: %v", err),
  99. ObservedGeneration: mkb.Generation,
  100. })
  101. return false, nil
  102. }
  103. apimeta.SetStatusCondition(&mkb.Status.Conditions, metav1.Condition{
  104. Type: "KnowledgeBaseRuntimeResolved",
  105. Status: metav1.ConditionTrue,
  106. Reason: "TemplateResolved",
  107. Message: fmt.Sprintf("Successfully resolved template for ManagedKnowledgeBase: %v", tmpl.Name),
  108. ObservedGeneration: mkb.Generation,
  109. })
  110. _, err = controllerutil.CreateOrUpdate(ctx, r.Client, comp, func() error {
  111. if comp.Labels == nil {
  112. comp.Labels = make(map[string]string)
  113. }
  114. stackName := ""
  115. if mkb.Spec.StackRef != nil {
  116. stackName = mkb.Spec.StackRef.Name
  117. }
  118. maps.Copy(comp.Labels, reconciler.ResourceLabels(stackName, "KnowledgeBase", mkb.Name, ""))
  119. dependencies := make([]corev1.ObjectReference, 0)
  120. if mkb.Spec.EmbeddingModelRef != nil {
  121. dependencies = append(dependencies, *mkb.Spec.EmbeddingModelRef)
  122. }
  123. if mkb.Spec.RerankerModelRef != nil {
  124. dependencies = append(dependencies, *mkb.Spec.RerankerModelRef)
  125. }
  126. comp.Spec.Category = "KnowledgeBase"
  127. comp.Spec.Template = &tmpl
  128. comp.Spec.Dependencies = dependencies
  129. if mkb.Spec.StackRef != nil {
  130. comp.Spec.StackRef = &corev1.LocalObjectReference{Name: mkb.Spec.StackRef.Name}
  131. }
  132. return controllerutil.SetControllerReference(mkb, comp, r.Scheme)
  133. })
  134. available := apimeta.IsStatusConditionTrue(comp.Status.Conditions, "Available")
  135. if available {
  136. mkb.Status.Endpoint = comp.Status.Endpoint
  137. apimeta.SetStatusCondition(&mkb.Status.Conditions, metav1.Condition{
  138. Type: "Ready",
  139. Status: metav1.ConditionTrue,
  140. Reason: "ComponentAvailable",
  141. Message: "Component is available",
  142. ObservedGeneration: mkb.Generation,
  143. })
  144. } else {
  145. apimeta.SetStatusCondition(&mkb.Status.Conditions, metav1.Condition{
  146. Type: "Ready",
  147. Status: metav1.ConditionFalse,
  148. Reason: "ComponentUnavailable",
  149. Message: "Component is not available",
  150. ObservedGeneration: mkb.Generation,
  151. })
  152. }
  153. return available, err
  154. }
  155. // SetupWithManager sets up the controller with the Manager.
  156. func (r *ManagedKnowledgeBaseReconciler) SetupWithManager(mgr ctrl.Manager) error {
  157. mapComponentToManagedKnowledgeBases := func(ctx context.Context, obj client.Object) []reconcile.Request {
  158. comp, ok := obj.(*v1alpha1.Component)
  159. if !ok {
  160. return nil
  161. }
  162. var reqs []reconcile.Request
  163. for _, em := range comp.Spec.Dependencies {
  164. if em.APIVersion == v1alpha1.GroupVersion.Group && em.Kind == "ManagedKnowledgeBase" {
  165. reqs = append(reqs, reconcile.Request{
  166. NamespacedName: types.NamespacedName{
  167. Name: em.Name,
  168. Namespace: em.Namespace,
  169. },
  170. })
  171. }
  172. }
  173. return reqs
  174. }
  175. return ctrl.NewControllerManagedBy(mgr).
  176. For(&v1alpha1.ManagedKnowledgeBase{}).
  177. Owns(&v1alpha1.Component{}).
  178. Watches(&v1alpha1.Component{}, handler.EnqueueRequestsFromMapFunc(mapComponentToManagedKnowledgeBases)).
  179. Named("managedknowledgebase").
  180. Complete(r)
  181. }