chore: migrate to gitea
Some checks failed
golangci-lint / lint (push) Successful in 1m33s
Test / test (push) Failing after 2m16s

This commit is contained in:
2026-01-27 00:12:32 +01:00
parent 79d9f55fdc
commit f81c902ca6
3170 changed files with 1216494 additions and 1586 deletions

294
vendor/google.golang.org/api/internal/cba.go generated vendored Normal file
View File

@@ -0,0 +1,294 @@
// Copyright 2020 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// cba.go (certificate-based access) contains utils for implementing Device Certificate
// Authentication according to https://google.aip.dev/auth/4114 and Default Credentials
// for Google Cloud Virtual Environments according to https://google.aip.dev/auth/4115.
//
// 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.
//
// If running within Google's cloud environment, and client certificate is not specified
// and not available through DCA, we will try mTLS with credentials held by
// the Secure Session Agent, which is part of Google's cloud infrastructure.
//
// 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 (
"context"
"crypto/tls"
"errors"
"net"
"net/url"
"os"
"strings"
"github.com/google/s2a-go"
"google.golang.org/api/internal/cert"
"google.golang.org/grpc/credentials"
)
const (
mTLSModeAlways = "always"
mTLSModeNever = "never"
mTLSModeAuto = "auto"
// Experimental: if true, the code will try MTLS with S2A as the default for transport security. Default value is false.
googleAPIUseS2AEnv = "EXPERIMENTAL_GOOGLE_API_USE_S2A"
universeDomainPlaceholder = "UNIVERSE_DOMAIN"
)
var (
errUniverseNotSupportedMTLS = errors.New("mTLS is not supported in any universe other than googleapis.com")
)
// 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
}
// TODO(chrisdsmith): https://github.com/googleapis/google-api-go-client/issues/2359
if settings.Endpoint == "" && !settings.IsUniverseDomainGDU() && settings.DefaultEndpointTemplate != "" {
// TODO(chrisdsmith): https://github.com/googleapis/google-api-go-client/issues/2359
// if settings.DefaultEndpointTemplate == "" {
// return nil, "", errors.New("internaloption.WithDefaultEndpointTemplate is required if option.WithUniverseDomain is not googleapis.com")
// }
endpoint = resolvedDefaultEndpoint(settings)
}
return clientCertSource, endpoint, nil
}
type transportConfig struct {
clientCertSource cert.Source // The client certificate source.
endpoint string // The corresponding endpoint to use based on client certificate source.
s2aAddress string // The S2A address if it can be used, otherwise an empty string.
s2aMTLSEndpoint string // The MTLS endpoint to use with S2A.
}
func getTransportConfig(settings *DialSettings) (*transportConfig, error) {
clientCertSource, endpoint, err := getClientCertificateSourceAndEndpoint(settings)
if err != nil {
return nil, err
}
defaultTransportConfig := transportConfig{
clientCertSource: clientCertSource,
endpoint: endpoint,
s2aAddress: "",
s2aMTLSEndpoint: "",
}
if !shouldUseS2A(clientCertSource, settings) {
return &defaultTransportConfig, nil
}
if !settings.IsUniverseDomainGDU() {
return nil, errUniverseNotSupportedMTLS
}
s2aAddress := GetS2AAddress()
if s2aAddress == "" {
return &defaultTransportConfig, nil
}
return &transportConfig{
clientCertSource: clientCertSource,
endpoint: endpoint,
s2aAddress: s2aAddress,
s2aMTLSEndpoint: settings.DefaultMTLSEndpoint,
}, 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 == "" {
if isMTLS(clientCertSource) {
if !settings.IsUniverseDomainGDU() {
return "", errUniverseNotSupportedMTLS
}
return settings.DefaultMTLSEndpoint, nil
}
return resolvedDefaultEndpoint(settings), nil
}
if strings.Contains(settings.Endpoint, "://") {
// User passed in a full URL path, use it verbatim.
return settings.Endpoint, nil
}
if resolvedDefaultEndpoint(settings) == "" {
// 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(resolvedDefaultEndpoint(settings), settings.Endpoint)
}
func isMTLS(clientCertSource cert.Source) bool {
mtlsMode := getMTLSMode()
return mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto)
}
// resolvedDefaultEndpoint returns the DefaultEndpointTemplate merged with the
// Universe Domain if the DefaultEndpointTemplate is set, otherwise returns the
// deprecated DefaultEndpoint value.
func resolvedDefaultEndpoint(settings *DialSettings) string {
if settings.DefaultEndpointTemplate == "" {
return settings.DefaultEndpoint
}
return strings.Replace(settings.DefaultEndpointTemplate, universeDomainPlaceholder, settings.GetUniverseDomain(), 1)
}
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
}
// GetGRPCTransportConfigAndEndpoint returns an instance of credentials.TransportCredentials, and the
// corresponding endpoint to use for GRPC client.
func GetGRPCTransportConfigAndEndpoint(settings *DialSettings) (credentials.TransportCredentials, string, error) {
config, err := getTransportConfig(settings)
if err != nil {
return nil, "", err
}
defaultTransportCreds := credentials.NewTLS(&tls.Config{
GetClientCertificate: config.clientCertSource,
})
if config.s2aAddress == "" {
return defaultTransportCreds, config.endpoint, nil
}
s2aTransportCreds, err := s2a.NewClientCreds(&s2a.ClientOptions{
S2AAddress: config.s2aAddress,
})
if err != nil {
// Use default if we cannot initialize S2A client transport credentials.
return defaultTransportCreds, config.endpoint, nil
}
return s2aTransportCreds, config.s2aMTLSEndpoint, nil
}
// GetHTTPTransportConfigAndEndpoint returns a client certificate source, a function for dialing MTLS with S2A,
// and the endpoint to use for HTTP client.
func GetHTTPTransportConfigAndEndpoint(settings *DialSettings) (cert.Source, func(context.Context, string, string) (net.Conn, error), string, error) {
config, err := getTransportConfig(settings)
if err != nil {
return nil, nil, "", err
}
if config.s2aAddress == "" {
return config.clientCertSource, nil, config.endpoint, nil
}
dialTLSContextFunc := s2a.NewS2ADialTLSContextFunc(&s2a.ClientOptions{
S2AAddress: config.s2aAddress,
})
return nil, dialTLSContextFunc, config.s2aMTLSEndpoint, nil
}
func shouldUseS2A(clientCertSource cert.Source, settings *DialSettings) bool {
// If client cert is found, use that over S2A.
if clientCertSource != nil {
return false
}
// If EXPERIMENTAL_GOOGLE_API_USE_S2A is not set to true, skip S2A.
if !isGoogleS2AEnabled() {
return false
}
// If DefaultMTLSEndpoint is not set or has endpoint override, skip S2A.
if settings.DefaultMTLSEndpoint == "" || settings.Endpoint != "" {
return false
}
// If custom HTTP client is provided, skip S2A.
if settings.HTTPClient != nil {
return false
}
return !settings.EnableDirectPath && !settings.EnableDirectPathXds
}
func isGoogleS2AEnabled() bool {
return strings.ToLower(os.Getenv(googleAPIUseS2AEnv)) == "true"
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -0,0 +1,122 @@
// 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"
"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 := os.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)
}

30
vendor/google.golang.org/api/internal/conn_pool.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
// 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 internal
import (
"google.golang.org/grpc"
)
// ConnPool is a pool of grpc.ClientConns.
type ConnPool interface {
// Conn returns a ClientConn from the pool.
//
// Conns aren't returned to the pool.
Conn() *grpc.ClientConn
// Num returns the number of connections in the pool.
//
// It will always return the same value.
Num() int
// Close closes every ClientConn in the pool.
//
// The error returned by Close may be a single error or multiple errors.
Close() error
// ConnPool implements grpc.ClientConnInterface to enable it to be used directly with generated proto stubs.
grpc.ClientConnInterface
}

View File

@@ -0,0 +1,113 @@
// Copyright 2024 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package credentialstype defines the CredType used for specifying the type of JSON credentials.
package credentialstype
import (
"encoding/json"
"fmt"
"slices"
)
// CredType specifies the type of JSON credentials.
type CredType string
const (
// Unknown represents an unknown JSON file type.
Unknown CredType = ""
// ServiceAccount represents a service account file type.
ServiceAccount CredType = "service_account"
// AuthorizedUser represents an authorized user credentials file type.
AuthorizedUser CredType = "authorized_user"
// ImpersonatedServiceAccount represents an impersonated service account file type.
//
// IMPORTANT:
// This credential type does not validate the credential configuration. A security
// risk occurs when a credential configuration configured with malicious urls
// is used.
// You should validate credential configurations provided by untrusted sources.
// See [Security requirements when using credential configurations from an external
// source] https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
// for more details.
ImpersonatedServiceAccount CredType = "impersonated_service_account"
// ExternalAccount represents an external account file type.
//
// IMPORTANT:
// This credential type does not validate the credential configuration. A security
// risk occurs when a credential configuration configured with malicious urls
// is used.
// You should validate credential configurations provided by untrusted sources.
// See [Security requirements when using credential configurations from an external
// source] https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
// for more details.
ExternalAccount CredType = "external_account"
// GDCHServiceAccount represents a GDCH service account file type.
GDCHServiceAccount CredType = "gdc_service_account"
// ExternalAccountAuthorizedUser represents an external account authorized user file type.
ExternalAccountAuthorizedUser CredType = "external_account_authorized_user"
)
var knownTypes = map[CredType]bool{
ServiceAccount: true,
AuthorizedUser: true,
ImpersonatedServiceAccount: true,
ExternalAccount: true,
GDCHServiceAccount: true,
ExternalAccountAuthorizedUser: true,
}
// GetCredType returns the credentials type or the Unknown type,
// or an error for empty data or failure to unmarshal JSON.
func GetCredType(data []byte) (CredType, error) {
var t CredType
if len(data) == 0 {
return t, fmt.Errorf("credential provided is 0 bytes")
}
var f struct {
Type string `json:"type"`
}
if err := json.Unmarshal(data, &f); err != nil {
return t, err
}
t = parseCredType(f.Type)
return t, nil
}
// CheckCredentialType checks if the provided JSON bytes match the expected
// credential type and, if present, one of the allowed credential types.
// An error is returned if the JSON is invalid, the type field is missing,
// or the types do not match expected and (if present) allowed.
func CheckCredentialType(b []byte, expected CredType, allowed ...CredType) error {
var f struct {
Type string `json:"type"`
}
if err := json.Unmarshal(b, &f); err != nil {
return fmt.Errorf("unable to parse credential type: %w", err)
}
if f.Type == "" {
return fmt.Errorf("missing `type` field in credential")
}
credType := CredType(f.Type)
if credType != expected {
return fmt.Errorf("credential type mismatch: got %q, expected %q", credType, expected)
}
if len(allowed) == 0 {
return nil
}
if !slices.Contains(allowed, credType) {
return fmt.Errorf("credential type not allowed: %q", credType)
}
return nil
}
// parseCredType returns the matching CredType for the JSON type string if
// it is in the list of publicly exposed types, otherwise Unknown.
func parseCredType(typeString string) CredType {
ct := CredType(typeString)
if knownTypes[ct] {
return ct
}
return Unknown
}

