Skip to content

Commit a212d59

Browse files
aplrclaude
andcommitted
gcp/cloudsql: add PSC support via cloud-sql-go-connector (backwards compatible)
Add cloud.google.com/go/cloudsqlconn alongside the existing cloudsql-proxy dependency. URLOpener now accepts both a Dialer (new, supports PSC and explicit IP type selection) and CertSource (existing, deprecated). Dialer takes precedence when both are set. Default URL-based access uses Dialer internally, preserving auto-IP behavior. Adds IPType enum (Auto/Public/Private/PSC) and ip_type URL query parameter support. CertSource remains fully functional — no breaking changes. Fixes #3670. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f35e35d commit a212d59

7 files changed

Lines changed: 367 additions & 41 deletions

File tree

gcp/cloudsql/cloudsql.go

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
package cloudsql // import "gocloud.dev/gcp/cloudsql"
1818

1919
import (
20+
"context"
21+
22+
"cloud.google.com/go/cloudsqlconn"
2023
"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/certs"
2124
"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy"
2225
"github.com/google/wire"
@@ -26,18 +29,86 @@ import (
2629

2730
// CertSourceSet is a Wire provider set that binds a Cloud SQL proxy
2831
// certificate source from an GCP-authenticated HTTP client.
32+
//
33+
// Deprecated: Use DialerSet instead, which supports PSC and explicit IP type
34+
// selection via URLOpener.IPType or the "ip_type" URL query parameter.
2935
var CertSourceSet = wire.NewSet(
3036
NewCertSource,
3137
wire.Bind(new(proxy.CertSource), new(*certs.RemoteCertSource)))
3238

3339
// NewCertSource creates a local certificate source that uses the given
3440
// HTTP client. The client is assumed to make authenticated requests.
41+
//
42+
// Deprecated: Use NewDialer instead.
3543
func NewCertSource(c *gcp.HTTPClient) *certs.RemoteCertSource {
3644
return certs.NewCertSourceOpts(&c.Client, certs.RemoteOpts{})
3745
}
3846

39-
// NewCertSourceWithIAM creates a local certificate source, including Token source for token information used in
40-
// cert creation, that uses the given HTTP client. The client is assumed to make authenticated requests.
47+
// NewCertSourceWithIAM creates a local certificate source, including Token
48+
// source for token information used in cert creation, that uses the given HTTP
49+
// client. The client is assumed to make authenticated requests.
50+
//
51+
// Deprecated: Use NewDialerWithIAM instead.
4152
func NewCertSourceWithIAM(c *gcp.HTTPClient, t oauth2.TokenSource) *certs.RemoteCertSource {
4253
return certs.NewCertSourceOpts(&c.Client, certs.RemoteOpts{EnableIAMLogin: true, TokenSource: t})
4354
}
55+
56+
// DialerSet is a Wire provider set that binds a Cloud SQL Dialer from a GCP
57+
// token source. Use this instead of CertSourceSet when PSC or explicit IP type
58+
// selection is required.
59+
var DialerSet = wire.NewSet(NewDialer)
60+
61+
// IPType specifies the type of IP address to use when connecting to a Cloud SQL instance.
62+
type IPType int
63+
64+
const (
65+
// IPTypeAuto uses a public IP if one is assigned to the instance; otherwise
66+
// falls back to private IP. This is the default and matches the behavior of
67+
// the legacy cloudsql-proxy v1 (equivalent to --auto-ip).
68+
IPTypeAuto IPType = iota
69+
// IPTypePublic uses the instance's public IP address.
70+
IPTypePublic
71+
// IPTypePrivate uses the instance's private IP address (requires VPC peering).
72+
IPTypePrivate
73+
// IPTypePSC uses a Private Service Connect endpoint.
74+
IPTypePSC
75+
)
76+
77+
// DialOptions returns the cloudsqlconn.DialOption(s) for this IPType.
78+
func (t IPType) DialOptions() []cloudsqlconn.DialOption {
79+
switch t {
80+
case IPTypePublic:
81+
return []cloudsqlconn.DialOption{cloudsqlconn.WithPublicIP()}
82+
case IPTypePrivate:
83+
return []cloudsqlconn.DialOption{cloudsqlconn.WithPrivateIP()}
84+
case IPTypePSC:
85+
return []cloudsqlconn.DialOption{cloudsqlconn.WithPSC()}
86+
default: // IPTypeAuto
87+
return []cloudsqlconn.DialOption{cloudsqlconn.WithAutoIP()}
88+
}
89+
}
90+
91+
// NewDialer creates a Cloud SQL Dialer using the given OAuth2 token source.
92+
// The returned cleanup function must be called when the dialer is no longer needed.
93+
func NewDialer(ctx context.Context, ts oauth2.TokenSource) (*cloudsqlconn.Dialer, func(), error) {
94+
d, err := cloudsqlconn.NewDialer(ctx, cloudsqlconn.WithTokenSource(ts))
95+
if err != nil {
96+
return nil, func() {}, err
97+
}
98+
return d, func() { d.Close() }, nil
99+
}
100+
101+
// NewDialerWithIAM creates a Cloud SQL Dialer with IAM authentication enabled,
102+
// using the given OAuth2 token source. IAM authentication allows passwordless
103+
// connections to Cloud SQL instances.
104+
// The returned cleanup function must be called when the dialer is no longer needed.
105+
func NewDialerWithIAM(ctx context.Context, ts oauth2.TokenSource) (*cloudsqlconn.Dialer, func(), error) {
106+
d, err := cloudsqlconn.NewDialer(ctx,
107+
cloudsqlconn.WithIAMAuthNTokenSources(ts, ts),
108+
cloudsqlconn.WithIAMAuthN(),
109+
)
110+
if err != nil {
111+
return nil, func() {}, err
112+
}
113+
return d, func() { d.Close() }, nil
114+
}

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414

1515
module gocloud.dev
1616

17-
go 1.25.0
17+
go 1.25.7
1818

1919
require (
20+
cloud.google.com/go/cloudsqlconn v1.20.2
2021
cloud.google.com/go/compute/metadata v0.9.0
2122
cloud.google.com/go/firestore v1.21.0
2223
cloud.google.com/go/iam v1.5.3
@@ -124,6 +125,7 @@ require (
124125
github.com/go-logr/logr v1.4.3 // indirect
125126
github.com/go-logr/stdr v1.2.2 // indirect
126127
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
128+
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
127129
github.com/google/martian/v3 v3.3.3 // indirect
128130
github.com/google/s2a-go v0.1.9 // indirect
129131
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect

go.sum

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
3333
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
3434
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
3535
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
36+
cloud.google.com/go/cloudsqlconn v1.20.2 h1:r1BFbgxKA7h0jY13pGk8wBueUeLhqF27e5Hyaxl8Ua8=
37+
cloud.google.com/go/cloudsqlconn v1.20.2/go.mod h1:cGBrxU+pKs1NppBkecFC+rKn9B5GnEdlz7XrHbuwn7E=
3638
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
3739
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
3840
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
@@ -244,12 +246,16 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1
244246
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
245247
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
246248
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
249+
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
250+
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
251+
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
252+
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
247253
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
248254
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
249255
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
250256
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
251-
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
252-
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
257+
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
258+
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
253259
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
254260
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
255261
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -340,6 +346,26 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
340346
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
341347
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
342348
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
349+
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
350+
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
351+
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
352+
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
353+
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
354+
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
355+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
356+
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
357+
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
358+
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
359+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
360+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
361+
github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8=
362+
github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA=
363+
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
364+
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
365+
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
366+
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
367+
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
368+
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
343369
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
344370
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
345371
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -358,6 +384,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
358384
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
359385
github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo=
360386
github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
387+
github.com/microsoft/go-mssqldb v1.9.8 h1:d4IFMvF/o+HdpXUqbBfzHvn/NlFA75YGcfHUUvDFJEM=
388+
github.com/microsoft/go-mssqldb v1.9.8/go.mod h1:eGSRSGAW4hKMy5YcAenhCDjIRm2rhqIdmmwgciMzLus=
361389
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
362390
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
363391
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
@@ -367,6 +395,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
367395
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
368396
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
369397
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
398+
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
399+
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
370400
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
371401
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
372402
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

mysql/gcpmysql/gcpmysql.go

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@
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.
2839
package 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,35 +97,72 @@ 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".
8799
type URLOpener struct {
100+
// Dialer creates Cloud SQL connections using the Cloud SQL Go Connector.
101+
// Supports PSC and explicit IP type selection via IPType or the "ip_type"
102+
// URL query parameter.
103+
// If both Dialer and CertSource are set, Dialer takes precedence.
104+
Dialer *cloudsqlconn.Dialer
105+
88106
// CertSource specifies how the opener will obtain authentication information.
89-
// CertSource must not be nil.
107+
//
108+
// Deprecated: Use Dialer instead. CertSource does not support PSC or
109+
// explicit IP type selection. Ignored if Dialer is also set.
90110
CertSource proxy.CertSource
91111

92112
// TraceOpts contains options for OpenTelemetry.
93113
TraceOpts []otelsql.Option
114+
115+
// IPType controls which IP address type is used to connect to the Cloud SQL
116+
// instance. Only applies when Dialer is set; ignored for CertSource connections.
117+
// Defaults to IPTypeAuto (public IP if available, otherwise private).
118+
// Can be overridden per-URL via the "ip_type" query parameter.
119+
IPType cloudsql.IPType
94120
}
95121

96122
// OpenMySQLURL opens a new GCP database connection wrapped with OpenTelemetry instrumentation.
97123
func (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")
124+
if uo.Dialer == nil && uo.CertSource == nil {
125+
return nil, fmt.Errorf("gcpmysql: URLOpener Dialer and CertSource are both nil")
100126
}
101-
var (
102-
client = &proxy.Client{Certs: uo.CertSource, Port: 3307}
103-
cfg, err = configFromURL(u)
104-
)
127+
128+
cfg, err := configFromURL(u)
105129
if err != nil {
106130
return nil, fmt.Errorf("gcpmysql: open config %v", err)
107131
}
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
132+
133+
if uo.Dialer != nil {
134+
// New path: Cloud SQL Go Connector with IP type and PSC support.
135+
ipType := uo.IPType
136+
if s := u.Query().Get("ip_type"); s != "" {
137+
parsed, err := parseIPType(s)
138+
if err != nil {
139+
return nil, fmt.Errorf("gcpmysql: open: %v", err)
140+
}
141+
ipType = parsed
142+
}
143+
dialOpts := ipType.DialOptions()
144+
d := uo.Dialer
145+
cfg.DialFunc = func(ctx context.Context, _, addr string) (net.Conn, error) {
146+
// MySQL driver's addr is in the form "[host]:3306" after normalized.
147+
// https://github.com/go-sql-driver/mysql/blob/76c00e35a8d48f8f70f0e7dffe584692bd3fa612/dsn.go#L193-L195
148+
instance, _, err := net.SplitHostPort(addr)
149+
if err != nil {
150+
return nil, err
151+
}
152+
return d.Dial(ctx, instance, dialOpts...)
153+
}
154+
} else {
155+
// Legacy path: cloudsql-proxy v1 (no PSC or IP type selection).
156+
client := &proxy.Client{Certs: uo.CertSource, Port: 3307}
157+
cfg.DialFunc = func(ctx context.Context, _, addr string) (net.Conn, error) {
158+
instance, _, err := net.SplitHostPort(addr)
159+
if err != nil {
160+
return nil, err
161+
}
162+
return client.DialContext(ctx, instance)
114163
}
115-
return client.DialContext(ctx, instance)
116164
}
165+
117166
c, err := mysql.NewConnector(cfg)
118167
if err != nil {
119168
return nil, fmt.Errorf("gcpmysql: open connector %v", err)
@@ -127,10 +176,14 @@ func configFromURL(u *url.URL) (*mysql.Config, error) {
127176
return nil, err
128177
}
129178

179+
// Strip ip_type from query before passing to MySQL DSN parser.
180+
query := u.Query()
181+
query.Del("ip_type")
182+
130183
var cfg *mysql.Config
131184
switch {
132-
case len(u.RawQuery) > 0:
133-
optDsn := fmt.Sprintf("/%s?%s", dbName, u.RawQuery)
185+
case len(query) > 0:
186+
optDsn := fmt.Sprintf("/%s?%s", dbName, query.Encode())
134187
if cfg, err = mysql.ParseDSN(optDsn); err != nil {
135188
return nil, err
136189
}
@@ -161,3 +214,18 @@ func instanceFromURL(u *url.URL) (instance, db string, _ error) {
161214
}
162215
return parts[0] + ":" + parts[1] + ":" + parts[2], parts[3], nil
163216
}
217+
218+
func parseIPType(s string) (cloudsql.IPType, error) {
219+
switch strings.ToLower(s) {
220+
case "auto", "":
221+
return cloudsql.IPTypeAuto, nil
222+
case "public":
223+
return cloudsql.IPTypePublic, nil
224+
case "private":
225+
return cloudsql.IPTypePrivate, nil
226+
case "psc":
227+
return cloudsql.IPTypePSC, nil
228+
default:
229+
return 0, fmt.Errorf("unknown ip_type %q (valid: auto, public, private, psc)", s)
230+
}
231+
}

0 commit comments

Comments
 (0)