github.com/IBM-Blockchain/fabric-operator@v1.0.4/controllers/ibpca/ibpca_controller.go (about) 1 /* 2 * Copyright contributors to the Hyperledger Fabric Operator project 3 * 4 * SPDX-License-Identifier: Apache-2.0 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package ibpca 20 21 import ( 22 "context" 23 "fmt" 24 "os" 25 "reflect" 26 "strings" 27 "sync" 28 "time" 29 30 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 31 commoncontroller "github.com/IBM-Blockchain/fabric-operator/controllers/common" 32 config "github.com/IBM-Blockchain/fabric-operator/operatorconfig" 33 "github.com/IBM-Blockchain/fabric-operator/pkg/global" 34 k8sclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" 35 "github.com/IBM-Blockchain/fabric-operator/pkg/offering" 36 baseca "github.com/IBM-Blockchain/fabric-operator/pkg/offering/base/ca" 37 "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common" 38 k8sca "github.com/IBM-Blockchain/fabric-operator/pkg/offering/k8s/ca" 39 openshiftca "github.com/IBM-Blockchain/fabric-operator/pkg/offering/openshift/ca" 40 "github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors" 41 "github.com/IBM-Blockchain/fabric-operator/pkg/restart/staggerrestarts" 42 "github.com/IBM-Blockchain/fabric-operator/pkg/util" 43 "github.com/go-test/deep" 44 "github.com/pkg/errors" 45 ctrl "sigs.k8s.io/controller-runtime" 46 yaml "sigs.k8s.io/yaml" 47 48 appsv1 "k8s.io/api/apps/v1" 49 corev1 "k8s.io/api/core/v1" 50 k8serrors "k8s.io/apimachinery/pkg/api/errors" 51 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 52 "k8s.io/apimachinery/pkg/labels" 53 "k8s.io/apimachinery/pkg/runtime" 54 "k8s.io/apimachinery/pkg/types" 55 "sigs.k8s.io/controller-runtime/pkg/client" 56 "sigs.k8s.io/controller-runtime/pkg/controller" 57 "sigs.k8s.io/controller-runtime/pkg/event" 58 "sigs.k8s.io/controller-runtime/pkg/handler" 59 logf "sigs.k8s.io/controller-runtime/pkg/log" 60 "sigs.k8s.io/controller-runtime/pkg/manager" 61 "sigs.k8s.io/controller-runtime/pkg/predicate" 62 "sigs.k8s.io/controller-runtime/pkg/reconcile" 63 "sigs.k8s.io/controller-runtime/pkg/source" 64 ) 65 66 const ( 67 KIND = "IBPCA" 68 ) 69 70 var log = logf.Log.WithName("controller_ibpca") 71 72 // Add creates a new IBPCA Controller and adds it to the Manager. The Manager will set fields on the Controller 73 // and Start it when the Manager is Started. 74 func Add(mgr manager.Manager, cfg *config.Config) error { 75 r, err := newReconciler(mgr, cfg) 76 if err != nil { 77 return err 78 } 79 return add(mgr, r) 80 } 81 82 // newReconciler returns a new reconcile.Reconciler 83 func newReconciler(mgr manager.Manager, cfg *config.Config) (*ReconcileIBPCA, error) { 84 client := k8sclient.New(mgr.GetClient(), &global.ConfigSetter{Config: cfg.Operator.Globals}) 85 scheme := mgr.GetScheme() 86 87 ibpca := &ReconcileIBPCA{ 88 client: client, 89 scheme: scheme, 90 Config: cfg, 91 update: map[string][]Update{}, 92 mutex: &sync.Mutex{}, 93 RestartService: staggerrestarts.New(client, cfg.Operator.Restart.Timeout.Get()), 94 } 95 96 switch cfg.Offering { 97 case offering.K8S: 98 ibpca.Offering = k8sca.New(client, scheme, cfg) 99 case offering.OPENSHIFT: 100 ibpca.Offering = openshiftca.New(client, scheme, cfg) 101 } 102 103 return ibpca, nil 104 } 105 106 // add adds a new Controller to mgr with r as the reconcile.Reconciler 107 func add(mgr manager.Manager, r *ReconcileIBPCA) error { 108 // Create a new controller 109 predicateFuncs := predicate.Funcs{ 110 CreateFunc: r.CreateFunc, 111 UpdateFunc: r.UpdateFunc, 112 } 113 114 c, err := controller.New("ibpca-controller", mgr, controller.Options{Reconciler: r}) 115 if err != nil { 116 return err 117 } 118 119 // Watch for changes to primary resource IBPCA 120 err = c.Watch(&source.Kind{Type: ¤t.IBPCA{}}, &handler.EnqueueRequestForObject{}, predicateFuncs) 121 if err != nil { 122 return err 123 } 124 125 // Watch for changes to config maps (Create and Update funcs handle only watching for restart config map) 126 err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForObject{}, predicateFuncs) 127 if err != nil { 128 return err 129 } 130 131 err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ 132 IsController: true, 133 OwnerType: ¤t.IBPCA{}, 134 }) 135 if err != nil { 136 return err 137 } 138 139 // Watch for changes to tertiary resource Secrets and requeue the owner IBPPeer 140 err = c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForOwner{ 141 IsController: true, 142 OwnerType: ¤t.IBPCA{}, 143 }, predicateFuncs) 144 if err != nil { 145 return err 146 } 147 148 return nil 149 } 150 151 var _ reconcile.Reconciler = &ReconcileIBPCA{} 152 153 //go:generate counterfeiter -o mocks/careconcile.go -fake-name CAReconcile . caReconcile 154 //counterfeiter:generate . caReconcile 155 type caReconcile interface { 156 Reconcile(*current.IBPCA, baseca.Update) (common.Result, error) 157 } 158 159 // ReconcileIBPCA reconciles a IBPCA object 160 type ReconcileIBPCA struct { 161 // This client, initialized using mgr.Client() above, is a split client 162 // that reads objects from the cache and writes to the apiserver 163 client k8sclient.Client 164 scheme *runtime.Scheme 165 166 Offering caReconcile 167 Config *config.Config 168 RestartService *staggerrestarts.StaggerRestartsService 169 170 update map[string][]Update 171 mutex *sync.Mutex 172 } 173 174 // Reconcile reads that state of the cluster for a IBPCA object and makes changes based on the state read 175 // and what is in the IBPCA.Spec 176 // Note: 177 // The Controller will requeue the Request to be processed again if the returned error is non-nil or 178 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. 179 // +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=persistentvolumeclaims;persistentvolumes,verbs=get;list;watch;create;update;patch;delete;deletecollection 180 // +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get 181 // +kubebuilder:rbac:groups=route.openshift.io,resources=routes;routes/custom-host,verbs=get;list;watch;create;update;patch;delete;deletecollection 182 // +kubebuilder:rbac:groups="",resources=pods;pods/log;persistentvolumeclaims;persistentvolumes;services;endpoints;events;configmaps;secrets;nodes;serviceaccounts,verbs=get;list;watch;create;update;patch;delete;deletecollection 183 // +kubebuilder:rbac:groups="batch",resources=jobs,verbs=get;list;watch;create;update;patch;delete;deletecollection 184 // +kubebuilder:rbac:groups="authorization.openshift.io";"rbac.authorization.k8s.io",resources=roles;rolebinding,verbs=get;list;watch;create;update;patch;delete;deletecollection;bind;escalate 185 // +kubebuilder:rbac:groups="",resources=namespaces,verbs=get 186 // +kubebuilder:rbac:groups=apps,resources=deployments;daemonsets;replicasets;statefulsets,verbs=get;list;watch;create;update;patch;delete;deletecollection 187 // +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create 188 // +kubebuilder:rbac:groups=apps,resourceNames=ibp-operator,resources=deployments/finalizers,verbs=update 189 // +kubebuilder:rbac:groups=ibp.com,resources=ibpcas.ibp.com;ibppeers.ibp.com;ibporderers.ibp.com;ibpcas;ibppeers;ibporderers;ibpconsoles;ibpcas/finalizers;ibppeer/finalizers;ibporderers/finalizers;ibpconsole/finalizers;ibpcas/status;ibppeers/status;ibporderers/status;ibpconsoles/status,verbs=get;list;watch;create;update;patch;delete;deletecollection 190 // +kubebuilder:rbac:groups=extensions;networking.k8s.io;config.openshift.io,resources=ingresses;networkpolicies,verbs=get;list;watch;create;update;patch;delete;deletecollection 191 // +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;watch;create;update;patch;delete;deletecollection 192 func (r *ReconcileIBPCA) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { 193 var err error 194 195 reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) 196 197 // If ca-restart-config configmap is the object being reconciled, reconcile the 198 // restart configmap. 199 if request.Name == "ca-restart-config" { 200 requeue, err := r.ReconcileRestart(request.Namespace) 201 // Error reconciling restart - requeue the request. 202 if err != nil { 203 return reconcile.Result{}, err 204 } 205 // Restart reconciled, requeue request if required. 206 return reconcile.Result{ 207 Requeue: requeue, 208 }, nil 209 } 210 211 reqLogger.Info("Reconciling IBPCA") 212 213 // Fetch the IBPCA instance 214 instance := ¤t.IBPCA{} 215 err = r.client.Get(context.TODO(), request.NamespacedName, instance) 216 if err != nil { 217 if k8serrors.IsNotFound(err) { 218 // Request object not found, could have been deleted after reconcile request. 219 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 220 // Return and don't requeue 221 return reconcile.Result{}, nil 222 } 223 // Error reading the object - requeue the request. 224 return reconcile.Result{}, err 225 } 226 227 reqLogger.Info(fmt.Sprintf("Current update stack to process: %+v", GetUpdateStack(r.update))) 228 229 update := r.GetUpdateStatus(instance) 230 reqLogger.Info(fmt.Sprintf("Reconciling IBPCA '%s' with update values of [ %+v ]", instance.GetName(), update.GetUpdateStackWithTrues())) 231 232 result, err := r.Offering.Reconcile(instance, r.PopUpdate(instance.GetName())) 233 setStatusErr := r.SetStatus(instance, result.Status, err) 234 if setStatusErr != nil { 235 return reconcile.Result{}, operatorerrors.IsBreakingError(setStatusErr, "failed to update status", log) 236 } 237 238 if err != nil { 239 return reconcile.Result{}, operatorerrors.IsBreakingError(errors.Wrapf(err, "CA instance '%s' encountered error", instance.GetName()), "stopping reconcile loop", log) 240 } 241 242 if result.Requeue { 243 r.PushUpdate(instance.GetName(), *update) 244 } 245 246 reqLogger.Info(fmt.Sprintf("Finished reconciling IBPCA '%s' with update values of [ %+v ]", instance.GetName(), update.GetUpdateStackWithTrues())) 247 248 // If the stack still has items that require processing, keep reconciling 249 // until the stack has been cleared 250 _, found := r.update[instance.GetName()] 251 if found { 252 if len(r.update[instance.GetName()]) > 0 { 253 return reconcile.Result{ 254 Requeue: true, 255 }, nil 256 } 257 } 258 259 return result.Result, nil 260 } 261 262 func (r *ReconcileIBPCA) SetStatus(instance *current.IBPCA, reconcileStatus *current.CRStatus, reconcileErr error) error { 263 log.Info(fmt.Sprintf("Setting status for '%s'", instance.GetName())) 264 265 err := r.SaveSpecState(instance) 266 if err != nil { 267 return errors.Wrap(err, "failed to save spec state") 268 } 269 270 err = r.client.Get(context.TODO(), types.NamespacedName{Name: instance.GetName(), Namespace: instance.GetNamespace()}, instance) 271 if err != nil { 272 return err 273 } 274 275 status := instance.Status.CRStatus 276 277 if reconcileErr != nil { 278 status.Type = current.Error 279 status.Status = current.True 280 status.Reason = "errorOccurredDuringReconcile" 281 status.Message = reconcileErr.Error() 282 status.LastHeartbeatTime = time.Now().String() 283 status.ErrorCode = operatorerrors.GetErrorCode(reconcileErr) 284 285 instance.Status = current.IBPCAStatus{ 286 CRStatus: status, 287 } 288 289 log.Info(fmt.Sprintf("Updating status of IBPCA custom resource to %s phase", instance.Status.Type)) 290 err = r.client.PatchStatus(context.TODO(), instance, nil, k8sclient.PatchOption{ 291 Resilient: &k8sclient.ResilientPatch{ 292 Retry: 2, 293 Into: ¤t.IBPCA{}, 294 Strategy: client.MergeFrom, 295 }, 296 }) 297 if err != nil { 298 return err 299 } 300 301 return nil 302 } 303 304 status.Versions.Reconciled = instance.Spec.FabricVersion 305 306 // Check if reconcile loop returned an updated status that differs from exisiting status. 307 // If so, set status to the reconcile status. 308 if reconcileStatus != nil { 309 if instance.Status.Type != reconcileStatus.Type || instance.Status.Reason != reconcileStatus.Reason || instance.Status.Message != reconcileStatus.Message { 310 status.Type = reconcileStatus.Type 311 status.Status = current.True 312 status.Reason = reconcileStatus.Reason 313 status.Message = reconcileStatus.Message 314 status.LastHeartbeatTime = time.Now().String() 315 316 instance.Status = current.IBPCAStatus{ 317 CRStatus: status, 318 } 319 320 log.Info(fmt.Sprintf("Updating status of IBPPeer custom resource to %s phase", instance.Status.Type)) 321 err := r.client.PatchStatus(context.TODO(), instance, nil, k8sclient.PatchOption{ 322 Resilient: &k8sclient.ResilientPatch{ 323 Retry: 2, 324 Into: ¤t.IBPCA{}, 325 Strategy: client.MergeFrom, 326 }, 327 }) 328 if err != nil { 329 return err 330 } 331 332 return nil 333 } 334 } 335 336 running, err := r.PodsRunning(instance) 337 if err != nil { 338 return err 339 } 340 341 if running { 342 if instance.Status.Type == current.Deployed { 343 return nil 344 } 345 status.Type = current.Deployed 346 status.Status = current.True 347 status.Reason = "allPodsRunning" 348 status.Message = "All pods running" 349 } else { 350 if instance.Status.Type == current.Deploying { 351 return nil 352 } 353 status.Type = current.Deploying 354 status.Status = current.True 355 status.Reason = "waitingForPods" 356 status.Message = "Waiting for pods" 357 } 358 359 instance.Status = current.IBPCAStatus{ 360 CRStatus: status, 361 } 362 instance.Status.LastHeartbeatTime = time.Now().String() 363 log.Info(fmt.Sprintf("Updating status of IBPCA custom resource to %s phase", instance.Status.Type)) 364 err = r.client.PatchStatus(context.TODO(), instance, nil, k8sclient.PatchOption{ 365 Resilient: &k8sclient.ResilientPatch{ 366 Retry: 2, 367 Into: ¤t.IBPCA{}, 368 Strategy: client.MergeFrom, 369 }, 370 }) 371 if err != nil { 372 return err 373 } 374 375 return nil 376 } 377 378 func (r *ReconcileIBPCA) SaveSpecState(instance *current.IBPCA) error { 379 data, err := yaml.Marshal(instance.Spec) 380 if err != nil { 381 return err 382 } 383 384 cm := &corev1.ConfigMap{ 385 ObjectMeta: v1.ObjectMeta{ 386 Name: fmt.Sprintf("%s-spec", instance.GetName()), 387 Namespace: instance.GetNamespace(), 388 Labels: instance.GetLabels(), 389 }, 390 BinaryData: map[string][]byte{ 391 "spec": data, 392 }, 393 } 394 395 err = r.client.CreateOrUpdate(context.TODO(), cm, k8sclient.CreateOrUpdateOption{ 396 Owner: instance, 397 Scheme: r.scheme, 398 }) 399 if err != nil { 400 return err 401 } 402 403 return nil 404 } 405 406 func (r *ReconcileIBPCA) GetSpecState(instance *current.IBPCA) (*corev1.ConfigMap, error) { 407 cm := &corev1.ConfigMap{} 408 nn := types.NamespacedName{ 409 Name: fmt.Sprintf("%s-spec", instance.GetName()), 410 Namespace: instance.GetNamespace(), 411 } 412 413 err := r.client.Get(context.TODO(), nn, cm) 414 if err != nil { 415 return nil, err 416 } 417 418 return cm, nil 419 } 420 421 func (r *ReconcileIBPCA) PodsRunning(instance *current.IBPCA) (bool, error) { 422 labelSelector, err := labels.Parse(fmt.Sprintf("app=%s", instance.GetName())) 423 if err != nil { 424 return false, errors.Wrap(err, "failed to parse label selector for app name") 425 } 426 427 listOptions := &client.ListOptions{ 428 LabelSelector: labelSelector, 429 Namespace: instance.GetNamespace(), 430 } 431 432 podList := &corev1.PodList{} 433 err = r.client.List(context.TODO(), podList, listOptions) 434 if err != nil { 435 return false, err 436 } 437 438 if len(podList.Items) == 0 { 439 return false, nil 440 } 441 442 for _, pod := range podList.Items { 443 if pod.Status.Phase != corev1.PodRunning { 444 return false, nil 445 } 446 } 447 448 return true, nil 449 } 450 451 func (r *ReconcileIBPCA) getIgnoreDiffs() []string { 452 return []string{ 453 `Template\.Spec\.Containers\.slice\[\d\]\.Resources\.Requests\.map\[memory\].s`, 454 `Template\.Spec\.InitContainers\.slice\[\d\]\.Resources`, 455 `Ports\.slice\[\d\]\.Protocol`, 456 } 457 } 458 459 func (r *ReconcileIBPCA) getLabels(instance v1.Object) map[string]string { 460 label := os.Getenv("OPERATOR_LABEL_PREFIX") 461 if label == "" { 462 label = "fabric" 463 } 464 465 return map[string]string{ 466 "app": instance.GetName(), 467 "creator": label, 468 "release": "operator", 469 "helm.sh/chart": "ibm-" + label, 470 "app.kubernetes.io/name": label, 471 "app.kubernetes.io/instance": label + "ca", 472 "app.kubernetes.io/managed-by": label + "-operator", 473 } 474 } 475 476 func (r *ReconcileIBPCA) getSelectorLabels(instance v1.Object) map[string]string { 477 return map[string]string{ 478 "app": instance.GetName(), 479 } 480 } 481 482 // TODO: Move to predicate.go 483 func (r *ReconcileIBPCA) CreateFunc(e event.CreateEvent) bool { 484 update := Update{} 485 486 switch e.Object.(type) { 487 case *current.IBPCA: 488 ca := e.Object.(*current.IBPCA) 489 log.Info(fmt.Sprintf("Create event detected for ca '%s'", ca.GetName())) 490 491 // Operator restart detected, want to trigger update logic for CA resource if changes detected 492 if ca.Status.HasType() { 493 log.Info(fmt.Sprintf("Operator restart detected, running update flow on existing ca '%s'", ca.GetName())) 494 495 // Get the spec state of the resource before the operator went down, this 496 // will be used to compare to see if the spec of resources has changed 497 cm, err := r.GetSpecState(ca) 498 if err != nil { 499 log.Info(fmt.Sprintf("Failed getting saved ca spec '%s', triggering create: %s", ca.GetName(), err.Error())) 500 return true 501 } 502 503 specBytes := cm.BinaryData["spec"] 504 existingCA := ¤t.IBPCA{} 505 err = yaml.Unmarshal(specBytes, &existingCA.Spec) 506 if err != nil { 507 log.Info(fmt.Sprintf("Unmarshal failed for saved ca spec '%s', triggering create: %s", ca.GetName(), err.Error())) 508 return true 509 } 510 511 diff := deep.Equal(ca.Spec, existingCA.Spec) 512 if diff != nil { 513 log.Info(fmt.Sprintf("IBPCA '%s' spec was updated while operator was down", ca.GetName())) 514 log.Info(fmt.Sprintf("Difference detected: %s", diff)) 515 update.specUpdated = true 516 } 517 518 // If existing CA spec did not have config overrides defined but new spec does, 519 // trigger update logic for both CA and TLSCA overrides 520 if ca.Spec.ConfigOverride == nil && existingCA.Spec.ConfigOverride != nil { 521 log.Info(fmt.Sprintf("IBPCA '%s' CA and TLSCA overrides were updated while operator was down", ca.GetName())) 522 update.caOverridesUpdated = true 523 update.tlscaOverridesUpdated = true 524 } 525 526 // If existing CA spec had config overrides defined, need to further check to see if CA or 527 // TLSCA specs have been updated and trigger update for the one on which updates are detected. 528 if ca.Spec.ConfigOverride != nil && existingCA.Spec.ConfigOverride != nil { 529 if ca.Spec.ConfigOverride.CA != nil && existingCA.Spec.ConfigOverride.CA != nil { 530 if !reflect.DeepEqual(ca.Spec.ConfigOverride.CA, existingCA.Spec.ConfigOverride.CA) { 531 log.Info(fmt.Sprintf("IBPCA '%s' CA overrides were updated while operator was down", ca.GetName())) 532 update.caOverridesUpdated = true 533 } 534 } 535 536 if ca.Spec.ConfigOverride.TLSCA != nil && existingCA.Spec.ConfigOverride.TLSCA != nil { 537 if !reflect.DeepEqual(ca.Spec.ConfigOverride.TLSCA, existingCA.Spec.ConfigOverride.TLSCA) { 538 log.Info(fmt.Sprintf("IBPCA '%s' TLSCA overrides were updated while operator was down", ca.GetName())) 539 update.tlscaOverridesUpdated = true 540 } 541 } 542 } 543 544 update.imagesUpdated = imagesUpdated(existingCA, ca) 545 update.fabricVersionUpdated = fabricVersionUpdated(existingCA, ca) 546 547 log.Info(fmt.Sprintf("Create event triggering reconcile for updating ca '%s'", ca.GetName())) 548 r.PushUpdate(ca.GetName(), update) 549 return true 550 } 551 552 // TODO: This seems more appropriate for the PreReconcileCheck method rather than the predicate function. Not 553 // sure if there was reason for putting it here, but if not we should consider moving it 554 // 555 // If creating resource for the first time, check that a unique name is provided 556 err := commoncontroller.ValidateCRName(r.client, ca.Name, ca.Namespace, commoncontroller.IBPCA) 557 if err != nil { 558 log.Error(err, "failed to validate ca name") 559 operror := operatorerrors.Wrap(err, operatorerrors.InvalidCustomResourceCreateRequest, "failed to validate custom resource name") 560 561 err = r.SetStatus(ca, nil, operror) 562 if err != nil { 563 log.Error(err, "failed to set status to error", "ca.name", ca.Name, "error", "InvalidCustomResourceCreateRequest") 564 } 565 return false 566 } 567 568 log.Info(fmt.Sprintf("Create event triggering reconcile for creating ca '%s'", ca.GetName())) 569 570 case *corev1.Secret: 571 secret := e.Object.(*corev1.Secret) 572 573 if secret.OwnerReferences == nil || len(secret.OwnerReferences) == 0 { 574 isCASecret, err := r.AddOwnerReferenceToSecret(secret) 575 if err != nil || !isCASecret { 576 return false 577 } 578 } 579 580 if secret.OwnerReferences[0].Kind == KIND { 581 instanceName := secret.OwnerReferences[0].Name 582 log.Info(fmt.Sprintf("Create event detected for secret '%s'", secret.GetName())) 583 584 if strings.HasSuffix(secret.Name, "-ca-crypto") { 585 update.caCryptoCreated = true 586 log.Info(fmt.Sprintf("CA crypto created, triggering reconcile for IBPCA custom resource %s: update [ %+v ]", instanceName, update.GetUpdateStackWithTrues())) 587 } else { 588 return false 589 } 590 591 r.PushUpdate(instanceName, update) 592 } 593 594 case *appsv1.Deployment: 595 dep := e.Object.(*appsv1.Deployment) 596 log.Info(fmt.Sprintf("Create event detected by IBPCA controller for deployment '%s', triggering reconcile", dep.GetName())) 597 598 case *corev1.ConfigMap: 599 cm := e.Object.(*corev1.ConfigMap) 600 if cm.Name == "ca-restart-config" { 601 log.Info(fmt.Sprintf("Create event detected by IBPCA contoller for config map '%s', triggering restart reconcile", cm.GetName())) 602 } else { 603 return false 604 } 605 } 606 607 return true 608 } 609 610 // TODO: Move to predicate.go 611 func (r *ReconcileIBPCA) UpdateFunc(e event.UpdateEvent) bool { 612 update := Update{} 613 614 switch e.ObjectOld.(type) { 615 case *current.IBPCA: 616 oldCA := e.ObjectOld.(*current.IBPCA) 617 newCA := e.ObjectNew.(*current.IBPCA) 618 log.Info(fmt.Sprintf("Update event detected for ca '%s'", oldCA.GetName())) 619 620 if util.CheckIfZoneOrRegionUpdated(oldCA.Spec.Zone, newCA.Spec.Zone) { 621 log.Error(errors.New("Zone update is not allowed"), "invalid spec update") 622 return false 623 } 624 625 if util.CheckIfZoneOrRegionUpdated(oldCA.Spec.Region, newCA.Spec.Region) { 626 log.Error(errors.New("Region update is not allowed"), "invalid spec update") 627 return false 628 } 629 630 if reflect.DeepEqual(oldCA.Spec, newCA.Spec) { 631 return false 632 } 633 634 update.specUpdated = true 635 636 // Check for changes to ca tag to determine if any migration logic needs to be executed 637 if oldCA.Spec.Images != nil && newCA.Spec.Images != nil { 638 if oldCA.Spec.Images.CATag != newCA.Spec.Images.CATag { 639 log.Info(fmt.Sprintf("CA tag update from %s to %s", oldCA.Spec.Images.CATag, newCA.Spec.Images.CATag)) 640 update.caTagUpdated = true 641 } 642 } 643 644 if oldCA.Spec.ConfigOverride == nil { 645 if newCA.Spec.ConfigOverride != nil { 646 update.caOverridesUpdated = true 647 update.tlscaOverridesUpdated = true 648 } 649 } else { 650 if !reflect.DeepEqual(oldCA.Spec.ConfigOverride.CA, newCA.Spec.ConfigOverride.CA) { 651 update.caOverridesUpdated = true 652 } 653 654 if !reflect.DeepEqual(oldCA.Spec.ConfigOverride.TLSCA, newCA.Spec.ConfigOverride.TLSCA) { 655 update.tlscaOverridesUpdated = true 656 } 657 } 658 659 if newCA.Spec.Action.Restart == true { 660 update.restartNeeded = true 661 } 662 663 if newCA.Spec.Action.Renew.TLSCert == true { 664 update.renewTLSCert = true 665 } 666 667 update.imagesUpdated = imagesUpdated(oldCA, newCA) 668 update.fabricVersionUpdated = fabricVersionUpdated(oldCA, newCA) 669 670 log.Info(fmt.Sprintf("Spec update triggering reconcile on IBPCA custom resource %s: update [ %+v ]", oldCA.Name, update.GetUpdateStackWithTrues())) 671 r.PushUpdate(oldCA.GetName(), update) 672 return true 673 674 case *corev1.Secret: 675 oldSecret := e.ObjectOld.(*corev1.Secret) 676 newSecret := e.ObjectNew.(*corev1.Secret) 677 678 if oldSecret.OwnerReferences == nil || len(oldSecret.OwnerReferences) == 0 { 679 isCASecret, err := r.AddOwnerReferenceToSecret(oldSecret) 680 if err != nil || !isCASecret { 681 return false 682 } 683 } 684 685 if oldSecret.OwnerReferences[0].Kind == KIND { 686 if reflect.DeepEqual(oldSecret.Data, newSecret.Data) { 687 return false 688 } 689 690 instanceName := oldSecret.OwnerReferences[0].Name 691 log.Info(fmt.Sprintf("Update event detected for secret '%s'", oldSecret.GetName())) 692 693 if util.IsSecretTLSCert(oldSecret.Name) { 694 update.caCryptoUpdated = true 695 } else { 696 return false 697 } 698 699 log.Info(fmt.Sprintf("CA crypto update triggering reconcile on IBPCA custom resource %s: update [ %+v ]", instanceName, update.GetUpdateStackWithTrues())) 700 r.PushUpdate(instanceName, update) 701 return true 702 } 703 704 case *appsv1.Deployment: 705 dep := e.ObjectOld.(*appsv1.Deployment) 706 log.Info(fmt.Sprintf("Spec update detected by IBPCA controller for deployment '%s'", dep.GetName())) 707 708 case *corev1.ConfigMap: 709 cm := e.ObjectOld.(*corev1.ConfigMap) 710 if cm.Name == "ca-restart-config" { 711 log.Info("Update event detected for ca-restart-config, triggering restart reconcile") 712 return true 713 } 714 715 } 716 717 return false 718 } 719 720 func (r *ReconcileIBPCA) GetUpdateStatusAtElement(instance *current.IBPCA, index int) *Update { 721 r.mutex.Lock() 722 defer r.mutex.Unlock() 723 724 update := Update{} 725 _, ok := r.update[instance.GetName()] 726 if !ok { 727 return &update 728 } 729 730 if len(r.update[instance.GetName()]) >= 1 { 731 update = r.update[instance.GetName()][index] 732 } 733 734 return &update 735 } 736 737 func (r *ReconcileIBPCA) GetUpdateStatus(instance *current.IBPCA) *Update { 738 return r.GetUpdateStatusAtElement(instance, 0) 739 } 740 741 func (r *ReconcileIBPCA) PushUpdate(instance string, update Update) { 742 r.mutex.Lock() 743 defer r.mutex.Unlock() 744 745 r.update[instance] = r.AppendUpdateIfMissing(r.update[instance], update) 746 } 747 748 func (r *ReconcileIBPCA) PopUpdate(instance string) *Update { 749 r.mutex.Lock() 750 defer r.mutex.Unlock() 751 752 update := Update{} 753 if len(r.update[instance]) >= 1 { 754 update = r.update[instance][0] 755 if len(r.update[instance]) == 1 { 756 r.update[instance] = []Update{} 757 } else { 758 r.update[instance] = r.update[instance][1:] 759 } 760 } 761 762 return &update 763 } 764 765 func (r *ReconcileIBPCA) AppendUpdateIfMissing(updates []Update, update Update) []Update { 766 for _, u := range updates { 767 if u == update { 768 return updates 769 } 770 } 771 return append(updates, update) 772 } 773 774 func (r *ReconcileIBPCA) AddOwnerReferenceToSecret(secret *corev1.Secret) (bool, error) { 775 // CA secrets we are looking to add owner references to are named: 776 // <instance name>-ca 777 // <instance name>-ca-crypto 778 // <instance name>-tlsca 779 // <instance name>-tlsca-crypto 780 781 items := strings.Split(secret.Name, "-") 782 var instanceName string 783 784 if strings.Contains(secret.Name, "-ca-crypto") || strings.Contains(secret.Name, "-tlsca-crypto") { 785 // If -ca-crypto or -tlsca-crypto, construct instance name from all but last 2 items 786 instanceName = strings.Join(items[:len(items)-2], "-") 787 } else if strings.Contains(secret.Name, "-ca") || strings.Contains(secret.Name, "-tlsca") { 788 // If -ca-crypto or -tlsca-crypto, construct instance name from all but last item 789 instanceName = strings.Join(items[:len(items)-1], "-") 790 } else { 791 return false, nil 792 } 793 794 listOptions := &client.ListOptions{ 795 Namespace: secret.Namespace, 796 } 797 798 caList := ¤t.IBPCAList{} 799 err := r.client.List(context.TODO(), caList, listOptions) 800 if err != nil { 801 return false, errors.Wrap(err, "failed to get list of CAs") 802 } 803 804 for _, o := range caList.Items { 805 ca := o 806 if ca.Name == instanceName { 807 // Instance 'i' found in list of orderers 808 err = r.client.Update(context.TODO(), secret, k8sclient.UpdateOption{ 809 Owner: &ca, 810 Scheme: r.scheme, 811 }) 812 if err != nil { 813 return false, err 814 } 815 return true, nil 816 } 817 } 818 819 return false, nil 820 } 821 822 func (r *ReconcileIBPCA) SetupWithManager(mgr ctrl.Manager) error { 823 return ctrl.NewControllerManagedBy(mgr). 824 For(¤t.IBPCA{}). 825 Complete(r) 826 } 827 828 func GetUpdateStack(allUpdates map[string][]Update) string { 829 stack := "" 830 831 for orderer, updates := range allUpdates { 832 currentStack := "" 833 for index, update := range updates { 834 currentStack += fmt.Sprintf("{ %s}", update.GetUpdateStackWithTrues()) 835 if index != len(updates)-1 { 836 currentStack += " , " 837 } 838 } 839 stack += fmt.Sprintf("%s: [ %s ] ", orderer, currentStack) 840 } 841 842 return stack 843 } 844 845 func (r *ReconcileIBPCA) ReconcileRestart(namespace string) (bool, error) { 846 requeue, err := r.RestartService.Reconcile("ca", namespace) 847 if err != nil { 848 log.Error(err, "failed to reconcile restart queues in ca-restart-config") 849 return false, err 850 } 851 852 return requeue, nil 853 }