341
vendor/google.golang.org/api/internal/creds.go generated vendored Normal file
View File

@@ -0,0 +1,341 @@
// Copyright 2017 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"os"
"time"
"cloud.google.com/go/auth"
"cloud.google.com/go/auth/credentials"
"cloud.google.com/go/auth/oauth2adapt"
"golang.org/x/oauth2"
"google.golang.org/api/internal/cert"
"google.golang.org/api/internal/credentialstype"
"google.golang.org/api/internal/impersonate"
"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) {
if ds.IsNewAuthLibraryEnabled() {
return credsNewAuth(ds)
}
creds, err := baseCreds(ctx, ds)
if err != nil {
return nil, err
}
if ds.ImpersonationConfig != nil {
return impersonateCredentials(ctx, creds, ds)
}
return creds, nil
}
// AuthCreds returns [cloud.google.com/go/auth.Credentials] based on credentials
// options provided via [option.ClientOption], including legacy oauth2/google
// options. If there are no applicable options, then it returns the result of
// [cloud.google.com/go/auth/credentials.DetectDefault].
// Note: If NoAuth is true, when [google.golang.org/api/option.WithoutAuthentication]
// is passed, then no authentication will be performed and this function will
// return nil, nil.
func AuthCreds(ctx context.Context, settings *DialSettings) (*auth.Credentials, error) {
if settings.NoAuth {
return nil, nil
}
if settings.AuthCredentials != nil {
return settings.AuthCredentials, nil
}
// Support oauth2/google options
var oauth2Creds *google.Credentials
if settings.InternalCredentials != nil {
oauth2Creds = settings.InternalCredentials
} else if settings.Credentials != nil {
oauth2Creds = settings.Credentials
} else if settings.TokenSource != nil {
oauth2Creds = &google.Credentials{TokenSource: settings.TokenSource}
}
if oauth2Creds != nil {
return oauth2adapt.AuthCredentialsFromOauth2Credentials(oauth2Creds), nil
}
return detectDefaultFromDialSettings(settings)
}
// GetOAuth2Configuration determines configurations for the OAuth2 transport, which is separate from the API transport.
// The OAuth2 transport and endpoint will be configured for mTLS if applicable.
func GetOAuth2Configuration(ctx context.Context, settings *DialSettings) (string, *http.Client, error) {
clientCertSource, err := getClientCertificateSource(settings)
if err != nil {
return "", nil, err
}
tokenURL := oAuth2Endpoint(clientCertSource)
var oauth2Client *http.Client
if clientCertSource != nil {
tlsConfig := &tls.Config{
GetClientCertificate: clientCertSource,
}
oauth2Client = customHTTPClient(tlsConfig)
} else {
oauth2Client = oauth2.NewClient(ctx, nil)
}
return tokenURL, oauth2Client, nil
}
func credsNewAuth(settings *DialSettings) (*google.Credentials, error) {
// Preserve old options behavior
if settings.InternalCredentials != nil {
return settings.InternalCredentials, nil
} else if settings.Credentials != nil {
return settings.Credentials, nil
} else if settings.TokenSource != nil {
return &google.Credentials{TokenSource: settings.TokenSource}, nil
}
if settings.AuthCredentials != nil {
return oauth2adapt.Oauth2CredentialsFromAuthCredentials(settings.AuthCredentials), nil
}
creds, err := detectDefaultFromDialSettings(settings)
if err != nil {
return nil, err
}
return oauth2adapt.Oauth2CredentialsFromAuthCredentials(creds), nil
}
func detectDefaultFromDialSettings(settings *DialSettings) (*auth.Credentials, error) {
var useSelfSignedJWT bool
var aud string
var scopes []string
// If scoped JWTs are enabled user provided an aud, allow self-signed JWT.
if settings.EnableJwtWithScope || len(settings.Audiences) > 0 {
useSelfSignedJWT = true
}
if len(settings.Scopes) > 0 {
scopes = make([]string, len(settings.Scopes))
copy(scopes, settings.Scopes)
}
if len(settings.Audiences) > 0 {
aud = settings.Audiences[0]
}
// Only default scopes if user did not also set an audience.
if len(settings.Scopes) == 0 && aud == "" && len(settings.DefaultScopes) > 0 {
scopes = make([]string, len(settings.DefaultScopes))
copy(scopes, settings.DefaultScopes)
}
if len(scopes) == 0 && aud == "" {
aud = settings.DefaultAudience
}
credsFile, _ := settings.GetAuthCredentialsFile()
credsJSON, _ := settings.GetAuthCredentialsJSON()
return credentials.DetectDefault(&credentials.DetectOptions{
Scopes: scopes,
Audience: aud,
CredentialsFile: credsFile,
CredentialsJSON: credsJSON,
UseSelfSignedJWT: useSelfSignedJWT,
Logger: settings.Logger,
})
}
func baseCreds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
if ds.InternalCredentials != nil {
return ds.InternalCredentials, nil
}
if ds.Credentials != nil {
return ds.Credentials, nil
}
if credsJSON, checkCredType := ds.GetAuthCredentialsJSON(); len(credsJSON) > 0 {
return credentialsFromJSON(ctx, credsJSON, ds, checkCredType)
}
if credsFile, checkCredType := ds.GetAuthCredentialsFile(); credsFile != "" {
data, err := os.ReadFile(credsFile)
if err != nil {
return nil, fmt.Errorf("cannot read credentials file: %v", err)
}
return credentialsFromJSON(ctx, data, ds, checkCredType)
}
if ds.TokenSource != nil {
return &google.Credentials{TokenSource: ds.TokenSource}, nil
}
cred, err := google.FindDefaultCredentials(ctx, ds.GetScopes()...)
if err != nil {
return nil, err
}
if len(cred.JSON) > 0 {
return credentialsFromJSON(ctx, cred.JSON, ds, credentialstype.Unknown)
}
// For GAE and GCE, the JSON is empty so return the default credentials directly.
return cred, nil
}
// JSON key file type.
const (
serviceAccountKey = "service_account"
)
// credentialsFromJSON returns a google.Credentials from the JSON data
//
// - A self-signed JWT flow will be executed if the following conditions are
// met:
//
// (1) At least one of the following is true:
// (a) Scope for self-signed JWT flow is enabled
// (b) 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, checkCredType credentialstype.CredType) (*google.Credentials, error) {
if checkCredType != credentialstype.Unknown {
if err := credentialstype.CheckCredentialType(data, checkCredType); err != nil {
return nil, err
}
}
var params google.CredentialsParams
params.Scopes = ds.GetScopes()
tokenURL, oauth2Client, err := GetOAuth2Configuration(ctx, ds)
if err != nil {
return nil, err
}
params.TokenURL = tokenURL
ctx = context.WithValue(ctx, oauth2.HTTPClient, oauth2Client)
// By default, a standard OAuth 2.0 token source is created
cred, err := google.CredentialsFromJSONWithParams(ctx, data, params)
if err != nil {
return nil, err
}
// Override the token source to use self-signed JWT if conditions are met
isJWTFlow, err := isSelfSignedJWTFlow(data, ds)
if err != nil {
return nil, err
}
if isJWTFlow {
ts, err := selfSignedJWTTokenSource(data, ds)
if err != nil {
return nil, err
}
cred.TokenSource = ts
}
return cred, err
}
func oAuth2Endpoint(clientCertSource cert.Source) string {
if isMTLS(clientCertSource) {
return google.MTLSTokenURL
}
return google.Endpoint.TokenURL
}
func isSelfSignedJWTFlow(data []byte, ds *DialSettings) (bool, error) {
// For non-GDU universe domains, token exchange is impossible and services
// must support self-signed JWTs with scopes.
if !ds.IsUniverseDomainGDU() {
return typeServiceAccount(data)
}
if (ds.EnableJwtWithScope || ds.HasCustomAudience()) && ds.ImpersonationConfig == nil {
return typeServiceAccount(data)
}
return false, nil
}
// typeServiceAccount checks if JSON data is for a service account.
func typeServiceAccount(data []byte) (bool, error) {
var f struct {
Type string `json:"type"`
// The remaining JSON fields are omitted because they are not used.
}
if err := json.Unmarshal(data, &f); err != nil {
return false, err
}
return f.Type == serviceAccountKey, nil
}
func selfSignedJWTTokenSource(data []byte, ds *DialSettings) (oauth2.TokenSource, error) {
if len(ds.GetScopes()) > 0 && !ds.HasCustomAudience() {
// Scopes are preferred in self-signed JWT unless the scope is not available
// or a custom audience is used.
return google.JWTAccessTokenSourceWithScope(data, ds.GetScopes()...)
} else if ds.GetAudience() != "" {
// Fallback to audience if scope is not provided
return google.JWTAccessTokenSourceFromJSON(data, ds.GetAudience())
} else {
return nil, errors.New("neither scopes or audience are available for the self-signed JWT")
}
}
// 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(creds.JSON, &v); err != nil {
return ""
}
return v.QuotaProject
}
func impersonateCredentials(ctx context.Context, creds *google.Credentials, ds *DialSettings) (*google.Credentials, error) {
if len(ds.ImpersonationConfig.Scopes) == 0 {
ds.ImpersonationConfig.Scopes = ds.GetScopes()
}
ts, err := impersonate.TokenSource(ctx, creds.TokenSource, ds.ImpersonationConfig)
if err != nil {
return nil, err
}
return &google.Credentials{
TokenSource: ts,
ProjectID: creds.ProjectID,
}, nil
}
// 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,
}
}

