github.com/argoproj/argo-cd@v1.8.7/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  	"time"
    10  
    11  	"github.com/argoproj/pkg/stats"
    12  	"github.com/go-redis/redis/v8"
    13  	log "github.com/sirupsen/logrus"
    14  	"github.com/spf13/cobra"
    15  	"google.golang.org/grpc/health/grpc_health_v1"
    16  
    17  	"github.com/argoproj/argo-cd/common"
    18  	"github.com/argoproj/argo-cd/reposerver"
    19  	"github.com/argoproj/argo-cd/reposerver/apiclient"
    20  	reposervercache "github.com/argoproj/argo-cd/reposerver/cache"
    21  	"github.com/argoproj/argo-cd/reposerver/metrics"
    22  	"github.com/argoproj/argo-cd/reposerver/repository"
    23  	cacheutil "github.com/argoproj/argo-cd/util/cache"
    24  	"github.com/argoproj/argo-cd/util/cli"
    25  	"github.com/argoproj/argo-cd/util/env"
    26  	"github.com/argoproj/argo-cd/util/errors"
    27  	"github.com/argoproj/argo-cd/util/gpg"
    28  	"github.com/argoproj/argo-cd/util/healthz"
    29  	ioutil "github.com/argoproj/argo-cd/util/io"
    30  	"github.com/argoproj/argo-cd/util/tls"
    31  )
    32  
    33  const (
    34  	// CLIName is the name of the CLI
    35  	cliName         = "argocd-repo-server"
    36  	gnuPGSourcePath = "/app/config/gpg/source"
    37  
    38  	defaultPauseGenerationAfterFailedGenerationAttempts = 3
    39  	defaultPauseGenerationOnFailureForMinutes           = 60
    40  	defaultPauseGenerationOnFailureForRequests          = 0
    41  )
    42  
    43  func getGnuPGSourcePath() string {
    44  	if path := os.Getenv("ARGOCD_GPG_DATA_PATH"); path != "" {
    45  		return path
    46  	} else {
    47  		return gnuPGSourcePath
    48  	}
    49  }
    50  
    51  func getPauseGenerationAfterFailedGenerationAttempts() int {
    52  	return env.ParseNumFromEnv(common.EnvPauseGenerationAfterFailedAttempts, defaultPauseGenerationAfterFailedGenerationAttempts, 0, math.MaxInt32)
    53  }
    54  
    55  func getPauseGenerationOnFailureForMinutes() int {
    56  	return env.ParseNumFromEnv(common.EnvPauseGenerationMinutes, defaultPauseGenerationOnFailureForMinutes, 0, math.MaxInt32)
    57  }
    58  
    59  func getPauseGenerationOnFailureForRequests() int {
    60  	return env.ParseNumFromEnv(common.EnvPauseGenerationRequests, defaultPauseGenerationOnFailureForRequests, 0, math.MaxInt32)
    61  }
    62  
    63  func NewCommand() *cobra.Command {
    64  	var (
    65  		logFormat              string
    66  		logLevel               string
    67  		parallelismLimit       int64
    68  		listenPort             int
    69  		metricsPort            int
    70  		cacheSrc               func() (*reposervercache.Cache, error)
    71  		tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
    72  		redisClient            *redis.Client
    73  	)
    74  	var command = cobra.Command{
    75  		Use:               cliName,
    76  		Short:             "Run ArgoCD Repository Server",
    77  		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.",
    78  		DisableAutoGenTag: true,
    79  		RunE: func(c *cobra.Command, args []string) error {
    80  			cli.SetLogFormat(logFormat)
    81  			cli.SetLogLevel(logLevel)
    82  
    83  			tlsConfigCustomizer, err := tlsConfigCustomizerSrc()
    84  			errors.CheckError(err)
    85  
    86  			cache, err := cacheSrc()
    87  			errors.CheckError(err)
    88  
    89  			metricsServer := metrics.NewMetricsServer()
    90  			cacheutil.CollectMetrics(redisClient, metricsServer)
    91  			server, err := reposerver.NewServer(metricsServer, cache, tlsConfigCustomizer, repository.RepoServerInitConstants{
    92  				ParallelismLimit: parallelismLimit,
    93  				PauseGenerationAfterFailedGenerationAttempts: getPauseGenerationAfterFailedGenerationAttempts(),
    94  				PauseGenerationOnFailureForMinutes:           getPauseGenerationOnFailureForMinutes(),
    95  				PauseGenerationOnFailureForRequests:          getPauseGenerationOnFailureForRequests(),
    96  			})
    97  			errors.CheckError(err)
    98  
    99  			grpc := server.CreateGRPC()
   100  			listener, err := net.Listen("tcp", fmt.Sprintf(":%d", listenPort))
   101  			errors.CheckError(err)
   102  
   103  			healthz.ServeHealthCheck(http.DefaultServeMux, func(r *http.Request) error {
   104  				if val, ok := r.URL.Query()["full"]; ok && len(val) > 0 && val[0] == "true" {
   105  					// connect to itself to make sure repo server is able to serve connection
   106  					// used by liveness probe to auto restart repo server
   107  					// see https://github.com/argoproj/argo-cd/issues/5110 for more information
   108  					conn, err := apiclient.NewConnection(fmt.Sprintf("localhost:%d", listenPort), 60)
   109  					if err != nil {
   110  						return err
   111  					}
   112  					defer ioutil.Close(conn)
   113  					client := grpc_health_v1.NewHealthClient(conn)
   114  					res, err := client.Check(r.Context(), &grpc_health_v1.HealthCheckRequest{})
   115  					if err != nil {
   116  						return err
   117  					}
   118  					if res.Status != grpc_health_v1.HealthCheckResponse_SERVING {
   119  						return fmt.Errorf("grpc health check status is '%v'", res.Status)
   120  					}
   121  					return nil
   122  				}
   123  				return nil
   124  			})
   125  			http.Handle("/metrics", metricsServer.GetHandler())
   126  			go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf(":%d", metricsPort), nil)) }()
   127  
   128  			if gpg.IsGPGEnabled() {
   129  				log.Infof("Initializing GnuPG keyring at %s", common.GetGnuPGHomePath())
   130  				err = gpg.InitializeGnuPG()
   131  				errors.CheckError(err)
   132  
   133  				log.Infof("Populating GnuPG keyring with keys from %s", getGnuPGSourcePath())
   134  				added, removed, err := gpg.SyncKeyRingFromDirectory(getGnuPGSourcePath())
   135  				errors.CheckError(err)
   136  				log.Infof("Loaded %d (and removed %d) keys from keyring", len(added), len(removed))
   137  
   138  				go func() { errors.CheckError(reposerver.StartGPGWatcher(getGnuPGSourcePath())) }()
   139  			}
   140  
   141  			log.Infof("argocd-repo-server %s serving on %s", common.GetVersion(), listener.Addr())
   142  			stats.RegisterStackDumper()
   143  			stats.StartStatsTicker(10 * time.Minute)
   144  			stats.RegisterHeapDumper("memprofile")
   145  			err = grpc.Serve(listener)
   146  			errors.CheckError(err)
   147  			return nil
   148  		},
   149  	}
   150  
   151  	command.Flags().StringVar(&logFormat, "logformat", "text", "Set the logging format. One of: text|json")
   152  	command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
   153  	command.Flags().Int64Var(&parallelismLimit, "parallelismlimit", 0, "Limit on number of concurrent manifests generate requests. Any value less the 1 means no limit.")
   154  	command.Flags().IntVar(&listenPort, "port", common.DefaultPortRepoServer, "Listen on given port for incoming connections")
   155  	command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortRepoServerMetrics, "Start metrics server on given port")
   156  
   157  	tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
   158  	cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
   159  		redisClient = client
   160  	})
   161  	return &command
   162  }