github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd-applicationset-controller/commands/applicationset_controller.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "math" 6 "net/http" 7 "os" 8 "runtime/debug" 9 "time" 10 11 "github.com/argoproj/pkg/v2/stats" 12 "k8s.io/apimachinery/pkg/runtime" 13 ctrl "sigs.k8s.io/controller-runtime" 14 15 "github.com/argoproj/argo-cd/v3/reposerver/apiclient" 16 logutils "github.com/argoproj/argo-cd/v3/util/log" 17 "github.com/argoproj/argo-cd/v3/util/profile" 18 "github.com/argoproj/argo-cd/v3/util/tls" 19 20 "github.com/argoproj/argo-cd/v3/applicationset/controllers" 21 "github.com/argoproj/argo-cd/v3/applicationset/generators" 22 "github.com/argoproj/argo-cd/v3/applicationset/utils" 23 "github.com/argoproj/argo-cd/v3/applicationset/webhook" 24 cmdutil "github.com/argoproj/argo-cd/v3/cmd/util" 25 "github.com/argoproj/argo-cd/v3/common" 26 "github.com/argoproj/argo-cd/v3/util/env" 27 "github.com/argoproj/argo-cd/v3/util/github_app" 28 29 log "github.com/sirupsen/logrus" 30 "github.com/spf13/cobra" 31 "k8s.io/client-go/dynamic" 32 "k8s.io/client-go/kubernetes" 33 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 34 _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 35 "k8s.io/client-go/tools/clientcmd" 36 ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache" 37 ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" 38 metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 39 40 appsetmetrics "github.com/argoproj/argo-cd/v3/applicationset/metrics" 41 "github.com/argoproj/argo-cd/v3/applicationset/services" 42 appv1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 43 "github.com/argoproj/argo-cd/v3/util/cli" 44 "github.com/argoproj/argo-cd/v3/util/db" 45 "github.com/argoproj/argo-cd/v3/util/errors" 46 argosettings "github.com/argoproj/argo-cd/v3/util/settings" 47 ) 48 49 var gitSubmoduleEnabled = env.ParseBoolFromEnv(common.EnvGitSubmoduleEnabled, true) 50 51 const ( 52 cliName = common.ApplicationSetController 53 ) 54 55 func NewCommand() *cobra.Command { 56 var ( 57 clientConfig clientcmd.ClientConfig 58 metricsAddr string 59 probeBindAddr string 60 webhookAddr string 61 enableLeaderElection bool 62 applicationSetNamespaces []string 63 argocdRepoServer string 64 policy string 65 enablePolicyOverride bool 66 debugLog bool 67 dryRun bool 68 enableProgressiveSyncs bool 69 enableNewGitFileGlobbing bool 70 repoServerPlaintext bool 71 repoServerStrictTLS bool 72 repoServerTimeoutSeconds int 73 maxConcurrentReconciliations int 74 scmRootCAPath string 75 allowedScmProviders []string 76 globalPreservedAnnotations []string 77 globalPreservedLabels []string 78 enableGitHubAPIMetrics bool 79 metricsAplicationsetLabels []string 80 enableScmProviders bool 81 webhookParallelism int 82 tokenRefStrictMode bool 83 maxResourcesStatusCount int 84 ) 85 scheme := runtime.NewScheme() 86 _ = clientgoscheme.AddToScheme(scheme) 87 _ = appv1alpha1.AddToScheme(scheme) 88 command := cobra.Command{ 89 Use: cliName, 90 Short: "Starts Argo CD ApplicationSet controller", 91 DisableAutoGenTag: true, 92 RunE: func(c *cobra.Command, _ []string) error { 93 ctx := c.Context() 94 95 vers := common.GetVersion() 96 namespace, _, err := clientConfig.Namespace() 97 applicationSetNamespaces = append(applicationSetNamespaces, namespace) 98 99 errors.CheckError(err) 100 vers.LogStartupInfo( 101 "ArgoCD ApplicationSet Controller", 102 map[string]any{ 103 "namespace": namespace, 104 }, 105 ) 106 107 cli.SetLogFormat(cmdutil.LogFormat) 108 cli.SetLogLevel(cmdutil.LogLevel) 109 110 ctrl.SetLogger(logutils.NewLogrusLogger(logutils.NewWithCurrentConfig())) 111 112 // Recover from panic and log the error using the configured logger instead of the default. 113 defer func() { 114 if r := recover(); r != nil { 115 log.WithField("trace", string(debug.Stack())).Fatal("Recovered from panic: ", r) 116 } 117 }() 118 119 restConfig, err := clientConfig.ClientConfig() 120 errors.CheckError(err) 121 122 restConfig.UserAgent = fmt.Sprintf("argocd-applicationset-controller/%s (%s)", vers.Version, vers.Platform) 123 124 policyObj, exists := utils.Policies[policy] 125 if !exists { 126 log.Error("Policy value can be: sync, create-only, create-update, create-delete, default value: sync") 127 os.Exit(1) 128 } 129 130 // By default, watch all namespaces 131 var watchedNamespace string 132 // If the applicationset-namespaces contains only one namespace it corresponds to the current namespace 133 if len(applicationSetNamespaces) == 1 { 134 watchedNamespace = (applicationSetNamespaces)[0] 135 } else if enableScmProviders && len(allowedScmProviders) == 0 { 136 log.Error("When enabling applicationset in any namespace using applicationset-namespaces, you must either set --enable-scm-providers=false or specify --allowed-scm-providers") 137 os.Exit(1) 138 } 139 140 var cacheOpt ctrlcache.Options 141 142 if watchedNamespace != "" { 143 cacheOpt = ctrlcache.Options{ 144 DefaultNamespaces: map[string]ctrlcache.Config{ 145 watchedNamespace: {}, 146 }, 147 } 148 } 149 150 cfg := ctrl.GetConfigOrDie() 151 err = appv1alpha1.SetK8SConfigDefaults(cfg) 152 if err != nil { 153 log.Error(err, "Unable to apply K8s REST config defaults") 154 os.Exit(1) 155 } 156 157 mgr, err := ctrl.NewManager(cfg, ctrl.Options{ 158 Scheme: scheme, 159 Metrics: metricsserver.Options{ 160 BindAddress: metricsAddr, 161 }, 162 Cache: cacheOpt, 163 HealthProbeBindAddress: probeBindAddr, 164 LeaderElection: enableLeaderElection, 165 LeaderElectionID: "58ac56fa.applicationsets.argoproj.io", 166 Client: ctrlclient.Options{ 167 DryRun: &dryRun, 168 }, 169 }) 170 if err != nil { 171 log.Error(err, "unable to start manager") 172 os.Exit(1) 173 } 174 175 pprofMux := http.NewServeMux() 176 profile.RegisterProfiler(pprofMux) 177 // This looks a little strange. Eg, not using ctrl.Options PprofBindAddress and then adding the pprof mux 178 // to the metrics server. However, it allows for the controller to dynamically expose the pprof endpoints 179 // and use the existing metrics server, the same pattern that the application controller and api-server follow. 180 if err = mgr.AddMetricsServerExtraHandler("/debug/pprof/", pprofMux); err != nil { 181 log.Error(err, "failed to register pprof handlers") 182 } 183 dynamicClient, err := dynamic.NewForConfig(mgr.GetConfig()) 184 errors.CheckError(err) 185 k8sClient, err := kubernetes.NewForConfig(mgr.GetConfig()) 186 errors.CheckError(err) 187 188 argoSettingsMgr := argosettings.NewSettingsManager(ctx, k8sClient, namespace) 189 argoCDDB := db.NewDB(namespace, argoSettingsMgr, k8sClient) 190 191 scmConfig := generators.NewSCMConfig(scmRootCAPath, allowedScmProviders, enableScmProviders, enableGitHubAPIMetrics, github_app.NewAuthCredentials(argoCDDB.(db.RepoCredsDB)), tokenRefStrictMode) 192 193 tlsConfig := apiclient.TLSConfiguration{ 194 DisableTLS: repoServerPlaintext, 195 StrictValidation: repoServerStrictTLS, 196 } 197 198 if !repoServerPlaintext && repoServerStrictTLS { 199 pool, err := tls.LoadX509CertPool( 200 env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)+"/reposerver/tls/tls.crt", 201 env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)+"/reposerver/tls/ca.crt", 202 ) 203 errors.CheckError(err) 204 tlsConfig.Certificates = pool 205 } 206 207 repoClientset := apiclient.NewRepoServerClientset(argocdRepoServer, repoServerTimeoutSeconds, tlsConfig) 208 argoCDService := services.NewArgoCDService(argoCDDB, gitSubmoduleEnabled, repoClientset, enableNewGitFileGlobbing) 209 210 topLevelGenerators := generators.GetGenerators(ctx, mgr.GetClient(), k8sClient, namespace, argoCDService, dynamicClient, scmConfig) 211 212 // start a webhook server that listens to incoming webhook payloads 213 webhookHandler, err := webhook.NewWebhookHandler(webhookParallelism, argoSettingsMgr, mgr.GetClient(), topLevelGenerators) 214 if err != nil { 215 log.Error(err, "failed to create webhook handler") 216 } 217 if webhookHandler != nil { 218 startWebhookServer(webhookHandler, webhookAddr) 219 } 220 221 metrics := appsetmetrics.NewApplicationsetMetrics( 222 utils.NewAppsetLister(mgr.GetClient()), 223 metricsAplicationsetLabels, 224 func(appset *appv1alpha1.ApplicationSet) bool { 225 return utils.IsNamespaceAllowed(applicationSetNamespaces, appset.Namespace) 226 }) 227 228 if err = (&controllers.ApplicationSetReconciler{ 229 Generators: topLevelGenerators, 230 Client: mgr.GetClient(), 231 Scheme: mgr.GetScheme(), 232 Recorder: mgr.GetEventRecorderFor("applicationset-controller"), 233 Renderer: &utils.Render{}, 234 Policy: policyObj, 235 EnablePolicyOverride: enablePolicyOverride, 236 KubeClientset: k8sClient, 237 ArgoDB: argoCDDB, 238 ArgoCDNamespace: namespace, 239 ApplicationSetNamespaces: applicationSetNamespaces, 240 EnableProgressiveSyncs: enableProgressiveSyncs, 241 SCMRootCAPath: scmRootCAPath, 242 GlobalPreservedAnnotations: globalPreservedAnnotations, 243 GlobalPreservedLabels: globalPreservedLabels, 244 Metrics: &metrics, 245 MaxResourcesStatusCount: maxResourcesStatusCount, 246 }).SetupWithManager(mgr, enableProgressiveSyncs, maxConcurrentReconciliations); err != nil { 247 log.Error(err, "unable to create controller", "controller", "ApplicationSet") 248 os.Exit(1) 249 } 250 251 stats.StartStatsTicker(10 * time.Minute) 252 log.Info("Starting manager") 253 if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 254 log.Error(err, "problem running manager") 255 os.Exit(1) 256 } 257 return nil 258 }, 259 } 260 clientConfig = cli.AddKubectlFlagsToCmd(&command) 261 command.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 262 command.Flags().StringVar(&probeBindAddr, "probe-addr", ":8081", "The address the probe endpoint binds to.") 263 command.Flags().StringVar(&webhookAddr, "webhook-addr", ":7000", "The address the webhook endpoint binds to.") 264 command.Flags().BoolVar(&enableLeaderElection, "enable-leader-election", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_LEADER_ELECTION", false), 265 "Enable leader election for controller manager. "+ 266 "Enabling this will ensure there is only one active controller manager.") 267 command.Flags().StringSliceVar(&applicationSetNamespaces, "applicationset-namespaces", env.StringsFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES", []string{}, ","), "Argo CD applicationset namespaces") 268 command.Flags().StringVar(&argocdRepoServer, "argocd-repo-server", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER", common.DefaultRepoServerAddr), "Argo CD repo server address") 269 command.Flags().StringVar(&policy, "policy", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_POLICY", ""), "Modify how application is synced between the generator and the cluster. Default is '' (empty), which means AppSets default to 'sync', but they may override that default. Setting an explicit value prevents AppSet-level overrides, unless --allow-policy-override is enabled. Explicit options are: 'sync' (create & update & delete), 'create-only', 'create-update' (no deletion), 'create-delete' (no update)") 270 command.Flags().BoolVar(&enablePolicyOverride, "enable-policy-override", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_POLICY_OVERRIDE", policy == ""), "For security reason if 'policy' is set, it is not possible to override it at applicationSet level. 'allow-policy-override' allows user to define their own policy") 271 command.Flags().BoolVar(&debugLog, "debug", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_DEBUG", false), "Print debug logs. Takes precedence over loglevel") 272 command.Flags().StringVar(&cmdutil.LogFormat, "logformat", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_LOGFORMAT", "json"), "Set the logging format. One of: json|text") 273 command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error") 274 command.Flags().StringSliceVar(&allowedScmProviders, "allowed-scm-providers", env.StringsFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS", []string{}, ","), "The list of allowed custom SCM provider API URLs. This restriction does not apply to SCM or PR generators which do not accept a custom API URL. (Default: Empty = all)") 275 command.Flags().BoolVar(&enableScmProviders, "enable-scm-providers", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_SCM_PROVIDERS", true), "Enable retrieving information from SCM providers, used by the SCM and PR generators (Default: true)") 276 command.Flags().BoolVar(&dryRun, "dry-run", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_DRY_RUN", false), "Enable dry run mode") 277 command.Flags().BoolVar(&tokenRefStrictMode, "token-ref-strict-mode", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_TOKENREF_STRICT_MODE", false), fmt.Sprintf("Set to true to require secrets referenced by SCM providers to have the %s=%s label set (Default: false)", common.LabelKeySecretType, common.LabelValueSecretTypeSCMCreds)) 278 command.Flags().BoolVar(&enableProgressiveSyncs, "enable-progressive-syncs", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS", false), "Enable use of the experimental progressive syncs feature.") 279 command.Flags().BoolVar(&enableNewGitFileGlobbing, "enable-new-git-file-globbing", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING", false), "Enable new globbing in Git files generator.") 280 command.Flags().BoolVar(&repoServerPlaintext, "repo-server-plaintext", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER_PLAINTEXT", false), "Disable TLS on connections to repo server") 281 command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER_STRICT_TLS", false), "Whether to use strict validation of the TLS cert presented by the repo server") 282 command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER_TIMEOUT_SECONDS", 60, 0, math.MaxInt64), "Repo server RPC call timeout seconds.") 283 command.Flags().IntVar(&maxConcurrentReconciliations, "concurrent-reconciliations", env.ParseNumFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_CONCURRENT_RECONCILIATIONS", 10, 1, math.MaxInt), "Max concurrent reconciliations limit for the controller") 284 command.Flags().StringVar(&scmRootCAPath, "scm-root-ca-path", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_SCM_ROOT_CA_PATH", ""), "Provide Root CA Path for self-signed TLS Certificates") 285 command.Flags().StringSliceVar(&globalPreservedAnnotations, "preserved-annotations", env.StringsFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_GLOBAL_PRESERVED_ANNOTATIONS", []string{}, ","), "Sets global preserved field values for annotations") 286 command.Flags().StringSliceVar(&globalPreservedLabels, "preserved-labels", env.StringsFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_GLOBAL_PRESERVED_LABELS", []string{}, ","), "Sets global preserved field values for labels") 287 command.Flags().IntVar(&webhookParallelism, "webhook-parallelism-limit", env.ParseNumFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_WEBHOOK_PARALLELISM_LIMIT", 50, 1, 1000), "Number of webhook requests processed concurrently") 288 command.Flags().StringSliceVar(&metricsAplicationsetLabels, "metrics-applicationset-labels", []string{}, "List of Application labels that will be added to the argocd_applicationset_labels metric") 289 command.Flags().BoolVar(&enableGitHubAPIMetrics, "enable-github-api-metrics", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_GITHUB_API_METRICS", false), "Enable GitHub API metrics for generators that use the GitHub API") 290 command.Flags().IntVar(&maxResourcesStatusCount, "max-resources-status-count", env.ParseNumFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT", 0, 0, math.MaxInt), "Max number of resources stored in appset status.") 291 292 return &command 293 } 294 295 func startWebhookServer(webhookHandler *webhook.WebhookHandler, webhookAddr string) { 296 mux := http.NewServeMux() 297 mux.HandleFunc("/api/webhook", webhookHandler.Handler) 298 go func() { 299 log.Infof("Starting webhook server %s", webhookAddr) 300 err := http.ListenAndServe(webhookAddr, mux) 301 if err != nil { 302 log.Error(err, "failed to start webhook server") 303 os.Exit(1) 304 } 305 }() 306 }