View File

@@ -0,0 +1,92 @@
// Copyright 2016 The Go Authors. 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 (
"bytes"
"hash/crc32"
"io"
"google.golang.org/api/googleapi"
)
// MediaBuffer buffers data from an io.Reader to support uploading media in
// retryable chunks. It should be created with NewMediaBuffer.
type MediaBuffer struct {
media io.Reader
chunk []byte // The current chunk which is pending upload. The capacity is the chunk size.
err error // Any error generated when populating chunk by reading media.
// The absolute position of chunk in the underlying media.
off int64
// fullObjectChecksum holds the running checksum of streamed media chunks when automatic checksum
// calculation is enabled via enableAutoChecksum.
fullObjectChecksum uint32
enableAutoChecksum bool
}
var (
crc32cTable = crc32.MakeTable(crc32.Castagnoli)
)
// NewMediaBuffer initializes a MediaBuffer.
func NewMediaBuffer(media io.Reader, chunkSize int) *MediaBuffer {
return &MediaBuffer{media: media, chunk: make([]byte, 0, chunkSize)}
}
// Chunk returns the current buffered chunk, the offset in the underlying media
// from which the chunk is drawn, and the size of the chunk.
// Successive calls to Chunk return the same chunk between calls to Next.
func (mb *MediaBuffer) Chunk() (chunk io.Reader, off int64, size int, err error) {
// There may already be data in chunk if Next has not been called since the previous call to Chunk.
if mb.err == nil && len(mb.chunk) == 0 {
mb.err = mb.loadChunk()
}
return bytes.NewReader(mb.chunk), mb.off, len(mb.chunk), mb.err
}
// loadChunk will read from media into chunk, up to the capacity of chunk.
func (mb *MediaBuffer) loadChunk() error {
bufSize := cap(mb.chunk)
mb.chunk = mb.chunk[:bufSize]
read := 0
var err error
for err == nil && read < bufSize {
var n int
n, err = mb.media.Read(mb.chunk[read:])
read += n
}
mb.chunk = mb.chunk[:read]
if mb.enableAutoChecksum {
mb.fullObjectChecksum = crc32.Update(mb.fullObjectChecksum, crc32cTable, mb.chunk)
}
return err
}
// Next advances to the next chunk, which will be returned by the next call to Chunk.
// Calls to Next without a corresponding prior call to Chunk will have no effect.
func (mb *MediaBuffer) Next() {
mb.off += int64(len(mb.chunk))
mb.chunk = mb.chunk[0:0]
}
type readerTyper struct {
io.Reader
googleapi.ContentTyper
}
// ReaderAtToReader adapts a ReaderAt to be used as a Reader.
// If ra implements googleapi.ContentTyper, then the returned reader
// will also implement googleapi.ContentTyper, delegating to ra.
func ReaderAtToReader(ra io.ReaderAt, size int64) io.Reader {
r := io.NewSectionReader(ra, 0, size)
if typer, ok := ra.(googleapi.ContentTyper); ok {
return readerTyper{r, typer}
}
return r
}

View File

@@ -0,0 +1,10 @@
// Copyright 2016 The Go Authors. 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 is an internal implementation detail used by code
// generated by the google-api-go-generator tool.
//
// This package may be modified at any time without regard for backwards
// compatibility. It should not be used directly by API users.
package gensupport

View File

@@ -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
}

View File

@@ -0,0 +1,236 @@
// Copyright 2015 The Go Authors. 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 (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// 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.
//
// 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 {
return json.Marshal(schema)
}
mustInclude := make(map[string]bool)
for _, f := range forceSendFields {
mustInclude[f] = true
}
useNull := make(map[string]bool)
useNullMaps := make(map[string]map[string]bool)
for _, nf := range nullFields {
parts := strings.SplitN(nf, ".", 2)
field := parts[0]
if len(parts) == 1 {
useNull[field] = true
} else {
if useNullMaps[field] == nil {
useNullMaps[field] = map[string]bool{}
}
useNullMaps[field][parts[1]] = true
}
}
dataMap, err := schemaToMap(schema, mustInclude, useNull, useNullMaps)
if err != nil {
return nil, err
}
return json.Marshal(dataMap)
}
func schemaToMap(schema interface{}, mustInclude, useNull map[string]bool, useNullMaps map[string]map[string]bool) (map[string]interface{}, error) {
m := make(map[string]interface{})
s := reflect.ValueOf(schema)
st := s.Type()
for i := 0; i < s.NumField(); i++ {
jsonTag := st.Field(i).Tag.Get("json")
if jsonTag == "" {
continue
}
tag, err := parseJSONTag(jsonTag)
if err != nil {
return nil, err
}
if tag.ignore {
continue
}
v := s.Field(i)
f := st.Field(i)
if useNull[f.Name] {
if !isEmptyValue(v) {
return nil, fmt.Errorf("field %q in NullFields has non-empty value", f.Name)
}
m[tag.apiName] = nil
continue
}
if !includeField(v, f, mustInclude) {
continue
}
// If map fields are explicitly set to null, use a map[string]interface{}.
if f.Type.Kind() == reflect.Map && useNullMaps[f.Name] != nil {
ms, ok := v.Interface().(map[string]string)
if !ok {
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 {
mi[k] = v
}
for k := range useNullMaps[f.Name] {
mi[k] = nil
}
m[tag.apiName] = mi
continue
}
// nil maps are treated as empty maps.
if f.Type.Kind() == reflect.Map && v.IsNil() {
m[tag.apiName] = map[string]string{}
continue
}
// nil slices are treated as empty slices.
if f.Type.Kind() == reflect.Slice && v.IsNil() {
m[tag.apiName] = []bool{}
continue
}
if tag.stringFormat {
m[tag.apiName] = formatAsString(v, f.Type.Kind())
} else {
m[tag.apiName] = v.Interface()
}
}
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() {
v = v.Elem()
}
return fmt.Sprintf("%v", v.Interface())
}
// jsonTag represents a restricted version of the struct tag format used by encoding/json.
// It is used to describe the JSON encoding of fields in a Schema struct.
type jsonTag struct {
apiName string
stringFormat bool
ignore bool
}
// parseJSONTag parses a restricted version of the struct tag format used by encoding/json.
// The format of the tag must match that generated by the Schema.writeSchemaStruct method
// in the api generator.
func parseJSONTag(val string) (jsonTag, error) {
if val == "-" {
return jsonTag{ignore: true}, nil
}
var tag jsonTag
i := strings.Index(val, ",")
if i == -1 || val[:i] == "" {
return tag, fmt.Errorf("malformed json tag: %s", val)
}
tag = jsonTag{
apiName: val[:i],
}
switch val[i+1:] {
case "omitempty":
case "omitempty,string":
tag.stringFormat = true
default:
return tag, fmt.Errorf("malformed json tag: %s", val)
}
return tag, nil
}
// Reports whether the struct field "f" with value "v" should be included in JSON output.
func includeField(v reflect.Value, f reflect.StructField, mustInclude map[string]bool) bool {
// The regular JSON encoding of a nil pointer is "null", which means "delete this field".
// Therefore, we could enable field deletion by honoring pointer fields' presence in the mustInclude set.
// However, many fields are not pointers, so there would be no way to delete these fields.
// Rather than partially supporting field deletion, we ignore mustInclude for nil pointer fields.
// Deletion will be handled by a separate mechanism.
if f.Type.Kind() == reflect.Ptr && v.IsNil() {
return false
}
// The "any" type is represented as an interface{}. If this interface
// is nil, there is no reasonable representation to send. We ignore
// these fields, for the same reasons as given above for pointers.
if f.Type.Kind() == reflect.Interface && v.IsNil() {
return false
}
return mustInclude[f.Name] || !isEmptyValue(v)
}
// isEmptyValue reports whether v is the empty value for its type. This
// implementation is based on that of the encoding/json package, but its
// correctness does not depend on it being identical. What's important is that
// this function return false in situations where v should not be sent as part
// of a PATCH operation.
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}

View File

@@ -0,0 +1,47 @@
// Copyright 2016 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 (
"encoding/json"
"errors"
"fmt"
"math"
)
// JSONFloat64 is a float64 that supports proper unmarshaling of special float
// values in JSON, according to
// https://developers.google.com/protocol-buffers/docs/proto3#json. Although
// that is a proto-to-JSON spec, it applies to all Google APIs.
//
// The jsonpb package
// (https://github.com/golang/protobuf/blob/master/jsonpb/jsonpb.go) has
// similar functionality, but only for direct translation from proto messages
// to JSON.
type JSONFloat64 float64
func (f *JSONFloat64) UnmarshalJSON(data []byte) error {
var ff float64
if err := json.Unmarshal(data, &ff); err == nil {
*f = JSONFloat64(ff)
return nil
}
var s string
if err := json.Unmarshal(data, &s); err == nil {
switch s {
case "NaN":
ff = math.NaN()
case "Infinity":
ff = math.Inf(1)
case "-Infinity":
ff = math.Inf(-1)
default:
return fmt.Errorf("google.golang.org/api/internal: bad float string %q", s)
}
*f = JSONFloat64(ff)
return nil
}
return errors.New("google.golang.org/api/internal: data not float or string")
}

View File

