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 }