2424// To customize the URL opener, or for more details on the URL format,
2525// see URLOpener.
2626//
27+ // # IP Type
28+ //
29+ // By default, connections use auto-IP selection (public IP if available,
30+ // otherwise private IP), matching the behavior of the legacy cloudsql-proxy.
31+ // To use a specific IP type, set URLOpener.IPType or pass the "ip_type" query
32+ // parameter in the URL (requires URLOpener.Dialer to be set):
33+ //
34+ // gcpmysql://user:pass@project/region/instance/dbname?ip_type=psc
35+ //
36+ // Valid values for ip_type: auto, public, private, psc.
37+ //
2738// See https://gocloud.dev/concepts/urls/ for background information.
2839package gcpmysql // import "gocloud.dev/mysql/gcpmysql"
2940
@@ -37,6 +48,7 @@ import (
3748 "strings"
3849 "sync"
3950
51+ "cloud.google.com/go/cloudsqlconn"
4052 "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy"
4153 "github.com/XSAM/otelsql"
4254 "github.com/go-sql-driver/mysql"
@@ -68,13 +80,13 @@ func (o *lazyCredsOpener) OpenMySQLURL(ctx context.Context, u *url.URL) (*sql.DB
6880 o .err = err
6981 return
7082 }
71- client , err := gcp .NewHTTPClient (gcp .DefaultTransport (), creds .TokenSource )
83+ // Ignore cleanup: the dialer lives for the process lifetime in this global opener.
84+ d , _ , err := cloudsql .NewDialer (ctx , creds .TokenSource )
7285 if err != nil {
7386 o .err = err
7487 return
7588 }
76- certSource := cloudsql .NewCertSource (client )
77- o .opener = & URLOpener {CertSource : certSource }
89+ o .opener = & URLOpener {Dialer : d }
7890 })
7991 if o .err != nil {
8092 return nil , fmt .Errorf ("gcpmysql open %v: %v" , u , o .err )
@@ -85,8 +97,15 @@ func (o *lazyCredsOpener) OpenMySQLURL(ctx context.Context, u *url.URL) (*sql.DB
8597// URLOpener opens Cloud MySQL URLs like
8698// "gcpmysql://user:password@project/region/instance/dbname".
8799type URLOpener struct {
100+ // Dialer creates Cloud SQL connections using the Cloud SQL Go Connector.
101+ // Supports PSC and explicit IP type selection via the "ip_type" URL query
102+ // parameter. If both Dialer and CertSource are set, Dialer takes precedence.
103+ Dialer * cloudsqlconn.Dialer
104+
88105 // CertSource specifies how the opener will obtain authentication information.
89- // CertSource must not be nil.
106+ //
107+ // Deprecated: Use Dialer instead. CertSource does not support PSC or
108+ // explicit IP type selection. Ignored if Dialer is also set.
90109 CertSource proxy.CertSource
91110
92111 // TraceOpts contains options for OpenTelemetry.
@@ -95,25 +114,48 @@ type URLOpener struct {
95114
96115// OpenMySQLURL opens a new GCP database connection wrapped with OpenTelemetry instrumentation.
97116func (uo * URLOpener ) OpenMySQLURL (ctx context.Context , u * url.URL ) (* sql.DB , error ) {
98- if uo .CertSource == nil {
99- return nil , fmt .Errorf ("gcpmysql: URLOpener CertSource is nil" )
117+ if uo .Dialer == nil && uo . CertSource == nil {
118+ return nil , fmt .Errorf ("gcpmysql: URLOpener Dialer and CertSource are both nil" )
100119 }
101- var (
102- client = & proxy.Client {Certs : uo .CertSource , Port : 3307 }
103- cfg , err = configFromURL (u )
104- )
120+
121+ cfg , err := configFromURL (u )
105122 if err != nil {
106123 return nil , fmt .Errorf ("gcpmysql: open config %v" , err )
107124 }
108- cfg .DialFunc = func (ctx context.Context , _ , addr string ) (net.Conn , error ) {
109- // MySQL driver's addr is in the form "[host]:3306" after normalized.
110- // https://github.com/go-sql-driver/mysql/blob/76c00e35a8d48f8f70f0e7dffe584692bd3fa612/dsn.go#L193-L195
111- instance , _ , err := net .SplitHostPort (addr )
112- if err != nil {
113- return nil , err
125+
126+ if uo .Dialer != nil {
127+ // New path: Cloud SQL Go Connector with IP type and PSC support.
128+ ipType := cloudsql .IPTypeAuto
129+ if s := u .Query ().Get ("ip_type" ); s != "" {
130+ parsed , err := parseIPType (s )
131+ if err != nil {
132+ return nil , fmt .Errorf ("gcpmysql: open: %v" , err )
133+ }
134+ ipType = parsed
135+ }
136+ dialOpts := ipType .DialOptions ()
137+ d := uo .Dialer
138+ cfg .DialFunc = func (ctx context.Context , _ , addr string ) (net.Conn , error ) {
139+ // MySQL driver's addr is in the form "[host]:3306" after normalized.
140+ // https://github.com/go-sql-driver/mysql/blob/76c00e35a8d48f8f70f0e7dffe584692bd3fa612/dsn.go#L193-L195
141+ instance , _ , err := net .SplitHostPort (addr )
142+ if err != nil {
143+ return nil , err
144+ }
145+ return d .Dial (ctx , instance , dialOpts ... )
146+ }
147+ } else {
148+ // Legacy path: cloudsql-proxy v1 (no PSC or IP type selection).
149+ client := & proxy.Client {Certs : uo .CertSource , Port : 3307 }
150+ cfg .DialFunc = func (ctx context.Context , _ , addr string ) (net.Conn , error ) {
151+ instance , _ , err := net .SplitHostPort (addr )
152+ if err != nil {
153+ return nil , err
154+ }
155+ return client .DialContext (ctx , instance )
114156 }
115- return client .DialContext (ctx , instance )
116157 }
158+
117159 c , err := mysql .NewConnector (cfg )
118160 if err != nil {
119161 return nil , fmt .Errorf ("gcpmysql: open connector %v" , err )
@@ -127,10 +169,14 @@ func configFromURL(u *url.URL) (*mysql.Config, error) {
127169 return nil , err
128170 }
129171
172+ // Strip ip_type from query before passing to MySQL DSN parser.
173+ query := u .Query ()
174+ query .Del ("ip_type" )
175+
130176 var cfg * mysql.Config
131177 switch {
132- case len (u . RawQuery ) > 0 :
133- optDsn := fmt .Sprintf ("/%s?%s" , dbName , u . RawQuery )
178+ case len (query ) > 0 :
179+ optDsn := fmt .Sprintf ("/%s?%s" , dbName , query . Encode () )
134180 if cfg , err = mysql .ParseDSN (optDsn ); err != nil {
135181 return nil , err
136182 }
@@ -161,3 +207,18 @@ func instanceFromURL(u *url.URL) (instance, db string, _ error) {
161207 }
162208 return parts [0 ] + ":" + parts [1 ] + ":" + parts [2 ], parts [3 ], nil
163209}
210+
211+ func parseIPType (s string ) (cloudsql.IPType , error ) {
212+ switch strings .ToLower (s ) {
213+ case "auto" , "" :
214+ return cloudsql .IPTypeAuto , nil
215+ case "public" :
216+ return cloudsql .IPTypePublic , nil
217+ case "private" :
218+ return cloudsql .IPTypePrivate , nil
219+ case "psc" :
220+ return cloudsql .IPTypePSC , nil
221+ default :
222+ return 0 , fmt .Errorf ("unknown ip_type %q (valid: auto, public, private, psc)" , s )
223+ }
224+ }
0 commit comments