@@ -0,0 +1,318 @@
// Copyright 2016 The Go Authors. 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 (
"bytes"
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"net/textproto"
"strings"
"sync"
"time"
gax "github.com/googleapis/gax-go/v2"
"google.golang.org/api/googleapi"
)
type typeReader struct {
io.Reader
typ string
}
// multipartReader combines the contents of multiple readers to create a multipart/related HTTP body.
// Close must be called if reads from the multipartReader are abandoned before reaching EOF.
type multipartReader struct {
pr *io.PipeReader
ctype string
mu sync.Mutex
pipeOpen bool
}
// boundary optionally specifies the MIME boundary
func newMultipartReader(parts []typeReader, boundary string) *multipartReader {
mp := &multipartReader{pipeOpen: true}
var pw *io.PipeWriter
mp.pr, pw = io.Pipe()
mpw := multipart.NewWriter(pw)
if boundary != "" {
mpw.SetBoundary(boundary)
}
mp.ctype = "multipart/related; boundary=" + mpw.Boundary()
go func() {
for _, part := range parts {
w, err := mpw.CreatePart(typeHeader(part.typ))
if err != nil {
mpw.Close()
pw.CloseWithError(fmt.Errorf("googleapi: CreatePart failed: %v", err))
return
}
_, err = io.Copy(w, part.Reader)
if err != nil {
mpw.Close()
pw.CloseWithError(fmt.Errorf("googleapi: Copy failed: %v", err))
return
}
}
mpw.Close()
pw.Close()
}()
return mp
}
func (mp *multipartReader) Read(data []byte) (n int, err error) {
return mp.pr.Read(data)
}
func (mp *multipartReader) Close() error {
mp.mu.Lock()
if !mp.pipeOpen {
mp.mu.Unlock()
return nil
}
mp.pipeOpen = false
mp.mu.Unlock()
return mp.pr.Close()
}
// CombineBodyMedia combines a json body with media content to create a multipart/related HTTP body.
// It returns a ReadCloser containing the combined body, and the overall "multipart/related" content type, with random boundary.
//
// The caller must call Close on the returned ReadCloser if reads are abandoned before reaching EOF.
func CombineBodyMedia(body io.Reader, bodyContentType string, media io.Reader, mediaContentType string) (io.ReadCloser, string) {
return combineBodyMedia(body, bodyContentType, media, mediaContentType, "")
}
// combineBodyMedia is CombineBodyMedia but with an optional mimeBoundary field.
func combineBodyMedia(body io.Reader, bodyContentType string, media io.Reader, mediaContentType, mimeBoundary string) (io.ReadCloser, string) {
mp := newMultipartReader([]typeReader{
{body, bodyContentType},
{media, mediaContentType},
}, mimeBoundary)
return mp, mp.ctype
}
func typeHeader(contentType string) textproto.MIMEHeader {
h := make(textproto.MIMEHeader)
if contentType != "" {
h.Set("Content-Type", contentType)
}
return h
}
// PrepareUpload determines whether the data in the supplied reader should be
// uploaded in a single request, or in sequential chunks.
// chunkSize is the size of the chunk that media should be split into.
//
// If chunkSize is zero, media is returned as the first value, and the other
// two return values are nil, true.
//
// Otherwise, a MediaBuffer is returned, along with a bool indicating whether the
// contents of media fit in a single chunk.
//
// After PrepareUpload has been called, media should no longer be used: the
// media content should be accessed via one of the return values.
func PrepareUpload(media io.Reader, chunkSize int, enableAutoChecksum bool) (r io.Reader, mb *MediaBuffer, singleChunk bool) {
if chunkSize == 0 { // do not chunk
return media, nil, true
}
mb = NewMediaBuffer(media, chunkSize)
mb.enableAutoChecksum = enableAutoChecksum
_, _, _, err := mb.Chunk()
// If err is io.EOF, we can upload this in a single request. Otherwise, err is
// either nil or a non-EOF error. If it is the latter, then the next call to
// mb.Chunk will return the same error. Returning a MediaBuffer ensures that this
// error will be handled at some point.
return nil, mb, err == io.EOF
}
// MediaInfo holds information for media uploads. It is intended for use by generated
// 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
chunkRetryDeadline time.Duration
chunkTransferTimeout time.Duration
}
// NewInfoFromMedia should be invoked from the Media method of a call. It returns a
// MediaInfo populated with chunk size and content type, and a reader or MediaBuffer
// if needed.
func NewInfoFromMedia(r io.Reader, options []googleapi.MediaOption) *MediaInfo {
mi := &MediaInfo{}
opts := googleapi.ProcessMediaOptions(options)
if !opts.ForceEmptyContentType {
mi.mType = opts.ContentType
if mi.mType == "" {
r, mi.mType = gax.DetermineContentType(r)
}
}
mi.chunkRetryDeadline = opts.ChunkRetryDeadline
mi.chunkTransferTimeout = opts.ChunkTransferTimeout
mi.media, mi.buffer, mi.singleChunk = PrepareUpload(r, opts.ChunkSize, opts.EnableAutoChecksum)
return mi
}
// NewInfoFromResumableMedia should be invoked from the ResumableMedia method of a
// 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)
mType := mediaType
if mType == "" {
rdr, mType = gax.DetermineContentType(rdr)
}
return &MediaInfo{
size: size,
mType: mType,
buffer: NewMediaBuffer(rdr, googleapi.DefaultUploadChunkSize),
media: nil,
singleChunk: false,
}
}
// SetProgressUpdater sets the progress updater for the media info.
func (mi *MediaInfo) SetProgressUpdater(pu googleapi.ProgressUpdater) {
if mi != nil {
mi.progressUpdater = pu
}
}
// UploadType determines the type of upload: a single request, or a resumable
// series of requests.
func (mi *MediaInfo) UploadType() string {
if mi.singleChunk {
return "multipart"
}
return "resumable"
}
// UploadRequest sets up an HTTP request for media upload. It adds headers
// as necessary, and returns a replacement for the body and a function for http.Request.GetBody.
func (mi *MediaInfo) UploadRequest(reqHeaders http.Header, body io.Reader) (newBody io.Reader, getBody func() (io.ReadCloser, error), cleanup func()) {
if body == nil {
body = new(bytes.Buffer)
}
cleanup = func() {}
if mi == nil {
return body, nil, cleanup
}
var media io.Reader
if mi.media != nil {
// This only happens when the caller has turned off chunking. In that
// case, we write all of media in a single non-retryable request.
media = mi.media
} else if mi.singleChunk {
// The data fits in a single chunk, which has now been read into the MediaBuffer.
// We obtain that chunk so we can write it in a single request. The request can
// 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 = append(toCleanup, combined)
if fb != nil && fm != nil {
getBody = func() (io.ReadCloser, error) {
rb := io.NopCloser(fb())
rm := io.NopCloser(fm())
var mimeBoundary string
if _, params, err := mime.ParseMediaType(ctype); err == nil {
mimeBoundary = params["boundary"]
}
r, _ := combineBodyMedia(rb, "application/json", rm, mi.mType, mimeBoundary)
toCleanup = append(toCleanup, r)
return r, nil
}
}
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 := io.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
}
// readerFunc returns a function that always returns an io.Reader that has the same
// contents as r, provided that can be done without consuming r. Otherwise, it
// returns nil.
// See http.NewRequest (in net/http/request.go).
func readerFunc(r io.Reader) func() io.Reader {
switch r := r.(type) {
case *bytes.Buffer:
buf := r.Bytes()
return func() io.Reader { return bytes.NewReader(buf) }
case *bytes.Reader:
snapshot := *r
return func() io.Reader { r := snapshot; return &r }
case *strings.Reader:
snapshot := *r
return func() io.Reader { r := snapshot; return &r }
default:
return nil
}
}
// ResumableUpload returns an appropriately configured ResumableUpload value if the
// upload is resumable, or nil otherwise.
func (mi *MediaInfo) ResumableUpload(locURI string) *ResumableUpload {
if mi == nil || mi.singleChunk {
return nil
}
return &ResumableUpload{
URI: locURI,
Media: mi.buffer,
MediaType: mi.mType,
Callback: func(curr int64) {
if mi.progressUpdater != nil {
mi.progressUpdater(curr, mi.size)
}
},
ChunkRetryDeadline: mi.chunkRetryDeadline,
ChunkTransferTimeout: mi.chunkTransferTimeout,
}
}
// SetGetBody sets the GetBody field of req to f. This was once needed
// to gracefully support Go 1.7 and earlier which didn't have that
// field.
//
// Deprecated: the code generator no longer uses this as of
// 2019-02-19. Nothing else should be calling this anyway, but we
// won't delete this immediately; it will be deleted in as early as 6
// months.
func SetGetBody(req *http.Request, f func() (io.ReadCloser, error)) {
req.GetBody = f
}

View File

@@ -0,0 +1,78 @@
// Copyright 2015 The Go Authors. 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 (
"net/http"
"net/url"
"google.golang.org/api/googleapi"
"google.golang.org/api/internal"
)
// URLParams is a simplified replacement for url.Values
// that safely builds up URL parameters for encoding.
type URLParams map[string][]string
// Get returns the first value for the given key, or "".
func (u URLParams) Get(key string) string {
vs := u[key]
if len(vs) == 0 {
return ""
}
return vs[0]
}
// Set sets the key to value.
// It replaces any existing values.
func (u URLParams) Set(key, value string) {
u[key] = []string{value}
}
// SetMulti sets the key to an array of values.
// It replaces any existing values.
// Note that values must not be modified after calling SetMulti
// so the caller is responsible for making a copy if necessary.
func (u URLParams) SetMulti(key string, values []string) {
u[key] = values
}
// 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()
}
// SetOptions sets the URL params and any additional `CallOption` or
// `MultiCallOption` passed in.
func SetOptions(u URLParams, opts ...googleapi.CallOption) {
for _, o := range opts {
m, ok := o.(googleapi.MultiCallOption)
if ok {
u.SetMulti(m.GetMulti())
continue
}
u.Set(o.Get())
}
}
// SetHeaders sets common headers for all requests. The keyvals header pairs
// should have a corresponding value for every key provided. If there is an odd
// number of keyvals this method will panic.
func SetHeaders(userAgent, contentType string, userHeaders http.Header, keyvals ...string) http.Header {
reqHeaders := make(http.Header)
reqHeaders.Set("x-goog-api-client", "gl-go/"+GoVersion()+" gdcl/"+internal.Version)
for i := 0; i < len(keyvals); i = i + 2 {
reqHeaders.Set(keyvals[i], keyvals[i+1])
}
reqHeaders.Set("User-Agent", userAgent)
if contentType != "" {
reqHeaders.Set("Content-Type", contentType)
}
for k, v := range userHeaders {
reqHeaders[k] = v
}
return reqHeaders
}

View File

