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(¶llelismLimit, "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 }