summaryrefslogtreecommitdiff
path: root/vendor/google.golang.org/api/internal
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/google.golang.org/api/internal')
-rw-r--r--vendor/google.golang.org/api/internal/cert/default_cert.go58
-rw-r--r--vendor/google.golang.org/api/internal/cert/enterprise_cert.go54
-rw-r--r--vendor/google.golang.org/api/internal/cert/secureconnect_cert.go123
-rw-r--r--vendor/google.golang.org/api/internal/creds.go87
-rw-r--r--vendor/google.golang.org/api/internal/dca.go144
-rw-r--r--vendor/google.golang.org/api/internal/gensupport/error.go24
-rw-r--r--vendor/google.golang.org/api/internal/gensupport/json.go33
-rw-r--r--vendor/google.golang.org/api/internal/gensupport/media.go140
-rw-r--r--vendor/google.golang.org/api/internal/gensupport/params.go2
-rw-r--r--vendor/google.golang.org/api/internal/gensupport/resumable.go128
-rw-r--r--vendor/google.golang.org/api/internal/gensupport/retry.go121
-rw-r--r--vendor/google.golang.org/api/internal/gensupport/send.go79
-rw-r--r--vendor/google.golang.org/api/internal/version.go8
13 files changed, 813 insertions, 188 deletions
diff --git a/vendor/google.golang.org/api/internal/cert/default_cert.go b/vendor/google.golang.org/api/internal/cert/default_cert.go
new file mode 100644
index 000000000..21d025153
--- /dev/null
+++ b/vendor/google.golang.org/api/internal/cert/default_cert.go
@@ -0,0 +1,58 @@
+// Copyright 2020 Google LLC.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package cert contains certificate tools for Google API clients.
+// This package is intended to be used with crypto/tls.Config.GetClientCertificate.
+//
+// The certificates can be used to satisfy Google's Endpoint Validation.
+// See https://cloud.google.com/endpoint-verification/docs/overview
+//
+// This package is not intended for use by end developers. Use the
+// google.golang.org/api/option package to configure API clients.
+package cert
+
+import (
+ "crypto/tls"
+ "errors"
+ "sync"
+)
+
+// defaultCertData holds all the variables pertaining to
+// the default certficate source created by DefaultSource.
+//
+// A singleton model is used to allow the source to be reused
+// by the transport layer.
+type defaultCertData struct {
+ once sync.Once
+ source Source
+ err error
+}
+
+var (
+ defaultCert defaultCertData
+)
+
+// Source is a function that can be passed into crypto/tls.Config.GetClientCertificate.
+type Source func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
+
+// errSourceUnavailable is a sentinel error to indicate certificate source is unavailable.
+var errSourceUnavailable = errors.New("certificate source is unavailable")
+
+// DefaultSource returns a certificate source using the preferred EnterpriseCertificateProxySource.
+// If EnterpriseCertificateProxySource is not available, fall back to the legacy SecureConnectSource.
+//
+// If neither source is available (due to missing configurations), a nil Source and a nil Error are
+// returned to indicate that a default certificate source is unavailable.
+func DefaultSource() (Source, error) {
+ defaultCert.once.Do(func() {
+ defaultCert.source, defaultCert.err = NewEnterpriseCertificateProxySource("")
+ if errors.Is(defaultCert.err, errSourceUnavailable) {
+ defaultCert.source, defaultCert.err = NewSecureConnectSource("")
+ if errors.Is(defaultCert.err, errSourceUnavailable) {
+ defaultCert.source, defaultCert.err = nil, nil
+ }
+ }
+ })
+ return defaultCert.source, defaultCert.err
+}
diff --git a/vendor/google.golang.org/api/internal/cert/enterprise_cert.go b/vendor/google.golang.org/api/internal/cert/enterprise_cert.go
new file mode 100644
index 000000000..1061b5f05
--- /dev/null
+++ b/vendor/google.golang.org/api/internal/cert/enterprise_cert.go
@@ -0,0 +1,54 @@
+// Copyright 2022 Google LLC.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package cert contains certificate tools for Google API clients.
+// This package is intended to be used with crypto/tls.Config.GetClientCertificate.
+//
+// The certificates can be used to satisfy Google's Endpoint Validation.
+// See https://cloud.google.com/endpoint-verification/docs/overview
+//
+// This package is not intended for use by end developers. Use the
+// google.golang.org/api/option package to configure API clients.
+package cert
+
+import (
+ "crypto/tls"
+ "errors"
+
+ "github.com/googleapis/enterprise-certificate-proxy/client"
+)
+
+type ecpSource struct {
+ key *client.Key
+}
+
+// NewEnterpriseCertificateProxySource creates a certificate source
+// using the Enterprise Certificate Proxy client, which delegates
+// certifcate related operations to an OS-specific "signer binary"
+// that communicates with the native keystore (ex. keychain on MacOS).
+//
+// The configFilePath points to a config file containing relevant parameters
+// such as the certificate issuer and the location of the signer binary.
+// If configFilePath is empty, the client will attempt to load the config from
+// a well-known gcloud location.
+func NewEnterpriseCertificateProxySource(configFilePath string) (Source, error) {
+ key, err := client.Cred(configFilePath)
+ if err != nil {
+ if errors.Is(err, client.ErrCredUnavailable) {
+ return nil, errSourceUnavailable
+ }
+ return nil, err
+ }
+
+ return (&ecpSource{
+ key: key,
+ }).getClientCertificate, nil
+}
+
+func (s *ecpSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
+ var cert tls.Certificate
+ cert.PrivateKey = s.key
+ cert.Certificate = s.key.CertificateChain()
+ return &cert, nil
+}
diff --git a/vendor/google.golang.org/api/internal/cert/secureconnect_cert.go b/vendor/google.golang.org/api/internal/cert/secureconnect_cert.go
new file mode 100644
index 000000000..5913cab80
--- /dev/null
+++ b/vendor/google.golang.org/api/internal/cert/secureconnect_cert.go
@@ -0,0 +1,123 @@
+// Copyright 2022 Google LLC.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package cert contains certificate tools for Google API clients.
+// This package is intended to be used with crypto/tls.Config.GetClientCertificate.
+//
+// The certificates can be used to satisfy Google's Endpoint Validation.
+// See https://cloud.google.com/endpoint-verification/docs/overview
+//
+// This package is not intended for use by end developers. Use the
+// google.golang.org/api/option package to configure API clients.
+package cert
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "os/user"
+ "path/filepath"
+ "sync"
+ "time"
+)
+
+const (
+ metadataPath = ".secureConnect"
+ metadataFile = "context_aware_metadata.json"
+)
+
+type secureConnectSource struct {
+ metadata secureConnectMetadata
+
+ // Cache the cert to avoid executing helper command repeatedly.
+ cachedCertMutex sync.Mutex
+ cachedCert *tls.Certificate
+}
+
+type secureConnectMetadata struct {
+ Cmd []string `json:"cert_provider_command"`
+}
+
+// NewSecureConnectSource creates a certificate source using
+// the Secure Connect Helper and its associated metadata file.
+//
+// The configFilePath points to the location of the context aware metadata file.
+// If configFilePath is empty, use the default context aware metadata location.
+func NewSecureConnectSource(configFilePath string) (Source, error) {
+ if configFilePath == "" {
+ user, err := user.Current()
+ if err != nil {
+ // Error locating the default config means Secure Connect is not supported.
+ return nil, errSourceUnavailable
+ }
+ configFilePath = filepath.Join(user.HomeDir, metadataPath, metadataFile)
+ }
+
+ file, err := ioutil.ReadFile(configFilePath)
+ if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ // Config file missing means Secure Connect is not supported.
+ return nil, errSourceUnavailable
+ }
+ return nil, err
+ }
+
+ var metadata secureConnectMetadata
+ if err := json.Unmarshal(file, &metadata); err != nil {
+ return nil, fmt.Errorf("cert: could not parse JSON in %q: %w", configFilePath, err)
+ }
+ if err := validateMetadata(metadata); err != nil {
+ return nil, fmt.Errorf("cert: invalid config in %q: %w", configFilePath, err)
+ }
+ return (&secureConnectSource{
+ metadata: metadata,
+ }).getClientCertificate, nil
+}
+
+func validateMetadata(metadata secureConnectMetadata) error {
+ if len(metadata.Cmd) == 0 {
+ return errors.New("empty cert_provider_command")
+ }
+ return nil
+}
+
+func (s *secureConnectSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
+ s.cachedCertMutex.Lock()
+ defer s.cachedCertMutex.Unlock()
+ if s.cachedCert != nil && !isCertificateExpired(s.cachedCert) {
+ return s.cachedCert, nil
+ }
+ // Expand OS environment variables in the cert provider command such as "$HOME".
+ for i := 0; i < len(s.metadata.Cmd); i++ {
+ s.metadata.Cmd[i] = os.ExpandEnv(s.metadata.Cmd[i])
+ }
+ command := s.metadata.Cmd
+ data, err := exec.Command(command[0], command[1:]...).Output()
+ if err != nil {
+ return nil, err
+ }
+ cert, err := tls.X509KeyPair(data, data)
+ if err != nil {
+ return nil, err
+ }
+ s.cachedCert = &cert
+ return &cert, nil
+}
+
+// isCertificateExpired returns true if the given cert is expired or invalid.
+func isCertificateExpired(cert *tls.Certificate) bool {
+ if len(cert.Certificate) == 0 {
+ return true
+ }
+ parsed, err := x509.ParseCertificate(cert.Certificate[0])
+ if err != nil {
+ return true
+ }
+ return time.Now().After(parsed.NotAfter)
+}
diff --git a/vendor/google.golang.org/api/internal/creds.go b/vendor/google.golang.org/api/internal/creds.go
index b067a179b..63c660922 100644
--- a/vendor/google.golang.org/api/internal/creds.go
+++ b/vendor/google.golang.org/api/internal/creds.go
@@ -6,10 +6,15 @@ package internal
import (
"context"
+ "crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
+ "net"
+ "net/http"
+ "os"
+ "time"
"golang.org/x/oauth2"
"google.golang.org/api/internal/impersonate"
@@ -17,6 +22,8 @@ import (
"golang.org/x/oauth2/google"
)
+const quotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
+
// Creds returns credential information obtained from DialSettings, or if none, then
// it returns default credential information.
func Creds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
@@ -70,17 +77,35 @@ const (
//
// - A self-signed JWT flow will be executed if the following conditions are
// met:
-// (1) At least one of the following is true:
-// (a) No scope is provided
-// (b) Scope for self-signed JWT flow is enabled
-// (c) Audiences are explicitly provided by users
-// (2) No service account impersontation
+//
+// (1) At least one of the following is true:
+// (a) No scope is provided
+// (b) Scope for self-signed JWT flow is enabled
+// (c) Audiences are explicitly provided by users
+// (2) No service account impersontation
//
// - Otherwise, executes standard OAuth 2.0 flow
// More details: google.aip.dev/auth/4111
func credentialsFromJSON(ctx context.Context, data []byte, ds *DialSettings) (*google.Credentials, error) {
+ var params google.CredentialsParams
+ params.Scopes = ds.GetScopes()
+
+ // Determine configurations for the OAuth2 transport, which is separate from the API transport.
+ // The OAuth2 transport and endpoint will be configured for mTLS if applicable.
+ clientCertSource, oauth2Endpoint, err := GetClientCertificateSourceAndEndpoint(oauth2DialSettings(ds))
+ if err != nil {
+ return nil, err
+ }
+ params.TokenURL = oauth2Endpoint
+ if clientCertSource != nil {
+ tlsConfig := &tls.Config{
+ GetClientCertificate: clientCertSource,
+ }
+ ctx = context.WithValue(ctx, oauth2.HTTPClient, customHTTPClient(tlsConfig))
+ }
+
// By default, a standard OAuth 2.0 token source is created
- cred, err := google.CredentialsFromJSON(ctx, data, ds.GetScopes()...)
+ cred, err := google.CredentialsFromJSONWithParams(ctx, data, params)
if err != nil {
return nil, err
}
@@ -130,14 +155,22 @@ func selfSignedJWTTokenSource(data []byte, ds *DialSettings) (oauth2.TokenSource
}
}
-// QuotaProjectFromCreds returns the quota project from the JSON blob in the provided credentials.
-//
-// NOTE(cbro): consider promoting this to a field on google.Credentials.
-func QuotaProjectFromCreds(cred *google.Credentials) string {
+// GetQuotaProject retrieves quota project with precedence being: client option,
+// environment variable, creds file.
+func GetQuotaProject(creds *google.Credentials, clientOpt string) string {
+ if clientOpt != "" {
+ return clientOpt
+ }
+ if env := os.Getenv(quotaProjectEnvVar); env != "" {
+ return env
+ }
+ if creds == nil {
+ return ""
+ }
var v struct {
QuotaProject string `json:"quota_project_id"`
}
- if err := json.Unmarshal(cred.JSON, &v); err != nil {
+ if err := json.Unmarshal(creds.JSON, &v); err != nil {
return ""
}
return v.QuotaProject
@@ -156,3 +189,35 @@ func impersonateCredentials(ctx context.Context, creds *google.Credentials, ds *
ProjectID: creds.ProjectID,
}, nil
}
+
+// oauth2DialSettings returns the settings to be used by the OAuth2 transport, which is separate from the API transport.
+func oauth2DialSettings(ds *DialSettings) *DialSettings {
+ var ods DialSettings
+ ods.DefaultEndpoint = google.Endpoint.TokenURL
+ ods.DefaultMTLSEndpoint = google.MTLSTokenURL
+ ods.ClientCertSource = ds.ClientCertSource
+ return &ods
+}
+
+// customHTTPClient constructs an HTTPClient using the provided tlsConfig, to support mTLS.
+func customHTTPClient(tlsConfig *tls.Config) *http.Client {
+ trans := baseTransport()
+ trans.TLSClientConfig = tlsConfig
+ return &http.Client{Transport: trans}
+}
+
+func baseTransport() *http.Transport {
+ return &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: (&net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ DualStack: true,
+ }).DialContext,
+ MaxIdleConns: 100,
+ MaxIdleConnsPerHost: 100,
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ }
+}
diff --git a/vendor/google.golang.org/api/internal/dca.go b/vendor/google.golang.org/api/internal/dca.go
new file mode 100644
index 000000000..204a3fd2f
--- /dev/null
+++ b/vendor/google.golang.org/api/internal/dca.go
@@ -0,0 +1,144 @@
+// Copyright 2020 Google LLC.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package dca contains utils for implementing Device Certificate
+// Authentication according to https://google.aip.dev/auth/4114
+//
+// The overall logic for DCA is as follows:
+// 1. If both endpoint override and client certificate are specified, use them as is.
+// 2. If user does not specify client certificate, we will attempt to use default
+// client certificate.
+// 3. If user does not specify endpoint override, we will use defaultMtlsEndpoint if
+// client certificate is available and defaultEndpoint otherwise.
+//
+// Implications of the above logic:
+// 1. If the user specifies a non-mTLS endpoint override but client certificate is
+// available, we will pass along the cert anyway and let the server decide what to do.
+// 2. If the user specifies an mTLS endpoint override but client certificate is not
+// available, we will not fail-fast, but let backend throw error when connecting.
+//
+// We would like to avoid introducing client-side logic that parses whether the
+// endpoint override is an mTLS url, since the url pattern may change at anytime.
+//
+// This package is not intended for use by end developers. Use the
+// google.golang.org/api/option package to configure API clients.
+
+// Package internal supports the options and transport packages.
+package internal
+
+import (
+ "net/url"
+ "os"
+ "strings"
+
+ "google.golang.org/api/internal/cert"
+)
+
+const (
+ mTLSModeAlways = "always"
+ mTLSModeNever = "never"
+ mTLSModeAuto = "auto"
+)
+
+// GetClientCertificateSourceAndEndpoint is a convenience function that invokes
+// getClientCertificateSource and getEndpoint sequentially and returns the client
+// cert source and endpoint as a tuple.
+func GetClientCertificateSourceAndEndpoint(settings *DialSettings) (cert.Source, string, error) {
+ clientCertSource, err := getClientCertificateSource(settings)
+ if err != nil {
+ return nil, "", err
+ }
+ endpoint, err := getEndpoint(settings, clientCertSource)
+ if err != nil {
+ return nil, "", err
+ }
+ return clientCertSource, endpoint, nil
+}
+
+// getClientCertificateSource returns a default client certificate source, if
+// not provided by the user.
+//
+// A nil default source can be returned if the source does not exist. Any exceptions
+// encountered while initializing the default source will be reported as client
+// error (ex. corrupt metadata file).
+//
+// Important Note: For now, the environment variable GOOGLE_API_USE_CLIENT_CERTIFICATE
+// must be set to "true" to allow certificate to be used (including user provided
+// certificates). For details, see AIP-4114.
+func getClientCertificateSource(settings *DialSettings) (cert.Source, error) {
+ if !isClientCertificateEnabled() {
+ return nil, nil
+ } else if settings.ClientCertSource != nil {
+ return settings.ClientCertSource, nil
+ } else {
+ return cert.DefaultSource()
+ }
+}
+
+func isClientCertificateEnabled() bool {
+ useClientCert := os.Getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE")
+ // TODO(andyrzhao): Update default to return "true" after DCA feature is fully released.
+ return strings.ToLower(useClientCert) == "true"
+}
+
+// getEndpoint returns the endpoint for the service, taking into account the
+// user-provided endpoint override "settings.Endpoint".
+//
+// If no endpoint override is specified, we will either return the default endpoint or
+// the default mTLS endpoint if a client certificate is available.
+//
+// You can override the default endpoint choice (mtls vs. regular) by setting the
+// GOOGLE_API_USE_MTLS_ENDPOINT environment variable.
+//
+// If the endpoint override is an address (host:port) rather than full base
+// URL (ex. https://...), then the user-provided address will be merged into
+// the default endpoint. For example, WithEndpoint("myhost:8000") and
+// WithDefaultEndpoint("https://foo.com/bar/baz") will return "https://myhost:8080/bar/baz"
+func getEndpoint(settings *DialSettings, clientCertSource cert.Source) (string, error) {
+ if settings.Endpoint == "" {
+ mtlsMode := getMTLSMode()
+ if mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto) {
+ return settings.DefaultMTLSEndpoint, nil
+ }
+ return settings.DefaultEndpoint, nil
+ }
+ if strings.Contains(settings.Endpoint, "://") {
+ // User passed in a full URL path, use it verbatim.
+ return settings.Endpoint, nil
+ }
+ if settings.DefaultEndpoint == "" {
+ // If DefaultEndpoint is not configured, use the user provided endpoint verbatim.
+ // This allows a naked "host[:port]" URL to be used with GRPC Direct Path.
+ return settings.Endpoint, nil
+ }
+
+ // Assume user-provided endpoint is host[:port], merge it with the default endpoint.
+ return mergeEndpoints(settings.DefaultEndpoint, settings.Endpoint)
+}
+
+func getMTLSMode() string {
+ mode := os.Getenv("GOOGLE_API_USE_MTLS_ENDPOINT")
+ if mode == "" {
+ mode = os.Getenv("GOOGLE_API_USE_MTLS") // Deprecated.
+ }
+ if mode == "" {
+ return mTLSModeAuto
+ }
+ return strings.ToLower(mode)
+}
+
+func mergeEndpoints(baseURL, newHost string) (string, error) {
+ u, err := url.Parse(fixScheme(baseURL))
+ if err != nil {
+ return "", err
+ }
+ return strings.Replace(baseURL, u.Host, newHost, 1), nil
+}
+
+func fixScheme(baseURL string) string {
+ if !strings.Contains(baseURL, "://") {
+ return "https://" + baseURL
+ }
+ return baseURL
+}
diff --git a/vendor/google.golang.org/api/internal/gensupport/error.go b/vendor/google.golang.org/api/internal/gensupport/error.go
new file mode 100644
index 000000000..886c6532b
--- /dev/null
+++ b/vendor/google.golang.org/api/internal/gensupport/error.go
@@ -0,0 +1,24 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gensupport
+
+import (
+ "errors"
+
+ "github.com/googleapis/gax-go/v2/apierror"
+ "google.golang.org/api/googleapi"
+)
+
+// WrapError creates an [apierror.APIError] from err, wraps it in err, and
+// returns err. If err is not a [googleapi.Error] (or a
+// [google.golang.org/grpc/status.Status]), it returns err without modification.
+func WrapError(err error) error {
+ var herr *googleapi.Error
+ apiError, ok := apierror.ParseError(err, false)
+ if ok && errors.As(err, &herr) {
+ herr.Wrap(apiError)
+ }
+ return err
+}
diff --git a/vendor/google.golang.org/api/internal/gensupport/json.go b/vendor/google.golang.org/api/internal/gensupport/json.go
index c01e32189..eab49a11e 100644
--- a/vendor/google.golang.org/api/internal/gensupport/json.go
+++ b/vendor/google.golang.org/api/internal/gensupport/json.go
@@ -13,9 +13,10 @@ import (
// MarshalJSON returns a JSON encoding of schema containing only selected fields.
// A field is selected if any of the following is true:
-// * it has a non-empty value
-// * its field name is present in forceSendFields and it is not a nil pointer or nil interface
-// * its field name is present in nullFields.
+// - it has a non-empty value
+// - its field name is present in forceSendFields and it is not a nil pointer or nil interface
+// - its field name is present in nullFields.
+//
// The JSON key for each selected field is taken from the field's json: struct tag.
func MarshalJSON(schema interface{}, forceSendFields, nullFields []string) ([]byte, error) {
if len(forceSendFields) == 0 && len(nullFields) == 0 {
@@ -85,7 +86,12 @@ func schemaToMap(schema interface{}, mustInclude, useNull map[string]bool, useNu
if f.Type.Kind() == reflect.Map && useNullMaps[f.Name] != nil {
ms, ok := v.Interface().(map[string]string)
if !ok {
- return nil, fmt.Errorf("field %q has keys in NullFields but is not a map[string]string", f.Name)
+ mi, err := initMapSlow(v, f.Name, useNullMaps)
+ if err != nil {
+ return nil, err
+ }
+ m[tag.apiName] = mi
+ continue
}
mi := map[string]interface{}{}
for k, v := range ms {
@@ -119,6 +125,25 @@ func schemaToMap(schema interface{}, mustInclude, useNull map[string]bool, useNu
return m, nil
}
+// initMapSlow uses reflection to build up a map object. This is slower than
+// the default behavior so it should be used only as a fallback.
+func initMapSlow(rv reflect.Value, fieldName string, useNullMaps map[string]map[string]bool) (map[string]interface{}, error) {
+ mi := map[string]interface{}{}
+ iter := rv.MapRange()
+ for iter.Next() {
+ k, ok := iter.Key().Interface().(string)
+ if !ok {
+ return nil, fmt.Errorf("field %q has keys in NullFields but is not a map[string]any", fieldName)
+ }
+ v := iter.Value().Interface()
+ mi[k] = v
+ }
+ for k := range useNullMaps[fieldName] {
+ mi[k] = nil
+ }
+ return mi, nil
+}
+
// formatAsString returns a string representation of v, dereferencing it first if possible.
func formatAsString(v reflect.Value, kind reflect.Kind) string {
if kind == reflect.Ptr && !v.IsNil() {
diff --git a/vendor/google.golang.org/api/internal/gensupport/media.go b/vendor/google.golang.org/api/internal/gensupport/media.go
index 0460ab594..8356e7f27 100644
--- a/vendor/google.golang.org/api/internal/gensupport/media.go
+++ b/vendor/google.golang.org/api/internal/gensupport/media.go
@@ -15,93 +15,12 @@ import (
"net/textproto"
"strings"
"sync"
+ "time"
+ gax "github.com/googleapis/gax-go/v2"
"google.golang.org/api/googleapi"
)
-const sniffBuffSize = 512
-
-func newContentSniffer(r io.Reader) *contentSniffer {
- return &contentSniffer{r: r}
-}
-
-// contentSniffer wraps a Reader, and reports the content type determined by sniffing up to 512 bytes from the Reader.
-type contentSniffer struct {
- r io.Reader
- start []byte // buffer for the sniffed bytes.
- err error // set to any error encountered while reading bytes to be sniffed.
-
- ctype string // set on first sniff.
- sniffed bool // set to true on first sniff.
-}
-
-func (cs *contentSniffer) Read(p []byte) (n int, err error) {
- // Ensure that the content type is sniffed before any data is consumed from Reader.
- _, _ = cs.ContentType()
-
- if len(cs.start) > 0 {
- n := copy(p, cs.start)
- cs.start = cs.start[n:]
- return n, nil
- }
-
- // We may have read some bytes into start while sniffing, even if the read ended in an error.
- // We should first return those bytes, then the error.
- if cs.err != nil {
- return 0, cs.err
- }
-
- // Now we have handled all bytes that were buffered while sniffing. Now just delegate to the underlying reader.
- return cs.r.Read(p)
-}
-
-// ContentType returns the sniffed content type, and whether the content type was successfully sniffed.
-func (cs *contentSniffer) ContentType() (string, bool) {
- if cs.sniffed {
- return cs.ctype, cs.ctype != ""
- }
- cs.sniffed = true
- // If ReadAll hits EOF, it returns err==nil.
- cs.start, cs.err = ioutil.ReadAll(io.LimitReader(cs.r, sniffBuffSize))
-
- // Don't try to detect the content type based on possibly incomplete data.
- if cs.err != nil {
- return "", false
- }
-
- cs.ctype = http.DetectContentType(cs.start)
- return cs.ctype, true
-}
-
-// DetermineContentType determines the content type of the supplied reader.
-// If the content type is already known, it can be specified via ctype.
-// Otherwise, the content of media will be sniffed to determine the content type.
-// If media implements googleapi.ContentTyper (deprecated), this will be used
-// instead of sniffing the content.
-// After calling DetectContentType the caller must not perform further reads on
-// media, but rather read from the Reader that is returned.
-func DetermineContentType(media io.Reader, ctype string) (io.Reader, string) {
- // Note: callers could avoid calling DetectContentType if ctype != "",
- // but doing the check inside this function reduces the amount of
- // generated code.
- if ctype != "" {
- return media, ctype
- }
-
- // For backwards compatibility, allow clients to set content
- // type by providing a ContentTyper for media.
- if typer, ok := media.(googleapi.ContentTyper); ok {
- return media, typer.ContentType()
- }
-
- sniffer := newContentSniffer(media)
- if ctype, ok := sniffer.ContentType(); ok {
- return sniffer, ctype
- }
- // If content type could not be sniffed, reads from sniffer will eventually fail with an error.
- return sniffer, ""
-}
-
type typeReader struct {
io.Reader
typ string
@@ -217,12 +136,13 @@ func PrepareUpload(media io.Reader, chunkSize int) (r io.Reader, mb *MediaBuffer
// code only.
type MediaInfo struct {
// At most one of Media and MediaBuffer will be set.
- media io.Reader
- buffer *MediaBuffer
- singleChunk bool
- mType string
- size int64 // mediaSize, if known. Used only for calls to progressUpdater_.
- progressUpdater googleapi.ProgressUpdater
+ media io.Reader
+ buffer *MediaBuffer
+ singleChunk bool
+ mType string
+ size int64 // mediaSize, if known. Used only for calls to progressUpdater_.
+ progressUpdater googleapi.ProgressUpdater
+ chunkRetryDeadline time.Duration
}
// NewInfoFromMedia should be invoked from the Media method of a call. It returns a
@@ -232,8 +152,12 @@ func NewInfoFromMedia(r io.Reader, options []googleapi.MediaOption) *MediaInfo {
mi := &MediaInfo{}
opts := googleapi.ProcessMediaOptions(options)
if !opts.ForceEmptyContentType {
- r, mi.mType = DetermineContentType(r, opts.ContentType)
+ mi.mType = opts.ContentType
+ if mi.mType == "" {
+ r, mi.mType = gax.DetermineContentType(r)
+ }
}
+ mi.chunkRetryDeadline = opts.ChunkRetryDeadline
mi.media, mi.buffer, mi.singleChunk = PrepareUpload(r, opts.ChunkSize)
return mi
}
@@ -242,7 +166,11 @@ func NewInfoFromMedia(r io.Reader, options []googleapi.MediaOption) *MediaInfo {
// call. It returns a MediaInfo using the given reader, size and media type.
func NewInfoFromResumableMedia(r io.ReaderAt, size int64, mediaType string) *MediaInfo {
rdr := ReaderAtToReader(r, size)
- rdr, mType := DetermineContentType(rdr, mediaType)
+ mType := mediaType
+ if mType == "" {
+ rdr, mType = gax.DetermineContentType(rdr)
+ }
+
return &MediaInfo{
size: size,
mType: mType,
@@ -286,13 +214,12 @@ func (mi *MediaInfo) UploadRequest(reqHeaders http.Header, body io.Reader) (newB
// be retried because the data is stored in the MediaBuffer.
media, _, _, _ = mi.buffer.Chunk()
}
+ toCleanup := []io.Closer{}
if media != nil {
fb := readerFunc(body)
fm := readerFunc(media)
combined, ctype := CombineBodyMedia(body, "application/json", media, mi.mType)
- toCleanup := []io.Closer{
- combined,
- }
+ toCleanup = append(toCleanup, combined)
if fb != nil && fm != nil {
getBody = func() (io.ReadCloser, error) {
rb := ioutil.NopCloser(fb())
@@ -306,18 +233,30 @@ func (mi *MediaInfo) UploadRequest(reqHeaders http.Header, body io.Reader) (newB
return r, nil
}
}
- cleanup = func() {
- for _, closer := range toCleanup {
- _ = closer.Close()
- }
-
- }
reqHeaders.Set("Content-Type", ctype)
body = combined
}
if mi.buffer != nil && mi.mType != "" && !mi.singleChunk {
+ // This happens when initiating a resumable upload session.
+ // The initial request contains a JSON body rather than media.
+ // It can be retried with a getBody function that re-creates the request body.
+ fb := readerFunc(body)
+ if fb != nil {
+ getBody = func() (io.ReadCloser, error) {
+ rb := ioutil.NopCloser(fb())
+ toCleanup = append(toCleanup, rb)
+ return rb, nil
+ }
+ }
reqHeaders.Set("X-Upload-Content-Type", mi.mType)
}
+ // Ensure that any bodies created in getBody are cleaned up.
+ cleanup = func() {
+ for _, closer := range toCleanup {
+ _ = closer.Close()
+ }
+
+ }
return body, getBody, cleanup
}
@@ -356,6 +295,7 @@ func (mi *MediaInfo) ResumableUpload(locURI string) *ResumableUpload {
mi.progressUpdater(curr, mi.size)
}
},
+ ChunkRetryDeadline: mi.chunkRetryDeadline,
}
}
diff --git a/vendor/google.golang.org/api/internal/gensupport/params.go b/vendor/google.golang.org/api/internal/gensupport/params.go
index 6703721ff..1a30d2ca2 100644
--- a/vendor/google.golang.org/api/internal/gensupport/params.go
+++ b/vendor/google.golang.org/api/internal/gensupport/params.go
@@ -37,7 +37,7 @@ func (u URLParams) SetMulti(key string, values []string) {
u[key] = values
}
-// Encode encodes the values into ``URL encoded'' form
+// Encode encodes the values into “URL encoded” form
// ("bar=baz&foo=quux") sorted by key.
func (u URLParams) Encode() string {
return url.Values(u).Encode()
diff --git a/vendor/google.golang.org/api/internal/gensupport/resumable.go b/vendor/google.golang.org/api/internal/gensupport/resumable.go
index edc87ec24..f168ea6d2 100644
--- a/vendor/google.golang.org/api/internal/gensupport/resumable.go
+++ b/vendor/google.golang.org/api/internal/gensupport/resumable.go
@@ -10,34 +10,12 @@ import (
"fmt"
"io"
"net/http"
+ "strings"
"sync"
"time"
- gax "github.com/googleapis/gax-go/v2"
-)
-
-// Backoff is an interface around gax.Backoff's Pause method, allowing tests to provide their
-// own implementation.
-type Backoff interface {
- Pause() time.Duration
-}
-
-// These are declared as global variables so that tests can overwrite them.
-var (
- retryDeadline = 32 * time.Second
- backoff = func() Backoff {
- return &gax.Backoff{Initial: 100 * time.Millisecond}
- }
- // isRetryable is a platform-specific hook, specified in retryable_linux.go
- syscallRetryable func(error) bool = func(err error) bool { return false }
-)
-
-const (
- // statusTooManyRequests is returned by the storage API if the
- // per-project limits have been temporarily exceeded. The request
- // should be retried.
- // https://cloud.google.com/storage/docs/json_api/v1/status-codes#standardcodes
- statusTooManyRequests = 429
+ "github.com/google/uuid"
+ "google.golang.org/api/internal"
)
// ResumableUpload is used by the generated APIs to provide resumable uploads.
@@ -57,6 +35,18 @@ type ResumableUpload struct {
// Callback is an optional function that will be periodically called with the cumulative number of bytes uploaded.
Callback func(int64)
+
+ // Retry optionally configures retries for requests made against the upload.
+ Retry *RetryConfig
+
+ // ChunkRetryDeadline configures the per-chunk deadline after which no further
+ // retries should happen.
+ ChunkRetryDeadline time.Duration
+
+ // Track current request invocation ID and attempt count for retry metric
+ // headers.
+ invocationID string
+ attempts int
}
// Progress returns the number of bytes uploaded at this point.
@@ -91,6 +81,10 @@ func (rx *ResumableUpload) doUploadRequest(ctx context.Context, data io.Reader,
req.Header.Set("Content-Type", rx.MediaType)
req.Header.Set("User-Agent", rx.UserAgent)
+ baseXGoogHeader := "gl-go/" + GoVersion() + " gdcl/" + internal.Version
+ invocationHeader := fmt.Sprintf("gccl-invocation-id/%s gccl-attempt-count/%d", rx.invocationID, rx.attempts)
+ req.Header.Set("X-Goog-Api-Client", strings.Join([]string{baseXGoogHeader, invocationHeader}, " "))
+
// Google's upload endpoint uses status code 308 for a
// different purpose than the "308 Permanent Redirect"
// since-standardized in RFC 7238. Because of the conflict in
@@ -174,29 +168,69 @@ func (rx *ResumableUpload) Upload(ctx context.Context) (resp *http.Response, err
}
return nil, err
}
+ // This case is very unlikely but possible only if rx.ChunkRetryDeadline is
+ // set to a very small value, in which case no requests will be sent before
+ // the deadline. Return an error to avoid causing a panic.
+ if resp == nil {
+ return nil, fmt.Errorf("upload request to %v not sent, choose larger value for ChunkRetryDealine", rx.URI)
+ }
return resp, nil
}
+ // Configure retryable error criteria.
+ errorFunc := rx.Retry.errorFunc()
+
+ // Configure per-chunk retry deadline.
+ var retryDeadline time.Duration
+ if rx.ChunkRetryDeadline != 0 {
+ retryDeadline = rx.ChunkRetryDeadline
+ } else {
+ retryDeadline = defaultRetryDeadline
+ }
// Send all chunks.
for {
var pause time.Duration
- // Each chunk gets its own initialized-at-zero retry.
- bo := backoff()
- quitAfter := time.After(retryDeadline)
+ // Each chunk gets its own initialized-at-zero backoff and invocation ID.
+ bo := rx.Retry.backoff()
+ quitAfterTimer := time.NewTimer(retryDeadline)
+ rx.attempts = 1
+ rx.invocationID = uuid.New().String()
// Retry loop for a single chunk.
for {
+ pauseTimer := time.NewTimer(pause)
select {
case <-ctx.Done():
+ quitAfterTimer.Stop()
+ pauseTimer.Stop()
if err == nil {
err = ctx.Err()
}
return prepareReturn(resp, err)
- case <-time.After(pause):
- case <-quitAfter:
+ case <-pauseTimer.C:
+ case <-quitAfterTimer.C:
+ pauseTimer.Stop()
return prepareReturn(resp, err)
}
+ pauseTimer.Stop()
+
+ // Check for context cancellation or timeout once more. If more than one
+ // case in the select statement above was satisfied at the same time, Go
+ // will choose one arbitrarily.
+ // That can cause an operation to go through even if the context was
+ // canceled before or the timeout was reached.
+ select {
+ case <-ctx.Done():
+ quitAfterTimer.Stop()
+ if err == nil {
+ err = ctx.Err()
+ }
+ return prepareReturn(resp, err)
+ case <-quitAfterTimer.C:
+ return prepareReturn(resp, err)
+ default:
+ }
resp, err = rx.transferChunk(ctx)
@@ -206,10 +240,12 @@ func (rx *ResumableUpload) Upload(ctx context.Context) (resp *http.Response, err
}
// Check if we should retry the request.
- if !shouldRetry(status, err) {
+ if !errorFunc(status, err) {
+ quitAfterTimer.Stop()
break
}
+ rx.attempts++
pause = bo.Pause()
if resp != nil && resp.Body != nil {
resp.Body.Close()
@@ -226,33 +262,3 @@ func (rx *ResumableUpload) Upload(ctx context.Context) (resp *http.Response, err
return prepareReturn(resp, err)
}
}
-
-// shouldRetry indicates whether an error is retryable for the purposes of this
-// package, following guidance from
-// https://cloud.google.com/storage/docs/exponential-backoff .
-func shouldRetry(status int, err error) bool {
- if 500 <= status && status <= 599 {
- return true
- }
- if status == statusTooManyRequests {
- return true
- }
- if err == io.ErrUnexpectedEOF {
- return true
- }
- // Transient network errors should be retried.
- if syscallRetryable(err) {
- return true
- }
- if err, ok := err.(interface{ Temporary() bool }); ok {
- if err.Temporary() {
- return true
- }
- }
- // If Go 1.13 error unwrapping is available, use this to examine wrapped
- // errors.
- if err, ok := err.(interface{ Unwrap() error }); ok {
- return shouldRetry(status, err.Unwrap())
- }
- return false
-}
diff --git a/vendor/google.golang.org/api/internal/gensupport/retry.go b/vendor/google.golang.org/api/internal/gensupport/retry.go
new file mode 100644
index 000000000..20b57d925
--- /dev/null
+++ b/vendor/google.golang.org/api/internal/gensupport/retry.go
@@ -0,0 +1,121 @@
+// Copyright 2021 Google LLC.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gensupport
+
+import (
+ "errors"
+ "io"
+ "net"
+ "strings"
+ "time"
+
+ "github.com/googleapis/gax-go/v2"
+ "google.golang.org/api/googleapi"
+)
+
+// Backoff is an interface around gax.Backoff's Pause method, allowing tests to provide their
+// own implementation.
+type Backoff interface {
+ Pause() time.Duration
+}
+
+// These are declared as global variables so that tests can overwrite them.
+var (
+ // Default per-chunk deadline for resumable uploads.
+ defaultRetryDeadline = 32 * time.Second
+ // Default backoff timer.
+ backoff = func() Backoff {
+ return &gax.Backoff{Initial: 100 * time.Millisecond}
+ }
+ // syscallRetryable is a platform-specific hook, specified in retryable_linux.go
+ syscallRetryable func(error) bool = func(err error) bool { return false }
+)
+
+const (
+ // statusTooManyRequests is returned by the storage API if the
+ // per-project limits have been temporarily exceeded. The request
+ // should be retried.
+ // https://cloud.google.com/storage/docs/json_api/v1/status-codes#standardcodes
+ statusTooManyRequests = 429
+
+ // statusRequestTimeout is returned by the storage API if the
+ // upload connection was broken. The request should be retried.
+ statusRequestTimeout = 408
+)
+
+// shouldRetry indicates whether an error is retryable for the purposes of this
+// package, unless a ShouldRetry func is specified by the RetryConfig instead.
+// It follows guidance from
+// https://cloud.google.com/storage/docs/exponential-backoff .
+func shouldRetry(status int, err error) bool {
+ if 500 <= status && status <= 599 {
+ return true
+ }
+ if status == statusTooManyRequests || status == statusRequestTimeout {
+ return true
+ }
+ if err == io.ErrUnexpectedEOF {
+ return true
+ }
+ // Transient network errors should be retried.
+ if syscallRetryable(err) {
+ return true
+ }
+ if err, ok := err.(interface{ Temporary() bool }); ok {
+ if err.Temporary() {
+ return true
+ }
+ }
+ var opErr *net.OpError
+ if errors.As(err, &opErr) {
+ if strings.Contains(opErr.Error(), "use of closed network connection") {
+ // TODO: check against net.ErrClosed (go 1.16+) instead of string
+ return true
+ }
+ }
+
+ // If Go 1.13 error unwrapping is available, use this to examine wrapped
+ // errors.
+ if err, ok := err.(interface{ Unwrap() error }); ok {
+ return shouldRetry(status, err.Unwrap())
+ }
+ return false
+}
+
+// RetryConfig allows configuration of backoff timing and retryable errors.
+type RetryConfig struct {
+ Backoff *gax.Backoff
+ ShouldRetry func(err error) bool
+}
+
+// Get a new backoff object based on the configured values.
+func (r *RetryConfig) backoff() Backoff {
+ if r == nil || r.Backoff == nil {
+ return backoff()
+ }
+ return &gax.Backoff{
+ Initial: r.Backoff.Initial,
+ Max: r.Backoff.Max,
+ Multiplier: r.Backoff.Multiplier,
+ }
+}
+
+// This is kind of hacky; it is necessary because ShouldRetry expects to
+// handle HTTP errors via googleapi.Error, but the error has not yet been
+// wrapped with a googleapi.Error at this layer, and the ErrorFunc type
+// in the manual layer does not pass in a status explicitly as it does
+// here. So, we must wrap error status codes in a googleapi.Error so that
+// ShouldRetry can parse this correctly.
+func (r *RetryConfig) errorFunc() func(status int, err error) bool {
+ if r == nil || r.ShouldRetry == nil {
+ return shouldRetry
+ }
+ return func(status int, err error) bool {
+ if status >= 400 {
+ return r.ShouldRetry(&googleapi.Error{Code: status})
+ }
+ return r.ShouldRetry(err)
+ }
+}
diff --git a/vendor/google.golang.org/api/internal/gensupport/send.go b/vendor/google.golang.org/api/internal/gensupport/send.go
index 276d6f696..85c7bcbfd 100644
--- a/vendor/google.golang.org/api/internal/gensupport/send.go
+++ b/vendor/google.golang.org/api/internal/gensupport/send.go
@@ -8,10 +8,36 @@ import (
"context"
"encoding/json"
"errors"
+ "fmt"
"net/http"
+ "strings"
"time"
+
+ "github.com/google/uuid"
+ "github.com/googleapis/gax-go/v2"
)
+// Use this error type to return an error which allows introspection of both
+// the context error and the error from the service.
+type wrappedCallErr struct {
+ ctxErr error
+ wrappedErr error
+}
+
+func (e wrappedCallErr) Error() string {
+ return fmt.Sprintf("retry failed with %v; last error: %v", e.ctxErr, e.wrappedErr)
+}
+
+func (e wrappedCallErr) Unwrap() error {
+ return e.wrappedErr
+}
+
+// Is allows errors.Is to match the error from the call as well as context
+// sentinel errors.
+func (e wrappedCallErr) Is(target error) bool {
+ return errors.Is(e.ctxErr, target) || errors.Is(e.wrappedErr, target)
+}
+
// SendRequest sends a single HTTP request using the given client.
// If ctx is non-nil, it calls all hooks, then sends the request with
// req.WithContext, then calls any functions returned by the hooks in
@@ -50,7 +76,7 @@ func send(ctx context.Context, client *http.Client, req *http.Request) (*http.Re
// If ctx is non-nil, it calls all hooks, then sends the request with
// req.WithContext, then calls any functions returned by the hooks in
// reverse order.
-func SendRequestWithRetry(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
+func SendRequestWithRetry(ctx context.Context, client *http.Client, req *http.Request, retry *RetryConfig) (*http.Response, error) {
// Disallow Accept-Encoding because it interferes with the automatic gzip handling
// done by the default http.Transport. See https://github.com/google/google-api-go-client/issues/219.
if _, ok := req.Header["Accept-Encoding"]; ok {
@@ -59,32 +85,62 @@ func SendRequestWithRetry(ctx context.Context, client *http.Client, req *http.Re
if ctx == nil {
return client.Do(req)
}
- return sendAndRetry(ctx, client, req)
+ return sendAndRetry(ctx, client, req, retry)
}
-func sendAndRetry(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
+func sendAndRetry(ctx context.Context, client *http.Client, req *http.Request, retry *RetryConfig) (*http.Response, error) {
if client == nil {
client = http.DefaultClient
}
var resp *http.Response
var err error
+ attempts := 1
+ invocationID := uuid.New().String()
+ baseXGoogHeader := req.Header.Get("X-Goog-Api-Client")
// Loop to retry the request, up to the context deadline.
var pause time.Duration
- bo := backoff()
+ var bo Backoff
+ if retry != nil && retry.Backoff != nil {
+ bo = &gax.Backoff{
+ Initial: retry.Backoff.Initial,
+ Max: retry.Backoff.Max,
+ Multiplier: retry.Backoff.Multiplier,
+ }
+ } else {
+ bo = backoff()
+ }
+
+ var errorFunc = retry.errorFunc()
for {
+ t := time.NewTimer(pause)
select {
case <-ctx.Done():
- // If we got an error, and the context has been canceled,
- // the context's error is probably more useful.
- if err == nil {
- err = ctx.Err()
+ t.Stop()
+ // If we got an error and the context has been canceled, return an error acknowledging
+ // both the context cancelation and the service error.
+ if err != nil {
+ return resp, wrappedCallErr{ctx.Err(), err}
+ }
+ return resp, ctx.Err()
+ case <-t.C:
+ }
+
+ if ctx.Err() != nil {
+ // Check for context cancellation once more. If more than one case in a
+ // select is satisfied at the same time, Go will choose one arbitrarily.
+ // That can cause an operation to go through even if the context was
+ // canceled before.
+ if err != nil {
+ return resp, wrappedCallErr{ctx.Err(), err}
}
- return resp, err
- case <-time.After(pause):
+ return resp, ctx.Err()
}
+ invocationHeader := fmt.Sprintf("gccl-invocation-id/%s gccl-attempt-count/%d", invocationID, attempts)
+ xGoogHeader := strings.Join([]string{invocationHeader, baseXGoogHeader}, " ")
+ req.Header.Set("X-Goog-Api-Client", xGoogHeader)
resp, err = client.Do(req.WithContext(ctx))
@@ -96,9 +152,10 @@ func sendAndRetry(ctx context.Context, client *http.Client, req *http.Request) (
// Check if we can retry the request. A retry can only be done if the error
// is retryable and the request body can be re-created using GetBody (this
// will not be possible if the body was unbuffered).
- if req.GetBody == nil || !shouldRetry(status, err) {
+ if req.GetBody == nil || !errorFunc(status, err) {
break
}
+ attempts++
var errBody error
req.Body, errBody = req.GetBody()
if errBody != nil {
diff --git a/vendor/google.golang.org/api/internal/version.go b/vendor/google.golang.org/api/internal/version.go
new file mode 100644
index 000000000..7a4f6d898
--- /dev/null
+++ b/vendor/google.golang.org/api/internal/version.go
@@ -0,0 +1,8 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+// Version is the current tagged release of the library.
+const Version = "0.114.0"