github.com/argoproj/argo-cd/v2@v2.10.9/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/v4"
    20  	"github.com/golang/protobuf/ptypes/empty"
    21  	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
    22  	grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
    23  	"github.com/hashicorp/go-retryablehttp"
    24  	log "github.com/sirupsen/logrus"
    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/v2/common"
    34  	accountpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/account"
    35  	applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
    36  	applicationsetpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/applicationset"
    37  	certificatepkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/certificate"
    38  	clusterpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
    39  	gpgkeypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/gpgkey"
    40  	notificationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/notification"
    41  	projectpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
    42  	repocredspkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repocreds"
    43  	repositorypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
    44  	sessionpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
    45  	settingspkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/settings"
    46  	versionpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/version"
    47  	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    48  	"github.com/argoproj/argo-cd/v2/util/argo"
    49  	"github.com/argoproj/argo-cd/v2/util/env"
    50  	grpc_util "github.com/argoproj/argo-cd/v2/util/grpc"
    51  	http_util "github.com/argoproj/argo-cd/v2/util/http"
    52  	argoio "github.com/argoproj/argo-cd/v2/util/io"
    53  	"github.com/argoproj/argo-cd/v2/util/kube"
    54  	"github.com/argoproj/argo-cd/v2/util/localconfig"
    55  	oidcutil "github.com/argoproj/argo-cd/v2/util/oidc"
    56  	tls_util "github.com/argoproj/argo-cd/v2/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  	// EnvArgoCDgRPCMaxSizeMB is the environment variable to look for a max gRPC message size
    66  	EnvArgoCDgRPCMaxSizeMB = "ARGOCD_GRPC_MAX_SIZE_MB"
    67  )
    68  
    69  var (
    70  	// MaxGRPCMessageSize contains max grpc message size
    71  	MaxGRPCMessageSize = env.ParseNumFromEnv(EnvArgoCDgRPCMaxSizeMB, 200, 0, math.MaxInt32) * 1024 * 1024
    72  )
    73  
    74  // Client defines an interface for interaction with an Argo CD server.
    75  type Client interface {
    76  	ClientOptions() ClientOptions
    77  	HTTPClient() (*http.Client, error)
    78  	OIDCConfig(context.Context, *settingspkg.Settings) (*oauth2.Config, *oidc.Provider, error)
    79  	NewRepoClient() (io.Closer, repositorypkg.RepositoryServiceClient, error)
    80  	NewRepoClientOrDie() (io.Closer, repositorypkg.RepositoryServiceClient)
    81  	NewRepoCredsClient() (io.Closer, repocredspkg.RepoCredsServiceClient, error)
    82  	NewRepoCredsClientOrDie() (io.Closer, repocredspkg.RepoCredsServiceClient)
    83  	NewCertClient() (io.Closer, certificatepkg.CertificateServiceClient, error)
    84  	NewCertClientOrDie() (io.Closer, certificatepkg.CertificateServiceClient)
    85  	NewClusterClient() (io.Closer, clusterpkg.ClusterServiceClient, error)
    86  	NewClusterClientOrDie() (io.Closer, clusterpkg.ClusterServiceClient)
    87  	NewGPGKeyClient() (io.Closer, gpgkeypkg.GPGKeyServiceClient, error)
    88  	NewGPGKeyClientOrDie() (io.Closer, gpgkeypkg.GPGKeyServiceClient)
    89  	NewApplicationClient() (io.Closer, applicationpkg.ApplicationServiceClient, error)
    90  	NewApplicationSetClient() (io.Closer, applicationsetpkg.ApplicationSetServiceClient, error)
    91  	NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient)
    92  	NewApplicationSetClientOrDie() (io.Closer, applicationsetpkg.ApplicationSetServiceClient)
    93  	NewNotificationClient() (io.Closer, notificationpkg.NotificationServiceClient, error)
    94  	NewNotificationClientOrDie() (io.Closer, notificationpkg.NotificationServiceClient)
    95  	NewSessionClient() (io.Closer, sessionpkg.SessionServiceClient, error)
    96  	NewSessionClientOrDie() (io.Closer, sessionpkg.SessionServiceClient)
    97  	NewSettingsClient() (io.Closer, settingspkg.SettingsServiceClient, error)
    98  	NewSettingsClientOrDie() (io.Closer, settingspkg.SettingsServiceClient)
    99  	NewVersionClient() (io.Closer, versionpkg.VersionServiceClient, error)
   100  	NewVersionClientOrDie() (io.Closer, versionpkg.VersionServiceClient)
   101  	NewProjectClient() (io.Closer, projectpkg.ProjectServiceClient, error)
   102  	NewProjectClientOrDie() (io.Closer, projectpkg.ProjectServiceClient)
   103  	NewAccountClient() (io.Closer, accountpkg.AccountServiceClient, error)
   104  	NewAccountClientOrDie() (io.Closer, accountpkg.AccountServiceClient)
   105  	WatchApplicationWithRetry(ctx context.Context, appName string, revision string) chan *v1alpha1.ApplicationWatchEvent
   106  }
   107  
   108  // ClientOptions hold address, security, and other settings for the API client.
   109  type ClientOptions struct {
   110  	ServerAddr           string
   111  	PlainText            bool
   112  	Insecure             bool
   113  	CertFile             string
   114  	ClientCertFile       string
   115  	ClientCertKeyFile    string
   116  	AuthToken            string
   117  	ConfigPath           string
   118  	Context              string
   119  	UserAgent            string
   120  	GRPCWeb              bool
   121  	GRPCWebRootPath      string
   122  	Core                 bool
   123  	PortForward          bool
   124  	PortForwardNamespace string
   125  	Headers              []string
   126  	HttpRetryMax         int
   127  	KubeOverrides        *clientcmd.ConfigOverrides
   128  	AppControllerName    string
   129  	ServerName           string
   130  	RedisHaProxyName     string
   131  	RedisName            string
   132  	RepoServerName       string
   133  }
   134  
   135  type client struct {
   136  	ServerAddr      string
   137  	PlainText       bool
   138  	Insecure        bool
   139  	CertPEMData     []byte
   140  	ClientCert      *tls.Certificate
   141  	AuthToken       string
   142  	RefreshToken    string
   143  	UserAgent       string
   144  	GRPCWeb         bool
   145  	GRPCWebRootPath string
   146  	Headers         []string
   147  
   148  	proxyMutex      *sync.Mutex
   149  	proxyListener   net.Listener
   150  	proxyServer     *grpc.Server
   151  	proxyUsersCount int
   152  	httpClient      *http.Client
   153  }
   154  
   155  // NewClient creates a new API client from a set of config options.
   156  func NewClient(opts *ClientOptions) (Client, error) {
   157  	var c client
   158  	localCfg, err := localconfig.ReadLocalConfig(opts.ConfigPath)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	c.proxyMutex = &sync.Mutex{}
   163  	var ctxName string
   164  	if localCfg != nil {
   165  		configCtx, err := localCfg.ResolveContext(opts.Context)
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  		if configCtx != nil {
   170  			c.ServerAddr = configCtx.Server.Server
   171  			if configCtx.Server.CACertificateAuthorityData != "" {
   172  				c.CertPEMData, err = base64.StdEncoding.DecodeString(configCtx.Server.CACertificateAuthorityData)
   173  				if err != nil {
   174  					return nil, err
   175  				}
   176  			}
   177  			if configCtx.Server.ClientCertificateData != "" && configCtx.Server.ClientCertificateKeyData != "" {
   178  				clientCertData, err := base64.StdEncoding.DecodeString(configCtx.Server.ClientCertificateData)
   179  				if err != nil {
   180  					return nil, err
   181  				}
   182  				clientCertKeyData, err := base64.StdEncoding.DecodeString(configCtx.Server.ClientCertificateKeyData)
   183  				if err != nil {
   184  					return nil, err
   185  				}
   186  				clientCert, err := tls.X509KeyPair(clientCertData, clientCertKeyData)
   187  				if err != nil {
   188  					return nil, err
   189  				}
   190  				c.ClientCert = &clientCert
   191  			} else if configCtx.Server.ClientCertificateData != "" || configCtx.Server.ClientCertificateKeyData != "" {
   192  				return nil, errors.New("ClientCertificateData and ClientCertificateKeyData must always be specified together")
   193  			}
   194  			c.PlainText = configCtx.Server.PlainText
   195  			c.Insecure = configCtx.Server.Insecure
   196  			c.GRPCWeb = configCtx.Server.GRPCWeb
   197  			c.GRPCWebRootPath = configCtx.Server.GRPCWebRootPath
   198  			c.AuthToken = configCtx.User.AuthToken
   199  			c.RefreshToken = configCtx.User.RefreshToken
   200  			ctxName = configCtx.Name
   201  		}
   202  	}
   203  	if opts.UserAgent != "" {
   204  		c.UserAgent = opts.UserAgent
   205  	} else {
   206  		c.UserAgent = fmt.Sprintf("%s/%s", common.ArgoCDUserAgentName, common.GetVersion().Version)
   207  	}
   208  	// Override server address if specified in env or CLI flag
   209  	c.ServerAddr = env.StringFromEnv(EnvArgoCDServer, c.ServerAddr)
   210  	if opts.PortForward || opts.PortForwardNamespace != "" {
   211  		if opts.KubeOverrides == nil {
   212  			opts.KubeOverrides = &clientcmd.ConfigOverrides{}
   213  		}
   214  		serverPodLabelSelector := common.LabelKeyAppName + "=" + opts.ServerName
   215  		port, err := kube.PortForward(8080, opts.PortForwardNamespace, opts.KubeOverrides, serverPodLabelSelector)
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  		opts.ServerAddr = fmt.Sprintf("127.0.0.1:%d", port)
   220  		opts.Insecure = true
   221  	}
   222  	if opts.ServerAddr != "" {
   223  		c.ServerAddr = opts.ServerAddr
   224  	}
   225  	// Make sure we got the server address and auth token from somewhere
   226  	if c.ServerAddr == "" {
   227  		return nil, errors.New("Argo CD server address unspecified")
   228  	}
   229  	// Override auth-token if specified in env variable or CLI flag
   230  	c.AuthToken = env.StringFromEnv(EnvArgoCDAuthToken, c.AuthToken)
   231  	if opts.AuthToken != "" {
   232  		c.AuthToken = strings.TrimSpace(opts.AuthToken)
   233  	}
   234  	// Override certificate data if specified from CLI flag
   235  	if opts.CertFile != "" {
   236  		b, err := os.ReadFile(opts.CertFile)
   237  		if err != nil {
   238  			return nil, err
   239  		}
   240  		c.CertPEMData = b
   241  	}
   242  	// Override client certificate data if specified from CLI flag
   243  	if opts.ClientCertFile != "" && opts.ClientCertKeyFile != "" {
   244  		clientCert, err := tls.LoadX509KeyPair(opts.ClientCertFile, opts.ClientCertKeyFile)
   245  		if err != nil {
   246  			return nil, err
   247  		}
   248  		c.ClientCert = &clientCert
   249  	} else if opts.ClientCertFile != "" || opts.ClientCertKeyFile != "" {
   250  		return nil, errors.New("--client-crt and --client-crt-key must always be specified together")
   251  	}
   252  	// Override insecure/plaintext options if specified from CLI
   253  	if opts.PlainText {
   254  		c.PlainText = true
   255  	}
   256  	if opts.Insecure {
   257  		c.Insecure = true
   258  	}
   259  	if opts.GRPCWeb {
   260  		c.GRPCWeb = true
   261  	}
   262  	if opts.GRPCWebRootPath != "" {
   263  		c.GRPCWebRootPath = opts.GRPCWebRootPath
   264  	}
   265  
   266  	if opts.HttpRetryMax > 0 {
   267  		retryClient := retryablehttp.NewClient()
   268  		retryClient.RetryMax = opts.HttpRetryMax
   269  		c.httpClient = retryClient.StandardClient()
   270  	} else {
   271  		c.httpClient = &http.Client{}
   272  	}
   273  
   274  	if !c.PlainText {
   275  		tlsConfig, err := c.tlsConfig()
   276  		if err != nil {
   277  			return nil, err
   278  		}
   279  		c.httpClient.Transport = &http.Transport{
   280  			TLSClientConfig: tlsConfig,
   281  		}
   282  	}
   283  	if !c.GRPCWeb {
   284  		if parts := strings.Split(c.ServerAddr, ":"); len(parts) == 1 {
   285  			// If port is unspecified, assume the most likely port
   286  			c.ServerAddr += ":443"
   287  		}
   288  		// test if we need to set it to true
   289  		// if a call to grpc failed, then try again with GRPCWeb
   290  		conn, versionIf, err := c.NewVersionClient()
   291  		if err == nil {
   292  			defer argoio.Close(conn)
   293  			_, err = versionIf.Version(context.Background(), &empty.Empty{})
   294  		}
   295  		if err != nil {
   296  			c.GRPCWeb = true
   297  			conn, versionIf := c.NewVersionClientOrDie()
   298  			defer argoio.Close(conn)
   299  
   300  			_, err := versionIf.Version(context.Background(), &empty.Empty{})
   301  			if err == nil {
   302  				log.Warnf("Failed to invoke grpc call. Use flag --grpc-web in grpc calls. To avoid this warning message, use flag --grpc-web.")
   303  			} else {
   304  				c.GRPCWeb = false
   305  			}
   306  		}
   307  	}
   308  	if localCfg != nil {
   309  		err = c.refreshAuthToken(localCfg, ctxName, opts.ConfigPath)
   310  		if err != nil {
   311  			return nil, err
   312  		}
   313  	}
   314  	c.Headers = opts.Headers
   315  
   316  	return &c, nil
   317  }
   318  
   319  // OIDCConfig returns OAuth2 client config and a OpenID Provider based on Argo CD settings
   320  // ctx can hold an appropriate http.Client to use for the exchange
   321  func (c *client) OIDCConfig(ctx context.Context, set *settingspkg.Settings) (*oauth2.Config, *oidc.Provider, error) {
   322  	var clientID string
   323  	var issuerURL string
   324  	var scopes []string
   325  	if 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 = set.OIDCConfig.Scopes
   333  	} else if set.DexConfig != nil && len(set.DexConfig.Connectors) > 0 {
   334  		clientID = common.ArgoCDCLIClientAppID
   335  		issuerURL = fmt.Sprintf("%s%s", set.URL, common.DexAPIEndpoint)
   336  	} else {
   337  		return nil, nil, fmt.Errorf("%s is not configured with SSO", c.ServerAddr)
   338  	}
   339  	provider, err := oidc.NewProvider(ctx, issuerURL)
   340  	if err != nil {
   341  		return nil, nil, fmt.Errorf("Failed to query provider %q: %v", issuerURL, err)
   342  	}
   343  	oidcConf, err := oidcutil.ParseConfig(provider)
   344  	if err != nil {
   345  		return nil, nil, fmt.Errorf("Failed to parse provider config: %v", err)
   346  	}
   347  	scopes = oidcutil.GetScopesOrDefault(scopes)
   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  	if claims.Valid() == nil {
   409  		// token is still valid
   410  		return nil
   411  	}
   412  
   413  	log.Debug("Auth token no longer valid. Refreshing")
   414  	rawIDToken, refreshToken, err := c.redeemRefreshToken()
   415  	if err != nil {
   416  		return err
   417  	}
   418  	c.AuthToken = rawIDToken
   419  	c.RefreshToken = refreshToken
   420  	localCfg.UpsertUser(localconfig.User{
   421  		Name:         ctxName,
   422  		AuthToken:    c.AuthToken,
   423  		RefreshToken: c.RefreshToken,
   424  	})
   425  	err = localconfig.WriteLocalConfig(*localCfg, configPath)
   426  	if err != nil {
   427  		return err
   428  	}
   429  	return nil
   430  }
   431  
   432  // redeemRefreshToken performs the exchange of a refresh_token for a new id_token and refresh_token
   433  func (c *client) redeemRefreshToken() (string, string, error) {
   434  	setConn, setIf, err := c.NewSettingsClient()
   435  	if err != nil {
   436  		return "", "", err
   437  	}
   438  	defer func() { _ = setConn.Close() }()
   439  	httpClient, err := c.HTTPClient()
   440  	if err != nil {
   441  		return "", "", err
   442  	}
   443  	ctx := oidc.ClientContext(context.Background(), httpClient)
   444  	acdSet, err := setIf.Get(ctx, &settingspkg.SettingsQuery{})
   445  	if err != nil {
   446  		return "", "", err
   447  	}
   448  	oauth2conf, _, err := c.OIDCConfig(ctx, acdSet)
   449  	if err != nil {
   450  		return "", "", err
   451  	}
   452  	t := &oauth2.Token{
   453  		RefreshToken: c.RefreshToken,
   454  	}
   455  	token, err := oauth2conf.TokenSource(ctx, t).Token()
   456  	if err != nil {
   457  		return "", "", err
   458  	}
   459  	rawIDToken, ok := token.Extra("id_token").(string)
   460  	if !ok {
   461  		return "", "", errors.New("no id_token in token response")
   462  	}
   463  	refreshToken, _ := token.Extra("refresh_token").(string)
   464  	return rawIDToken, refreshToken, nil
   465  }
   466  
   467  // NewClientOrDie creates a new API client from a set of config options, or fails fatally if the new client creation fails.
   468  func NewClientOrDie(opts *ClientOptions) Client {
   469  	client, err := NewClient(opts)
   470  	if err != nil {
   471  		log.Fatal(err)
   472  	}
   473  	return client
   474  }
   475  
   476  // JwtCredentials implements the gRPC credentials.Credentials interface which we is used to do
   477  // grpc.WithPerRPCCredentials(), for authentication
   478  type jwtCredentials struct {
   479  	Token string
   480  }
   481  
   482  func (c jwtCredentials) RequireTransportSecurity() bool {
   483  	return false
   484  }
   485  
   486  func (c jwtCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
   487  	return map[string]string{
   488  		MetaDataTokenKey: c.Token,
   489  	}, nil
   490  }
   491  
   492  func (c *client) newConn() (*grpc.ClientConn, io.Closer, error) {
   493  	closers := make([]io.Closer, 0)
   494  	serverAddr := c.ServerAddr
   495  	network := "tcp"
   496  	if c.GRPCWeb || c.GRPCWebRootPath != "" {
   497  		// start local grpc server which proxies requests using grpc-web protocol
   498  		addr, closer, err := c.useGRPCProxy()
   499  		if err != nil {
   500  			return nil, nil, err
   501  		}
   502  		network = addr.Network()
   503  		serverAddr = addr.String()
   504  		closers = append(closers, closer)
   505  	}
   506  
   507  	var creds credentials.TransportCredentials
   508  	if !c.PlainText && !c.GRPCWeb && c.GRPCWebRootPath == "" {
   509  		tlsConfig, err := c.tlsConfig()
   510  		if err != nil {
   511  			return nil, nil, err
   512  		}
   513  		creds = credentials.NewTLS(tlsConfig)
   514  	}
   515  	endpointCredentials := jwtCredentials{
   516  		Token: c.AuthToken,
   517  	}
   518  	retryOpts := []grpc_retry.CallOption{
   519  		grpc_retry.WithMax(3),
   520  		grpc_retry.WithBackoff(grpc_retry.BackoffLinear(1000 * time.Millisecond)),
   521  	}
   522  	var dialOpts []grpc.DialOption
   523  	dialOpts = append(dialOpts, grpc.WithPerRPCCredentials(endpointCredentials))
   524  	dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)))
   525  	dialOpts = append(dialOpts, grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)))
   526  	dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(grpc_retry.UnaryClientInterceptor(retryOpts...))))
   527  	dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(grpc_util.OTELUnaryClientInterceptor()))
   528  	dialOpts = append(dialOpts, grpc.WithStreamInterceptor(grpc_util.OTELStreamClientInterceptor()))
   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.BlockingDial(ctx, network, serverAddr, creds, dialOpts...)
   546  	closers = append(closers, conn)
   547  	return conn, argoio.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, fmt.Errorf("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 == context.Canceled {
   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  }