github.com/argoproj/argo-cd/v3@v3.2.1/pkg/apiclient/apiclient.go (about)

     1  package apiclient
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"encoding/base64"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"math"
    11  	"net"
    12  	"net/http"
    13  	"os"
    14  	"strings"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/coreos/go-oidc/v3/oidc"
    19  	"github.com/golang-jwt/jwt/v5"
    20  	"github.com/golang/protobuf/ptypes/empty"
    21  	grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry"
    22  	"github.com/hashicorp/go-retryablehttp"
    23  	log "github.com/sirupsen/logrus"
    24  	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
    25  	"golang.org/x/oauth2"
    26  	"google.golang.org/grpc"
    27  	"google.golang.org/grpc/codes"
    28  	"google.golang.org/grpc/credentials"
    29  	"google.golang.org/grpc/metadata"
    30  	"google.golang.org/grpc/status"
    31  	"k8s.io/client-go/tools/clientcmd"
    32  
    33  	"github.com/argoproj/argo-cd/v3/common"
    34  	accountpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/account"
    35  	applicationpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/application"
    36  	applicationsetpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/applicationset"
    37  	certificatepkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/certificate"
    38  	clusterpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/cluster"
    39  	gpgkeypkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/gpgkey"
    40  	notificationpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/notification"
    41  	projectpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/project"
    42  	repocredspkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/repocreds"
    43  	repositorypkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/repository"
    44  	sessionpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/session"
    45  	settingspkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/settings"
    46  	versionpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/version"
    47  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    48  	"github.com/argoproj/argo-cd/v3/util/argo"
    49  	"github.com/argoproj/argo-cd/v3/util/env"
    50  	grpc_util "github.com/argoproj/argo-cd/v3/util/grpc"
    51  	http_util "github.com/argoproj/argo-cd/v3/util/http"
    52  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    53  	"github.com/argoproj/argo-cd/v3/util/kube"
    54  	"github.com/argoproj/argo-cd/v3/util/localconfig"
    55  	oidcutil "github.com/argoproj/argo-cd/v3/util/oidc"
    56  	tls_util "github.com/argoproj/argo-cd/v3/util/tls"
    57  )
    58  
    59  const (
    60  	MetaDataTokenKey = "token"
    61  	// EnvArgoCDServer is the environment variable to look for an Argo CD server address
    62  	EnvArgoCDServer = "ARGOCD_SERVER"
    63  	// EnvArgoCDAuthToken is the environment variable to look for an Argo CD auth token
    64  	EnvArgoCDAuthToken = "ARGOCD_AUTH_TOKEN"
    65  )
    66  
    67  // MaxGRPCMessageSize contains max grpc message size
    68  var MaxGRPCMessageSize = env.ParseNumFromEnv(common.EnvGRPCMaxSizeMB, 200, 0, math.MaxInt32) * 1024 * 1024
    69  
    70  // Client defines an interface for interaction with an Argo CD server.
    71  type Client interface {
    72  	ClientOptions() ClientOptions
    73  	HTTPClient() (*http.Client, error)
    74  	OIDCConfig(context.Context, *settingspkg.Settings) (*oauth2.Config, *oidc.Provider, error)
    75  	NewRepoClient() (io.Closer, repositorypkg.RepositoryServiceClient, error)
    76  	NewRepoClientOrDie() (io.Closer, repositorypkg.RepositoryServiceClient)
    77  	NewRepoCredsClient() (io.Closer, repocredspkg.RepoCredsServiceClient, error)
    78  	NewRepoCredsClientOrDie() (io.Closer, repocredspkg.RepoCredsServiceClient)
    79  	NewCertClient() (io.Closer, certificatepkg.CertificateServiceClient, error)
    80  	NewCertClientOrDie() (io.Closer, certificatepkg.CertificateServiceClient)
    81  	NewClusterClient() (io.Closer, clusterpkg.ClusterServiceClient, error)
    82  	NewClusterClientOrDie() (io.Closer, clusterpkg.ClusterServiceClient)
    83  	NewGPGKeyClient() (io.Closer, gpgkeypkg.GPGKeyServiceClient, error)
    84  	NewGPGKeyClientOrDie() (io.Closer, gpgkeypkg.GPGKeyServiceClient)
    85  	NewApplicationClient() (io.Closer, applicationpkg.ApplicationServiceClient, error)
    86  	NewApplicationSetClient() (io.Closer, applicationsetpkg.ApplicationSetServiceClient, error)
    87  	NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient)
    88  	NewApplicationSetClientOrDie() (io.Closer, applicationsetpkg.ApplicationSetServiceClient)
    89  	NewNotificationClient() (io.Closer, notificationpkg.NotificationServiceClient, error)
    90  	NewNotificationClientOrDie() (io.Closer, notificationpkg.NotificationServiceClient)
    91  	NewSessionClient() (io.Closer, sessionpkg.SessionServiceClient, error)
    92  	NewSessionClientOrDie() (io.Closer, sessionpkg.SessionServiceClient)
    93  	NewSettingsClient() (io.Closer, settingspkg.SettingsServiceClient, error)
    94  	NewSettingsClientOrDie() (io.Closer, settingspkg.SettingsServiceClient)
    95  	NewVersionClient() (io.Closer, versionpkg.VersionServiceClient, error)
    96  	NewVersionClientOrDie() (io.Closer, versionpkg.VersionServiceClient)
    97  	NewProjectClient() (io.Closer, projectpkg.ProjectServiceClient, error)
    98  	NewProjectClientOrDie() (io.Closer, projectpkg.ProjectServiceClient)
    99  	NewAccountClient() (io.Closer, accountpkg.AccountServiceClient, error)
   100  	NewAccountClientOrDie() (io.Closer, accountpkg.AccountServiceClient)
   101  	WatchApplicationWithRetry(ctx context.Context, appName string, revision string) chan *v1alpha1.ApplicationWatchEvent
   102  }
   103  
   104  // ClientOptions hold address, security, and other settings for the API client.
   105  type ClientOptions struct {
   106  	ServerAddr           string
   107  	PlainText            bool
   108  	Insecure             bool
   109  	CertFile             string
   110  	ClientCertFile       string
   111  	ClientCertKeyFile    string
   112  	AuthToken            string
   113  	ConfigPath           string
   114  	Context              string
   115  	UserAgent            string
   116  	GRPCWeb              bool
   117  	GRPCWebRootPath      string
   118  	Core                 bool
   119  	PortForward          bool
   120  	PortForwardNamespace string
   121  	Headers              []string
   122  	HttpRetryMax         int //nolint:revive //FIXME(var-naming)
   123  	KubeOverrides        *clientcmd.ConfigOverrides
   124  	AppControllerName    string
   125  	ServerName           string
   126  	RedisHaProxyName     string
   127  	RedisName            string
   128  	RedisCompression     string
   129  	RepoServerName       string
   130  	PromptsEnabled       bool
   131  }
   132  
   133  type client struct {
   134  	ServerAddr      string
   135  	PlainText       bool
   136  	Insecure        bool
   137  	CertPEMData     []byte
   138  	ClientCert      *tls.Certificate
   139  	AuthToken       string
   140  	RefreshToken    string
   141  	UserAgent       string
   142  	GRPCWeb         bool
   143  	GRPCWebRootPath string
   144  	Headers         []string
   145  
   146  	proxyMutex      *sync.Mutex
   147  	proxyListener   net.Listener
   148  	proxyServer     *grpc.Server
   149  	proxyUsersCount int
   150  	httpClient      *http.Client
   151  }
   152  
   153  // NewClient creates a new API client from a set of config options.
   154  func NewClient(opts *ClientOptions) (Client, error) {
   155  	var c client
   156  	localCfg, err := localconfig.ReadLocalConfig(opts.ConfigPath)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	c.proxyMutex = &sync.Mutex{}
   161  	var ctxName string
   162  	if localCfg != nil {
   163  		configCtx, err := localCfg.ResolveContext(opts.Context)
   164  		if err != nil {
   165  			return nil, err
   166  		}
   167  		if configCtx != nil {
   168  			c.ServerAddr = configCtx.Server.Server
   169  			if configCtx.Server.CACertificateAuthorityData != "" {
   170  				c.CertPEMData, err = base64.StdEncoding.DecodeString(configCtx.Server.CACertificateAuthorityData)
   171  				if err != nil {
   172  					return nil, err
   173  				}
   174  			}
   175  			if configCtx.Server.ClientCertificateData != "" && configCtx.Server.ClientCertificateKeyData != "" {
   176  				clientCertData, err := base64.StdEncoding.DecodeString(configCtx.Server.ClientCertificateData)
   177  				if err != nil {
   178  					return nil, err
   179  				}
   180  				clientCertKeyData, err := base64.StdEncoding.DecodeString(configCtx.Server.ClientCertificateKeyData)
   181  				if err != nil {
   182  					return nil, err
   183  				}
   184  				clientCert, err := tls.X509KeyPair(clientCertData, clientCertKeyData)
   185  				if err != nil {
   186  					return nil, err
   187  				}
   188  				c.ClientCert = &clientCert
   189  			} else if configCtx.Server.ClientCertificateData != "" || configCtx.Server.ClientCertificateKeyData != "" {
   190  				return nil, errors.New("ClientCertificateData and ClientCertificateKeyData must always be specified together")
   191  			}
   192  			c.PlainText = configCtx.Server.PlainText
   193  			c.Insecure = configCtx.Server.Insecure
   194  			c.GRPCWeb = configCtx.Server.GRPCWeb
   195  			c.GRPCWebRootPath = configCtx.Server.GRPCWebRootPath
   196  			c.AuthToken = configCtx.User.AuthToken
   197  			c.RefreshToken = configCtx.User.RefreshToken
   198  			ctxName = configCtx.Name
   199  		}
   200  	}
   201  	if opts.UserAgent != "" {
   202  		c.UserAgent = opts.UserAgent
   203  	} else {
   204  		c.UserAgent = fmt.Sprintf("%s/%s", common.ArgoCDUserAgentName, common.GetVersion().Version)
   205  	}
   206  	// Override server address if specified in env or CLI flag
   207  	c.ServerAddr = env.StringFromEnv(EnvArgoCDServer, c.ServerAddr)
   208  	if opts.PortForward || opts.PortForwardNamespace != "" {
   209  		if opts.KubeOverrides == nil {
   210  			opts.KubeOverrides = &clientcmd.ConfigOverrides{}
   211  		}
   212  		serverPodLabelSelector := common.LabelKeyAppName + "=" + opts.ServerName
   213  		port, err := kube.PortForward(8080, opts.PortForwardNamespace, opts.KubeOverrides, serverPodLabelSelector)
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  		opts.ServerAddr = fmt.Sprintf("127.0.0.1:%d", port)
   218  		opts.Insecure = true
   219  	}
   220  	if opts.ServerAddr != "" {
   221  		c.ServerAddr = opts.ServerAddr
   222  	}
   223  	// Make sure we got the server address and auth token from somewhere
   224  	if c.ServerAddr == "" {
   225  		//nolint:staticcheck // First letter of error is intentionally capitalized.
   226  		return nil, errors.New("Argo CD server address unspecified")
   227  	}
   228  	// Override auth-token if specified in env variable or CLI flag
   229  	c.AuthToken = env.StringFromEnv(EnvArgoCDAuthToken, c.AuthToken)
   230  	if opts.AuthToken != "" {
   231  		c.AuthToken = strings.TrimSpace(opts.AuthToken)
   232  	}
   233  	// Override certificate data if specified from CLI flag
   234  	if opts.CertFile != "" {
   235  		b, err := os.ReadFile(opts.CertFile)
   236  		if err != nil {
   237  			return nil, err
   238  		}
   239  		c.CertPEMData = b
   240  	}
   241  	// Override client certificate data if specified from CLI flag
   242  	if opts.ClientCertFile != "" && opts.ClientCertKeyFile != "" {
   243  		clientCert, err := tls.LoadX509KeyPair(opts.ClientCertFile, opts.ClientCertKeyFile)
   244  		if err != nil {
   245  			return nil, err
   246  		}
   247  		c.ClientCert = &clientCert
   248  	} else if opts.ClientCertFile != "" || opts.ClientCertKeyFile != "" {
   249  		return nil, errors.New("--client-crt and --client-crt-key must always be specified together")
   250  	}
   251  	// Override insecure/plaintext options if specified from CLI
   252  	if opts.PlainText {
   253  		c.PlainText = true
   254  	}
   255  	if opts.Insecure {
   256  		c.Insecure = true
   257  	}
   258  	if opts.GRPCWeb {
   259  		c.GRPCWeb = true
   260  	}
   261  	if opts.GRPCWebRootPath != "" {
   262  		c.GRPCWebRootPath = opts.GRPCWebRootPath
   263  	}
   264  
   265  	if opts.HttpRetryMax > 0 {
   266  		retryClient := retryablehttp.NewClient()
   267  		retryClient.RetryMax = opts.HttpRetryMax
   268  		c.httpClient = retryClient.StandardClient()
   269  	} else {
   270  		c.httpClient = &http.Client{}
   271  	}
   272  
   273  	if !c.PlainText {
   274  		tlsConfig, err := c.tlsConfig()
   275  		if err != nil {
   276  			return nil, err
   277  		}
   278  		c.httpClient.Transport = &http.Transport{
   279  			TLSClientConfig: tlsConfig,
   280  		}
   281  	}
   282  	if !c.GRPCWeb {
   283  		if parts := strings.Split(c.ServerAddr, ":"); len(parts) == 1 {
   284  			// If port is unspecified, assume the most likely port
   285  			c.ServerAddr += ":443"
   286  		}
   287  		// test if we need to set it to true
   288  		// if a call to grpc failed, then try again with GRPCWeb
   289  		conn, versionIf, err := c.NewVersionClient()
   290  		if err == nil {
   291  			defer utilio.Close(conn)
   292  			_, err = versionIf.Version(context.Background(), &empty.Empty{})
   293  		}
   294  		if err != nil {
   295  			c.GRPCWeb = true
   296  			conn, versionIf := c.NewVersionClientOrDie()
   297  			defer utilio.Close(conn)
   298  
   299  			_, err := versionIf.Version(context.Background(), &empty.Empty{})
   300  			if err == nil {
   301  				log.Warnf("Failed to invoke grpc call. Use flag --grpc-web in grpc calls. To avoid this warning message, use flag --grpc-web.")
   302  			} else {
   303  				c.GRPCWeb = false
   304  			}
   305  		}
   306  	}
   307  	if localCfg != nil {
   308  		err = c.refreshAuthToken(localCfg, ctxName, opts.ConfigPath)
   309  		if err != nil {
   310  			return nil, err
   311  		}
   312  	}
   313  	c.Headers = opts.Headers
   314  
   315  	return &c, nil
   316  }
   317  
   318  // OIDCConfig returns OAuth2 client config and a OpenID Provider based on Argo CD settings
   319  // ctx can hold an appropriate http.Client to use for the exchange
   320  func (c *client) OIDCConfig(ctx context.Context, set *settingspkg.Settings) (*oauth2.Config, *oidc.Provider, error) {
   321  	var clientID string
   322  	var issuerURL string
   323  	var scopes []string
   324  	switch {
   325  	case set.OIDCConfig != nil && set.OIDCConfig.Issuer != "":
   326  		if set.OIDCConfig.CLIClientID != "" {
   327  			clientID = set.OIDCConfig.CLIClientID
   328  		} else {
   329  			clientID = set.OIDCConfig.ClientID
   330  		}
   331  		issuerURL = set.OIDCConfig.Issuer
   332  		scopes = oidcutil.GetScopesOrDefault(set.OIDCConfig.Scopes)
   333  	case set.DexConfig != nil && len(set.DexConfig.Connectors) > 0:
   334  		clientID = common.ArgoCDCLIClientAppID
   335  		scopes = append(oidcutil.GetScopesOrDefault(nil), common.DexFederatedScope)
   336  		issuerURL = fmt.Sprintf("%s%s", set.URL, common.DexAPIEndpoint)
   337  	default:
   338  		return nil, nil, fmt.Errorf("%s is not configured with SSO", c.ServerAddr)
   339  	}
   340  	provider, err := oidc.NewProvider(ctx, issuerURL)
   341  	if err != nil {
   342  		return nil, nil, fmt.Errorf("failed to query provider %q: %w", issuerURL, err)
   343  	}
   344  	oidcConf, err := oidcutil.ParseConfig(provider)
   345  	if err != nil {
   346  		return nil, nil, fmt.Errorf("failed to parse provider config: %w", err)
   347  	}
   348  	if oidcutil.OfflineAccess(oidcConf.ScopesSupported) {
   349  		scopes = append(scopes, oidc.ScopeOfflineAccess)
   350  	}
   351  	oauth2conf := oauth2.Config{
   352  		ClientID: clientID,
   353  		Scopes:   scopes,
   354  		Endpoint: provider.Endpoint(),
   355  	}
   356  	return &oauth2conf, provider, nil
   357  }
   358  
   359  // HTTPClient returns a HTTPClient appropriate for performing OAuth, based on TLS settings
   360  func (c *client) HTTPClient() (*http.Client, error) {
   361  	tlsConfig, err := c.tlsConfig()
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  
   366  	headers, err := parseHeaders(c.Headers)
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  
   371  	if c.UserAgent != "" {
   372  		headers.Set("User-Agent", c.UserAgent)
   373  	}
   374  
   375  	return &http.Client{
   376  		Transport: &http_util.TransportWithHeader{
   377  			RoundTripper: &http.Transport{
   378  				TLSClientConfig: tlsConfig,
   379  				Proxy:           http.ProxyFromEnvironment,
   380  				Dial: (&net.Dialer{
   381  					Timeout:   30 * time.Second,
   382  					KeepAlive: 30 * time.Second,
   383  				}).Dial,
   384  				TLSHandshakeTimeout:   10 * time.Second,
   385  				ExpectContinueTimeout: 1 * time.Second,
   386  			},
   387  			Header: headers,
   388  		},
   389  	}, nil
   390  }
   391  
   392  // refreshAuthToken refreshes a JWT auth token if it is invalid (e.g. expired)
   393  func (c *client) refreshAuthToken(localCfg *localconfig.LocalConfig, ctxName, configPath string) error {
   394  	if c.RefreshToken == "" {
   395  		// If we have no refresh token, there's no point in doing anything
   396  		return nil
   397  	}
   398  	configCtx, err := localCfg.ResolveContext(ctxName)
   399  	if err != nil {
   400  		return err
   401  	}
   402  	parser := jwt.NewParser(jwt.WithoutClaimsValidation())
   403  	var claims jwt.RegisteredClaims
   404  	_, _, err = parser.ParseUnverified(configCtx.User.AuthToken, &claims)
   405  	if err != nil {
   406  		return err
   407  	}
   408  	validator := jwt.NewValidator()
   409  	if validator.Validate(claims) == nil {
   410  		// token is still valid
   411  		return nil
   412  	}
   413  
   414  	log.Debug("Auth token no longer valid. Refreshing")
   415  	rawIDToken, refreshToken, err := c.redeemRefreshToken()
   416  	if err != nil {
   417  		return err
   418  	}
   419  	c.AuthToken = rawIDToken
   420  	c.RefreshToken = refreshToken
   421  	localCfg.UpsertUser(localconfig.User{
   422  		Name:         ctxName,
   423  		AuthToken:    c.AuthToken,
   424  		RefreshToken: c.RefreshToken,
   425  	})
   426  	err = localconfig.WriteLocalConfig(*localCfg, configPath)
   427  	if err != nil {
   428  		return err
   429  	}
   430  	return nil
   431  }
   432  
   433  // redeemRefreshToken performs the exchange of a refresh_token for a new id_token and refresh_token
   434  func (c *client) redeemRefreshToken() (string, string, error) {
   435  	setConn, setIf, err := c.NewSettingsClient()
   436  	if err != nil {
   437  		return "", "", err
   438  	}
   439  	defer func() { _ = setConn.Close() }()
   440  	httpClient, err := c.HTTPClient()
   441  	if err != nil {
   442  		return "", "", err
   443  	}
   444  	ctx := oidc.ClientContext(context.Background(), httpClient)
   445  	acdSet, err := setIf.Get(ctx, &settingspkg.SettingsQuery{})
   446  	if err != nil {
   447  		return "", "", err
   448  	}
   449  	oauth2conf, _, err := c.OIDCConfig(ctx, acdSet)
   450  	if err != nil {
   451  		return "", "", err
   452  	}
   453  	t := &oauth2.Token{
   454  		RefreshToken: c.RefreshToken,
   455  	}
   456  	token, err := oauth2conf.TokenSource(ctx, t).Token()
   457  	if err != nil {
   458  		return "", "", err
   459  	}
   460  	rawIDToken, ok := token.Extra("id_token").(string)
   461  	if !ok {
   462  		return "", "", errors.New("no id_token in token response")
   463  	}
   464  	refreshToken, _ := token.Extra("refresh_token").(string)
   465  	return rawIDToken, refreshToken, nil
   466  }
   467  
   468  // NewClientOrDie creates a new API client from a set of config options, or fails fatally if the new client creation fails.
   469  func NewClientOrDie(opts *ClientOptions) Client {
   470  	client, err := NewClient(opts)
   471  	if err != nil {
   472  		log.Fatal(err)
   473  	}
   474  	return client
   475  }
   476  
   477  // JwtCredentials implements the gRPC credentials.Credentials interface which we is used to do
   478  // grpc.WithPerRPCCredentials(), for authentication
   479  type jwtCredentials struct {
   480  	Token string
   481  }
   482  
   483  func (c jwtCredentials) RequireTransportSecurity() bool {
   484  	return false
   485  }
   486  
   487  func (c jwtCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
   488  	return map[string]string{
   489  		MetaDataTokenKey: c.Token,
   490  	}, nil
   491  }
   492  
   493  func (c *client) newConn() (*grpc.ClientConn, io.Closer, error) {
   494  	closers := make([]io.Closer, 0)
   495  	serverAddr := c.ServerAddr
   496  	network := "tcp"
   497  	if c.GRPCWeb || c.GRPCWebRootPath != "" {
   498  		// start local grpc server which proxies requests using grpc-web protocol
   499  		addr, closer, err := c.useGRPCProxy()
   500  		if err != nil {
   501  			return nil, nil, err
   502  		}
   503  		network = addr.Network()
   504  		serverAddr = addr.String()
   505  		closers = append(closers, closer)
   506  	}
   507  
   508  	var creds credentials.TransportCredentials
   509  	if !c.PlainText && !c.GRPCWeb && c.GRPCWebRootPath == "" {
   510  		tlsConfig, err := c.tlsConfig()
   511  		if err != nil {
   512  			return nil, nil, err
   513  		}
   514  		creds = credentials.NewTLS(tlsConfig)
   515  	}
   516  	endpointCredentials := jwtCredentials{
   517  		Token: c.AuthToken,
   518  	}
   519  	retryOpts := []grpc_retry.CallOption{
   520  		grpc_retry.WithMax(3),
   521  		grpc_retry.WithBackoff(grpc_retry.BackoffLinear(1000 * time.Millisecond)),
   522  	}
   523  	var dialOpts []grpc.DialOption
   524  	dialOpts = append(dialOpts, grpc.WithPerRPCCredentials(endpointCredentials))
   525  	dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)))
   526  	dialOpts = append(dialOpts, grpc.WithStreamInterceptor(grpc_util.RetryOnlyForServerStreamInterceptor(retryOpts...)))
   527  	dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpts...)))
   528  	dialOpts = append(dialOpts, grpc.WithStatsHandler(otelgrpc.NewClientHandler()))
   529  
   530  	ctx := context.Background()
   531  
   532  	headers, err := parseHeaders(c.Headers)
   533  	if err != nil {
   534  		return nil, nil, err
   535  	}
   536  	for k, vs := range headers {
   537  		for _, v := range vs {
   538  			ctx = metadata.AppendToOutgoingContext(ctx, k, v)
   539  		}
   540  	}
   541  
   542  	if c.UserAgent != "" {
   543  		dialOpts = append(dialOpts, grpc.WithUserAgent(c.UserAgent))
   544  	}
   545  	conn, e := grpc_util.BlockingNewClient(ctx, network, serverAddr, creds, dialOpts...)
   546  	closers = append(closers, conn)
   547  	return conn, utilio.NewCloser(func() error {
   548  		var firstErr error
   549  		for i := range closers {
   550  			err := closers[i].Close()
   551  			if err != nil {
   552  				firstErr = err
   553  			}
   554  		}
   555  		return firstErr
   556  	}), e
   557  }
   558  
   559  func (c *client) tlsConfig() (*tls.Config, error) {
   560  	var tlsConfig tls.Config
   561  	if len(c.CertPEMData) > 0 {
   562  		cp := tls_util.BestEffortSystemCertPool()
   563  		if !cp.AppendCertsFromPEM(c.CertPEMData) {
   564  			return nil, errors.New("credentials: failed to append certificates")
   565  		}
   566  		tlsConfig.RootCAs = cp
   567  	}
   568  	if c.ClientCert != nil {
   569  		tlsConfig.Certificates = append(tlsConfig.Certificates, *c.ClientCert)
   570  	}
   571  	if c.Insecure {
   572  		tlsConfig.InsecureSkipVerify = true
   573  	}
   574  	return &tlsConfig, nil
   575  }
   576  
   577  func (c *client) ClientOptions() ClientOptions {
   578  	return ClientOptions{
   579  		ServerAddr: c.ServerAddr,
   580  		PlainText:  c.PlainText,
   581  		Insecure:   c.Insecure,
   582  		AuthToken:  c.AuthToken,
   583  	}
   584  }
   585  
   586  func (c *client) NewRepoClient() (io.Closer, repositorypkg.RepositoryServiceClient, error) {
   587  	conn, closer, err := c.newConn()
   588  	if err != nil {
   589  		return nil, nil, err
   590  	}
   591  	repoIf := repositorypkg.NewRepositoryServiceClient(conn)
   592  	return closer, repoIf, nil
   593  }
   594  
   595  func (c *client) NewRepoClientOrDie() (io.Closer, repositorypkg.RepositoryServiceClient) {
   596  	conn, repoIf, err := c.NewRepoClient()
   597  	if err != nil {
   598  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   599  	}
   600  	return conn, repoIf
   601  }
   602  
   603  func (c *client) NewRepoCredsClient() (io.Closer, repocredspkg.RepoCredsServiceClient, error) {
   604  	conn, closer, err := c.newConn()
   605  	if err != nil {
   606  		return nil, nil, err
   607  	}
   608  	repoIf := repocredspkg.NewRepoCredsServiceClient(conn)
   609  	return closer, repoIf, nil
   610  }
   611  
   612  func (c *client) NewRepoCredsClientOrDie() (io.Closer, repocredspkg.RepoCredsServiceClient) {
   613  	conn, repoIf, err := c.NewRepoCredsClient()
   614  	if err != nil {
   615  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   616  	}
   617  	return conn, repoIf
   618  }
   619  
   620  func (c *client) NewCertClient() (io.Closer, certificatepkg.CertificateServiceClient, error) {
   621  	conn, closer, err := c.newConn()
   622  	if err != nil {
   623  		return nil, nil, err
   624  	}
   625  	certIf := certificatepkg.NewCertificateServiceClient(conn)
   626  	return closer, certIf, nil
   627  }
   628  
   629  func (c *client) NewCertClientOrDie() (io.Closer, certificatepkg.CertificateServiceClient) {
   630  	conn, certIf, err := c.NewCertClient()
   631  	if err != nil {
   632  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   633  	}
   634  	return conn, certIf
   635  }
   636  
   637  func (c *client) NewClusterClient() (io.Closer, clusterpkg.ClusterServiceClient, error) {
   638  	conn, closer, err := c.newConn()
   639  	if err != nil {
   640  		return nil, nil, err
   641  	}
   642  	clusterIf := clusterpkg.NewClusterServiceClient(conn)
   643  	return closer, clusterIf, nil
   644  }
   645  
   646  func (c *client) NewClusterClientOrDie() (io.Closer, clusterpkg.ClusterServiceClient) {
   647  	conn, clusterIf, err := c.NewClusterClient()
   648  	if err != nil {
   649  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   650  	}
   651  	return conn, clusterIf
   652  }
   653  
   654  func (c *client) NewGPGKeyClient() (io.Closer, gpgkeypkg.GPGKeyServiceClient, error) {
   655  	conn, closer, err := c.newConn()
   656  	if err != nil {
   657  		return nil, nil, err
   658  	}
   659  	gpgkeyIf := gpgkeypkg.NewGPGKeyServiceClient(conn)
   660  	return closer, gpgkeyIf, nil
   661  }
   662  
   663  func (c *client) NewGPGKeyClientOrDie() (io.Closer, gpgkeypkg.GPGKeyServiceClient) {
   664  	conn, gpgkeyIf, err := c.NewGPGKeyClient()
   665  	if err != nil {
   666  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   667  	}
   668  	return conn, gpgkeyIf
   669  }
   670  
   671  func (c *client) NewApplicationClient() (io.Closer, applicationpkg.ApplicationServiceClient, error) {
   672  	conn, closer, err := c.newConn()
   673  	if err != nil {
   674  		return nil, nil, err
   675  	}
   676  	appIf := applicationpkg.NewApplicationServiceClient(conn)
   677  	return closer, appIf, nil
   678  }
   679  
   680  func (c *client) NewApplicationSetClient() (io.Closer, applicationsetpkg.ApplicationSetServiceClient, error) {
   681  	conn, closer, err := c.newConn()
   682  	if err != nil {
   683  		return nil, nil, err
   684  	}
   685  	appIf := applicationsetpkg.NewApplicationSetServiceClient(conn)
   686  	return closer, appIf, nil
   687  }
   688  
   689  func (c *client) NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient) {
   690  	conn, appIf, err := c.NewApplicationClient()
   691  	if err != nil {
   692  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   693  	}
   694  	return conn, appIf
   695  }
   696  
   697  func (c *client) NewNotificationClient() (io.Closer, notificationpkg.NotificationServiceClient, error) {
   698  	conn, closer, err := c.newConn()
   699  	if err != nil {
   700  		return nil, nil, err
   701  	}
   702  	notifIf := notificationpkg.NewNotificationServiceClient(conn)
   703  	return closer, notifIf, nil
   704  }
   705  
   706  func (c *client) NewNotificationClientOrDie() (io.Closer, notificationpkg.NotificationServiceClient) {
   707  	conn, notifIf, err := c.NewNotificationClient()
   708  	if err != nil {
   709  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   710  	}
   711  	return conn, notifIf
   712  }
   713  
   714  func (c *client) NewApplicationSetClientOrDie() (io.Closer, applicationsetpkg.ApplicationSetServiceClient) {
   715  	conn, repoIf, err := c.NewApplicationSetClient()
   716  	if err != nil {
   717  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   718  	}
   719  	return conn, repoIf
   720  }
   721  
   722  func (c *client) NewSessionClient() (io.Closer, sessionpkg.SessionServiceClient, error) {
   723  	conn, closer, err := c.newConn()
   724  	if err != nil {
   725  		return nil, nil, err
   726  	}
   727  	sessionIf := sessionpkg.NewSessionServiceClient(conn)
   728  	return closer, sessionIf, nil
   729  }
   730  
   731  func (c *client) NewSessionClientOrDie() (io.Closer, sessionpkg.SessionServiceClient) {
   732  	conn, sessionIf, err := c.NewSessionClient()
   733  	if err != nil {
   734  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   735  	}
   736  	return conn, sessionIf
   737  }
   738  
   739  func (c *client) NewSettingsClient() (io.Closer, settingspkg.SettingsServiceClient, error) {
   740  	conn, closer, err := c.newConn()
   741  	if err != nil {
   742  		return nil, nil, err
   743  	}
   744  	setIf := settingspkg.NewSettingsServiceClient(conn)
   745  	return closer, setIf, nil
   746  }
   747  
   748  func (c *client) NewSettingsClientOrDie() (io.Closer, settingspkg.SettingsServiceClient) {
   749  	conn, setIf, err := c.NewSettingsClient()
   750  	if err != nil {
   751  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   752  	}
   753  	return conn, setIf
   754  }
   755  
   756  func (c *client) NewVersionClient() (io.Closer, versionpkg.VersionServiceClient, error) {
   757  	conn, closer, err := c.newConn()
   758  	if err != nil {
   759  		return nil, nil, err
   760  	}
   761  	versionIf := versionpkg.NewVersionServiceClient(conn)
   762  	return closer, versionIf, nil
   763  }
   764  
   765  func (c *client) NewVersionClientOrDie() (io.Closer, versionpkg.VersionServiceClient) {
   766  	conn, versionIf, err := c.NewVersionClient()
   767  	if err != nil {
   768  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   769  	}
   770  	return conn, versionIf
   771  }
   772  
   773  func (c *client) NewProjectClient() (io.Closer, projectpkg.ProjectServiceClient, error) {
   774  	conn, closer, err := c.newConn()
   775  	if err != nil {
   776  		return nil, nil, err
   777  	}
   778  	projIf := projectpkg.NewProjectServiceClient(conn)
   779  	return closer, projIf, nil
   780  }
   781  
   782  func (c *client) NewProjectClientOrDie() (io.Closer, projectpkg.ProjectServiceClient) {
   783  	conn, projIf, err := c.NewProjectClient()
   784  	if err != nil {
   785  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   786  	}
   787  	return conn, projIf
   788  }
   789  
   790  func (c *client) NewAccountClient() (io.Closer, accountpkg.AccountServiceClient, error) {
   791  	conn, closer, err := c.newConn()
   792  	if err != nil {
   793  		return nil, nil, err
   794  	}
   795  	usrIf := accountpkg.NewAccountServiceClient(conn)
   796  	return closer, usrIf, nil
   797  }
   798  
   799  func (c *client) NewAccountClientOrDie() (io.Closer, accountpkg.AccountServiceClient) {
   800  	conn, usrIf, err := c.NewAccountClient()
   801  	if err != nil {
   802  		log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
   803  	}
   804  	return conn, usrIf
   805  }
   806  
   807  // WatchApplicationWithRetry returns a channel of watch events for an application, retrying the
   808  // watch upon errors. Closes the returned channel when the context is cancelled.
   809  func (c *client) WatchApplicationWithRetry(ctx context.Context, appName string, revision string) chan *v1alpha1.ApplicationWatchEvent {
   810  	appEventsCh := make(chan *v1alpha1.ApplicationWatchEvent)
   811  	cancelled := false
   812  	appName, appNs := argo.ParseFromQualifiedName(appName, "")
   813  	go func() {
   814  		defer close(appEventsCh)
   815  		for !cancelled {
   816  			conn, appIf, err := c.NewApplicationClient()
   817  			if err == nil {
   818  				var wc applicationpkg.ApplicationService_WatchClient
   819  				wc, err = appIf.Watch(ctx, &applicationpkg.ApplicationQuery{
   820  					Name:            &appName,
   821  					AppNamespace:    &appNs,
   822  					ResourceVersion: &revision,
   823  				})
   824  				if err == nil {
   825  					for {
   826  						var appEvent *v1alpha1.ApplicationWatchEvent
   827  						appEvent, err = wc.Recv()
   828  						if err != nil {
   829  							break
   830  						}
   831  						revision = appEvent.Application.ResourceVersion
   832  						appEventsCh <- appEvent
   833  					}
   834  				}
   835  			}
   836  			if err != nil {
   837  				if isCanceledContextErr(err) {
   838  					cancelled = true
   839  				} else {
   840  					time.Sleep(1 * time.Second)
   841  				}
   842  			}
   843  			if conn != nil {
   844  				_ = conn.Close()
   845  			}
   846  		}
   847  	}()
   848  	return appEventsCh
   849  }
   850  
   851  func isCanceledContextErr(err error) bool {
   852  	if err != nil && errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
   853  		return true
   854  	}
   855  	if stat, ok := status.FromError(err); ok {
   856  		if stat.Code() == codes.Canceled || stat.Code() == codes.DeadlineExceeded {
   857  			return true
   858  		}
   859  	}
   860  	return false
   861  }
   862  
   863  func parseHeaders(headerStrings []string) (http.Header, error) {
   864  	headers := http.Header{}
   865  	for _, kv := range headerStrings {
   866  		i := strings.IndexByte(kv, ':')
   867  		// zero means meaningless empty header name
   868  		if i <= 0 {
   869  			return nil, fmt.Errorf("additional headers must be colon(:)-separated: %s", kv)
   870  		}
   871  		headers.Add(kv[0:i], kv[i+1:])
   872  	}
   873  	return headers, nil
   874  }