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 }