@@ -0,0 +1,356 @@
// Copyright 2016 The Go Authors. 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 (
"context"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"io"
"net/http"
"strings"
"sync"
"time"
"github.com/google/uuid"
"google.golang.org/api/internal"
)
const (
crc32cPrefix = "crc32c"
hashHeaderKey = "X-Goog-Hash"
)
// ResumableUpload is used by the generated APIs to provide resumable uploads.
// It is not used by developers directly.
type ResumableUpload struct {
Client *http.Client
// URI is the resumable resource destination provided by the server after specifying "&uploadType=resumable".
URI string
UserAgent string // User-Agent for header of the request
// Media is the object being uploaded.
Media *MediaBuffer
// MediaType defines the media type, e.g. "image/jpeg".
MediaType string
mu sync.Mutex // guards progress
progress int64 // number of bytes uploaded so far
// 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
// ChunkTransferTimeout configures the per-chunk transfer timeout. If a chunk upload stalls for longer than
// this duration, the upload will be retried.
ChunkTransferTimeout time.Duration
// Track current request invocation ID and attempt count for retry metrics
// and idempotency headers.
invocationID string
attempts int
}
// Progress returns the number of bytes uploaded at this point.
func (rx *ResumableUpload) Progress() int64 {
rx.mu.Lock()
defer rx.mu.Unlock()
return rx.progress
}
// doUploadRequest performs a single HTTP request to upload data.
// off specifies the offset in rx.Media from which data is drawn.
// size is the number of bytes in data.
// final specifies whether data is the final chunk to be uploaded.
func (rx *ResumableUpload) doUploadRequest(ctx context.Context, data io.Reader, off, size int64, final bool) (*http.Response, error) {
req, err := http.NewRequest("POST", rx.URI, data)
if err != nil {
return nil, err
}
req.ContentLength = size
var contentRange string
if final {
if size == 0 {
contentRange = fmt.Sprintf("bytes */%v", off)
} else {
contentRange = fmt.Sprintf("bytes %v-%v/%v", off, off+size-1, off+size)
}
} else {
contentRange = fmt.Sprintf("bytes %v-%v/*", off, off+size-1)
}
req.Header.Set("Content-Range", contentRange)
req.Header.Set("Content-Type", rx.MediaType)
req.Header.Set("User-Agent", rx.UserAgent)
// TODO(b/274504690): Consider dropping gccl-invocation-id key since it
// duplicates the X-Goog-Gcs-Idempotency-Token header (added in v0.115.0).
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}, " "))
// Set idempotency token header which is used by GCS uploads.
req.Header.Set("X-Goog-Gcs-Idempotency-Token", rx.invocationID)
// 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
// semantics, Google added this new request header which
// causes it to not use "308" and instead reply with 200 OK
// and sets the upload-specific "X-HTTP-Status-Code-Override:
// 308" response header.
req.Header.Set("X-GUploader-No-308", "yes")
// Server accepts checksum only on final request through header.
if final && rx.Media.enableAutoChecksum {
req.Header.Set(hashHeaderKey, fmt.Sprintf("%v=%v", crc32cPrefix, encodeUint32(rx.Media.fullObjectChecksum)))
}
return SendRequest(ctx, rx.Client, req)
}
func statusResumeIncomplete(resp *http.Response) bool {
// This is how the server signals "status resume incomplete"
// when X-GUploader-No-308 is set to "yes":
return resp != nil && resp.Header.Get("X-Http-Status-Code-Override") == "308"
}
// reportProgress calls a user-supplied callback to report upload progress.
// If old==updated, the callback is not called.
func (rx *ResumableUpload) reportProgress(old, updated int64) {
if updated-old == 0 {
return
}
rx.mu.Lock()
rx.progress = updated
rx.mu.Unlock()
if rx.Callback != nil {
rx.Callback(updated)
}
}
// transferChunk performs a single HTTP request to upload a single chunk.
// It uses a goroutine to perform the upload and a timer to enforce ChunkTransferTimeout.
func (rx *ResumableUpload) transferChunk(ctx context.Context, chunk io.Reader, off, size int64, done bool) (*http.Response, error) {
// If no timeout is specified, perform the request synchronously without a timer.
if rx.ChunkTransferTimeout == 0 {
res, err := rx.doUploadRequest(ctx, chunk, off, size, done)
if err != nil {
return res, err
}
return res, nil
}
// Start a timer for the ChunkTransferTimeout duration.
timer := time.NewTimer(rx.ChunkTransferTimeout)
// A struct to hold the result from the goroutine.
type uploadResult struct {
res *http.Response
err error
}
// A buffered channel to receive the result of the upload.
resultCh := make(chan uploadResult, 1)
// Create a cancellable context for the upload request. This allows us to
// abort the request if the timer fires first.
rCtx, cancel := context.WithCancel(ctx)
// NOTE: We do NOT use `defer cancel()` here. The context must remain valid
// for the caller to read the response body of a successful request.
// Cancellation is handled manually on timeout paths.
// Starting the chunk upload in parallel.
go func() {
res, err := rx.doUploadRequest(rCtx, chunk, off, size, done)
resultCh <- uploadResult{res: res, err: err}
}()
// Wait for timer to fire or result channel to have the uploadResult or ctx to be cancelled.
select {
// Note: Calling cancel() will guarantee that the goroutine finishes,
// so these two cases will never block forever on draining the resultCh.
case <-ctx.Done():
// Context is cancelled for the overall upload.
cancel()
// Drain resultCh.
<-resultCh
return nil, ctx.Err()
case <-timer.C:
// Chunk Transfer timer fired before resultCh so we return context.DeadlineExceeded.
cancel()
// Drain resultCh.
<-resultCh
return nil, context.DeadlineExceeded
case result := <-resultCh:
// Handle the result from the upload.
if result.err != nil {
return result.res, result.err
}
return result.res, nil
}
}
// uploadChunkWithRetries attempts to upload a single chunk, with retries
// within ChunkRetryDeadline if ChunkTransferTimeout is non-zero.
func (rx *ResumableUpload) uploadChunkWithRetries(ctx context.Context, chunk io.Reader, off, size int64, done bool) (*http.Response, error) {
// Configure error retryable criteria.
shouldRetry := rx.Retry.errorFunc()
// Configure single chunk retry deadline.
chunkRetryDeadline := defaultRetryDeadline
if rx.ChunkRetryDeadline != 0 {
chunkRetryDeadline = rx.ChunkRetryDeadline
}
// Each chunk gets its own initialized-at-zero backoff and invocation ID.
bo := rx.Retry.backoff()
quitAfterTimer := time.NewTimer(chunkRetryDeadline)
defer quitAfterTimer.Stop()
rx.attempts = 1
rx.invocationID = uuid.New().String()
var pause time.Duration
var resp *http.Response
var err error
// Retry loop for a single chunk.
for {
// Wait for the backoff period, unless the context is canceled or the
// retry deadline is hit.
backoffPauseTimer := time.NewTimer(pause)
select {
case <-ctx.Done():
backoffPauseTimer.Stop()
if err == nil {
err = ctx.Err()
}
return resp, err
case <-backoffPauseTimer.C:
case <-quitAfterTimer.C:
backoffPauseTimer.Stop()
return resp, err
}
backoffPauseTimer.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():
if err == nil {
err = ctx.Err()
}
return resp, err
case <-quitAfterTimer.C:
return resp, err
default:
}
// We close the response's body here, since we definitely will not
// return `resp` now. If we close it before the select case above, a
// timer may fire and cause us to return a response with a closed body
// (in which case, the caller will not get the error message in the body).
if resp != nil && resp.Body != nil {
// Read the body to EOF - if the Body is not both read to EOF and closed,
// the Client's underlying RoundTripper may not be able to re-use the
// persistent TCP connection to the server for a subsequent "keep-alive" request.
// See https://pkg.go.dev/net/http#Client.Do
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}
resp, err = rx.transferChunk(ctx, chunk, off, size, done)
status := 0
if resp != nil {
status = resp.StatusCode
}
// We sent "X-GUploader-No-308: yes" (see comment elsewhere in
// this file), so we don't expect to get a 308.
if status == 308 {
return nil, errors.New("unexpected 308 response status code")
}
// Chunk upload should be retried if the ChunkTransferTimeout is non-zero and err is context deadline exceeded
// or we encounter a retryable error.
if (rx.ChunkTransferTimeout != 0 && errors.Is(err, context.DeadlineExceeded)) || shouldRetry(status, err) {
rx.attempts++
pause = bo.Pause()
chunk, _, _, _ = rx.Media.Chunk()
continue
}
return resp, err
}
}
// Upload starts the process of a resumable upload with a cancellable context.
// It is called from the auto-generated API code and is not visible to the user.
// Before sending an HTTP request, Upload calls any registered hook functions,
// and calls the returned functions after the request returns (see send.go).
// rx is private to the auto-generated API code.
// Exactly one of resp or err will be nil. If resp is non-nil, the caller must call resp.Body.Close.
// Upload does not parse the response into the error on a non 200 response;
// it is the caller's responsibility to call resp.Body.Close.
func (rx *ResumableUpload) Upload(ctx context.Context) (*http.Response, error) {
for {
chunk, off, size, err := rx.Media.Chunk()
done := err == io.EOF
if !done && err != nil {
return nil, err
}
resp, err := rx.uploadChunkWithRetries(ctx, chunk, off, int64(size), done)
// There are a couple of cases where it's possible for err and resp to both
// be non-nil. However, we expose a simpler contract to our callers: exactly
// one of resp and err will be non-nil. This means that any response body
// must be closed here before returning a non-nil error.
if err != nil {
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
// If there were retries, indicate this in the error message and wrap the final error.
if rx.attempts > 1 {
return nil, fmt.Errorf("chunk upload failed after %d attempts, final error: %w", rx.attempts, 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 ChunkRetryDeadline", rx.URI)
}
if resp.StatusCode == http.StatusOK {
rx.reportProgress(off, off+int64(size))
}
if statusResumeIncomplete(resp) {
// The upload is not yet complete, but the server has acknowledged this chunk.
// We don't have anything to do with the response body.
if resp.Body != nil {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}
rx.Media.Next()
continue
}
return resp, nil
}
}
// Encode a uint32 as Base64 in big-endian byte order.
func encodeUint32(u uint32) string {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, u)
return base64.StdEncoding.EncodeToString(b)
}

View File

@@ -0,0 +1,123 @@
// 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"
"net/url"
"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}
}
)
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 errors.Is(err, io.ErrUnexpectedEOF) {
return true
}
if errors.Is(err, net.ErrClosed) {
return true
}
switch e := err.(type) {
case *net.OpError, *url.Error:
// Retry socket-level errors ECONNREFUSED and ECONNRESET (from syscall).
// Unfortunately the error type is unexported, so we resort to string
// matching.
retriable := []string{"connection refused", "connection reset", "broken pipe"}
for _, s := range retriable {
if strings.Contains(e.Error(), s) {
return true
}
}
case interface{ Temporary() bool }:
if e.Temporary() {
return true
}
}
// If error unwrapping is available, use this to examine wrapped
// errors.
if e, ok := err.(interface{ Unwrap() error }); ok {
return shouldRetry(status, e.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)
}
}

