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: &current.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:    &current.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:    &current.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 := &current.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:     &current.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:     &current.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:     &current.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 := &current.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 := &current.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(&current.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  }