diff --git a/internal/proxy/other_test.go b/internal/proxy/other_test.go index 6258f400..a102b9c6 100644 --- a/internal/proxy/other_test.go +++ b/internal/proxy/other_test.go @@ -29,6 +29,55 @@ func TestClientUsesSyncAtomicAlignment(t *testing.T) { } } +func TestUnixSocketDir(t *testing.T) { + tcs := []struct { + desc string + in string + want string + wantErr bool + }{ + { + desc: "good input", + in: "projects/proj/locations/reg/clusters/clust/instances/inst", + want: "proj.reg.clust.inst", + }, + { + desc: "irregular casing", + in: "projects/PROJ/locations/REG/clusters/CLUST/instances/INST", + want: "proj.reg.clust.inst", + }, + { + desc: "legacy domain project", + in: "projects/google.com:proj/locations/reg/clusters/clust/instances/inst", + want: "google.com_proj.reg.clust.inst", + }, + { + in: "projects/myproj/locations/reg/clusters/clust/instances/", + wantErr: true, + }, + { + in: "projects/google.com:bad:PROJECT/locations/reg/clusters/clust/instances/inst", + wantErr: true, + }, + } + + for _, tc := range tcs { + t.Run(tc.desc, func(t *testing.T) { + got, gotErr := UnixSocketDir("", tc.in) + if tc.wantErr { + if gotErr == nil { + t.Fatal("want err != nil, got err == nil") + } + return + } + if got != tc.want { + t.Fatalf("want = %v, got = %v", tc.want, got) + } + + }) + } +} + func TestToFullURI(t *testing.T) { tcs := []struct { desc string @@ -41,6 +90,11 @@ func TestToFullURI(t *testing.T) { in: "myproj.reg.clust.inst", want: "projects/myproj/locations/reg/clusters/clust/instances/inst", }, + { + desc: "legacy project name", + in: "google.com_myproj.reg.clust.inst", + want: "projects/google.com:myproj/locations/reg/clusters/clust/instances/inst", + }, { desc: "invalid name", in: ".Trash", diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index ef674ceb..bcb2200b 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -159,23 +159,30 @@ var ( // Instance URI is in the format: // 'projects//locations//clusters//instances/' // Additionally, we have to support legacy "domain-scoped" projects (e.g. "google.com:PROJECT") - instURIRegex = regexp.MustCompile("projects/([^:]+(:[^:]+)?)/locations/([^:]+)/clusters/([^:]+)/instances/([^:]+)") + instURIRegex = regexp.MustCompile("projects/([^:]+(?::[^:]+)?)/locations/(.+)/clusters/(.+)/instances/(.+)") // unixRegex is the expected format for a Unix socket // e.g. project.region.cluster.instance - unixRegex = regexp.MustCompile(`([^:]+)\.([^:]+)\.([^:]+)\.([^:]+)`) + unixRegex = regexp.MustCompile(`([^:]+(?:-[^:]+)?)\.(.+)\.(.+)\.(.+)`) ) // UnixSocketDir returns a shorted instance connection name to prevent // exceeding the Unix socket length, e.g., project.region.cluster.instance func UnixSocketDir(dir, inst string) (string, error) { + inst = strings.ToLower(inst) m := instURIRegex.FindSubmatch([]byte(inst)) if m == nil { return "", fmt.Errorf("invalid instance name: %v", inst) } project := string(m[1]) - region := string(m[3]) - cluster := string(m[4]) - name := string(m[5]) + // Colons are not allowed on Windows, but are present in legacy project + // names (e.g., google.com:myproj). Replace any colon with an underscore to + // support Windows. Underscores are not allowed in project names. So use an + // underscore to have a Windows-friendly delimitor that can serve as a + // marker to recover the legacy project name when necessary (e.g., FUSE). + project = strings.ReplaceAll(project, ":", "_") + region := string(m[2]) + cluster := string(m[3]) + name := string(m[4]) shortName := strings.Join([]string{project, region, cluster, name}, ".") return filepath.Join(dir, shortName), nil } @@ -188,11 +195,15 @@ func toFullURI(short string) (string, error) { return "", fmt.Errorf("invalid short name: %v", short) } project := string(m[1]) + // Adjust short name for legacy projects. Google Cloud projects cannot have + // underscores in them. When there's an underscore in the short name, it's a + // marker for a colon. So replace the underscore with the original colon. + project = strings.ReplaceAll(project, "_", ":") region := string(m[2]) cluster := string(m[3]) name := string(m[4]) return fmt.Sprintf( - "projects/%v/locations/%v/clusters/%v/instances/%v", + "projects/%s/locations/%s/clusters/%s/instances/%s", project, region, cluster, name, ), nil }