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

     1  package commands
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"runtime/debug"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/redis/go-redis/v9"
    12  	"k8s.io/apimachinery/pkg/runtime"
    13  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    14  
    15  	"github.com/argoproj/pkg/v2/stats"
    16  	log "github.com/sirupsen/logrus"
    17  	"github.com/spf13/cobra"
    18  	"k8s.io/client-go/dynamic"
    19  	"k8s.io/client-go/kubernetes"
    20  	"k8s.io/client-go/tools/clientcmd"
    21  	"sigs.k8s.io/controller-runtime/pkg/client"
    22  
    23  	cmdutil "github.com/argoproj/argo-cd/v3/cmd/util"
    24  	"github.com/argoproj/argo-cd/v3/common"
    25  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    26  	appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned"
    27  	"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
    28  	reposervercache "github.com/argoproj/argo-cd/v3/reposerver/cache"
    29  	"github.com/argoproj/argo-cd/v3/server"
    30  	servercache "github.com/argoproj/argo-cd/v3/server/cache"
    31  	"github.com/argoproj/argo-cd/v3/util/argo"
    32  	cacheutil "github.com/argoproj/argo-cd/v3/util/cache"
    33  	"github.com/argoproj/argo-cd/v3/util/cli"
    34  	"github.com/argoproj/argo-cd/v3/util/dex"
    35  	"github.com/argoproj/argo-cd/v3/util/env"
    36  	"github.com/argoproj/argo-cd/v3/util/errors"
    37  	"github.com/argoproj/argo-cd/v3/util/kube"
    38  	"github.com/argoproj/argo-cd/v3/util/templates"
    39  	"github.com/argoproj/argo-cd/v3/util/tls"
    40  	traceutil "github.com/argoproj/argo-cd/v3/util/trace"
    41  )
    42  
    43  const (
    44  	failureRetryCountEnv              = "ARGOCD_K8S_RETRY_COUNT"
    45  	failureRetryPeriodMilliSecondsEnv = "ARGOCD_K8S_RETRY_DURATION_MILLISECONDS"
    46  )
    47  
    48  var (
    49  	failureRetryCount              = env.ParseNumFromEnv(failureRetryCountEnv, 0, 0, 10)
    50  	failureRetryPeriodMilliSeconds = env.ParseNumFromEnv(failureRetryPeriodMilliSecondsEnv, 100, 0, 1000)
    51  	gitSubmoduleEnabled            = env.ParseBoolFromEnv(common.EnvGitSubmoduleEnabled, true)
    52  )
    53  
    54  // NewCommand returns a new instance of an argocd command
    55  func NewCommand() *cobra.Command {
    56  	var (
    57  		redisClient              *redis.Client
    58  		insecure                 bool
    59  		listenHost               string
    60  		listenPort               int
    61  		metricsHost              string
    62  		metricsPort              int
    63  		otlpAddress              string
    64  		otlpInsecure             bool
    65  		otlpHeaders              map[string]string
    66  		otlpAttrs                []string
    67  		glogLevel                int
    68  		clientConfig             clientcmd.ClientConfig
    69  		repoServerTimeoutSeconds int
    70  		baseHRef                 string
    71  		rootPath                 string
    72  		repoServerAddress        string
    73  		dexServerAddress         string
    74  		disableAuth              bool
    75  		contentTypes             string
    76  		enableGZip               bool
    77  		tlsConfigCustomizerSrc   func() (tls.ConfigCustomizer, error)
    78  		cacheSrc                 func() (*servercache.Cache, error)
    79  		repoServerCacheSrc       func() (*reposervercache.Cache, error)
    80  		frameOptions             string
    81  		contentSecurityPolicy    string
    82  		repoServerPlaintext      bool
    83  		repoServerStrictTLS      bool
    84  		dexServerPlaintext       bool
    85  		dexServerStrictTLS       bool
    86  		staticAssetsDir          string
    87  		applicationNamespaces    []string
    88  		enableProxyExtension     bool
    89  		webhookParallelism       int
    90  		hydratorEnabled          bool
    91  		syncWithReplaceAllowed   bool
    92  
    93  		// ApplicationSet
    94  		enableNewGitFileGlobbing bool
    95  		scmRootCAPath            string
    96  		allowedScmProviders      []string
    97  		enableScmProviders       bool
    98  		enableGitHubAPIMetrics   bool
    99  
   100  		// argocd k8s event logging flag
   101  		enableK8sEvent []string
   102  	)
   103  	command := &cobra.Command{
   104  		Use:               cliName,
   105  		Short:             "Run the ArgoCD API server",
   106  		Long:              "The API server is a gRPC/REST server which exposes the API consumed by the Web UI, CLI, and CI/CD systems.  This command runs API server in the foreground.  It can be configured by following options.",
   107  		DisableAutoGenTag: true,
   108  		Run: func(c *cobra.Command, _ []string) {
   109  			ctx := c.Context()
   110  
   111  			vers := common.GetVersion()
   112  			namespace, _, err := clientConfig.Namespace()
   113  			errors.CheckError(err)
   114  			vers.LogStartupInfo(
   115  				"ArgoCD API Server",
   116  				map[string]any{
   117  					"namespace": namespace,
   118  					"port":      listenPort,
   119  				},
   120  			)
   121  
   122  			cli.SetLogFormat(cmdutil.LogFormat)
   123  			cli.SetLogLevel(cmdutil.LogLevel)
   124  			cli.SetGLogLevel(glogLevel)
   125  
   126  			// Recover from panic and log the error using the configured logger instead of the default.
   127  			defer func() {
   128  				if r := recover(); r != nil {
   129  					log.WithField("trace", string(debug.Stack())).Fatal("Recovered from panic: ", r)
   130  				}
   131  			}()
   132  
   133  			config, err := clientConfig.ClientConfig()
   134  			errors.CheckError(err)
   135  			errors.CheckError(v1alpha1.SetK8SConfigDefaults(config))
   136  
   137  			tlsConfigCustomizer, err := tlsConfigCustomizerSrc()
   138  			errors.CheckError(err)
   139  			cache, err := cacheSrc()
   140  			errors.CheckError(err)
   141  			repoServerCache, err := repoServerCacheSrc()
   142  			errors.CheckError(err)
   143  
   144  			kubeclientset := kubernetes.NewForConfigOrDie(config)
   145  
   146  			appclientsetConfig, err := clientConfig.ClientConfig()
   147  			errors.CheckError(err)
   148  			errors.CheckError(v1alpha1.SetK8SConfigDefaults(appclientsetConfig))
   149  			config.UserAgent = fmt.Sprintf("argocd-server/%s (%s)", vers.Version, vers.Platform)
   150  
   151  			if failureRetryCount > 0 {
   152  				appclientsetConfig = kube.AddFailureRetryWrapper(appclientsetConfig, failureRetryCount, failureRetryPeriodMilliSeconds)
   153  			}
   154  			appClientSet := appclientset.NewForConfigOrDie(appclientsetConfig)
   155  			tlsConfig := apiclient.TLSConfiguration{
   156  				DisableTLS:       repoServerPlaintext,
   157  				StrictValidation: repoServerStrictTLS,
   158  			}
   159  
   160  			dynamicClient := dynamic.NewForConfigOrDie(config)
   161  
   162  			scheme := runtime.NewScheme()
   163  			_ = clientgoscheme.AddToScheme(scheme)
   164  			_ = v1alpha1.AddToScheme(scheme)
   165  
   166  			controllerClient, err := client.New(config, client.Options{Scheme: scheme})
   167  			errors.CheckError(err)
   168  			controllerClient = client.NewDryRunClient(controllerClient)
   169  			controllerClient = client.NewNamespacedClient(controllerClient, namespace)
   170  
   171  			// Load CA information to use for validating connections to the
   172  			// repository server, if strict TLS validation was requested.
   173  			if !repoServerPlaintext && repoServerStrictTLS {
   174  				pool, err := tls.LoadX509CertPool(
   175  					env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)+"/server/tls/tls.crt",
   176  					env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)+"/server/tls/ca.crt",
   177  				)
   178  				if err != nil {
   179  					log.Fatalf("%v", err)
   180  				}
   181  				tlsConfig.Certificates = pool
   182  			}
   183  
   184  			dexTLSConfig := &dex.DexTLSConfig{
   185  				DisableTLS:       dexServerPlaintext,
   186  				StrictValidation: dexServerStrictTLS,
   187  			}
   188  
   189  			if !dexServerPlaintext && dexServerStrictTLS {
   190  				pool, err := tls.LoadX509CertPool(
   191  					env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath) + "/dex/tls/ca.crt",
   192  				)
   193  				if err != nil {
   194  					log.Fatalf("%v", err)
   195  				}
   196  				dexTLSConfig.RootCAs = pool
   197  				cert, err := tls.LoadX509Cert(
   198  					env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath) + "/dex/tls/tls.crt",
   199  				)
   200  				if err != nil {
   201  					log.Fatalf("%v", err)
   202  				}
   203  				dexTLSConfig.Certificate = cert.Raw
   204  			}
   205  
   206  			repoclientset := apiclient.NewRepoServerClientset(repoServerAddress, repoServerTimeoutSeconds, tlsConfig)
   207  			if rootPath != "" {
   208  				if baseHRef != "" && baseHRef != rootPath {
   209  					log.Warnf("--basehref and --rootpath had conflict: basehref: %s rootpath: %s", baseHRef, rootPath)
   210  				}
   211  				baseHRef = rootPath
   212  			}
   213  
   214  			var contentTypesList []string
   215  			if contentTypes != "" {
   216  				contentTypesList = strings.Split(contentTypes, ";")
   217  			}
   218  
   219  			argoCDOpts := server.ArgoCDServerOpts{
   220  				Insecure:                insecure,
   221  				ListenPort:              listenPort,
   222  				ListenHost:              listenHost,
   223  				MetricsPort:             metricsPort,
   224  				MetricsHost:             metricsHost,
   225  				Namespace:               namespace,
   226  				BaseHRef:                baseHRef,
   227  				RootPath:                rootPath,
   228  				DynamicClientset:        dynamicClient,
   229  				KubeControllerClientset: controllerClient,
   230  				KubeClientset:           kubeclientset,
   231  				AppClientset:            appClientSet,
   232  				RepoClientset:           repoclientset,
   233  				DexServerAddr:           dexServerAddress,
   234  				DexTLSConfig:            dexTLSConfig,
   235  				DisableAuth:             disableAuth,
   236  				ContentTypes:            contentTypesList,
   237  				EnableGZip:              enableGZip,
   238  				TLSConfigCustomizer:     tlsConfigCustomizer,
   239  				Cache:                   cache,
   240  				RepoServerCache:         repoServerCache,
   241  				XFrameOptions:           frameOptions,
   242  				ContentSecurityPolicy:   contentSecurityPolicy,
   243  				RedisClient:             redisClient,
   244  				StaticAssetsDir:         staticAssetsDir,
   245  				ApplicationNamespaces:   applicationNamespaces,
   246  				EnableProxyExtension:    enableProxyExtension,
   247  				WebhookParallelism:      webhookParallelism,
   248  				EnableK8sEvent:          enableK8sEvent,
   249  				HydratorEnabled:         hydratorEnabled,
   250  				SyncWithReplaceAllowed:  syncWithReplaceAllowed,
   251  			}
   252  
   253  			appsetOpts := server.ApplicationSetOpts{
   254  				GitSubmoduleEnabled:      gitSubmoduleEnabled,
   255  				EnableNewGitFileGlobbing: enableNewGitFileGlobbing,
   256  				ScmRootCAPath:            scmRootCAPath,
   257  				AllowedScmProviders:      allowedScmProviders,
   258  				EnableScmProviders:       enableScmProviders,
   259  				EnableGitHubAPIMetrics:   enableGitHubAPIMetrics,
   260  			}
   261  
   262  			stats.RegisterStackDumper()
   263  			stats.StartStatsTicker(10 * time.Minute)
   264  			stats.RegisterHeapDumper("memprofile")
   265  			argocd := server.NewServer(ctx, argoCDOpts, appsetOpts)
   266  			argocd.Init(ctx)
   267  			for {
   268  				var closer func()
   269  				serverCtx, cancel := context.WithCancel(ctx)
   270  				lns, err := argocd.Listen()
   271  				errors.CheckError(err)
   272  				if otlpAddress != "" {
   273  					closer, err = traceutil.InitTracer(serverCtx, "argocd-server", otlpAddress, otlpInsecure, otlpHeaders, otlpAttrs)
   274  					if err != nil {
   275  						log.Fatalf("failed to initialize tracing: %v", err)
   276  					}
   277  				}
   278  				argocd.Run(serverCtx, lns)
   279  				if closer != nil {
   280  					closer()
   281  				}
   282  				cancel()
   283  				if argocd.TerminateRequested() {
   284  					break
   285  				}
   286  			}
   287  		},
   288  		Example: templates.Examples(`
   289  			# Start the Argo CD API server with default settings
   290  			$ argocd-server
   291  
   292  			# Start the Argo CD API server on a custom port and enable tracing
   293  			$ argocd-server --port 8888 --otlp-address localhost:4317
   294  		`),
   295  	}
   296  
   297  	clientConfig = cli.AddKubectlFlagsToCmd(command)
   298  	command.Flags().BoolVar(&insecure, "insecure", env.ParseBoolFromEnv("ARGOCD_SERVER_INSECURE", false), "Run server without TLS")
   299  	command.Flags().StringVar(&staticAssetsDir, "staticassets", env.StringFromEnv("ARGOCD_SERVER_STATIC_ASSETS", "/shared/app"), "Directory path that contains additional static assets")
   300  	command.Flags().StringVar(&baseHRef, "basehref", env.StringFromEnv("ARGOCD_SERVER_BASEHREF", "/"), "Value for base href in index.html. Used if Argo CD is running behind reverse proxy under subpath different from /")
   301  	command.Flags().StringVar(&rootPath, "rootpath", env.StringFromEnv("ARGOCD_SERVER_ROOTPATH", ""), "Used if Argo CD is running behind reverse proxy under subpath different from /")
   302  	command.Flags().StringVar(&cmdutil.LogFormat, "logformat", env.StringFromEnv("ARGOCD_SERVER_LOGFORMAT", "json"), "Set the logging format. One of: json|text")
   303  	command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", env.StringFromEnv("ARGOCD_SERVER_LOG_LEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
   304  	command.Flags().IntVar(&glogLevel, "gloglevel", 0, "Set the glog logging level")
   305  	command.Flags().StringVar(&repoServerAddress, "repo-server", env.StringFromEnv("ARGOCD_SERVER_REPO_SERVER", common.DefaultRepoServerAddr), "Repo server address")
   306  	command.Flags().StringVar(&dexServerAddress, "dex-server", env.StringFromEnv("ARGOCD_SERVER_DEX_SERVER", common.DefaultDexServerAddr), "Dex server address")
   307  	command.Flags().BoolVar(&disableAuth, "disable-auth", env.ParseBoolFromEnv("ARGOCD_SERVER_DISABLE_AUTH", false), "Disable client authentication")
   308  	command.Flags().StringVar(&contentTypes, "api-content-types", env.StringFromEnv("ARGOCD_API_CONTENT_TYPES", "application/json", env.StringFromEnvOpts{AllowEmpty: true}), "Semicolon separated list of allowed content types for non GET api requests. Any content type is allowed if empty.")
   309  	command.Flags().BoolVar(&enableGZip, "enable-gzip", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_GZIP", true), "Enable GZIP compression")
   310  	command.AddCommand(cli.NewVersionCmd(cliName))
   311  	command.Flags().StringVar(&listenHost, "address", env.StringFromEnv("ARGOCD_SERVER_LISTEN_ADDRESS", common.DefaultAddressAPIServer), "Listen on given address")
   312  	command.Flags().IntVar(&listenPort, "port", common.DefaultPortAPIServer, "Listen on given port")
   313  	command.Flags().StringVar(&metricsHost, env.StringFromEnv("ARGOCD_SERVER_METRICS_LISTEN_ADDRESS", "metrics-address"), common.DefaultAddressAPIServerMetrics, "Listen for metrics on given address")
   314  	command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDAPIServerMetrics, "Start metrics on given port")
   315  	command.Flags().StringVar(&otlpAddress, "otlp-address", env.StringFromEnv("ARGOCD_SERVER_OTLP_ADDRESS", ""), "OpenTelemetry collector address to send traces to")
   316  	command.Flags().BoolVar(&otlpInsecure, "otlp-insecure", env.ParseBoolFromEnv("ARGOCD_SERVER_OTLP_INSECURE", true), "OpenTelemetry collector insecure mode")
   317  	command.Flags().StringToStringVar(&otlpHeaders, "otlp-headers", env.ParseStringToStringFromEnv("ARGOCD_SERVER_OTLP_HEADERS", map[string]string{}, ","), "List of OpenTelemetry collector extra headers sent with traces, headers are comma-separated key-value pairs(e.g. key1=value1,key2=value2)")
   318  	command.Flags().StringSliceVar(&otlpAttrs, "otlp-attrs", env.StringsFromEnv("ARGOCD_SERVER_OTLP_ATTRS", []string{}, ","), "List of OpenTelemetry collector extra attrs when send traces, each attribute is separated by a colon(e.g. key:value)")
   319  	command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", env.ParseNumFromEnv("ARGOCD_SERVER_REPO_SERVER_TIMEOUT_SECONDS", 60, 0, math.MaxInt64), "Repo server RPC call timeout seconds.")
   320  	command.Flags().StringVar(&frameOptions, "x-frame-options", env.StringFromEnv("ARGOCD_SERVER_X_FRAME_OPTIONS", "sameorigin"), "Set X-Frame-Options header in HTTP responses to `value`. To disable, set to \"\".")
   321  	command.Flags().StringVar(&contentSecurityPolicy, "content-security-policy", env.StringFromEnv("ARGOCD_SERVER_CONTENT_SECURITY_POLICY", "frame-ancestors 'self';"), "Set Content-Security-Policy header in HTTP responses to `value`. To disable, set to \"\".")
   322  	command.Flags().BoolVar(&repoServerPlaintext, "repo-server-plaintext", env.ParseBoolFromEnv("ARGOCD_SERVER_REPO_SERVER_PLAINTEXT", false), "Use a plaintext client (non-TLS) to connect to repository server")
   323  	command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_SERVER_REPO_SERVER_STRICT_TLS", false), "Perform strict validation of TLS certificates when connecting to repo server")
   324  	command.Flags().BoolVar(&dexServerPlaintext, "dex-server-plaintext", env.ParseBoolFromEnv("ARGOCD_SERVER_DEX_SERVER_PLAINTEXT", false), "Use a plaintext client (non-TLS) to connect to dex server")
   325  	command.Flags().BoolVar(&dexServerStrictTLS, "dex-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_SERVER_DEX_SERVER_STRICT_TLS", false), "Perform strict validation of TLS certificates when connecting to dex server")
   326  	command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces where application resources can be managed in")
   327  	command.Flags().BoolVar(&enableProxyExtension, "enable-proxy-extension", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_PROXY_EXTENSION", false), "Enable Proxy Extension feature")
   328  	command.Flags().IntVar(&webhookParallelism, "webhook-parallelism-limit", env.ParseNumFromEnv("ARGOCD_SERVER_WEBHOOK_PARALLELISM_LIMIT", 50, 1, 1000), "Number of webhook requests processed concurrently")
   329  	command.Flags().StringSliceVar(&enableK8sEvent, "enable-k8s-event", env.StringsFromEnv("ARGOCD_ENABLE_K8S_EVENT", argo.DefaultEnableEventList(), ","), "Enable ArgoCD to use k8s event. For disabling all events, set the value as `none`. (e.g --enable-k8s-event=none), For enabling specific events, set the value as `event reason`. (e.g --enable-k8s-event=StatusRefreshed,ResourceCreated)")
   330  	command.Flags().BoolVar(&hydratorEnabled, "hydrator-enabled", env.ParseBoolFromEnv("ARGOCD_HYDRATOR_ENABLED", false), "Feature flag to enable Hydrator. Default (\"false\")")
   331  	command.Flags().BoolVar(&syncWithReplaceAllowed, "sync-with-replace-allowed", env.ParseBoolFromEnv("ARGOCD_SYNC_WITH_REPLACE_ALLOWED", true), "Whether to allow users to select replace for syncs from UI/CLI")
   332  
   333  	// Flags related to the applicationSet component.
   334  	command.Flags().StringVar(&scmRootCAPath, "appset-scm-root-ca-path", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_SCM_ROOT_CA_PATH", ""), "Provide Root CA Path for self-signed TLS Certificates")
   335  	command.Flags().BoolVar(&enableScmProviders, "appset-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)")
   336  	command.Flags().StringSliceVar(&allowedScmProviders, "appset-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)")
   337  	command.Flags().BoolVar(&enableNewGitFileGlobbing, "appset-enable-new-git-file-globbing", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING", false), "Enable new globbing in Git files generator.")
   338  	command.Flags().BoolVar(&enableGitHubAPIMetrics, "appset-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")
   339  
   340  	tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(command)
   341  	cacheSrc = servercache.AddCacheFlagsToCmd(command, cacheutil.Options{
   342  		OnClientCreated: func(client *redis.Client) {
   343  			redisClient = client
   344  		},
   345  	})
   346  	repoServerCacheSrc = reposervercache.AddCacheFlagsToCmd(command, cacheutil.Options{FlagPrefix: "repo-server-"})
   347  	return command
   348  }