github.com/kiali/kiali@v1.84.0/kiali.go (about)

     1  // Kiali
     2  //
     3  // # Kiali Project, The Console for Istio Service Mesh
     4  //
     5  // NOTE! The Kiali API is not for public use and is not supported for any use outside of the Kiali UI itself.
     6  // The API can and will change from version to version with no guarantee of backwards compatibility.
     7  //
     8  // To generate this API document:
     9  // ```
    10  //
    11  //	> alias swagger='docker run --rm -it  --user $(id -u):$(id -g) -e GOCACHE=/tmp -e GOPATH=$(go env GOPATH):/go -v $HOME:$HOME -w $(pwd) quay.io/goswagger/swagger'
    12  //	> swagger generate spec -o ./swagger.json
    13  //	> swagger generate markdown --quiet --spec ./swagger.json --output ./kiali_internal_api.md
    14  //
    15  // ```
    16  //
    17  //	Schemes: http, https
    18  //	BasePath: /api
    19  //	Version: _
    20  //
    21  //	Consumes:
    22  //	- application/json
    23  //
    24  //	Produces:
    25  //	- application/json
    26  //
    27  // swagger:meta
    28  package main
    29  
    30  import (
    31  	"context"
    32  	"flag"
    33  	"os"
    34  	"os/signal"
    35  	"strings"
    36  	"syscall"
    37  
    38  	_ "go.uber.org/automaxprocs"
    39  
    40  	"github.com/kiali/kiali/business"
    41  	"github.com/kiali/kiali/config"
    42  	"github.com/kiali/kiali/kubernetes"
    43  	"github.com/kiali/kiali/kubernetes/cache"
    44  	"github.com/kiali/kiali/log"
    45  	"github.com/kiali/kiali/prometheus"
    46  	"github.com/kiali/kiali/prometheus/internalmetrics"
    47  	"github.com/kiali/kiali/server"
    48  	"github.com/kiali/kiali/status"
    49  	"github.com/kiali/kiali/tracing"
    50  	"github.com/kiali/kiali/util"
    51  )
    52  
    53  // Identifies the build. These are set via ldflags during the build (see Makefile).
    54  var (
    55  	version    = "unknown"
    56  	commitHash = "unknown"
    57  	goVersion  = "unknown"
    58  )
    59  
    60  // Command line arguments
    61  var (
    62  	argConfigFile = flag.String("config", "", "Path to the YAML configuration file. If not specified, environment variables will be used for configuration.")
    63  )
    64  
    65  func init() {
    66  	// log everything to stderr so that it can be easily gathered by logs, separate log files are problematic with containers
    67  	_ = flag.Set("logtostderr", "true")
    68  }
    69  
    70  func main() {
    71  	log.InitializeLogger()
    72  	util.Clock = util.RealClock{}
    73  
    74  	// process command line
    75  	flag.Parse()
    76  	validateFlags()
    77  
    78  	// log startup information
    79  	log.Infof("Kiali: Version: %v, Commit: %v, Go: %v", version, commitHash, goVersion)
    80  	log.Debugf("Kiali: Command line: [%v]", strings.Join(os.Args, " "))
    81  
    82  	// load config file if specified, otherwise, rely on environment variables to configure us
    83  	if *argConfigFile != "" {
    84  		c, err := config.LoadFromFile(*argConfigFile)
    85  		if err != nil {
    86  			log.Fatal(err)
    87  		}
    88  		config.Set(c)
    89  	} else {
    90  		log.Infof("No configuration file specified. Will rely on environment for configuration.")
    91  		config.Set(config.NewConfig())
    92  	}
    93  
    94  	updateConfigWithIstioInfo()
    95  
    96  	cfg := config.Get()
    97  	log.Tracef("Kiali Configuration:\n%s", cfg)
    98  
    99  	if err := config.Validate(*cfg); err != nil {
   100  		log.Fatal(err)
   101  	}
   102  
   103  	status.Put(status.CoreVersion, version)
   104  	status.Put(status.CoreCommitHash, commitHash)
   105  	status.Put(status.ContainerVersion, determineContainerVersion(version))
   106  
   107  	// prepare our internal metrics so Prometheus can scrape them
   108  	internalmetrics.RegisterInternalMetrics()
   109  
   110  	// Create the business package dependencies.
   111  	clientFactory, err := kubernetes.GetClientFactory()
   112  	if err != nil {
   113  		log.Fatalf("Failed to create client factory. Err: %s", err)
   114  	}
   115  
   116  	log.Info("Initializing Kiali Cache")
   117  	cache, err := cache.NewKialiCache(clientFactory, *cfg)
   118  	if err != nil {
   119  		log.Fatalf("Error initializing Kiali Cache. Details: %s", err)
   120  	}
   121  	defer cache.Stop()
   122  
   123  	namespaceService := business.NewNamespaceService(clientFactory.GetSAClients(), clientFactory.GetSAClients(), cache, cfg)
   124  	meshService := business.NewMeshService(clientFactory.GetSAClients(), cache, namespaceService, *cfg)
   125  	cpm := business.NewControlPlaneMonitor(cache, clientFactory, *cfg, &meshService)
   126  
   127  	// This context is used for polling and for creating some high level clients like tracing.
   128  	ctx, cancel := context.WithCancel(context.Background())
   129  	defer cancel()
   130  
   131  	if cfg.ExternalServices.Istio.IstioAPIEnabled {
   132  		cpm.PollIstiodForProxyStatus(ctx)
   133  	}
   134  
   135  	// Create shared prometheus client shared by all prometheus requests in the business layer.
   136  	prom, err := prometheus.NewClient()
   137  	if err != nil {
   138  		log.Fatalf("Error creating Prometheus client: %s", err)
   139  	}
   140  
   141  	// Create shared tracing client shared by all tracing requests in the business layer.
   142  	// Because tracing is not an essential component, we don't want to block startup
   143  	// of the server if the tracing client fails to initialize. tracing.NewClient will
   144  	// continue to retry until the client is initialized or the context is cancelled.
   145  	// Passing in a loader function allows the tracing client to be used once it is
   146  	// finally initialized.
   147  	var tracingClient tracing.ClientInterface
   148  	tracingLoader := func() tracing.ClientInterface {
   149  		return tracingClient
   150  	}
   151  	if cfg.ExternalServices.Tracing.Enabled {
   152  		go func() {
   153  			client, err := tracing.NewClient(ctx, cfg, clientFactory.GetSAHomeClusterClient().GetToken())
   154  			if err != nil {
   155  				log.Fatalf("Error creating tracing client: %s", err)
   156  				return
   157  			}
   158  			tracingClient = client
   159  		}()
   160  	} else {
   161  		log.Debug("Tracing is disabled")
   162  	}
   163  
   164  	// Start listening to requests
   165  	server, err := server.NewServer(cpm, clientFactory, cache, cfg, prom, tracingLoader)
   166  	if err != nil {
   167  		log.Fatal(err)
   168  	}
   169  	server.Start()
   170  
   171  	// wait forever, or at least until we are told to exit
   172  	waitForTermination()
   173  
   174  	// Shutdown internal components
   175  	log.Info("Shutting down internal components")
   176  	server.Stop()
   177  }
   178  
   179  func waitForTermination() {
   180  	// Channel that is notified when we are done and should exit
   181  	// TODO: may want to make this a package variable - other things might want to tell us to exit
   182  	doneChan := make(chan bool)
   183  
   184  	signalChan := make(chan os.Signal, 1)
   185  	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
   186  	go func() {
   187  		for range signalChan {
   188  			log.Info("Termination Signal Received")
   189  			doneChan <- true
   190  		}
   191  	}()
   192  
   193  	<-doneChan
   194  }
   195  
   196  func validateFlags() {
   197  	if *argConfigFile != "" {
   198  		if _, err := os.Stat(*argConfigFile); err != nil {
   199  			if os.IsNotExist(err) {
   200  				log.Debugf("Configuration file [%v] does not exist.", *argConfigFile)
   201  			}
   202  		}
   203  	}
   204  }
   205  
   206  // determineContainerVersion will return the version of the image container.
   207  // It does this by looking at an ENV defined in the Dockerfile when the image is built.
   208  // If the ENV is not defined, the version is assumed the same as the given default value.
   209  func determineContainerVersion(defaultVersion string) string {
   210  	v := os.Getenv("KIALI_CONTAINER_VERSION")
   211  	if v == "" {
   212  		return defaultVersion
   213  	}
   214  	return v
   215  }
   216  
   217  // This is used to update the config with information about istio that
   218  // comes from the environment such as the cluster name.
   219  func updateConfigWithIstioInfo() {
   220  	conf := *config.Get()
   221  
   222  	homeCluster := conf.KubernetesConfig.ClusterName
   223  	if homeCluster != "" {
   224  		// If the cluster name is already set, we don't need to do anything
   225  		return
   226  	}
   227  
   228  	err := func() error {
   229  		log.Debug("Cluster name is not set. Attempting to auto-detect the cluster name from the home cluster environment.")
   230  		ctx, cancel := context.WithCancel(context.Background())
   231  		defer cancel()
   232  
   233  		// Need to create a temporary client factory here so that we can create a client
   234  		// to auto-detect the istio cluster name from the environment. There's a bit of a
   235  		// chicken and egg problem with the client factory because the client factory
   236  		// uses the cluster id to keep track of all the clients. But in order to create
   237  		// a client to get the cluster id from the environment, you need to create a client factory.
   238  		// To get around that we create a temporary client factory here and then set the kiali
   239  		// config cluster name. We then create the global client factory later in the business
   240  		// package and that global client factory has the cluster id set properly.
   241  		cf, err := kubernetes.NewClientFactory(ctx, conf)
   242  		if err != nil {
   243  			return err
   244  		}
   245  
   246  		// Try to auto-detect the cluster name
   247  		homeCluster, _, err = kubernetes.ClusterInfoFromIstiod(conf, cf.GetSAHomeClusterClient())
   248  		if err != nil {
   249  			return err
   250  		}
   251  
   252  		return nil
   253  	}()
   254  	if err != nil {
   255  		log.Warningf("Cannot resolve local cluster name. Err: %s. Falling back to [%s]", err, config.DefaultClusterID)
   256  		homeCluster = config.DefaultClusterID
   257  	}
   258  
   259  	log.Debugf("Auto-detected the istio cluster name to be [%s]. Updating the kiali config", homeCluster)
   260  	conf.KubernetesConfig.ClusterName = homeCluster
   261  	config.Set(&conf)
   262  }