github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/adoption_controller.go (about) 1 package operators 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 8 "github.com/go-logr/logr" 9 appsv1 "k8s.io/api/apps/v1" 10 corev1 "k8s.io/api/core/v1" 11 rbacv1 "k8s.io/api/rbac/v1" 12 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 13 apierrors "k8s.io/apimachinery/pkg/api/errors" 14 "k8s.io/apimachinery/pkg/api/meta" 15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 "k8s.io/apimachinery/pkg/labels" 17 "k8s.io/apimachinery/pkg/runtime" 18 "k8s.io/apimachinery/pkg/types" 19 utilerrors "k8s.io/apimachinery/pkg/util/errors" 20 apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" 21 ctrl "sigs.k8s.io/controller-runtime" 22 "sigs.k8s.io/controller-runtime/pkg/builder" 23 "sigs.k8s.io/controller-runtime/pkg/client" 24 "sigs.k8s.io/controller-runtime/pkg/handler" 25 "sigs.k8s.io/controller-runtime/pkg/reconcile" 26 27 operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" 28 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" 29 operatorsv2 "github.com/operator-framework/api/pkg/operators/v2" 30 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/decorators" 31 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" 32 "github.com/operator-framework/operator-lifecycle-manager/pkg/metrics" 33 ) 34 35 // AdoptionReconciler automagically associates Operator components with their respective operator resource. 36 type AdoptionReconciler struct { 37 client.Client 38 39 log logr.Logger 40 factory decorators.OperatorFactory 41 } 42 43 // +kubebuilder:rbac:groups=operators.coreos.com,resources=operators,verbs=create;update;patch;delete 44 // +kubebuilder:rbac:groups=operators.coreos.com,resources=operators/status,verbs=update;patch 45 // +kubebuilder:rbac:groups=*,resources=*,verbs=get;list;watch 46 47 // SetupWithManager adds the operator reconciler to the given controller manager. 48 func (r *AdoptionReconciler) SetupWithManager(mgr ctrl.Manager) error { 49 // Trigger operator events from the events of their compoenents. 50 enqueueSub := handler.EnqueueRequestsFromMapFunc(r.mapToSubscriptions) 51 52 // Create multiple controllers for resource types that require automatic adoption 53 err := ctrl.NewControllerManagedBy(mgr). 54 For(&operatorsv1alpha1.Subscription{}). 55 Watches(&operatorsv1alpha1.ClusterServiceVersion{}, enqueueSub). 56 Watches(&operatorsv1alpha1.InstallPlan{}, enqueueSub). 57 Complete(reconcile.Func(r.ReconcileSubscription)) 58 if err != nil { 59 return err 60 } 61 62 var ( 63 enqueueCSV = handler.EnqueueRequestsFromMapFunc(r.mapToClusterServiceVersions) 64 enqueueProviders = handler.EnqueueRequestsFromMapFunc(r.mapToProviders) 65 ) 66 err = ctrl.NewControllerManagedBy(mgr). 67 For(&operatorsv1alpha1.ClusterServiceVersion{}). 68 Watches(&appsv1.Deployment{}, enqueueCSV). 69 Watches(&corev1.Namespace{}, enqueueCSV). 70 Watches(&corev1.Service{}, enqueueCSV). 71 Watches(&apiextensionsv1.CustomResourceDefinition{}, enqueueProviders). 72 Watches(&apiregistrationv1.APIService{}, enqueueCSV). 73 Watches(&operatorsv1alpha1.Subscription{}, enqueueCSV). 74 Watches(&operatorsv2.OperatorCondition{}, enqueueCSV). 75 Watches(&corev1.Secret{}, enqueueCSV, builder.OnlyMetadata). 76 Watches(&corev1.ConfigMap{}, enqueueCSV, builder.OnlyMetadata). 77 Watches(&corev1.ServiceAccount{}, enqueueCSV, builder.OnlyMetadata). 78 Watches(&rbacv1.Role{}, enqueueCSV, builder.OnlyMetadata). 79 Watches(&rbacv1.RoleBinding{}, enqueueCSV, builder.OnlyMetadata). 80 Watches(&rbacv1.ClusterRole{}, enqueueCSV, builder.OnlyMetadata). 81 Watches(&rbacv1.ClusterRoleBinding{}, enqueueCSV, builder.OnlyMetadata). 82 Complete(reconcile.Func(r.ReconcileClusterServiceVersion)) 83 if err != nil { 84 return err 85 } 86 87 return nil 88 } 89 90 // NewAdoptionReconciler constructs and returns an AdoptionReconciler. 91 // As a side effect, the given scheme has operator discovery types added to it. 92 func NewAdoptionReconciler(cli client.Client, log logr.Logger, scheme *runtime.Scheme) (*AdoptionReconciler, error) { 93 // Add watched types to scheme. 94 if err := AddToScheme(scheme); err != nil { 95 return nil, err 96 } 97 98 factory, err := decorators.NewSchemedOperatorFactory(scheme) 99 if err != nil { 100 return nil, err 101 } 102 103 return &AdoptionReconciler{ 104 Client: cli, 105 106 log: log, 107 factory: factory, 108 }, nil 109 } 110 111 // ReconcileSubscription labels the CSVs installed by a Subscription as components of an operator named after the subscribed package and install namespace. 112 func (r *AdoptionReconciler) ReconcileSubscription(ctx context.Context, req ctrl.Request) (reconcile.Result, error) { 113 // Set up a convenient log object so we don't have to type request over and over again 114 log := r.log.WithValues("request", req) 115 log.V(1).Info("reconciling subscription") 116 metrics.EmitAdoptionSubscriptionReconcile(req.Namespace, req.Name) 117 118 // Fetch the Subscription from the cache 119 in := &operatorsv1alpha1.Subscription{} 120 if err := r.Get(ctx, req.NamespacedName, in); err != nil { 121 if apierrors.IsNotFound(err) { 122 log.Info("Could not find Subscription") 123 } else { 124 log.Error(err, "Error finding Subscription") 125 } 126 127 return reconcile.Result{}, nil 128 } 129 130 // OLM generated Operators are named after their packages and further qualified by the install namespace 131 if in.Spec == nil || in.Spec.Package == "" { 132 log.Info("subscription spec missing package, ignoring") 133 return reconcile.Result{}, nil 134 } 135 136 // Wrap with convenience decorator 137 operator, err := r.factory.NewPackageOperator(in.Spec.Package, in.GetNamespace()) 138 if err != nil { 139 log.Error(err, "Could not wrap Operator with convenience decorator") 140 return reconcile.Result{}, nil 141 } 142 143 // Adopt the Subscription 144 var errs []error 145 if err := r.adopt(ctx, operator, in); err != nil { 146 log.Error(err, "Error adopting Subscription") 147 errs = append(errs, err) 148 } 149 150 // Adopt the Subscription's installed CSV 151 if name := in.Status.InstalledCSV; name != "" { 152 csv := &operatorsv1alpha1.ClusterServiceVersion{} 153 csv.SetNamespace(in.GetNamespace()) 154 csv.SetName(name) 155 if err := r.adopt(ctx, operator, csv); err != nil { 156 log.Error(err, "Error adopting installed CSV") 157 errs = append(errs, err) 158 } 159 } 160 161 // Adopt the Subscription's latest InstallPlan and Disown all others in the same namespace 162 if ref := in.Status.InstallPlanRef; ref != nil { 163 ip := &operatorsv1alpha1.InstallPlan{} 164 ip.SetNamespace(ref.Namespace) 165 ip.SetName(ref.Name) 166 if err := r.adoptInstallPlan(ctx, operator, ip); err != nil { 167 errs = append(errs, err) 168 } 169 } 170 171 return reconcile.Result{}, utilerrors.NewAggregate(errs) 172 } 173 174 // ReconcileClusterServiceVersion projects the component labels of a given CSV onto all resources owned by it. 175 func (r *AdoptionReconciler) ReconcileClusterServiceVersion(ctx context.Context, req ctrl.Request) (reconcile.Result, error) { 176 // Set up a convenient log object so we don't have to type request over and over again 177 log := r.log.WithValues("request", req) 178 log.V(1).Info("reconciling csv") 179 metrics.EmitAdoptionCSVReconcile(req.Namespace, req.Name) 180 181 // Fetch the CSV from the cache 182 in := &operatorsv1alpha1.ClusterServiceVersion{} 183 if err := r.Get(ctx, req.NamespacedName, in); err != nil { 184 if apierrors.IsNotFound(err) { 185 err = nil 186 } else { 187 log.Error(err, "Error finding ClusterServiceVersion") 188 } 189 190 return reconcile.Result{}, err 191 } 192 193 // disown copied csvs - a previous release inadvertently adopted them, this cleans any up if they are adopted 194 if in.IsCopied() { 195 err := r.disownFromAll(ctx, in) 196 return reconcile.Result{}, err 197 } 198 199 // Adopt all resources owned by the CSV if necessary 200 return reconcile.Result{}, r.adoptComponents(ctx, in) 201 } 202 203 func (r *AdoptionReconciler) adoptComponents(ctx context.Context, csv *operatorsv1alpha1.ClusterServiceVersion) error { 204 if csv.IsCopied() { 205 // For now, skip copied CSVs 206 return nil 207 } 208 209 var operators []decorators.Operator 210 for _, name := range decorators.OperatorNames(csv.GetLabels()) { 211 o := &operatorsv1.Operator{} 212 o.SetName(name.Name) 213 operator, err := r.factory.NewOperator(o) 214 if err != nil { 215 return err 216 } 217 operators = append(operators, *operator) 218 } 219 220 if len(operators) < 1 { 221 // No operators to adopt for 222 return nil 223 } 224 225 // Label (adopt) prospective components 226 var ( 227 errs []error 228 mu sync.Mutex 229 wg sync.WaitGroup 230 ) 231 for _, operator := range operators { 232 components, err := r.adoptees(ctx, operator, csv) 233 if err != nil { 234 func() { 235 mu.Lock() 236 defer mu.Unlock() 237 errs = append(errs, err) 238 }() 239 } 240 241 for _, component := range components { 242 var ( 243 // Copy variables into iteration scope 244 operator = operator 245 component = component 246 ) 247 wg.Add(1) 248 249 go func() { 250 defer wg.Done() 251 if err := r.adopt(ctx, &operator, component); err != nil { 252 mu.Lock() 253 defer mu.Unlock() 254 errs = append(errs, err) 255 } 256 }() 257 } 258 } 259 wg.Wait() 260 261 return utilerrors.NewAggregate(errs) 262 } 263 264 func (r *AdoptionReconciler) adopt(ctx context.Context, operator *decorators.Operator, component runtime.Object) error { 265 m, err := meta.Accessor(component) 266 if err != nil { 267 return nil 268 } 269 270 cObj, ok := component.(client.Object) 271 if !ok { 272 return fmt.Errorf("unable to typecast runtime.Object to client.Object") 273 } 274 275 if err := r.Get(ctx, types.NamespacedName{Namespace: m.GetNamespace(), Name: m.GetName()}, cObj); err != nil { 276 if apierrors.IsNotFound(err) { 277 r.log.V(1).Info("not found", "component", cObj) 278 err = nil 279 } 280 281 return err 282 } 283 candidate := cObj.DeepCopyObject() 284 285 adopted, err := operator.AdoptComponent(candidate) 286 if err != nil { 287 return err 288 } 289 290 if adopted { 291 // Only update if freshly adopted 292 pCObj, ok := candidate.(client.Object) 293 if !ok { 294 return fmt.Errorf("unable to typecast runtime.Object to client.Object") 295 } 296 return r.Patch(ctx, pCObj, client.MergeFrom(cObj)) 297 } 298 299 return nil 300 } 301 302 func (r *AdoptionReconciler) disown(ctx context.Context, operator *decorators.Operator, component runtime.Object) error { 303 cObj, ok := component.(client.Object) 304 if !ok { 305 return fmt.Errorf("unable to typecast runtime.Object to client.Object") 306 } 307 candidate := component.DeepCopyObject() 308 disowned, err := operator.DisownComponent(candidate) 309 if err != nil { 310 return err 311 } 312 313 if !disowned { 314 // Wasn't a component 315 return nil 316 } 317 318 // Only update if freshly disowned 319 r.log.V(1).Info("component disowned", "component", candidate) 320 uCObj, ok := candidate.(client.Object) 321 if !ok { 322 return fmt.Errorf("unable to typecast runtime.Object to client.Object") 323 } 324 return r.Patch(ctx, uCObj, client.MergeFrom(cObj)) 325 } 326 327 func (r *AdoptionReconciler) disownFromAll(ctx context.Context, component runtime.Object) error { 328 cObj, ok := component.(client.Object) 329 if !ok { 330 return fmt.Errorf("unable to typecast runtime.Object to client.Object") 331 } 332 var operators []decorators.Operator 333 for _, name := range decorators.OperatorNames(cObj.GetLabels()) { 334 o := &operatorsv1.Operator{} 335 o.SetName(name.Name) 336 operator, err := r.factory.NewOperator(o) 337 if err != nil { 338 return err 339 } 340 operators = append(operators, *operator) 341 } 342 errs := make([]error, 0) 343 for _, operator := range operators { 344 if err := r.disown(ctx, &operator, component); err != nil { 345 errs = append(errs, err) 346 } 347 } 348 349 return utilerrors.NewAggregate(errs) 350 } 351 352 func (r *AdoptionReconciler) adoptees(ctx context.Context, operator decorators.Operator, csv *operatorsv1alpha1.ClusterServiceVersion) ([]runtime.Object, error) { 353 // Note: We need to figure out how to dynamically add new list types here (or some equivalent) in 354 // order to support operators composed of custom resources. 355 componentLists := componentLists() 356 357 // Only resources that aren't already labelled are adoption candidates 358 selector, err := operator.NonComponentSelector() 359 if err != nil { 360 return nil, err 361 } 362 opt := client.MatchingLabelsSelector{Selector: selector} 363 for _, list := range componentLists { 364 cList, ok := list.(client.ObjectList) 365 if !ok { 366 return nil, fmt.Errorf("unable to typecast runtime.Object to client.ObjectList") 367 } 368 if err := r.List(ctx, cList, opt); err != nil { 369 return nil, err 370 } 371 } 372 373 var ( 374 components []runtime.Object 375 errs []error 376 ) 377 for _, candidate := range flatten(componentLists) { 378 m, err := meta.Accessor(candidate) 379 if err != nil { 380 errs = append(errs, err) 381 continue 382 } 383 384 if ownerutil.IsOwnedBy(m, csv) || ownerutil.IsOwnedByLabel(m, csv) { 385 components = append(components, candidate) 386 } 387 } 388 389 // Pick up owned CRDs 390 for _, provided := range csv.Spec.CustomResourceDefinitions.Owned { 391 crd := &metav1.PartialObjectMetadata{} 392 crd.SetGroupVersionKind(apiextensionsv1.SchemeGroupVersion.WithKind("CustomResourceDefinition")) 393 if err := r.Get(ctx, types.NamespacedName{Name: provided.Name}, crd); err != nil { 394 if !apierrors.IsNotFound(err) { 395 // Inform requeue on transient error 396 errs = append(errs, err) 397 } 398 399 // Skip on transient error or missing CRD 400 continue 401 } 402 403 if crd == nil || !selector.Matches(labels.Set(crd.GetLabels())) { 404 // Skip empty and labelled CRDs 405 continue 406 } 407 408 components = append(components, crd) 409 } 410 411 return components, utilerrors.NewAggregate(errs) 412 } 413 414 func (r *AdoptionReconciler) adoptInstallPlan(ctx context.Context, operator *decorators.Operator, latest *operatorsv1alpha1.InstallPlan) error { 415 // Adopt the latest InstallPlan 416 if err := r.adopt(ctx, operator, latest); err != nil { 417 return err 418 } 419 420 // Disown older InstallPlans 421 selector, err := operator.ComponentSelector() 422 if err != nil { 423 return err 424 } 425 426 var ( 427 ips = &operatorsv1alpha1.InstallPlanList{} 428 opt = client.MatchingLabelsSelector{Selector: selector} 429 ) 430 if err := r.List(ctx, ips, opt, client.InNamespace(latest.GetNamespace())); err != nil { 431 return err 432 } 433 434 var errs []error 435 for _, ip := range ips.Items { 436 if ip.GetName() == latest.GetName() { 437 // Don't disown latest 438 continue 439 } 440 441 if err := r.disown(ctx, operator, &ip); err != nil { 442 errs = append(errs, err) 443 } 444 } 445 446 return utilerrors.NewAggregate(errs) 447 } 448 449 func (r *AdoptionReconciler) mapToSubscriptions(ctx context.Context, obj client.Object) (requests []reconcile.Request) { 450 if obj == nil { 451 return 452 } 453 454 // Requeue all Subscriptions in the resource namespace 455 // The Subscription reconciler will sort out the important changes 456 subs := &operatorsv1alpha1.SubscriptionList{} 457 if err := r.List(ctx, subs, client.InNamespace(obj.GetNamespace())); err != nil { 458 r.log.Error(err, "error listing subscriptions") 459 } 460 461 visited := map[types.NamespacedName]struct{}{} 462 for _, sub := range subs.Items { 463 nsn := types.NamespacedName{Namespace: sub.GetNamespace(), Name: sub.GetName()} 464 465 if _, ok := visited[nsn]; ok { 466 // Already requested 467 continue 468 } 469 470 requests = append(requests, reconcile.Request{NamespacedName: nsn}) 471 visited[nsn] = struct{}{} 472 } 473 474 return 475 } 476 477 func (r *AdoptionReconciler) mapToClusterServiceVersions(_ context.Context, obj client.Object) (requests []reconcile.Request) { 478 if obj == nil { 479 return 480 } 481 482 // Get all owner CSV from owner labels if cluster scoped 483 namespace := obj.GetNamespace() 484 if namespace == metav1.NamespaceAll { 485 name, ns, ok := ownerutil.GetOwnerByKindLabel(obj, operatorsv1alpha1.ClusterServiceVersionKind) 486 if ok { 487 nsn := types.NamespacedName{Namespace: ns, Name: name} 488 requests = append(requests, reconcile.Request{NamespacedName: nsn}) 489 } 490 return 491 } 492 493 // Get all owner CSVs from OwnerReferences 494 owners := ownerutil.GetOwnersByKind(obj, operatorsv1alpha1.ClusterServiceVersionKind) 495 for _, owner := range owners { 496 nsn := types.NamespacedName{Namespace: namespace, Name: owner.Name} 497 requests = append(requests, reconcile.Request{NamespacedName: nsn}) 498 } 499 500 return 501 } 502 503 func (r *AdoptionReconciler) mapToProviders(ctx context.Context, obj client.Object) (requests []reconcile.Request) { 504 if obj == nil { 505 return nil 506 } 507 508 var ( 509 csvs = &operatorsv1alpha1.ClusterServiceVersionList{} 510 ) 511 if err := r.List(ctx, csvs); err != nil { 512 r.log.Error(err, "error listing csvs") 513 return 514 } 515 516 for _, csv := range csvs.Items { 517 request := reconcile.Request{ 518 NamespacedName: types.NamespacedName{Namespace: csv.GetNamespace(), Name: csv.GetName()}, 519 } 520 for _, provided := range csv.Spec.CustomResourceDefinitions.Owned { 521 if provided.Name == obj.GetName() { 522 requests = append(requests, request) 523 break 524 } 525 } 526 } 527 528 return 529 } 530 531 func flatten(objs []runtime.Object) (flattened []runtime.Object) { 532 for _, obj := range objs { 533 if nested, err := meta.ExtractList(obj); err == nil { 534 flattened = append(flattened, flatten(nested)...) 535 continue 536 } 537 538 flattened = append(flattened, obj) 539 } 540 541 return 542 }