Skip to content

Commit 75a2672

Browse files
committed
feat: Use Proxy v2 engine by default.
Cloud SQL Auth Proxy v1 will internally use Proxy V2 by default. This will allow Auth Proxy V2 users to benefit from the improved compatibility with Cloud SQL services (for example, connecting to MySQL with caching_sha2_password) without any updates to their proxy configuration. Customers may fall back to the legacy Proxy v1 code by adding the -legacy-v1-proxy flag. The proxy will automatically fall back to legacy Proxy v1 code if flags are used that are unsupported by Proxy v2, for example: -projects, -instances_metadata. This also adds an end-to-end test suite in proxy-v1/translatev2/translatev2_test.go that verifies: - Parity: Every test case runs in both translated_v2 and legacy_v1 modes to ensure identical behavior. - Database Support: Verified connectivity for MySQL, PostgreSQL, and SQL Server. - Auth Methods: Validated Password Auth, IAM Auth, and CAS/MCP instance types. - Connection Types: Tested TCP, Unix sockets, and FUSE (on supported platforms). - Edge Cases: Used URL-style DSNs to ensure special characters in IAM usernames are handled correctly across both modes.
1 parent f0c2140 commit 75a2672

File tree

6 files changed

+1499
-13
lines changed

6 files changed

+1499
-13
lines changed