View File

@@ -0,0 +1,241 @@
// Copyright 2016 The Go Authors. 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 (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/google/uuid"
"github.com/googleapis/gax-go/v2"
"github.com/googleapis/gax-go/v2/callctx"
)
// 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
// reverse order.
func SendRequest(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
// Add headers set in context metadata.
if ctx != nil {
headers := callctx.HeadersFromContext(ctx)
for k, vals := range headers {
if k == "x-goog-api-client" {
// Merge all values into a single "x-goog-api-client" header.
var mergedVal strings.Builder
baseXGoogHeader := req.Header.Get("X-Goog-Api-Client")
if baseXGoogHeader != "" {
mergedVal.WriteString(baseXGoogHeader)
mergedVal.WriteRune(' ')
}
for _, v := range vals {
mergedVal.WriteString(v)
mergedVal.WriteRune(' ')
}
// Remove the last space and replace the header on the request.
req.Header.Set(k, mergedVal.String()[:mergedVal.Len()-1])
} else {
for _, v := range vals {
req.Header.Add(k, v)
}
}
}
}
// 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 {
return nil, errors.New("google api: custom Accept-Encoding headers not allowed")
}
if ctx == nil {
return client.Do(req)
}
return send(ctx, client, req)
}
func send(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
if client == nil {
client = http.DefaultClient
}
resp, err := client.Do(req.WithContext(ctx))
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
if err != nil {
select {
case <-ctx.Done():
err = ctx.Err()
default:
}
}
return resp, err
}
// SendRequestWithRetry sends a single HTTP request using the given client,
// with retries if a retryable error is returned.
// 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, retry *RetryConfig) (*http.Response, error) {
// Add headers set in context metadata.
if ctx != nil {
headers := callctx.HeadersFromContext(ctx)
for k, vals := range headers {
for _, v := range vals {
req.Header.Add(k, v)
}
}
}
// 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 {
return nil, errors.New("google api: custom Accept-Encoding headers not allowed")
}
if ctx == nil {
return client.Do(req)
}
return sendAndRetry(ctx, client, req, retry)
}
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()
xGoogHeaderVals := req.Header.Values("X-Goog-Api-Client")
baseXGoogHeader := strings.Join(xGoogHeaderVals, " ")
// Loop to retry the request, up to the context deadline.
var pause time.Duration
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():
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, ctx.Err()
}
// Set retry metrics and idempotency headers for GCS.
// TODO(b/274504690): Consider dropping gccl-invocation-id key since it
// duplicates the X-Goog-Gcs-Idempotency-Token header (added in v0.115.0).
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)
req.Header.Set("X-Goog-Gcs-Idempotency-Token", invocationID)
resp, err = client.Do(req.WithContext(ctx))
var status int
if resp != nil {
status = resp.StatusCode
}
// 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 || !errorFunc(status, err) {
break
}
attempts++
var errBody error
req.Body, errBody = req.GetBody()
if errBody != nil {
break
}
pause = bo.Pause()
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
}
return resp, err
}
// DecodeResponse decodes the body of res into target. If there is no body,
// target is unchanged.
func DecodeResponse(target interface{}, res *http.Response) error {
if res.StatusCode == http.StatusNoContent {
return nil
}
return json.NewDecoder(res.Body).Decode(target)
}
// DecodeResponseBytes decodes the body of res into target and returns bytes read
// from the body. If there is no body, target is unchanged.
func DecodeResponseBytes(target interface{}, res *http.Response) ([]byte, error) {
if res.StatusCode == http.StatusNoContent {
return nil, nil
}
b, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
if err := json.Unmarshal(b, target); err != nil {
return nil, err
}
return b, nil
}

View File

@@ -0,0 +1,53 @@
// Copyright 2020 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 (
"runtime"
"strings"
"unicode"
)
// GoVersion returns the Go runtime version. The returned string
// has no whitespace.
func GoVersion() string {
return goVersion
}
var goVersion = goVer(runtime.Version())
const develPrefix = "devel +"
func goVer(s string) string {
if strings.HasPrefix(s, develPrefix) {
s = s[len(develPrefix):]
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
s = s[:p]
}
return s
}
if strings.HasPrefix(s, "go1") {
s = s[2:]
var prerelease string
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
s, prerelease = s[:p], s[p:]
}
if strings.HasSuffix(s, ".") {
s += "0"
} else if strings.Count(s, ".") < 2 {
s += ".0"
}
if prerelease != "" {
s += "-" + prerelease
}
return s
}
return ""
}
func notSemverRune(r rune) bool {
return !strings.ContainsRune("0123456789.", r)
}

View File

@@ -0,0 +1,127 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package impersonate is used to impersonate Google Credentials.
package impersonate
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"golang.org/x/oauth2"
)
// Config for generating impersonated credentials.
type Config struct {
// Target is the service account to impersonate. Required.
Target string
// Scopes the impersonated credential should have. Required.
Scopes []string
// Delegates are the service accounts in a delegation chain. Each service
// account must be granted roles/iam.serviceAccountTokenCreator on the next
// service account in the chain. Optional.
Delegates []string
}
// TokenSource returns an impersonated TokenSource configured with the provided
// config using ts as the base credential provider for making requests.
func TokenSource(ctx context.Context, ts oauth2.TokenSource, config *Config) (oauth2.TokenSource, error) {
if len(config.Scopes) == 0 {
return nil, fmt.Errorf("impersonate: scopes must be provided")
}
its := impersonatedTokenSource{
ctx: ctx,
ts: ts,
name: formatIAMServiceAccountName(config.Target),
// Default to the longest acceptable value of one hour as the token will
// be refreshed automatically.
lifetime: "3600s",
}
its.delegates = make([]string, len(config.Delegates))
for i, v := range config.Delegates {
its.delegates[i] = formatIAMServiceAccountName(v)
}
its.scopes = make([]string, len(config.Scopes))
copy(its.scopes, config.Scopes)
return oauth2.ReuseTokenSource(nil, its), nil
}
func formatIAMServiceAccountName(name string) string {
return fmt.Sprintf("projects/-/serviceAccounts/%s", name)
}
type generateAccessTokenReq struct {
Delegates []string `json:"delegates,omitempty"`
Lifetime string `json:"lifetime,omitempty"`
Scope []string `json:"scope,omitempty"`
}
type generateAccessTokenResp struct {
AccessToken string `json:"accessToken"`
ExpireTime string `json:"expireTime"`
}
type impersonatedTokenSource struct {
ctx context.Context
ts oauth2.TokenSource
name string
lifetime string
scopes []string
delegates []string
}
// Token returns an impersonated Token.
func (i impersonatedTokenSource) Token() (*oauth2.Token, error) {
hc := oauth2.NewClient(i.ctx, i.ts)
reqBody := generateAccessTokenReq{
Delegates: i.delegates,
Lifetime: i.lifetime,
Scope: i.scopes,
}
b, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("impersonate: unable to marshal request: %v", err)
}
url := fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateAccessToken", i.name)
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
if err != nil {
return nil, fmt.Errorf("impersonate: unable to create request: %v", err)
}
req = req.WithContext(i.ctx)
req.Header.Set("Content-Type", "application/json")
resp, err := hc.Do(req)
if err != nil {
return nil, fmt.Errorf("impersonate: unable to generate access token: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return nil, fmt.Errorf("impersonate: unable to read body: %v", err)
}
if c := resp.StatusCode; c < 200 || c > 299 {
return nil, fmt.Errorf("impersonate: status code %d: %s", c, body)
}
var accessTokenResp generateAccessTokenResp
if err := json.Unmarshal(body, &accessTokenResp); err != nil {
return nil, fmt.Errorf("impersonate: unable to parse response: %v", err)
}
expiry, err := time.Parse(time.RFC3339, accessTokenResp.ExpireTime)
if err != nil {
return nil, fmt.Errorf("impersonate: unable to parse expiry: %v", err)
}
return &oauth2.Token{
AccessToken: accessTokenResp.AccessToken,
Expiry: expiry,
}, nil
}

136
vendor/google.golang.org/api/internal/s2a.go generated vendored Normal file
View File

