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 }