build.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,16 @@ function build() {
3838

3939
## test - Runs local unit tests.
4040
function test() {
41-
go test -v -race -cover -short
41+
go test -v -race -cover -short ./...
42+
}
43+
44+
## test_translatev2 - Runs e2e tests for the transelatev2 code path tests
45+
function test_translatev2() {
46+
if [[ ! -f .envrc ]] ; then
47+
write_e2e_env .envrc
48+
fi
49+
source .envrc
50+
go test -v -race -cover ./translatev2
4251
}
4352

4453
## e2e - Runs end-to-end integration tests.

cmd/cloud_sql_proxy/cloud_sql_proxy.go

Lines changed: 234 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ import (
2424
"errors"
2525
"flag"
2626
"fmt"
27+
"io"
2728
"io/ioutil"
29+
"net"
2830
"net/http"
2931
"os"
3032
"os/signal"
3133
"path/filepath"
34+
"strconv"
3235
"strings"
3336
"sync"
3437
"syscall"
@@ -48,6 +51,8 @@ import (
4851
"golang.org/x/oauth2"
4952
goauth "golang.org/x/oauth2/google"
5053
sqladmin "google.golang.org/api/sqladmin/v1beta4"
54+
55+
proxyv2cmd "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd"
5156
)
5257

5358
var (
@@ -132,6 +137,8 @@ https://sqladmin.googleapis.com`,
132137
// Settings for healthcheck
133138
useHTTPHealthCheck = flag.Bool("use_http_health_check", false, "When set, creates an HTTP server that checks and communicates the health of the proxy client.")
134139
healthCheckPort = flag.String("health_check_port", "8090", "When applicable, health checks take place on this port number. Defaults to 8090.")
140+
141+
_ = flag.Bool("legacy-v1-proxy", false, "If true, the proxy will run in legacy v1 mode.")
135142
)
136143

137144
const (
@@ -754,6 +761,231 @@ use the value from the flag, Not compatible with -fuse.`,
754761
}
755762

756763
func main() {
757-
code := runProxy()
758-
os.Exit(code)
764+
translatedArgs, logDebugStdout, verbose, ok := translateV2Args()
765+
if !ok {
766+
code := runProxy()
767+
os.Exit(code)
768+
}
769+
770+
opts := []proxyv2cmd.Option{
771+
proxyv2cmd.WithProxyV1Compatibility(),
772+
proxyv2cmd.WithProxyV1Verbose(verbose),
773+
}
774+
if logDebugStdout {
775+
opts = append(opts, proxyv2cmd.WithProxyV1LogDebugStdout())
776+
}
777+
778+
v2cmd := proxyv2cmd.NewCommand(opts...)
779+
v2cmd.SetArgs(translatedArgs)
780+
proxyv2cmd.ExecuteCommand(v2cmd)
781+
}
782+
783+
// translateV2Args translates the v1 command line args to V2 args.
784+
// Checks the command line arguments applied to the v1 proxy and then
785+
// translates it for the v2 proxy.
786+
//
787+
// If the arguments use a feature that is not supported in Proxy v2, this
788+
// returns false for OK, indicating that the legacy proxy v1 should run the
789+
// code.
790+
//
791+
// Returns:
792+
// - v2args the arguments translated for the v2 command
793+
// - logDebugStdout true if the v1 -log_debug_stdout flag was set
794+
// - verbose true if the v1 -verbose flag was set (default true)
795+
// - ok true when it is OK to use the v2 proxy command
796+
func translateV2Args() (v2args []string, logDebugStdout, verbose, ok bool) {
797+
// Check for --legacy-v1-proxy before anything else
798+
for _, arg := range os.Args {
799+
if arg == "--legacy-v1-proxy" || arg == "-legacy-v1-proxy" {
800+
return nil, false, false, false
801+
}
802+
}
803+
804+
fs := flag.NewFlagSet("translate", flag.ContinueOnError)
805+
fs.SetOutput(io.Discard)
806+
807+
credentialFile := fs.String("credential_file", "", "")
808+
token := fs.String("token", "", "")
809+
loginToken := fs.String("login_token", "", "")
810+
enableIAMLogin := fs.Bool("enable_iam_login", false, "")
811+
dir := fs.String("dir", "", "")
812+
useFuse := fs.Bool("fuse", false, "")
813+
fuseTmp := fs.String("fuse_tmp", "", "")
814+
healthCheckPort := fs.String("health_check_port", "", "")
815+
useHTTPHealthCheck := fs.Bool("use_http_health_check", false, "")
816+
host := fs.String("host", "", "")
817+
ipAddressTypes := fs.String("ip_address_types", "PUBLIC,PRIVATE", "")
818+
maxConnections := fs.Uint64("max_connections", 0, "")
819+
quiet := fs.Bool("quiet", false, "")
820+
quotaProject := fs.String("quota_project", "", "")
821+
skipFailedInstanceConfig := fs.Bool("skip_failed_instance_config", false, "")
822+
structuredLogs := fs.Bool("structured_logs", false, "")
823+
termTimeout := fs.Duration("term_timeout", 0, "")
824+
version := fs.Bool("version", false, "")
825+
verboseFlag := fs.Bool("verbose", true, "")
826+
logDebugStdoutFlag := fs.Bool("log_debug_stdout", false, "")
827+
828+
// Untranslatable flags
829+
_ = fs.Bool("check_region", false, "")
830+
_ = fs.String("projects", "", "")
831+
_ = fs.String("instances_metadata", "", "")
832+
_ = fs.Duration("refresh_config_throttle", 0, "")
833+
_ = fs.Uint64("fd_rlimit", 0, "")
834+
_ = fs.Bool("legacy-v1-proxy", false, "")
835+
836+
var instances stringListValue
837+
fs.Var(&instances, "instances", "")
838+
839+
if err := fs.Parse(os.Args[1:]); err != nil {
840+
return nil, false, false, false
841+
}
842+
843+
// Check for unsupported flags being set
844+
unsupportedSet := false
845+
fs.Visit(func(f *flag.Flag) {
846+
switch f.Name {
847+
case "check_region", "projects", "instances_metadata",
848+
"refresh_config_throttle", "fd_rlimit", "legacy-v1-proxy":
849+
unsupportedSet = true
850+
}
851+
})
852+
if unsupportedSet {
853+
return nil, false, false, false
854+
}
855+
856+
var args []string
857+
fs.Visit(func(f *flag.Flag) {
858+
switch f.Name {
859+
case "credential_file":
860+
args = append(args, "--credentials-file", *credentialFile)
861+
case "token":
862+
args = append(args, "--token", *token)
863+
case "login_token":
864+
args = append(args, "--login-token", *loginToken)
865+
case "enable_iam_login":
866+
if *enableIAMLogin {
867+
args = append(args, "--auto-iam-authn")
868+
}
869+
case "dir":
870+
args = append(args, "--unix-socket", *dir)
871+
case "fuse":
872+
if *useFuse {
873+
args = append(args, "--fuse", *dir)
874+
}
875+
case "fuse_tmp":
876+
args = append(args, "--fuse-temp-dir", *fuseTmp)
877+
case "health_check_port":
878+
args = append(args, "--http-port", *healthCheckPort)
879+
case "use_http_health_check":
880+
if *useHTTPHealthCheck {
881+
args = append(args, "--health-check")
882+
}
883+
case "host":
884+
args = append(args, "--sqladmin-api-endpoint", *host)
885+
case "max_connections":
886+
args = append(args, "--max-connections", strconv.FormatUint(*maxConnections, 10))
887+
case "quiet":
888+
if *quiet {
889+
args = append(args, "--quiet")
890+
}
891+
case "quota_project":
892+
args = append(args, "--quota-project", *quotaProject)
893+
case "skip_failed_instance_config":
894+
if *skipFailedInstanceConfig {
895+
args = append(args, "--skip-failed-instance-config")
896+
}
897+
case "structured_logs":
898+
if *structuredLogs {
899+
args = append(args, "--structured-logs")
900+
}
901+
case "term_timeout":
902+
args = append(args, "--max-sigterm-delay", termTimeout.String())
903+
case "version":
904+
if *version {
905+
args = append(args, "--version")
906+
}
907+
}
908+
})
909+
910+
// ip_address_types handling
911+
ipTypesSet := false
912+
fs.Visit(func(f *flag.Flag) {
913+
if f.Name == "ip_address_types" {
914+
ipTypesSet = true
915+
}
916+
})
917+
918+
if !ipTypesSet {
919+
args = append(args, "--auto-ip")
920+
} else {
921+
types := strings.Split(*ipAddressTypes, ",")
922+
hasPublic := false
923+
hasPrivate := false
924+
for _, t := range types {
925+
switch strings.TrimSpace(t) {
926+
case "PUBLIC":
927+
hasPublic = true
928+
case "PRIVATE":
929+
hasPrivate = true
930+
default:
931+
// Unknown type, fallback to v1
932+
return nil, false, false, false
933+
}
934+
}
935+
if hasPublic && hasPrivate {
936+
args = append(args, "--auto-ip")
937+
} else if hasPrivate {
938+
args = append(args, "--private-ip")
939+
} else if hasPublic {
940+
// default in v2 is public
941+
}
942+
}
943+
944+
// instances handling
945+
for _, inst := range instances {
946+
parts := strings.SplitN(inst, "=", 2)
947+
connName := parts[0]
948+
if len(parts) == 1 {
949+
args = append(args, connName)
950+
continue
951+
}
952+
953+
opts := strings.SplitN(parts[1], ":", 2)
954+
if len(opts) != 2 {
955+
return nil, false, false, false // Invalid format
956+
}
957+
958+
netType := opts[0]
959+
netAddr := opts[1]
960+
961+
switch netType {
962+
case "tcp":
963+
if strings.Contains(netAddr, ":") {
964+
// host:port
965+
h, p, err := net.SplitHostPort(netAddr)
966+
if err != nil {
967+
return nil, false, false, false
968+
}
969+
args = append(args, fmt.Sprintf("%s?address=%s&port=%s", connName, h, p))
970+
} else {
971+
// just port
972+
args = append(args, fmt.Sprintf("%s?port=%s", connName, netAddr))
973+
}
974+
case "unix":
975+
if strings.HasPrefix(netAddr, "/") {
976+
args = append(args, fmt.Sprintf("%s?unix-socket-path=%s", connName, netAddr))
977+
} else {
978+
args = append(args, fmt.Sprintf("%s?unix-socket=%s", connName, netAddr))
979+
}
980+
default:
981+
return nil, false, false, false // Unsupported network
982+
}
983+
}
984+
985+
// V2 requires at least one instance OR fuse mode OR version
986+
if len(instances) == 0 && !*useFuse && !*version {
987+
return nil, false, false, false
988+
}
989+
990+
return args, *logDebugStdoutFlag, *verboseFlag, true
759991
}

0 commit comments

Comments
 (0)