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

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"net"
     7  	"net/http"
     8  	"os"
     9  	"os/signal"
    10  	"runtime/debug"
    11  	"sync"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/argoproj/pkg/v2/stats"
    16  	"github.com/redis/go-redis/v9"
    17  	log "github.com/sirupsen/logrus"
    18  	"github.com/spf13/cobra"
    19  	"google.golang.org/grpc/health/grpc_health_v1"
    20  	"k8s.io/apimachinery/pkg/api/resource"
    21  
    22  	cmdutil "github.com/argoproj/argo-cd/v3/cmd/util"
    23  	"github.com/argoproj/argo-cd/v3/common"
    24  	"github.com/argoproj/argo-cd/v3/reposerver"
    25  	"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
    26  	reposervercache "github.com/argoproj/argo-cd/v3/reposerver/cache"
    27  	"github.com/argoproj/argo-cd/v3/reposerver/metrics"
    28  	"github.com/argoproj/argo-cd/v3/reposerver/repository"
    29  	"github.com/argoproj/argo-cd/v3/util/askpass"
    30  	cacheutil "github.com/argoproj/argo-cd/v3/util/cache"
    31  	"github.com/argoproj/argo-cd/v3/util/cli"
    32  	"github.com/argoproj/argo-cd/v3/util/env"
    33  	"github.com/argoproj/argo-cd/v3/util/errors"
    34  	"github.com/argoproj/argo-cd/v3/util/gpg"
    35  	"github.com/argoproj/argo-cd/v3/util/healthz"
    36  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    37  	"github.com/argoproj/argo-cd/v3/util/tls"
    38  	traceutil "github.com/argoproj/argo-cd/v3/util/trace"
    39  )
    40  
    41  const (
    42  	// CLIName is the name of the CLI
    43  	cliName = "argocd-repo-server"
    44  )
    45  
    46  var (
    47  	gnuPGSourcePath                              = env.StringFromEnv(common.EnvGPGDataPath, "/app/config/gpg/source")
    48  	pauseGenerationAfterFailedGenerationAttempts = env.ParseNumFromEnv(common.EnvPauseGenerationAfterFailedAttempts, 3, 0, math.MaxInt32)
    49  	pauseGenerationOnFailureForMinutes           = env.ParseNumFromEnv(common.EnvPauseGenerationMinutes, 60, 0, math.MaxInt32)
    50  	pauseGenerationOnFailureForRequests          = env.ParseNumFromEnv(common.EnvPauseGenerationRequests, 0, 0, math.MaxInt32)
    51  	gitSubmoduleEnabled                          = env.ParseBoolFromEnv(common.EnvGitSubmoduleEnabled, true)
    52  )
    53  
    54  func NewCommand() *cobra.Command {
    55  	var (
    56  		parallelismLimit                   int64
    57  		listenPort                         int
    58  		listenHost                         string
    59  		metricsPort                        int
    60  		metricsHost                        string
    61  		otlpAddress                        string
    62  		otlpInsecure                       bool
    63  		otlpHeaders                        map[string]string
    64  		otlpAttrs                          []string
    65  		cacheSrc                           func() (*reposervercache.Cache, error)
    66  		tlsConfigCustomizer                tls.ConfigCustomizer
    67  		tlsConfigCustomizerSrc             func() (tls.ConfigCustomizer, error)
    68  		redisClient                        *redis.Client
    69  		disableTLS                         bool
    70  		maxCombinedDirectoryManifestsSize  string
    71  		cmpTarExcludedGlobs                []string
    72  		allowOutOfBoundsSymlinks           bool
    73  		streamedManifestMaxTarSize         string
    74  		streamedManifestMaxExtractedSize   string
    75  		helmManifestMaxExtractedSize       string
    76  		helmRegistryMaxIndexSize           string
    77  		ociManifestMaxExtractedSize        string
    78  		disableOCIManifestMaxExtractedSize bool
    79  		disableManifestMaxExtractedSize    bool
    80  		includeHiddenDirectories           bool
    81  		cmpUseManifestGeneratePaths        bool
    82  		ociMediaTypes                      []string
    83  		enableBuiltinGitConfig             bool
    84  	)
    85  	command := cobra.Command{
    86  		Use:               cliName,
    87  		Short:             "Run ArgoCD Repository Server",
    88  		Long:              "ArgoCD Repository Server is an internal service which maintains a local cache of the Git repository holding the application manifests, and is responsible for generating and returning the Kubernetes manifests.  This command runs Repository Server in the foreground.  It can be configured by following options.",
    89  		DisableAutoGenTag: true,
    90  		RunE: func(c *cobra.Command, _ []string) error {
    91  			ctx := c.Context()
    92  
    93  			vers := common.GetVersion()
    94  			vers.LogStartupInfo(
    95  				"ArgoCD Repository Server",
    96  				map[string]any{
    97  					"port": listenPort,
    98  				},
    99  			)
   100  
   101  			cli.SetLogFormat(cmdutil.LogFormat)
   102  			cli.SetLogLevel(cmdutil.LogLevel)
   103  
   104  			// Recover from panic and log the error using the configured logger instead of the default.
   105  			defer func() {
   106  				if r := recover(); r != nil {
   107  					log.WithField("trace", string(debug.Stack())).Fatal("Recovered from panic: ", r)
   108  				}
   109  			}()
   110  
   111  			if !disableTLS {
   112  				var err error
   113  				tlsConfigCustomizer, err = tlsConfigCustomizerSrc()
   114  				errors.CheckError(err)
   115  			}
   116  
   117  			cache, err := cacheSrc()
   118  			errors.CheckError(err)
   119  
   120  			maxCombinedDirectoryManifestsQuantity, err := resource.ParseQuantity(maxCombinedDirectoryManifestsSize)
   121  			errors.CheckError(err)
   122  
   123  			streamedManifestMaxTarSizeQuantity, err := resource.ParseQuantity(streamedManifestMaxTarSize)
   124  			errors.CheckError(err)
   125  
   126  			streamedManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(streamedManifestMaxExtractedSize)
   127  			errors.CheckError(err)
   128  
   129  			helmManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(helmManifestMaxExtractedSize)
   130  			errors.CheckError(err)
   131  
   132  			ociManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(ociManifestMaxExtractedSize)
   133  			errors.CheckError(err)
   134  
   135  			helmRegistryMaxIndexSizeQuantity, err := resource.ParseQuantity(helmRegistryMaxIndexSize)
   136  			errors.CheckError(err)
   137  
   138  			askPassServer := askpass.NewServer(askpass.SocketPath)
   139  			metricsServer := metrics.NewMetricsServer()
   140  			cacheutil.CollectMetrics(redisClient, metricsServer, nil)
   141  			server, err := reposerver.NewServer(metricsServer, cache, tlsConfigCustomizer, repository.RepoServerInitConstants{
   142  				ParallelismLimit: parallelismLimit,
   143  				PauseGenerationAfterFailedGenerationAttempts: pauseGenerationAfterFailedGenerationAttempts,
   144  				PauseGenerationOnFailureForMinutes:           pauseGenerationOnFailureForMinutes,
   145  				PauseGenerationOnFailureForRequests:          pauseGenerationOnFailureForRequests,
   146  				SubmoduleEnabled:                             gitSubmoduleEnabled,
   147  				MaxCombinedDirectoryManifestsSize:            maxCombinedDirectoryManifestsQuantity,
   148  				CMPTarExcludedGlobs:                          cmpTarExcludedGlobs,
   149  				AllowOutOfBoundsSymlinks:                     allowOutOfBoundsSymlinks,
   150  				StreamedManifestMaxExtractedSize:             streamedManifestMaxExtractedSizeQuantity.ToDec().Value(),
   151  				StreamedManifestMaxTarSize:                   streamedManifestMaxTarSizeQuantity.ToDec().Value(),
   152  				HelmManifestMaxExtractedSize:                 helmManifestMaxExtractedSizeQuantity.ToDec().Value(),
   153  				HelmRegistryMaxIndexSize:                     helmRegistryMaxIndexSizeQuantity.ToDec().Value(),
   154  				OCIManifestMaxExtractedSize:                  ociManifestMaxExtractedSizeQuantity.ToDec().Value(),
   155  				DisableOCIManifestMaxExtractedSize:           disableOCIManifestMaxExtractedSize,
   156  				IncludeHiddenDirectories:                     includeHiddenDirectories,
   157  				CMPUseManifestGeneratePaths:                  cmpUseManifestGeneratePaths,
   158  				OCIMediaTypes:                                ociMediaTypes,
   159  				EnableBuiltinGitConfig:                       enableBuiltinGitConfig,
   160  			}, askPassServer)
   161  			errors.CheckError(err)
   162  
   163  			if otlpAddress != "" {
   164  				var closer func()
   165  				var err error
   166  				closer, err = traceutil.InitTracer(ctx, "argocd-repo-server", otlpAddress, otlpInsecure, otlpHeaders, otlpAttrs)
   167  				if err != nil {
   168  					log.Fatalf("failed to initialize tracing: %v", err)
   169  				}
   170  				defer closer()
   171  			}
   172  
   173  			grpc := server.CreateGRPC()
   174  			listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", listenHost, listenPort))
   175  			errors.CheckError(err)
   176  
   177  			healthz.ServeHealthCheck(http.DefaultServeMux, func(r *http.Request) error {
   178  				if val, ok := r.URL.Query()["full"]; ok && len(val) > 0 && val[0] == "true" {
   179  					// connect to itself to make sure repo server is able to serve connection
   180  					// used by liveness probe to auto restart repo server
   181  					// see https://github.com/argoproj/argo-cd/issues/5110 for more information
   182  					conn, err := apiclient.NewConnection(fmt.Sprintf("localhost:%d", listenPort), 60, &apiclient.TLSConfiguration{DisableTLS: disableTLS})
   183  					if err != nil {
   184  						return err
   185  					}
   186  					defer utilio.Close(conn)
   187  					client := grpc_health_v1.NewHealthClient(conn)
   188  					res, err := client.Check(r.Context(), &grpc_health_v1.HealthCheckRequest{})
   189  					if err != nil {
   190  						return err
   191  					}
   192  					if res.Status != grpc_health_v1.HealthCheckResponse_SERVING {
   193  						return fmt.Errorf("grpc health check status is '%v'", res.Status)
   194  					}
   195  					return nil
   196  				}
   197  				return nil
   198  			})
   199  			http.Handle("/metrics", metricsServer.GetHandler())
   200  			go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf("%s:%d", metricsHost, metricsPort), nil)) }()
   201  			go func() { errors.CheckError(askPassServer.Run()) }()
   202  
   203  			if gpg.IsGPGEnabled() {
   204  				log.Infof("Initializing GnuPG keyring at %s", common.GetGnuPGHomePath())
   205  				err = gpg.InitializeGnuPG()
   206  				errors.CheckError(err)
   207  
   208  				log.Infof("Populating GnuPG keyring with keys from %s", gnuPGSourcePath)
   209  				added, removed, err := gpg.SyncKeyRingFromDirectory(gnuPGSourcePath)
   210  				errors.CheckError(err)
   211  				log.Infof("Loaded %d (and removed %d) keys from keyring", len(added), len(removed))
   212  
   213  				go func() { errors.CheckError(reposerver.StartGPGWatcher(gnuPGSourcePath)) }()
   214  			}
   215  
   216  			log.Infof("argocd-repo-server is listening on %s", listener.Addr())
   217  			stats.RegisterStackDumper()
   218  			stats.StartStatsTicker(10 * time.Minute)
   219  			stats.RegisterHeapDumper("memprofile")
   220  
   221  			// Graceful shutdown code adapted from https://gist.github.com/embano1/e0bf49d24f1cdd07cffad93097c04f0a
   222  			sigCh := make(chan os.Signal, 1)
   223  			signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
   224  			wg := sync.WaitGroup{}
   225  			wg.Add(1)
   226  			go func() {
   227  				s := <-sigCh
   228  				log.Printf("got signal %v, attempting graceful shutdown", s)
   229  				grpc.GracefulStop()
   230  				wg.Done()
   231  			}()
   232  
   233  			log.Println("starting grpc server")
   234  			err = grpc.Serve(listener)
   235  			if err != nil {
   236  				log.Fatalf("could not serve: %v", err)
   237  			}
   238  			wg.Wait()
   239  			log.Println("clean shutdown")
   240  
   241  			return nil
   242  		},
   243  	}
   244  	command.Flags().StringVar(&cmdutil.LogFormat, "logformat", env.StringFromEnv("ARGOCD_REPO_SERVER_LOGFORMAT", "json"), "Set the logging format. One of: json|text")
   245  	command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", env.StringFromEnv("ARGOCD_REPO_SERVER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
   246  	command.Flags().Int64Var(&parallelismLimit, "parallelismlimit", int64(env.ParseNumFromEnv("ARGOCD_REPO_SERVER_PARALLELISM_LIMIT", 0, 0, math.MaxInt32)), "Limit on number of concurrent manifests generate requests. Any value less the 1 means no limit.")
   247  	command.Flags().StringVar(&listenHost, "address", env.StringFromEnv("ARGOCD_REPO_SERVER_LISTEN_ADDRESS", common.DefaultAddressRepoServer), "Listen on given address for incoming connections")
   248  	command.Flags().IntVar(&listenPort, "port", common.DefaultPortRepoServer, "Listen on given port for incoming connections")
   249  	command.Flags().StringVar(&metricsHost, "metrics-address", env.StringFromEnv("ARGOCD_REPO_SERVER_METRICS_LISTEN_ADDRESS", common.DefaultAddressRepoServerMetrics), "Listen on given address for metrics")
   250  	command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortRepoServerMetrics, "Start metrics server on given port")
   251  	command.Flags().StringVar(&otlpAddress, "otlp-address", env.StringFromEnv("ARGOCD_REPO_SERVER_OTLP_ADDRESS", ""), "OpenTelemetry collector address to send traces to")
   252  	command.Flags().BoolVar(&otlpInsecure, "otlp-insecure", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_OTLP_INSECURE", true), "OpenTelemetry collector insecure mode")
   253  	command.Flags().StringToStringVar(&otlpHeaders, "otlp-headers", env.ParseStringToStringFromEnv("ARGOCD_REPO_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)")
   254  	command.Flags().StringSliceVar(&otlpAttrs, "otlp-attrs", env.StringsFromEnv("ARGOCD_REPO_SERVER_OTLP_ATTRS", []string{}, ","), "List of OpenTelemetry collector extra attrs when send traces, each attribute is separated by a colon(e.g. key:value)")
   255  	command.Flags().BoolVar(&disableTLS, "disable-tls", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_TLS", false), "Disable TLS on the gRPC endpoint")
   256  	command.Flags().StringVar(&maxCombinedDirectoryManifestsSize, "max-combined-directory-manifests-size", env.StringFromEnv("ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE", "10M"), "Max combined size of manifest files in a directory-type Application")
   257  	command.Flags().StringArrayVar(&cmpTarExcludedGlobs, "plugin-tar-exclude", env.StringsFromEnv("ARGOCD_REPO_SERVER_PLUGIN_TAR_EXCLUSIONS", []string{}, ";"), "Globs to filter when sending tarballs to plugins.")
   258  	command.Flags().BoolVar(&allowOutOfBoundsSymlinks, "allow-oob-symlinks", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_ALLOW_OUT_OF_BOUNDS_SYMLINKS", false), "Allow out-of-bounds symlinks in repositories (not recommended)")
   259  	command.Flags().StringVar(&streamedManifestMaxTarSize, "streamed-manifest-max-tar-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_TAR_SIZE", "100M"), "Maximum size of streamed manifest archives")
   260  	command.Flags().StringVar(&streamedManifestMaxExtractedSize, "streamed-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of streamed manifest archives when extracted")
   261  	command.Flags().StringVar(&helmManifestMaxExtractedSize, "helm-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of helm manifest archives when extracted")
   262  	command.Flags().StringVar(&helmRegistryMaxIndexSize, "helm-registry-max-index-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_INDEX_SIZE", "1G"), "Maximum size of registry index file")
   263  	command.Flags().StringVar(&ociManifestMaxExtractedSize, "oci-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_OCI_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of oci manifest archives when extracted")
   264  	command.Flags().BoolVar(&disableOCIManifestMaxExtractedSize, "disable-oci-manifest-max-extracted-size", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_OCI_MANIFEST_MAX_EXTRACTED_SIZE", false), "Disable maximum size of oci manifest archives when extracted")
   265  	command.Flags().BoolVar(&disableManifestMaxExtractedSize, "disable-helm-manifest-max-extracted-size", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE", false), "Disable maximum size of helm manifest archives when extracted")
   266  	command.Flags().BoolVar(&includeHiddenDirectories, "include-hidden-directories", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_INCLUDE_HIDDEN_DIRECTORIES", false), "Include hidden directories from Git")
   267  	command.Flags().BoolVar(&cmpUseManifestGeneratePaths, "plugin-use-manifest-generate-paths", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_PLUGIN_USE_MANIFEST_GENERATE_PATHS", false), "Pass the resources described in argocd.argoproj.io/manifest-generate-paths value to the cmpserver to generate the application manifests.")
   268  	command.Flags().StringSliceVar(&ociMediaTypes, "oci-layer-media-types", env.StringsFromEnv("ARGOCD_REPO_SERVER_OCI_LAYER_MEDIA_TYPES", []string{"application/vnd.oci.image.layer.v1.tar", "application/vnd.oci.image.layer.v1.tar+gzip", "application/vnd.cncf.helm.chart.content.v1.tar+gzip"}, ","), "Comma separated list of allowed media types for OCI media types. This only accounts for media types within layers.")
   269  	command.Flags().BoolVar(&enableBuiltinGitConfig, "enable-builtin-git-config", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_ENABLE_BUILTIN_GIT_CONFIG", true), "Enable builtin git configuration options that are required for correct argocd-repo-server operation.")
   270  	tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
   271  	cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, cacheutil.Options{
   272  		OnClientCreated: func(client *redis.Client) {
   273  			redisClient = client
   274  		},
   275  	})
   276  	return &command
   277  }