Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
/logs/
.tools
test_results.txt
test-results
62 changes: 55 additions & 7 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,25 @@ function clean() {

## build - Builds the project without running tests.
function build() {
go build -o ./cloud-sql-proxy main.go
local metadata="${1:-}"
local ldflags=""
if [[ -n "$metadata" ]] ; then
ldflags="-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=$metadata"
fi
go build -ldflags "$ldflags" -o ./cloud-sql-proxy main.go
}

## test - Runs local unit tests.
function test() {
go test -v -race -cover -short ./...
get_golang_tool 'go-junit-report' 'jstemmer/go-junit-report' 'github.com/jstemmer/go-junit-report/v2'
mkdir -p test-results
local args=( "./..." )
if [[ "$#" -gt 0 ]] ; then
args=( "$@" )
fi
go test -v -race -cover -short "${args[@]}" -json \
| .tools/go-junit-report -iocopy -parser gojson -out test-results/unit.xml \
| jq -j 'select(.Output) | .Output '
}

## e2e - Runs end-to-end integration tests.
Expand All @@ -53,7 +66,11 @@ function e2e() {
# e2e_ci - Run end-to-end integration tests in the CI system.
# This assumes that the secrets in the env vars are already set.
function e2e_ci() {
go test -race -v ./... | tee test_results.txt
get_golang_tool 'go-junit-report' 'jstemmer/go-junit-report' 'github.com/jstemmer/go-junit-report/v2'
mkdir -p test-results
go test -race -v ./... -json \
| .tools/go-junit-report -iocopy -parser gojson -out test-results/e2e.xml \
| jq -j 'select(.Output) | .Output '
}

function get_golang_tool() {
Expand Down Expand Up @@ -227,16 +244,47 @@ function write_e2e_env(){
done

# Set IAM User env vars to the local gcloud user
echo "export MYSQL_IAM_USER='${local_user%%@*}'"
echo "export POSTGRES_USER_IAM='$local_user'"
echo "export MYSQL_IAM_USER='$(iam_user_mysql)'"
echo "export POSTGRES_USER_IAM='$(iam_user_pg)'"
} > "$1"

}

function iam_user_pg() {
# Truncate the suffix `.iam.gserviceaccount.com` if it exists. Otherwise return the email.
local email
local pguser

email="$(iam_user_email)"
pguser="${email%%.iam.gserviceaccount.com}"
if [[ -n "$pguser" ]] ; then
echo "$pguser"
else
echo "$email"
fi

}

function iam_user_mysql() {
# Truncate the part after the @
local email
local pguser

email=$(iam_user_email)
mysqluser="${email%%@*}"
echo "$mysqluser"
}

function iam_user_email() {
gcloud auth list --format json | jq -r '.[] | select (.status == "ACTIVE") | .account'
}


## build_image - Builds and pushes the proxy container image using local source.
## Usage: ./build.sh build_image [image-url]
## Usage: ./build.sh build_image [image-url] [metadata]
function build_image() {
local image_url="${1:-}"
local metadata="${2:-container}"
local push_arg=""

if [[ -n "$image_url" ]]; then
Expand All @@ -254,7 +302,7 @@ function build_image() {
trap cleanup_build EXIT

echo "Building binary locally..."
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=container" -o cloud-sql-proxy
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=$metadata" -o cloud-sql-proxy

echo "Creating temporary Dockerfile..."
cat > Dockerfile.local <<EOF
Expand Down
18 changes: 18 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,9 @@ the cached copy has expired. Use this setting in environments where the
CPU may be throttled and a background refresh cannot run reliably
(e.g., Cloud Run)`,
)
localFlags.StringVar(&c.conf.SQLDataEndpoint, "sqldata-api-endpoint", "",
"Override the SQL Data API endpoint",
)

localFlags.BoolVar(&c.conf.RunConnectionTest, "run-connection-test", false, `Runs a connection test
against all specified instances. If an instance is unreachable, the Proxy exits with a failure
Expand All @@ -606,6 +609,10 @@ only applicable to Unix sockets)`)
"(*) Connect to the private ip address for all instances")
localFlags.BoolVar(&c.conf.PSC, "psc", false,
"(*) Connect to the PSC endpoint for all instances")
localFlags.BoolVar(&c.conf.SQLDataEnabled, "sql-data", false,
"Enable SQL Data to tunnel through the Cloud SQL Admin API without"+
" needing network access to your public or private IP",
)

return c
}
Expand Down Expand Up @@ -898,6 +905,7 @@ and re-try with just --auto-iam-authn`)
p, pok := q["port"]
u, uok := q["unix-socket"]
up, upok := q["unix-socket-path"]
sd, sdok := q["sql-data"]

if aok && uok {
return newBadCommandError("cannot specify both address and unix-socket query params")
Expand Down Expand Up @@ -955,6 +963,16 @@ and re-try with just --auto-iam-authn`)
}
ic.UnixSocketPath = up[0]
}
if sdok {
if len(sd) != 1 {
return newBadCommandError(fmt.Sprintf("sql-data query param should be only one value %q", a))
}
if sd[0] != "true" && sd[0] != "false" {
return newBadCommandError(fmt.Sprintf("sql-data query param should be \"true\" or \"false\" %q", a))
}
b := sd[0] == "true"
ic.SQLDataEnabled = &b
}

ic.IAMAuthN, err = parseBoolOpt(q, "auto-iam-authn")
if err != nil {
Expand Down
95 changes: 95 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,29 @@ func TestNewCommandArguments(t *testing.T) {
RunConnectionTest: true,
}),
},
{
desc: "using the sql-data flag",
args: []string{"--sql-data", "proj:region:inst"},
want: withDefaults(&proxy.Config{
SQLDataEnabled: true,
}),
},
{
desc: "using the sqldata-api-endpoint flag",
args: []string{"--sqldata-api-endpoint", "https://test.googleapis.com", "proj:region:inst"},
want: withDefaults(&proxy.Config{
SQLDataEndpoint: "https://test.googleapis.com",
}),
},
{
desc: "using the sql-data query param",
args: []string{"proj:region:inst?sql-data=true"},
want: withDefaults(&proxy.Config{
Instances: []proxy.InstanceConnConfig{{
SQLDataEnabled: pointer(true),
}},
}),
},
}

for _, tc := range tcs {
Expand Down Expand Up @@ -821,6 +844,22 @@ func TestNewCommandWithEnvironmentConfig(t *testing.T) {
AutoIP: true,
}),
},
{
desc: "using the sql-data envvar",
envName: "CSQL_PROXY_SQL_DATA",
envValue: "true",
want: withDefaults(&proxy.Config{
SQLDataEnabled: true,
}),
},
{
desc: "using the sqldata-api-endpoint envvar",
envName: "CSQL_PROXY_SQLDATA_API_ENDPOINT",
envValue: "https://test.googleapis.com",
want: withDefaults(&proxy.Config{
SQLDataEndpoint: "https://test.googleapis.com",
}),
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
Expand Down Expand Up @@ -1033,6 +1072,54 @@ func TestPSCQueryParams(t *testing.T) {
}
}

func TestSQLDataQueryParams(t *testing.T) {
tcs := []struct {
desc string
args []string
want *bool
}{
{
desc: "when the query string is absent",
args: []string{"proj:region:inst"},
want: nil,
},
{
desc: "when the query string is true",
args: []string{"proj:region:inst?sql-data=true"},
want: pointer(true),
},
{
desc: "when the query string is false",
args: []string{"proj:region:inst?sql-data=false"},
want: pointer(false),
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
c, err := invokeProxyCommand(tc.args)
if err != nil {
t.Fatalf("command.Execute: %v", err)
}
if tc.want == nil {
if len(c.conf.Instances) > 0 && c.conf.Instances[0].SQLDataEnabled != nil {
t.Fatalf("args = %v, want nil, got = %v", tc.args, *c.conf.Instances[0].SQLDataEnabled)
}
return
}
if len(c.conf.Instances) == 0 {
t.Fatal("expected at least one instance")
}
got := c.conf.Instances[0].SQLDataEnabled
if got == nil {
t.Fatalf("args = %v, want = %v, got = nil", tc.args, *tc.want)
}
if *got != *tc.want {
t.Errorf("args = %v, want = %v, got = %v", tc.args, *tc.want, *got)
}
})
}
}

func TestNewCommandWithErrors(t *testing.T) {
tcs := []struct {
desc string
Expand Down Expand Up @@ -1152,6 +1239,14 @@ func TestNewCommandWithErrors(t *testing.T) {
desc: "when the iam authn login query param contains multiple values",
args: []string{"proj:region:inst?auto-iam-authn=true&auto-iam-authn=false"},
},
{
desc: "when the sql-data query param contains multiple values",
args: []string{"proj:region:inst?sql-data=true&sql-data=false"},
},
{
desc: "when the sql-data query param is bogus",
args: []string{"proj:region:inst?sql-data=nope"},
},
{
desc: "when the iam authn login query param is bogus",
args: []string{"proj:region:inst?auto-iam-authn=nope"},
Expand Down
2 changes: 2 additions & 0 deletions docs/cmd/cloud-sql-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,9 @@ cloud-sql-proxy INSTANCE_CONNECTION_NAME... [flags]
status code.
--skip-failed-instance-config If set, the Proxy will skip any instances that are invalid/unreachable (
only applicable to Unix sockets)
--sql-data Enable SQL Data to tunnel through the Cloud SQL Admin API without needing network access to your public or private IP
--sqladmin-api-endpoint string API endpoint for all Cloud SQL Admin API requests. (default: https://sqladmin.googleapis.com)
--sqldata-api-endpoint string Override the SQL Data API endpoint
-l, --structured-logs Enable structured logging with LogEntry format
--telemetry-prefix string Prefix for Cloud Monitoring metrics.
--telemetry-project string Enable Cloud Monitoring and Cloud Trace with the provided project ID.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/GoogleCloudPlatform/cloud-sql-proxy/v2
go 1.25.8

require (
cloud.google.com/go/cloudsqlconn v1.21.2
cloud.google.com/go/cloudsqlconn v1.22.0
contrib.go.opencensus.io/exporter/prometheus v0.4.2
contrib.go.opencensus.io/exporter/stackdriver v0.13.14
github.com/coreos/go-systemd/v22 v22.7.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/cloudsqlconn v1.21.2 h1:Iw/3W+6eIB9AChWFF2wlqLwpgaZ+DOX2DBUKKfsSDMI=
cloud.google.com/go/cloudsqlconn v1.21.2/go.mod h1:AXcbXAjdud2Hl6JLe80VHCaOjAsvh8O/JgQnhrRvJl8=
cloud.google.com/go/cloudsqlconn v1.22.0 h1:4+uh5gGbjmFzitCD9owKZhwjBN2U+4sWxD9gJjcpOeE=
cloud.google.com/go/cloudsqlconn v1.22.0/go.mod h1:AXcbXAjdud2Hl6JLe80VHCaOjAsvh8O/JgQnhrRvJl8=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
Expand Down
39 changes: 34 additions & 5 deletions internal/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ type InstanceConnConfig struct {
// necessary. If set, UnixSocketPath takes precedence over UnixSocket, Addr
// and Port.
UnixSocketPath string
// SQLDataEnabled enables connections through the SqlDataService for this connection.
SQLDataEnabled *bool
// IAMAuthN enables automatic IAM DB Authentication for the instance.
// MySQL and Postgres only. If it is nil, the value was not specified.
IAMAuthN *bool
Expand Down Expand Up @@ -200,6 +202,11 @@ type Config struct {
// of a request context, e.g., Cloud Run.
LazyRefresh bool

// SQLDataEnabled configures the dialer to use the SQL Data API.
SQLDataEnabled bool
// SQLDataEndpoint configures the endpoint of the SQL Data service.
SQLDataEndpoint string

// Instances are configuration for individual instances. Instance
// configuration takes precedence over global configuration.
Instances []InstanceConnConfig
Expand Down Expand Up @@ -282,6 +289,9 @@ func dialOptions(c Config, i InstanceConnConfig) []cloudsqlconn.DialOption {
if i.IAMAuthN != nil {
opts = append(opts, cloudsqlconn.WithDialIAMAuthN(*i.IAMAuthN))
}
if i.SQLDataEnabled != nil && *i.SQLDataEnabled || c.SQLDataEnabled {
opts = append(opts, cloudsqlconn.WithSQLData())
}

switch {
// If private IP is enabled at the instance level, or private IP is enabled globally
Expand Down Expand Up @@ -469,6 +479,10 @@ func (c *Config) DialerOptions(l cloudsql.Logger) ([]cloudsqlconn.Option, error)
opts = append(opts, cloudsqlconn.WithLazyRefresh())
}

if c.SQLDataEndpoint != "" {
opts = append(opts, cloudsqlconn.WithSQLDataEndpoint(c.SQLDataEndpoint))
}

return opts, nil
}

Expand Down Expand Up @@ -564,8 +578,12 @@ func NewClient(ctx context.Context, d cloudsql.Dialer, l cloudsql.Logger, conf *
return configureFUSE(c, conf)
}

// unless the proxy is in SqlDataEnabled mode, initiate a refresh operation to warm the cache
for _, inst := range conf.Instances {
// Initiate refresh operation and warm the cache.
// Skip instances with SqlDataEnabled
if conf.SQLDataEnabled || inst.SQLDataEnabled != nil && *inst.SQLDataEnabled {
continue
}
go func(name string) { _, _ = d.EngineVersion(ctx, name) }(inst.Name)
}

Expand Down Expand Up @@ -859,6 +877,10 @@ func (c *Client) newSocketMount(ctx context.Context, conf *Config, pc *portConfi
np = inst.Port
case conf.Port != 0:
np = pc.nextPort()
case conf.SQLDataEnabled || inst.SQLDataEnabled != nil && *inst.SQLDataEnabled:
// Only Postgres is supported by the SqlDataService
// when more engines are supported, this code will need to change.
np = pc.nextDBPort("POSTGRES")
default:
version, err := c.dialer.EngineVersion(ctx, inst.Name)
// Exit if the port is not specified for inactive instance
Expand All @@ -873,10 +895,17 @@ func (c *Client) newSocketMount(ctx context.Context, conf *Config, pc *portConfi
} else {
network = "unix"

version, err := c.dialer.EngineVersion(ctx, inst.Name)
if err != nil {
c.logger.Errorf("[%v] could not resolve instance version: %v", inst.Name, err)
return nil, err
var version string
switch {
case conf.SQLDataEnabled || inst.SQLDataEnabled != nil && *inst.SQLDataEnabled:
version = "POSTGRES"
default:
var err error
version, err = c.dialer.EngineVersion(ctx, inst.Name)
if err != nil {
c.logger.Errorf("[%v] could not resolve instance version: %v", inst.Name, err)
return nil, err
}
}

address, err = newUnixSocketMount(inst, conf.UnixSocket, strings.HasPrefix(version, "POSTGRES"))
Expand Down
Loading
Loading