github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd-notification/commands/controller.go (about)

     1  package commands
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"os/signal"
     9  	"runtime/debug"
    10  	"strings"
    11  	"sync"
    12  	"syscall"
    13  
    14  	"github.com/argoproj/notifications-engine/pkg/controller"
    15  	"github.com/prometheus/client_golang/prometheus"
    16  	"github.com/prometheus/client_golang/prometheus/promhttp"
    17  	log "github.com/sirupsen/logrus"
    18  	"github.com/spf13/cobra"
    19  	"k8s.io/client-go/dynamic"
    20  	"k8s.io/client-go/kubernetes"
    21  	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    22  	"k8s.io/client-go/tools/clientcmd"
    23  
    24  	"github.com/argoproj/argo-cd/v3/common"
    25  	notificationscontroller "github.com/argoproj/argo-cd/v3/notification_controller/controller"
    26  	"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
    27  	"github.com/argoproj/argo-cd/v3/util/cli"
    28  	"github.com/argoproj/argo-cd/v3/util/env"
    29  	"github.com/argoproj/argo-cd/v3/util/errors"
    30  	service "github.com/argoproj/argo-cd/v3/util/notification/argocd"
    31  	"github.com/argoproj/argo-cd/v3/util/tls"
    32  )
    33  
    34  const (
    35  	defaultMetricsPort = 9001
    36  )
    37  
    38  func NewCommand() *cobra.Command {
    39  	var (
    40  		clientConfig                   clientcmd.ClientConfig
    41  		processorsCount                int
    42  		appLabelSelector               string
    43  		logLevel                       string
    44  		logFormat                      string
    45  		metricsPort                    int
    46  		argocdRepoServer               string
    47  		argocdRepoServerPlaintext      bool
    48  		argocdRepoServerStrictTLS      bool
    49  		configMapName                  string
    50  		secretName                     string
    51  		applicationNamespaces          []string
    52  		selfServiceNotificationEnabled bool
    53  	)
    54  	command := cobra.Command{
    55  		Use:   "controller",
    56  		Short: "Starts Argo CD Notifications controller",
    57  		RunE: func(_ *cobra.Command, _ []string) error {
    58  			ctx, cancel := context.WithCancel(context.Background())
    59  			defer cancel()
    60  
    61  			vers := common.GetVersion()
    62  			namespace, _, err := clientConfig.Namespace()
    63  			errors.CheckError(err)
    64  			vers.LogStartupInfo(
    65  				"ArgoCD Notifications Controller",
    66  				map[string]any{
    67  					"namespace": namespace,
    68  				},
    69  			)
    70  
    71  			restConfig, err := clientConfig.ClientConfig()
    72  			if err != nil {
    73  				return fmt.Errorf("failed to create REST client config: %w", err)
    74  			}
    75  			restConfig.UserAgent = fmt.Sprintf("argocd-notifications-controller/%s (%s)", vers.Version, vers.Platform)
    76  			dynamicClient, err := dynamic.NewForConfig(restConfig)
    77  			if err != nil {
    78  				return fmt.Errorf("failed to create dynamic client: %w", err)
    79  			}
    80  			k8sClient, err := kubernetes.NewForConfig(restConfig)
    81  			if err != nil {
    82  				return fmt.Errorf("failed to create Kubernetes client: %w", err)
    83  			}
    84  			if namespace == "" {
    85  				namespace, _, err = clientConfig.Namespace()
    86  				if err != nil {
    87  					return fmt.Errorf("failed to determine controller's host namespace: %w", err)
    88  				}
    89  			}
    90  			level, err := log.ParseLevel(logLevel)
    91  			if err != nil {
    92  				return fmt.Errorf("failed to parse log level: %w", err)
    93  			}
    94  			log.SetLevel(level)
    95  
    96  			switch strings.ToLower(logFormat) {
    97  			case "json":
    98  				log.SetFormatter(&log.JSONFormatter{})
    99  			case "text":
   100  				if os.Getenv("FORCE_LOG_COLORS") == "1" {
   101  					log.SetFormatter(&log.TextFormatter{ForceColors: true})
   102  				}
   103  			default:
   104  				return fmt.Errorf("unknown log format '%s'", logFormat)
   105  			}
   106  
   107  			// Recover from panic and log the error using the configured logger instead of the default.
   108  			defer func() {
   109  				if r := recover(); r != nil {
   110  					log.WithField("trace", string(debug.Stack())).Fatal("Recovered from panic: ", r)
   111  				}
   112  			}()
   113  
   114  			tlsConfig := apiclient.TLSConfiguration{
   115  				DisableTLS:       argocdRepoServerPlaintext,
   116  				StrictValidation: argocdRepoServerStrictTLS,
   117  			}
   118  			if !tlsConfig.DisableTLS && tlsConfig.StrictValidation {
   119  				pool, err := tls.LoadX509CertPool(
   120  					env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)+"/reposerver/tls/tls.crt",
   121  					env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)+"/reposerver/tls/ca.crt",
   122  				)
   123  				if err != nil {
   124  					return fmt.Errorf("failed to load repo-server certificate pool: %w", err)
   125  				}
   126  				tlsConfig.Certificates = pool
   127  			}
   128  			repoClientset := apiclient.NewRepoServerClientset(argocdRepoServer, 5, tlsConfig)
   129  			argocdService, err := service.NewArgoCDService(k8sClient, namespace, repoClientset)
   130  			if err != nil {
   131  				return fmt.Errorf("failed to initialize Argo CD service: %w", err)
   132  			}
   133  			defer argocdService.Close()
   134  
   135  			registry := controller.NewMetricsRegistry("argocd")
   136  			http.Handle("/metrics", promhttp.HandlerFor(prometheus.Gatherers{registry, prometheus.DefaultGatherer}, promhttp.HandlerOpts{}))
   137  
   138  			go func() {
   139  				log.Fatal(http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", metricsPort), http.DefaultServeMux))
   140  			}()
   141  			log.Infof("serving metrics on port %d", metricsPort)
   142  			log.Infof("loading configuration %d", metricsPort)
   143  
   144  			ctrl := notificationscontroller.NewController(k8sClient, dynamicClient, argocdService, namespace, applicationNamespaces, appLabelSelector, registry, secretName, configMapName, selfServiceNotificationEnabled)
   145  			err = ctrl.Init(ctx)
   146  			if err != nil {
   147  				return fmt.Errorf("failed to initialize controller: %w", err)
   148  			}
   149  
   150  			sigCh := make(chan os.Signal, 1)
   151  			signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
   152  			wg := sync.WaitGroup{}
   153  			wg.Add(1)
   154  			go func() {
   155  				defer wg.Done()
   156  				s := <-sigCh
   157  				log.Printf("got signal %v, attempting graceful shutdown", s)
   158  				cancel()
   159  			}()
   160  
   161  			go ctrl.Run(ctx, processorsCount)
   162  			<-ctx.Done()
   163  			return nil
   164  		},
   165  	}
   166  	clientConfig = cli.AddKubectlFlagsToCmd(&command)
   167  	command.Flags().IntVar(&processorsCount, "processors-count", 1, "Processors count.")
   168  	command.Flags().StringVar(&appLabelSelector, "app-label-selector", "", "App label selector.")
   169  	command.Flags().StringVar(&logLevel, "loglevel", env.StringFromEnv("ARGOCD_NOTIFICATIONS_CONTROLLER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
   170  	command.Flags().StringVar(&logFormat, "logformat", env.StringFromEnv("ARGOCD_NOTIFICATIONS_CONTROLLER_LOGFORMAT", "json"), "Set the logging format. One of: json|text")
   171  	command.Flags().IntVar(&metricsPort, "metrics-port", defaultMetricsPort, "Metrics port")
   172  	command.Flags().StringVar(&argocdRepoServer, "argocd-repo-server", common.DefaultRepoServerAddr, "Argo CD repo server address")
   173  	command.Flags().BoolVar(&argocdRepoServerPlaintext, "argocd-repo-server-plaintext", env.ParseBoolFromEnv("ARGOCD_NOTIFICATION_CONTROLLER_REPO_SERVER_PLAINTEXT", false), "Use a plaintext client (non-TLS) to connect to repository server")
   174  	command.Flags().BoolVar(&argocdRepoServerStrictTLS, "argocd-repo-server-strict-tls", false, "Perform strict validation of TLS certificates when connecting to repo server")
   175  	command.Flags().StringVar(&configMapName, "config-map-name", "argocd-notifications-cm", "Set notifications ConfigMap name")
   176  	command.Flags().StringVar(&secretName, "secret-name", "argocd-notifications-secret", "Set notifications Secret name")
   177  	command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces that this controller should send notifications for")
   178  	command.Flags().BoolVar(&selfServiceNotificationEnabled, "self-service-notification-enabled", env.ParseBoolFromEnv("ARGOCD_NOTIFICATION_CONTROLLER_SELF_SERVICE_NOTIFICATION_ENABLED", false), "Allows the Argo CD notification controller to pull notification config from the namespace that the resource is in. This is useful for self-service notification.")
   179  	return &command
   180  }