@@ -0,0 +1,136 @@
// Copyright 2023 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal
import (
"encoding/json"
"log"
"sync"
"time"
"cloud.google.com/go/compute/metadata"
)
const configEndpointSuffix = "instance/platform-security/auto-mtls-configuration"
// The period an MTLS config can be reused before needing refresh.
var configExpiry = time.Hour
// GetS2AAddress returns the S2A address to be reached via plaintext connection.
func GetS2AAddress() string {
c, err := getMetadataMTLSAutoConfig().Config()
if err != nil {
return ""
}
if !c.Valid() {
return ""
}
return c.S2A.PlaintextAddress
}
type mtlsConfigSource interface {
Config() (*mtlsConfig, error)
}
// mdsMTLSAutoConfigSource is an instance of reuseMTLSConfigSource, with metadataMTLSAutoConfig as its config source.
var (
mdsMTLSAutoConfigSource mtlsConfigSource
once sync.Once
)
// getMetadataMTLSAutoConfig returns mdsMTLSAutoConfigSource, which is backed by config from MDS with auto-refresh.
func getMetadataMTLSAutoConfig() mtlsConfigSource {
once.Do(func() {
mdsMTLSAutoConfigSource = &reuseMTLSConfigSource{
src: &metadataMTLSAutoConfig{},
}
})
return mdsMTLSAutoConfigSource
}
// reuseMTLSConfigSource caches a valid version of mtlsConfig, and uses `src` to refresh upon config expiry.
// It implements the mtlsConfigSource interface, so calling Config() on it returns an mtlsConfig.
type reuseMTLSConfigSource struct {
src mtlsConfigSource // src.Config() is called when config is expired
mu sync.Mutex // mutex guards config
config *mtlsConfig // cached config
}
func (cs *reuseMTLSConfigSource) Config() (*mtlsConfig, error) {
cs.mu.Lock()
defer cs.mu.Unlock()
if cs.config.Valid() {
return cs.config, nil
}
c, err := cs.src.Config()
if err != nil {
return nil, err
}
cs.config = c
return c, nil
}
// metadataMTLSAutoConfig is an implementation of the interface mtlsConfigSource
// It has the logic to query MDS and return an mtlsConfig
type metadataMTLSAutoConfig struct{}
var httpGetMetadataMTLSConfig = func() (string, error) {
return metadata.Get(configEndpointSuffix)
}
func (cs *metadataMTLSAutoConfig) Config() (*mtlsConfig, error) {
resp, err := httpGetMetadataMTLSConfig()
if err != nil {
log.Printf("querying MTLS config from MDS endpoint failed: %v", err)
return defaultMTLSConfig(), nil
}
var config mtlsConfig
err = json.Unmarshal([]byte(resp), &config)
if err != nil {
log.Printf("unmarshalling MTLS config from MDS endpoint failed: %v", err)
return defaultMTLSConfig(), nil
}
if config.S2A == nil {
log.Printf("returned MTLS config from MDS endpoint is invalid: %v", config)
return defaultMTLSConfig(), nil
}
// set new expiry
config.Expiry = time.Now().Add(configExpiry)
return &config, nil
}
func defaultMTLSConfig() *mtlsConfig {
return &mtlsConfig{
S2A: &s2aAddresses{
PlaintextAddress: "",
MTLSAddress: "",
},
Expiry: time.Now().Add(configExpiry),
}
}
// s2aAddresses contains the plaintext and/or MTLS S2A addresses.
type s2aAddresses struct {
// PlaintextAddress is the plaintext address to reach S2A
PlaintextAddress string `json:"plaintext_address"`
// MTLSAddress is the MTLS address to reach S2A
MTLSAddress string `json:"mtls_address"`
}
// mtlsConfig contains the configuration for establishing MTLS connections with Google APIs.
type mtlsConfig struct {
S2A *s2aAddresses `json:"s2a"`
Expiry time.Time
}
func (c *mtlsConfig) Valid() bool {
return c != nil && c.S2A != nil && !c.expired()
}
func (c *mtlsConfig) expired() bool {
return c.Expiry.Before(time.Now())
}

303
vendor/google.golang.org/api/internal/settings.go generated vendored Normal file
View File

@@ -0,0 +1,303 @@
// Copyright 2017 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package internal supports the options and transport packages.
package internal
import (
"crypto/tls"
"errors"
"log/slog"
"net/http"
"os"
"strconv"
"time"
"cloud.google.com/go/auth"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/internal/credentialstype"
"google.golang.org/api/internal/impersonate"
"google.golang.org/grpc"
)
const (
newAuthLibEnvVar = "GOOGLE_API_GO_EXPERIMENTAL_ENABLE_NEW_AUTH_LIB"
newAuthLibDisabledEnVar = "GOOGLE_API_GO_EXPERIMENTAL_DISABLE_NEW_AUTH_LIB"
universeDomainEnvVar = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"
defaultUniverseDomain = "googleapis.com"
)
// DialSettings holds information needed to establish a connection with a
// Google API service.
type DialSettings struct {
Endpoint string
DefaultEndpoint string
DefaultEndpointTemplate string
DefaultMTLSEndpoint string
Scopes []string
DefaultScopes []string
EnableJwtWithScope bool
TokenSource oauth2.TokenSource
Credentials *google.Credentials
// Deprecated: Use AuthCredentialsFile instead, due to security risk.
CredentialsFile string
// Deprecated: Use AuthCredentialsJSON instead, due to security risk.
CredentialsJSON []byte
InternalCredentials *google.Credentials
UserAgent string
APIKey string
Audiences []string
DefaultAudience string
HTTPClient *http.Client
GRPCDialOpts []grpc.DialOption
GRPCConn *grpc.ClientConn
GRPCConnPool ConnPool
GRPCConnPoolSize int
NoAuth bool
TelemetryDisabled bool
ClientCertSource func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
CustomClaims map[string]interface{}
SkipValidation bool
ImpersonationConfig *impersonate.Config
EnableDirectPath bool
EnableDirectPathXds bool
AllowNonDefaultServiceAccount bool
DefaultUniverseDomain string
UniverseDomain string
AllowHardBoundTokens []string
Logger *slog.Logger
// Google API system parameters. For more information please read:
// https://cloud.google.com/apis/docs/system-parameters
QuotaProject string
RequestReason string
// TelemetryAttributes specifies a map of telemetry attributes to be added
// to all OpenTelemetry signals, such as tracing and metrics, for purposes
// including representing the static identity of the client (e.g., service
// name, version). These attributes are expected to be consistent across all
// signals to enable cross-signal correlation.
TelemetryAttributes map[string]string
// New Auth library Options
AuthCredentials *auth.Credentials
AuthCredentialsJSON []byte
AuthCredentialsFile string
AuthCredentialsType credentialstype.CredType
EnableNewAuthLibrary bool
// TODO(b/372244283): Remove after b/358175516 has been fixed
EnableAsyncRefreshDryRun func()
}
// GetScopes returns the user-provided scopes, if set, or else falls back to the
// default scopes.
func (ds *DialSettings) GetScopes() []string {
if len(ds.Scopes) > 0 {
return ds.Scopes
}
return ds.DefaultScopes
}
// GetAudience returns the user-provided audience, if set, or else falls back to the default audience.
func (ds *DialSettings) GetAudience() string {
if ds.HasCustomAudience() {
return ds.Audiences[0]
}
return ds.DefaultAudience
}
// HasCustomAudience returns true if a custom audience is provided by users.
func (ds *DialSettings) HasCustomAudience() bool {
return len(ds.Audiences) > 0
}
// IsNewAuthLibraryEnabled returns true if the new auth library should be used.
func (ds *DialSettings) IsNewAuthLibraryEnabled() bool {
// Disabled env is for future rollouts to make sure there is a way to easily
// disable this behaviour once we switch in on by default.
if b, err := strconv.ParseBool(os.Getenv(newAuthLibDisabledEnVar)); err == nil && b {
return false
}
if ds.EnableNewAuthLibrary {
return true
}
if ds.AuthCredentials != nil {
return true
}
if len(ds.AuthCredentialsJSON) > 0 {
return true
}
if ds.AuthCredentialsFile != "" {
return true
}
if b, err := strconv.ParseBool(os.Getenv(newAuthLibEnvVar)); err == nil {
return b
}
return false
}
// GetAuthCredentialsJSON returns the AuthCredentialsJSON and AuthCredentialsType, if set.
// Otherwise it falls back to the deprecated CredentialsJSON with an Unknown type.
//
// Use AuthCredentialsJSON if provided, as it is the safer, recommended option.
// CredentialsJSON is populated by the deprecated WithCredentialsJSON.
func (ds *DialSettings) GetAuthCredentialsJSON() ([]byte, credentialstype.CredType) {
if len(ds.AuthCredentialsJSON) > 0 {
return ds.AuthCredentialsJSON, ds.AuthCredentialsType
}
return ds.CredentialsJSON, credentialstype.Unknown
}
// GetAuthCredentialsFile returns the AuthCredentialsFile and AuthCredentialsType, if set.
// Otherwise it falls back to the deprecated CredentialsFile with an Unknown type.
//
// Use AuthCredentialsFile if provided, as it is the safer, recommended option.
// CredentialsFile is populated by the deprecated WithCredentialsFile.
func (ds *DialSettings) GetAuthCredentialsFile() (string, credentialstype.CredType) {
if ds.AuthCredentialsFile != "" {
return ds.AuthCredentialsFile, ds.AuthCredentialsType
}
return ds.CredentialsFile, credentialstype.Unknown
}
// Validate reports an error if ds is invalid.
func (ds *DialSettings) Validate() error {
if ds.SkipValidation {
return nil
}
hasCreds := ds.APIKey != "" || ds.TokenSource != nil || ds.CredentialsFile != "" || ds.Credentials != nil || ds.AuthCredentials != nil || len(ds.AuthCredentialsJSON) > 0 || ds.AuthCredentialsFile != ""
if ds.NoAuth && hasCreds {
return errors.New("options.WithoutAuthentication is incompatible with any option that provides credentials")
}
// Credentials should not appear with other options.
// AuthCredentials is a special case that may be present with
// with other options in order to facilitate automatic conversion of
// oauth2 types (old auth) to cloud.google.com/go/auth types (new auth).
// We currently allow TokenSource and CredentialsFile to coexist.
// TODO(jba): make TokenSource & CredentialsFile an error (breaking change).
nCreds := 0
if ds.Credentials != nil {
nCreds++
}
if len(ds.CredentialsJSON) > 0 {
nCreds++
}
if len(ds.AuthCredentialsJSON) > 0 {
nCreds++
}
if ds.AuthCredentialsFile != "" {
nCreds++
}
if ds.CredentialsFile != "" {
nCreds++
}
if ds.APIKey != "" {
nCreds++
}
if ds.TokenSource != nil {
nCreds++
}
if len(ds.Scopes) > 0 && len(ds.Audiences) > 0 {
return errors.New("WithScopes is incompatible with WithAudience")
}
// Accept only one form of credentials, except we allow TokenSource and CredentialsFile for backwards compatibility.
if nCreds > 1 && !(nCreds == 2 && ds.TokenSource != nil && ds.CredentialsFile != "") {
return errors.New("multiple credential options provided")
}
if ds.GRPCConn != nil && ds.GRPCConnPool != nil {
return errors.New("WithGRPCConn is incompatible with WithConnPool")
}
if ds.HTTPClient != nil && ds.GRPCConnPool != nil {
return errors.New("WithHTTPClient is incompatible with WithConnPool")
}
if ds.HTTPClient != nil && ds.GRPCConn != nil {
return errors.New("WithHTTPClient is incompatible with WithGRPCConn")
}
if ds.HTTPClient != nil && ds.GRPCDialOpts != nil {
return errors.New("WithHTTPClient is incompatible with gRPC dial options")
}
if ds.HTTPClient != nil && ds.QuotaProject != "" {
return errors.New("WithHTTPClient is incompatible with QuotaProject")
}
if ds.HTTPClient != nil && ds.RequestReason != "" {
return errors.New("WithHTTPClient is incompatible with RequestReason")
}
if ds.HTTPClient != nil && ds.ClientCertSource != nil {
return errors.New("WithHTTPClient is incompatible with WithClientCertSource")
}
if ds.ClientCertSource != nil && (ds.GRPCConn != nil || ds.GRPCConnPool != nil || ds.GRPCConnPoolSize != 0 || ds.GRPCDialOpts != nil) {
return errors.New("WithClientCertSource is currently only supported for HTTP. gRPC settings are incompatible")
}
if ds.ImpersonationConfig != nil && len(ds.ImpersonationConfig.Scopes) == 0 && len(ds.Scopes) == 0 {
return errors.New("WithImpersonatedCredentials requires scopes being provided")
}
return nil
}
// GetDefaultUniverseDomain returns the Google default universe domain
// ("googleapis.com").
func (ds *DialSettings) GetDefaultUniverseDomain() string {
return defaultUniverseDomain
}
// GetUniverseDomain returns the default service domain for a given Cloud
// universe, with the following precedence:
//
// 1. A non-empty option.WithUniverseDomain.
// 2. A non-empty environment variable GOOGLE_CLOUD_UNIVERSE_DOMAIN.
// 3. The default value "googleapis.com".
func (ds *DialSettings) GetUniverseDomain() string {
if ds.UniverseDomain != "" {
return ds.UniverseDomain
}
if envUD := os.Getenv(universeDomainEnvVar); envUD != "" {
return envUD
}
return defaultUniverseDomain
}
// IsUniverseDomainGDU returns true if the universe domain is the default Google
// universe ("googleapis.com").
func (ds *DialSettings) IsUniverseDomainGDU() bool {
return ds.GetUniverseDomain() == defaultUniverseDomain
}
// GetUniverseDomain returns the default service domain for a given Cloud
// universe, from google.Credentials. This wrapper function should be removed
// to close https://github.com/googleapis/google-api-go-client/issues/2399.
func GetUniverseDomain(creds *google.Credentials) (string, error) {
timer := time.NewTimer(time.Second)
defer timer.Stop()
errors := make(chan error)
results := make(chan string)
go func() {
result, err := creds.GetUniverseDomain()
if err != nil {
errors <- err
return
}
results <- result
}()
select {
case <-errors:
// An error that is returned before the timer expires is likely to be
// connection refused. Temporarily (2024-03-21) return the GDU domain.
return defaultUniverseDomain, nil
case res := <-results:
return res, nil
case <-timer.C: // Timer is expired.
// If err or res was not returned, it means that creds.GetUniverseDomain()
// did not complete in 1s. Assume that MDS is likely never responding to
// the endpoint and will timeout. This is the source of issues such as
// https://github.com/googleapis/google-cloud-go/issues/9350.
// Temporarily (2024-02-02) return the GDU domain. Restore the original
// calls to creds.GetUniverseDomain() in grpc/dial.go and http/dial.go
// and remove this method to close
// https://github.com/googleapis/google-api-go-client/issues/2399.
return defaultUniverseDomain, nil
}
}

