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