github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/command/operator.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 command
    20  
    21  import (
    22  	"context"
    23  	"flag"
    24  	"fmt"
    25  	"os"
    26  	"runtime"
    27  	"time"
    28  
    29  	k8sruntime "k8s.io/apimachinery/pkg/runtime"
    30  
    31  	routev1 "github.com/openshift/api/route/v1"
    32  	"github.com/operator-framework/operator-lib/leader"
    33  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    34  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    35  	ctrl "sigs.k8s.io/controller-runtime"
    36  
    37  	"github.com/pkg/errors"
    38  	"sigs.k8s.io/controller-runtime/pkg/log/zap"
    39  
    40  	apis "github.com/IBM-Blockchain/fabric-operator/api"
    41  	ibpv1beta1 "github.com/IBM-Blockchain/fabric-operator/api/v1beta1"
    42  	controller "github.com/IBM-Blockchain/fabric-operator/controllers"
    43  	oconfig "github.com/IBM-Blockchain/fabric-operator/operatorconfig"
    44  	"github.com/IBM-Blockchain/fabric-operator/pkg/migrator"
    45  	"github.com/IBM-Blockchain/fabric-operator/pkg/offering"
    46  	openshiftv1 "github.com/openshift/api/config/v1"
    47  
    48  	"k8s.io/apimachinery/pkg/types"
    49  	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    50  	"sigs.k8s.io/controller-runtime/pkg/client"
    51  
    52  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    53  	"sigs.k8s.io/controller-runtime/pkg/manager/signals"
    54  )
    55  
    56  var log = logf.Log.WithName("cmd_operator")
    57  
    58  var (
    59  	scheme   = k8sruntime.NewScheme()
    60  	setupLog = ctrl.Log.WithName("setup")
    61  )
    62  
    63  func init() {
    64  	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
    65  	utilruntime.Must(ibpv1beta1.AddToScheme(scheme))
    66  	// +kubebuilder:scaffold:scheme
    67  }
    68  
    69  func printVersion() {
    70  	log.Info(fmt.Sprintf("Go Version: %s", runtime.Version()))
    71  	log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH))
    72  }
    73  
    74  func Operator(operatorCfg *oconfig.Config) error {
    75  	signalHandler := signals.SetupSignalHandler()
    76  
    77  	// In local mode, the operator may be launched and debugged directly as a native process without
    78  	// being deployed to a Kubernetes cluster.
    79  	local := os.Getenv("OPERATOR_LOCAL_MODE") == "true"
    80  
    81  	return OperatorWithSignal(operatorCfg, signalHandler, true, local)
    82  }
    83  
    84  func OperatorWithSignal(operatorCfg *oconfig.Config, signalHandler context.Context, blocking, local bool) error {
    85  	var err error
    86  
    87  	// Add the zap logger flag set to the CLI. The flag set must
    88  	// be added before calling pflag.Parse().
    89  	// pflag.CommandLine.AddFlagSet(flagset)
    90  
    91  	// Add flags registered by imported packages (e.g. glog and
    92  	// controller-runtime)
    93  	// pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
    94  	// pflag.Parse()
    95  
    96  	// Use a zap logr.Logger implementation. If none of the zap
    97  	// flags are configured (or if the zap flag set is not being
    98  	// used), this defaults to a production zap logger.
    99  	//
   100  	// The logger instantiated here can be changed to any logger
   101  	// implementing the logr.Logger interface. This logger will
   102  	// be propagated through the whole operator, generating
   103  	// uniform and structured logs.
   104  	if operatorCfg.Logger != nil {
   105  		logf.SetLogger(*operatorCfg.Logger)
   106  		ctrl.SetLogger(*operatorCfg.Logger)
   107  	} else {
   108  		// Use the unstructured log formatter when running locally.
   109  		logf.SetLogger(zap.New(zap.UseDevMode(local)))
   110  		ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
   111  	}
   112  
   113  	printVersion()
   114  
   115  	watchNamespace := os.Getenv("WATCH_NAMESPACE")
   116  	var operatorNamespace string
   117  	if watchNamespace == "" {
   118  		// Operator is running in all namespace mode
   119  		log.Info("Installing operator in all namespace mode")
   120  		operatorNamespace, err = GetOperatorNamespace()
   121  		if err != nil {
   122  			log.Error(err, "Failed to get operator namespace")
   123  			time.Sleep(15 * time.Second)
   124  			return err
   125  		}
   126  	} else {
   127  		log.Info("Installing operator in own namespace mode", "WATCH_NAMESPACE", watchNamespace)
   128  		operatorNamespace = watchNamespace
   129  	}
   130  
   131  	if !local {
   132  		label := os.Getenv("OPERATOR_LABEL_PREFIX")
   133  		if label == "" {
   134  			label = "fabric"
   135  		}
   136  		err = leader.Become(context.TODO(), label+"-operator-lock")
   137  		if err != nil {
   138  			log.Error(err, "Failed to retry for leader lock")
   139  			os.Exit(1)
   140  		}
   141  	} else {
   142  		log.Info("local run detected, skipping leader election")
   143  	}
   144  
   145  	var metricsAddr string
   146  	var enableLeaderElection bool
   147  
   148  	if flag.Lookup("metrics-addr") == nil {
   149  		flag.StringVar(&metricsAddr, "metrics-addr", ":8383", "The address the metric endpoint binds to.")
   150  	}
   151  	if flag.Lookup("enable-leader-election") == nil {
   152  		flag.BoolVar(&enableLeaderElection, "enable-leader-election", true,
   153  			"Enable leader election for controller manager. "+
   154  				"Enabling this will ensure there is only one active controller manager.")
   155  	}
   156  	flag.Parse()
   157  
   158  	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
   159  		Scheme:             scheme,
   160  		MetricsBindAddress: metricsAddr,
   161  		Port:               9443,
   162  		// LeaderElection:          enableLeaderElection,
   163  		LeaderElectionID:        "c30dd930.ibp.com",
   164  		LeaderElectionNamespace: operatorNamespace,
   165  		Namespace:               watchNamespace,
   166  	})
   167  	if err != nil {
   168  		setupLog.Error(err, "unable to start manager")
   169  		return err
   170  	}
   171  
   172  	log.Info("Registering Components.")
   173  
   174  	// Setup Scheme for all resources
   175  	if err := apis.AddToScheme(mgr.GetScheme()); err != nil {
   176  		log.Error(err, "")
   177  		return err
   178  	}
   179  
   180  	//Add route scheme
   181  	if err := routev1.AddToScheme(mgr.GetScheme()); err != nil {
   182  		log.Error(err, "")
   183  		return err
   184  	}
   185  
   186  	//Add clusterversion scheme
   187  	if err := openshiftv1.AddToScheme(mgr.GetScheme()); err != nil {
   188  		log.Error(err, "")
   189  		return err
   190  	}
   191  
   192  	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
   193  	utilruntime.Must(ibpv1beta1.AddToScheme(scheme))
   194  
   195  	go func() {
   196  		runtime.Gosched()
   197  		mgrSyncContext, mgrSyncContextCancel := context.WithTimeout(context.Background(), 30*time.Second)
   198  		defer mgrSyncContextCancel()
   199  
   200  		log.Info("Waiting for cache sync")
   201  		if synced := mgr.GetCache().WaitForCacheSync(mgrSyncContext); !synced {
   202  			log.Error(nil, "Timed out waiting for cache sync")
   203  			os.Exit(1)
   204  		}
   205  
   206  		log.Info("Cache sync done")
   207  
   208  		// Migrate first
   209  		m := migrator.New(mgr, operatorCfg, operatorNamespace)
   210  		err = m.Migrate()
   211  		if err != nil {
   212  			log.Error(err, "Unable to complete migration")
   213  			os.Exit(1)
   214  		}
   215  
   216  		// Setup all Controllers
   217  		if err := controller.AddToManager(mgr, operatorCfg); err != nil {
   218  			log.Error(err, "")
   219  			os.Exit(1)
   220  		}
   221  	}()
   222  
   223  	if err := InitConfig(operatorNamespace, operatorCfg, mgr.GetAPIReader()); err != nil {
   224  		log.Error(err, "Invalid configuration")
   225  		time.Sleep(15 * time.Second)
   226  		return err
   227  	}
   228  
   229  	log.Info("Starting the Cmd.")
   230  
   231  	// Start the Cmd
   232  	if blocking {
   233  		if err := mgr.Start(signalHandler); err != nil {
   234  			log.Error(err, "Manager exited non-zero")
   235  			return err
   236  		}
   237  	} else {
   238  		go mgr.Start(signalHandler)
   239  	}
   240  
   241  	return nil
   242  }
   243  
   244  //go:generate counterfeiter -o mocks/reader.go -fake-name Reader . Reader
   245  
   246  type Reader interface {
   247  	client.Reader
   248  }
   249  
   250  // InitConfig initializes the passed in config by overriding values from environment variable
   251  // or config map if set
   252  func InitConfig(namespace string, cfg *oconfig.Config, client client.Reader) error {
   253  	// Read from config map if it exists otherwise return default values
   254  	err := oconfig.LoadFromConfigMap(
   255  		types.NamespacedName{Name: "operator-config", Namespace: namespace},
   256  		"config.yaml",
   257  		client,
   258  		&cfg.Operator,
   259  	)
   260  	if err != nil {
   261  		return errors.Wrap(err, "failed to get 'config.yaml' from 'ibp-operator' config map")
   262  	}
   263  
   264  	clusterType := os.Getenv("CLUSTERTYPE")
   265  	offeringType, err := offering.GetType(clusterType)
   266  	if err != nil {
   267  		return err
   268  	}
   269  	cfg.Offering = offeringType
   270  
   271  	log.Info(fmt.Sprintf("Operator configured for cluster type '%s'", cfg.Offering))
   272  
   273  	if cfg.Operator.Versions == nil {
   274  		return errors.New("no default images defined")
   275  	}
   276  
   277  	if cfg.Operator.Versions.CA == nil {
   278  		return errors.New("no default CA images defined")
   279  	}
   280  
   281  	if cfg.Operator.Versions.Peer == nil {
   282  		return errors.New("no default Peer images defined")
   283  	}
   284  
   285  	if cfg.Operator.Versions.Orderer == nil {
   286  		return errors.New("no default Orderer images defined")
   287  	}
   288  
   289  	return nil
   290  }
   291  
   292  func GetOperatorNamespace() (string, error) {
   293  	operatorNamespace := os.Getenv("OPERATOR_NAMESPACE")
   294  	if operatorNamespace == "" {
   295  		return "", fmt.Errorf("OPERATOR_NAMESPACE not found")
   296  	}
   297  
   298  	return operatorNamespace, nil
   299  }