View File

@@ -0,0 +1,27 @@
Copyright (c) 2013 Joshua Tacoma. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,14 @@
name: "uritemplates"
description:
"Package uritemplates is a level 4 implementation of RFC 6570 (URI "
"Template, http://tools.ietf.org/html/rfc6570)."
third_party {
url {
type: GIT
value: "https://github.com/jtacoma/uritemplates"
}
version: "0.1"
last_upgrade_date { year: 2014 month: 8 day: 18 }
license_type: NOTICE
}

View File

@@ -0,0 +1,248 @@
// Copyright 2013 Joshua Tacoma. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package uritemplates is a level 3 implementation of RFC 6570 (URI
// Template, http://tools.ietf.org/html/rfc6570).
// uritemplates does not support composite values (in Go: slices or maps)
// and so does not qualify as a level 4 implementation.
package uritemplates
import (
"bytes"
"errors"
"regexp"
"strconv"
"strings"
)
var (
unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")
validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$")
hex = []byte("0123456789ABCDEF")
)
func pctEncode(src []byte) []byte {
dst := make([]byte, len(src)*3)
for i, b := range src {
buf := dst[i*3 : i*3+3]
buf[0] = 0x25
buf[1] = hex[b/16]
buf[2] = hex[b%16]
}
return dst
}
// pairWriter is a convenience struct which allows escaped and unescaped
// versions of the template to be written in parallel.
type pairWriter struct {
escaped, unescaped bytes.Buffer
}
// Write writes the provided string directly without any escaping.
func (w *pairWriter) Write(s string) {
w.escaped.WriteString(s)
w.unescaped.WriteString(s)
}
// Escape writes the provided string, escaping the string for the
// escaped output.
func (w *pairWriter) Escape(s string, allowReserved bool) {
w.unescaped.WriteString(s)
if allowReserved {
w.escaped.Write(reserved.ReplaceAllFunc([]byte(s), pctEncode))
} else {
w.escaped.Write(unreserved.ReplaceAllFunc([]byte(s), pctEncode))
}
}
// Escaped returns the escaped string.
func (w *pairWriter) Escaped() string {
return w.escaped.String()
}
// Unescaped returns the unescaped string.
func (w *pairWriter) Unescaped() string {
return w.unescaped.String()
}
// A uriTemplate is a parsed representation of a URI template.
type uriTemplate struct {
raw string
parts []templatePart
}
// parse parses a URI template string into a uriTemplate object.
func parse(rawTemplate string) (*uriTemplate, error) {
split := strings.Split(rawTemplate, "{")
parts := make([]templatePart, len(split)*2-1)
for i, s := range split {
if i == 0 {
if strings.Contains(s, "}") {
return nil, errors.New("unexpected }")
}
parts[i].raw = s
continue
}
subsplit := strings.Split(s, "}")
if len(subsplit) != 2 {
return nil, errors.New("malformed template")
}
expression := subsplit[0]
var err error
parts[i*2-1], err = parseExpression(expression)
if err != nil {
return nil, err
}
parts[i*2].raw = subsplit[1]
}
return &uriTemplate{
raw: rawTemplate,
parts: parts,
}, nil
}
type templatePart struct {
raw string
terms []templateTerm
first string
sep string
named bool
ifemp string
allowReserved bool
}
type templateTerm struct {
name string
explode bool
truncate int
}
func parseExpression(expression string) (result templatePart, err error) {
switch expression[0] {
case '+':
result.sep = ","
result.allowReserved = true
expression = expression[1:]
case '.':
result.first = "."
result.sep = "."
expression = expression[1:]
case '/':
result.first = "/"
result.sep = "/"
expression = expression[1:]
case ';':
result.first = ";"
result.sep = ";"
result.named = true
expression = expression[1:]
case '?':
result.first = "?"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '&':
result.first = "&"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '#':
result.first = "#"
result.sep = ","
result.allowReserved = true
expression = expression[1:]
default:
result.sep = ","
}
rawterms := strings.Split(expression, ",")
result.terms = make([]templateTerm, len(rawterms))
for i, raw := range rawterms {
result.terms[i], err = parseTerm(raw)
if err != nil {
break
}
}
return result, err
}
func parseTerm(term string) (result templateTerm, err error) {
// TODO(djd): Remove "*" suffix parsing once we check that no APIs have
// mistakenly used that attribute.
if strings.HasSuffix(term, "*") {
result.explode = true
term = term[:len(term)-1]
}
split := strings.Split(term, ":")
if len(split) == 1 {
result.name = term
} else if len(split) == 2 {
result.name = split[0]
var parsed int64
parsed, err = strconv.ParseInt(split[1], 10, 0)
result.truncate = int(parsed)
} else {
err = errors.New("multiple colons in same term")
}
if !validname.MatchString(result.name) {
err = errors.New("not a valid name: " + result.name)
}
if result.explode && result.truncate > 0 {
err = errors.New("both explode and prefix modifiers on same term")
}
return result, err
}
// Expand expands a URI template with a set of values to produce the
// resultant URI. Two forms of the result are returned: one with all the
// elements escaped, and one with the elements unescaped.
func (t *uriTemplate) Expand(values map[string]string) (escaped, unescaped string) {
var w pairWriter
for _, p := range t.parts {
p.expand(&w, values)
}
return w.Escaped(), w.Unescaped()
}
func (tp *templatePart) expand(w *pairWriter, values map[string]string) {
if len(tp.raw) > 0 {
w.Write(tp.raw)
return
}
var first = true
for _, term := range tp.terms {
value, exists := values[term.name]
if !exists {
continue
}
if first {
w.Write(tp.first)
first = false
} else {
w.Write(tp.sep)
}
tp.expandString(w, term, value)
}
}
func (tp *templatePart) expandName(w *pairWriter, name string, empty bool) {
if tp.named {
w.Write(name)
if empty {
w.Write(tp.ifemp)
} else {
w.Write("=")
}
}
}
func (tp *templatePart) expandString(w *pairWriter, t templateTerm, s string) {
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
tp.expandName(w, t.name, len(s) == 0)
w.Escape(s, tp.allowReserved)
}

View File

@@ -0,0 +1,17 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uritemplates
// Expand parses then expands a URI template with a set of values to produce
// the resultant URI. Two forms of the result are returned: one with all the
// elements escaped, and one with the elements unescaped.
func Expand(path string, values map[string]string) (escaped, unescaped string, err error) {
template, err := parse(path)
if err != nil {
return "", "", err
}
escaped, unescaped = template.Expand(values)
return escaped, unescaped, nil
}

8
vendor/google.golang.org/api/internal/version.go generated vendored Normal file
View File

@@ -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.262.0"