...

Source file src/cloud.google.com/go/cloudsqlconn/options.go

Documentation: cloud.google.com/go/cloudsqlconn

     1  // Copyright 2020 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cloudsqlconn
    16  
    17  import (
    18  	"context"
    19  	"crypto/rsa"
    20  	"net"
    21  	"net/http"
    22  	"os"
    23  	"time"
    24  
    25  	"cloud.google.com/go/cloudsqlconn/debug"
    26  	"cloud.google.com/go/cloudsqlconn/errtype"
    27  	"cloud.google.com/go/cloudsqlconn/internal/cloudsql"
    28  	"golang.org/x/oauth2"
    29  	"golang.org/x/oauth2/google"
    30  	apiopt "google.golang.org/api/option"
    31  	sqladmin "google.golang.org/api/sqladmin/v1beta4"
    32  )
    33  
    34  // An Option is an option for configuring a Dialer.
    35  type Option func(d *dialerConfig)
    36  
    37  type dialerConfig struct {
    38  	rsaKey                 *rsa.PrivateKey
    39  	sqladminOpts           []apiopt.ClientOption
    40  	dialOpts               []DialOption
    41  	dialFunc               func(ctx context.Context, network, addr string) (net.Conn, error)
    42  	refreshTimeout         time.Duration
    43  	useIAMAuthN            bool
    44  	logger                 debug.ContextLogger
    45  	lazyRefresh            bool
    46  	iamLoginTokenSource    oauth2.TokenSource
    47  	useragents             []string
    48  	credentialsUniverse    string
    49  	serviceUniverse        string
    50  	setAdminAPIEndpoint    bool
    51  	setUniverseDomain      bool
    52  	setCredentials         bool
    53  	setTokenSource         bool
    54  	setIAMAuthNTokenSource bool
    55  	// err tracks any dialer options that may have failed.
    56  	err error
    57  }
    58  
    59  // WithOptions turns a list of Option's into a single Option.
    60  func WithOptions(opts ...Option) Option {
    61  	return func(d *dialerConfig) {
    62  		for _, opt := range opts {
    63  			opt(d)
    64  		}
    65  	}
    66  }
    67  
    68  // WithCredentialsFile returns an Option that specifies a service account
    69  // or refresh token JSON credentials file to be used as the basis for
    70  // authentication.
    71  func WithCredentialsFile(filename string) Option {
    72  	return func(d *dialerConfig) {
    73  		b, err := os.ReadFile(filename)
    74  		if err != nil {
    75  			d.err = errtype.NewConfigError(err.Error(), "n/a")
    76  			return
    77  		}
    78  		opt := WithCredentialsJSON(b)
    79  		opt(d)
    80  	}
    81  }
    82  
    83  // WithCredentialsJSON returns an Option that specifies a service account
    84  // or refresh token JSON credentials to be used as the basis for authentication.
    85  func WithCredentialsJSON(b []byte) Option {
    86  	return func(d *dialerConfig) {
    87  		c, err := google.CredentialsFromJSON(context.Background(), b, sqladmin.SqlserviceAdminScope)
    88  		if err != nil {
    89  			d.err = errtype.NewConfigError(err.Error(), "n/a")
    90  			return
    91  		}
    92  		ud, err := c.GetUniverseDomain()
    93  		if err != nil {
    94  			d.err = errtype.NewConfigError(err.Error(), "n/a")
    95  			return
    96  		}
    97  		d.credentialsUniverse = ud
    98  		d.sqladminOpts = append(d.sqladminOpts, apiopt.WithCredentials(c))
    99  
   100  		// Create another set of credentials scoped to login only
   101  		scoped, err := google.CredentialsFromJSON(context.Background(), b, iamLoginScope)
   102  		if err != nil {
   103  			d.err = errtype.NewConfigError(err.Error(), "n/a")
   104  			return
   105  		}
   106  		d.iamLoginTokenSource = scoped.TokenSource
   107  		d.setCredentials = true
   108  	}
   109  }
   110  
   111  // WithUserAgent returns an Option that sets the User-Agent.
   112  func WithUserAgent(ua string) Option {
   113  	return func(d *dialerConfig) {
   114  		d.useragents = append(d.useragents, ua)
   115  	}
   116  }
   117  
   118  // WithDefaultDialOptions returns an Option that specifies the default
   119  // DialOptions used.
   120  func WithDefaultDialOptions(opts ...DialOption) Option {
   121  	return func(d *dialerConfig) {
   122  		d.dialOpts = append(d.dialOpts, opts...)
   123  	}
   124  }
   125  
   126  // WithTokenSource returns an Option that specifies an OAuth2 token source to be
   127  // used as the basis for authentication.
   128  //
   129  // When Auth IAM AuthN is enabled, use WithIAMAuthNTokenSources to set the token
   130  // source for login tokens separately from the API client token source.
   131  // WithTokenSource should not be used with WithIAMAuthNTokenSources.
   132  func WithTokenSource(s oauth2.TokenSource) Option {
   133  	return func(d *dialerConfig) {
   134  		d.setTokenSource = true
   135  		d.setCredentials = true
   136  		d.sqladminOpts = append(d.sqladminOpts, apiopt.WithTokenSource(s))
   137  	}
   138  }
   139  
   140  // WithIAMAuthNTokenSources sets the oauth2.TokenSource for the API client and a
   141  // second token source for IAM AuthN login tokens. The API client token source
   142  // should have the following scopes:
   143  //
   144  //  1. https://www.googleapis.com/auth/sqlservice.admin, and
   145  //  2. https://www.googleapis.com/auth/cloud-platform
   146  //
   147  // The IAM AuthN token source on the other hand should only have:
   148  //
   149  //  1. https://www.googleapis.com/auth/sqlservice.login.
   150  //
   151  // Prefer this option over WithTokenSource when using IAM AuthN which does not
   152  // distinguish between the two token sources. WithIAMAuthNTokenSources should
   153  // not be used with WithTokenSource.
   154  func WithIAMAuthNTokenSources(apiTS, iamLoginTS oauth2.TokenSource) Option {
   155  	return func(d *dialerConfig) {
   156  		d.setIAMAuthNTokenSource = true
   157  		d.setCredentials = true
   158  		d.iamLoginTokenSource = iamLoginTS
   159  		d.sqladminOpts = append(d.sqladminOpts, apiopt.WithTokenSource(apiTS))
   160  	}
   161  }
   162  
   163  // WithRSAKey returns an Option that specifies a rsa.PrivateKey used to represent the client.
   164  func WithRSAKey(k *rsa.PrivateKey) Option {
   165  	return func(d *dialerConfig) {
   166  		d.rsaKey = k
   167  	}
   168  }
   169  
   170  // WithRefreshTimeout returns an Option that sets a timeout on refresh
   171  // operations. Defaults to 60s.
   172  func WithRefreshTimeout(t time.Duration) Option {
   173  	return func(d *dialerConfig) {
   174  		d.refreshTimeout = t
   175  	}
   176  }
   177  
   178  // WithHTTPClient configures the underlying SQL Admin API client with the
   179  // provided HTTP client. This option is generally unnecessary except for
   180  // advanced use-cases.
   181  func WithHTTPClient(client *http.Client) Option {
   182  	return func(d *dialerConfig) {
   183  		d.sqladminOpts = append(d.sqladminOpts, apiopt.WithHTTPClient(client))
   184  	}
   185  }
   186  
   187  // WithAdminAPIEndpoint configures the underlying SQL Admin API client to use
   188  // the provided URL.
   189  func WithAdminAPIEndpoint(url string) Option {
   190  	return func(d *dialerConfig) {
   191  		d.sqladminOpts = append(d.sqladminOpts, apiopt.WithEndpoint(url))
   192  		d.setAdminAPIEndpoint = true
   193  		d.serviceUniverse = ""
   194  	}
   195  }
   196  
   197  // WithUniverseDomain configures the underlying SQL Admin API client to use
   198  // the provided universe domain. Enables Trusted Partner Cloud (TPC).
   199  func WithUniverseDomain(ud string) Option {
   200  	return func(d *dialerConfig) {
   201  		d.sqladminOpts = append(d.sqladminOpts, apiopt.WithUniverseDomain(ud))
   202  		d.serviceUniverse = ud
   203  		d.setUniverseDomain = true
   204  	}
   205  }
   206  
   207  // WithQuotaProject returns an Option that specifies the project used for quota and billing purposes.
   208  func WithQuotaProject(p string) Option {
   209  	return func(cfg *dialerConfig) {
   210  		cfg.sqladminOpts = append(cfg.sqladminOpts, apiopt.WithQuotaProject(p))
   211  	}
   212  }
   213  
   214  // WithDialFunc configures the function used to connect to the address on the
   215  // named network. This option is generally unnecessary except for advanced
   216  // use-cases. The function is used for all invocations of Dial. To configure
   217  // a dial function per individual calls to dial, use WithOneOffDialFunc.
   218  func WithDialFunc(dial func(ctx context.Context, network, addr string) (net.Conn, error)) Option {
   219  	return func(d *dialerConfig) {
   220  		d.dialFunc = dial
   221  	}
   222  }
   223  
   224  // WithIAMAuthN enables automatic IAM Authentication. If no token source has
   225  // been configured (such as with WithTokenSource, WithCredentialsFile, etc), the
   226  // dialer will use the default token source as defined by
   227  // https://pkg.go.dev/golang.org/x/oauth2/google#FindDefaultCredentialsWithParams.
   228  //
   229  // For documentation on automatic IAM Authentication, see
   230  // https://cloud.google.com/sql/docs/postgres/authentication.
   231  func WithIAMAuthN() Option {
   232  	return func(d *dialerConfig) {
   233  		d.useIAMAuthN = true
   234  	}
   235  }
   236  
   237  type debugLoggerWithoutContext struct {
   238  	logger debug.Logger
   239  }
   240  
   241  // Debugf implements debug.ContextLogger.
   242  func (d *debugLoggerWithoutContext) Debugf(_ context.Context, format string, args ...interface{}) {
   243  	d.logger.Debugf(format, args...)
   244  }
   245  
   246  var _ debug.ContextLogger = new(debugLoggerWithoutContext)
   247  
   248  // WithDebugLogger configures a debug lgoger for reporting on internal
   249  // operations. By default the debug logger is disabled.
   250  //
   251  // Deprecated: use WithContextDebugLogger instead
   252  func WithDebugLogger(l debug.Logger) Option {
   253  	return func(d *dialerConfig) {
   254  		d.logger = &debugLoggerWithoutContext{l}
   255  	}
   256  }
   257  
   258  // WithContextDebugLogger configures a debug logger for reporting on internal
   259  // operations. By default the debug logger is disabled.
   260  func WithContextDebugLogger(l debug.ContextLogger) Option {
   261  	return func(d *dialerConfig) {
   262  		d.logger = l
   263  	}
   264  }
   265  
   266  // WithLazyRefresh configures the dialer to refresh certificates on an
   267  // as-needed basis. If a certificate is expired when a connection request
   268  // occurs, the Go Connector will block the attempt and refresh the certificate
   269  // immediately. This option is useful when running the Go Connector in
   270  // environments where the CPU may be throttled, thus preventing a background
   271  // goroutine from running consistently (e.g., in Cloud Run the CPU is throttled
   272  // outside of a request context causing the background refresh to fail).
   273  func WithLazyRefresh() Option {
   274  	return func(d *dialerConfig) {
   275  		d.lazyRefresh = true
   276  	}
   277  }
   278  
   279  // A DialOption is an option for configuring how a Dialer's Dial call is executed.
   280  type DialOption func(d *dialConfig)
   281  
   282  type dialConfig struct {
   283  	dialFunc     func(ctx context.Context, network, addr string) (net.Conn, error)
   284  	ipType       string
   285  	tcpKeepAlive time.Duration
   286  	useIAMAuthN  bool
   287  }
   288  
   289  // DialOptions turns a list of DialOption instances into an DialOption.
   290  func DialOptions(opts ...DialOption) DialOption {
   291  	return func(cfg *dialConfig) {
   292  		for _, opt := range opts {
   293  			opt(cfg)
   294  		}
   295  	}
   296  }
   297  
   298  // WithOneOffDialFunc configures the dial function on a one-off basis for an
   299  // individual call to Dial. To configure a dial function across all invocations
   300  // of Dial, use WithDialFunc.
   301  func WithOneOffDialFunc(dial func(ctx context.Context, network, addr string) (net.Conn, error)) DialOption {
   302  	return func(c *dialConfig) {
   303  		c.dialFunc = dial
   304  	}
   305  }
   306  
   307  // WithTCPKeepAlive returns a DialOption that specifies the tcp keep alive period for the connection returned by Dial.
   308  func WithTCPKeepAlive(d time.Duration) DialOption {
   309  	return func(cfg *dialConfig) {
   310  		cfg.tcpKeepAlive = d
   311  	}
   312  }
   313  
   314  // WithPublicIP returns a DialOption that specifies a public IP will be used to connect.
   315  func WithPublicIP() DialOption {
   316  	return func(cfg *dialConfig) {
   317  		cfg.ipType = cloudsql.PublicIP
   318  	}
   319  }
   320  
   321  // WithPrivateIP returns a DialOption that specifies a private IP (VPC) will be used to connect.
   322  func WithPrivateIP() DialOption {
   323  	return func(cfg *dialConfig) {
   324  		cfg.ipType = cloudsql.PrivateIP
   325  	}
   326  }
   327  
   328  // WithPSC returns a DialOption that specifies a PSC endpoint will be used to connect.
   329  func WithPSC() DialOption {
   330  	return func(cfg *dialConfig) {
   331  		cfg.ipType = cloudsql.PSC
   332  	}
   333  }
   334  
   335  // WithAutoIP returns a DialOption that selects the public IP if available and
   336  // otherwise falls back to private IP. This option is present for backwards
   337  // compatibility only and is not recommended for use in production.
   338  func WithAutoIP() DialOption {
   339  	return func(cfg *dialConfig) {
   340  		cfg.ipType = cloudsql.AutoIP
   341  	}
   342  }
   343  
   344  // WithDialIAMAuthN allows you to enable or disable IAM Authentication for this
   345  // instance as described in the documentation for WithIAMAuthN. This value will
   346  // override the Dialer-level configuration set with WithIAMAuthN.
   347  //
   348  // WARNING: This DialOption can cause a new Refresh operation to be triggered.
   349  // Toggling this option on or off between Dials may cause increased API usage
   350  // and/or delayed connection attempts.
   351  func WithDialIAMAuthN(b bool) DialOption {
   352  	return func(cfg *dialConfig) {
   353  		cfg.useIAMAuthN = b
   354  	}
   355  }
   356  

View as plain text