github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/mysql/azuremysql/azuremysql.go (about)

     1  // Copyright 2019 The Go Cloud Development Kit Authors
     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 azuremysql provides connections to Azure Database for MySQL.
    16  // See https://docs.microsoft.com/en-us/azure/mysql.
    17  //
    18  // # URLs
    19  //
    20  // For mysql.Open, azuremysql registers for the scheme "azuremysql".
    21  // To customize the URL opener, or for more details on the URL format,
    22  // see URLOpener.
    23  //
    24  // See https://gocloud.dev/concepts/urls/ for background information.
    25  package azuremysql // import "gocloud.dev/mysql/azuremysql"
    26  
    27  import (
    28  	"context"
    29  	"crypto/tls"
    30  	"database/sql"
    31  	"database/sql/driver"
    32  	"fmt"
    33  	"net/url"
    34  	"strings"
    35  	"sync"
    36  
    37  	"contrib.go.opencensus.io/integrations/ocsql"
    38  	"github.com/go-sql-driver/mysql"
    39  	"gocloud.dev/azure/azuredb"
    40  	cdkmysql "gocloud.dev/mysql"
    41  )
    42  
    43  // URLOpener opens Azure MySQL URLs
    44  // like "azuremysql://user:password@myinstance.mysql.database.azure.com/mydb".
    45  type URLOpener struct {
    46  	// CertSource specifies how the opener will obtain the Azure Certificate
    47  	// Authority. If nil, it will use the default *azuredb.CertFetcher.
    48  	CertSource azuredb.CertPoolProvider
    49  	// TraceOpts contains options for OpenCensus.
    50  	TraceOpts []ocsql.TraceOption
    51  }
    52  
    53  // Scheme is the URL scheme azuremysql registers its URLOpener under on
    54  // mysql.DefaultMux.
    55  const Scheme = "azuremysql"
    56  
    57  func init() {
    58  	cdkmysql.DefaultURLMux().RegisterMySQL(Scheme, &URLOpener{})
    59  }
    60  
    61  // OpenMySQLURL opens an encrypted connection to an Azure MySQL database.
    62  func (uo *URLOpener) OpenMySQLURL(ctx context.Context, u *url.URL) (*sql.DB, error) {
    63  	source := uo.CertSource
    64  	if source == nil {
    65  		source = new(azuredb.CertFetcher)
    66  	}
    67  	if u.Host == "" {
    68  		return nil, fmt.Errorf("open Azure database: empty endpoint")
    69  	}
    70  	password, _ := u.User.Password()
    71  	c := &connector{
    72  		addr:     u.Host,
    73  		user:     u.User.Username(),
    74  		password: password,
    75  		dbName:   strings.TrimPrefix(u.Path, "/"),
    76  		// Make a copy of TraceOpts to avoid caller modifying.
    77  		traceOpts: append([]ocsql.TraceOption(nil), uo.TraceOpts...),
    78  		provider:  source,
    79  
    80  		sem:   make(chan struct{}, 1),
    81  		ready: make(chan struct{}),
    82  	}
    83  	c.sem <- struct{}{}
    84  	return sql.OpenDB(c), nil
    85  }
    86  
    87  type connector struct {
    88  	addr      string
    89  	user      string
    90  	password  string
    91  	dbName    string
    92  	traceOpts []ocsql.TraceOption
    93  
    94  	sem      chan struct{}    // receive to acquire, send to release
    95  	provider CertPoolProvider // provides the CA certificate pool
    96  
    97  	ready chan struct{} // closed after writing dsn
    98  	dsn   string
    99  }
   100  
   101  func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
   102  	select {
   103  	case <-c.sem:
   104  		certPool, err := c.provider.AzureCertPool(ctx)
   105  		if err != nil {
   106  			c.sem <- struct{}{} // release
   107  			return nil, fmt.Errorf("connect Azure MySql: %v", err)
   108  		}
   109  
   110  		// TODO(light): Avoid global registry once https://github.com/go-sql-driver/mysql/issues/771 is fixed.
   111  		tlsConfigCounter.mu.Lock()
   112  		tlsConfigNum := tlsConfigCounter.n
   113  		tlsConfigCounter.n++
   114  		tlsConfigCounter.mu.Unlock()
   115  		tlsConfigName := fmt.Sprintf("gocloud.dev/mysql/azuremysql/%d", tlsConfigNum)
   116  		err = mysql.RegisterTLSConfig(tlsConfigName, &tls.Config{
   117  			RootCAs: certPool,
   118  		})
   119  		if err != nil {
   120  			c.sem <- struct{}{} // release
   121  			return nil, fmt.Errorf("connect Azure MySql: register TLS: %v", err)
   122  		}
   123  		cfg := &mysql.Config{
   124  			Net:                     "tcp",
   125  			Addr:                    c.addr,
   126  			User:                    c.user,
   127  			Passwd:                  c.password,
   128  			TLSConfig:               tlsConfigName,
   129  			AllowCleartextPasswords: true,
   130  			AllowNativePasswords:    true,
   131  			DBName:                  c.dbName,
   132  		}
   133  		c.dsn = cfg.FormatDSN()
   134  		close(c.ready)
   135  		// Don't release sem: make it block forever, so this case won't be run again.
   136  	case <-c.ready:
   137  		// Already succeeded.
   138  	case <-ctx.Done():
   139  		return nil, fmt.Errorf("connect Azure MySql: waiting for certificates: %v", ctx.Err())
   140  	}
   141  	return c.Driver().Open(c.dsn)
   142  }
   143  
   144  func (c *connector) Driver() driver.Driver {
   145  	return ocsql.Wrap(mysql.MySQLDriver{}, c.traceOpts...)
   146  }
   147  
   148  var tlsConfigCounter struct {
   149  	mu sync.Mutex
   150  	n  int
   151  }
   152  
   153  // A CertPoolProvider obtains a certificate pool that contains the Azure CA certificate.
   154  type CertPoolProvider = azuredb.CertPoolProvider
   155  
   156  // CertFetcher is a default CertPoolProvider that can fetch CA certificates from
   157  // any publicly accessible URI or File.
   158  type CertFetcher = azuredb.CertFetcher