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