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 }