Skip to content

Commit 89c742f

Browse files
committed
feat: Add support for v1 projects flag.
1 parent 79e0f2a commit 89c742f

4 files changed

Lines changed: 134 additions & 2 deletions

File tree

cmd/options.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ func WithProxyV1LogDebugStdout() Option {
9292
}
9393
}
9494

95+
// WithProxyV1Projects enables legacy behavior of v1 and will connect to
96+
// all Second Generation instances in the provided projects.
97+
func WithProxyV1Projects(projects []string) Option {
98+
return func(c *Command) {
99+
c.conf.Projects = projects
100+
}
101+
}
102+
95103
// WithProxyV1Verbose enables legacy behavior of v1 and will set
96104
// the debug-logs flag on the v2 proxy.
97105
func WithProxyV1Verbose(v bool) Option {

cmd/root.go

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ import (
3333
"syscall"
3434
"time"
3535

36+
"cloud.google.com/go/compute/metadata"
3637
"contrib.go.opencensus.io/exporter/prometheus"
3738
"contrib.go.opencensus.io/exporter/stackdriver"
38-
"cloud.google.com/go/compute/metadata"
3939
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"
4040
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/healthcheck"
4141
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log"
@@ -45,6 +45,10 @@ import (
4545
"github.com/spf13/pflag"
4646
"github.com/spf13/viper"
4747
"go.opencensus.io/trace"
48+
"golang.org/x/oauth2"
49+
"google.golang.org/api/impersonate"
50+
"google.golang.org/api/option"
51+
sqladmin "google.golang.org/api/sqladmin/v1"
4852
)
4953

5054
var (
@@ -659,6 +663,15 @@ func loadConfig(c *Command, args []string, opts []Option) error {
659663
args = append(args, mArgs...)
660664
}
661665

666+
// If projects is set, fetch instances from those projects.
667+
if len(c.conf.Projects) > 0 {
668+
pArgs, err := instanceFromProjects(c.Context(), c.conf)
669+
if err != nil {
670+
return err
671+
}
672+
args = append(args, pArgs...)
673+
}
674+
662675
// Handle logger separately from config
663676
if c.conf.StructuredLogs {
664677
c.logger = log.NewStructuredLogger(c.conf.Quiet)
@@ -1304,3 +1317,110 @@ func instanceFromMetadata(path string) ([]string, error) {
13041317
}
13051318
return args, nil
13061319
}
1320+
1321+
func instanceFromProjects(ctx context.Context, conf *proxy.Config) ([]string, error) {
1322+
var opts []option.ClientOption
1323+
if conf.APIEndpointURL != "" {
1324+
opts = append(opts, option.WithEndpoint(conf.APIEndpointURL))
1325+
}
1326+
if conf.QuotaProject != "" {
1327+
opts = append(opts, option.WithQuotaProject(conf.QuotaProject))
1328+
}
1329+
1330+
// Handle credentials
1331+
switch {
1332+
case conf.ImpersonationChain != "":
1333+
target, delegates := parseImpersonationChain(conf.ImpersonationChain)
1334+
var iopts []option.ClientOption
1335+
switch {
1336+
case conf.Token != "":
1337+
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: conf.Token})
1338+
iopts = append(iopts, option.WithTokenSource(ts))
1339+
case conf.CredentialsFile != "":
1340+
iopts = append(iopts, option.WithCredentialsFile(conf.CredentialsFile))
1341+
case conf.CredentialsJSON != "":
1342+
iopts = append(iopts, option.WithCredentialsJSON([]byte(conf.CredentialsJSON)))
1343+
}
1344+
ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
1345+
TargetPrincipal: target,
1346+
Delegates: delegates,
1347+
Scopes: []string{sqladmin.SqlserviceAdminScope},
1348+
}, iopts...)
1349+
if err != nil {
1350+
return nil, err
1351+
}
1352+
opts = append(opts, option.WithTokenSource(ts))
1353+
case conf.Token != "":
1354+
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: conf.Token})
1355+
opts = append(opts, option.WithTokenSource(ts))
1356+
case conf.CredentialsFile != "":
1357+
opts = append(opts, option.WithCredentialsFile(conf.CredentialsFile))
1358+
case conf.CredentialsJSON != "":
1359+
opts = append(opts, option.WithCredentialsJSON([]byte(conf.CredentialsJSON)))
1360+
}
1361+
1362+
sql, err := sqladmin.NewService(ctx, opts...)
1363+
if err != nil {
1364+
return nil, err
1365+
}
1366+
1367+
ch := make(chan string)
1368+
errCh := make(chan error, len(conf.Projects))
1369+
var wg sync.WaitGroup
1370+
wg.Add(len(conf.Projects))
1371+
for _, proj := range conf.Projects {
1372+
proj := proj
1373+
go func() {
1374+
defer wg.Done()
1375+
err := sql.Instances.List(proj).Pages(ctx, func(r *sqladmin.InstancesListResponse) error {
1376+
for _, in := range r.Items {
1377+
// The Proxy only supports Second Gen
1378+
if in.BackendType == "SECOND_GEN" {
1379+
ch <- in.ConnectionName
1380+
}
1381+
}
1382+
return nil
1383+
})
1384+
if err != nil {
1385+
errCh <- fmt.Errorf("failed to list instances in %q: %v", proj, err)
1386+
}
1387+
}()
1388+
}
1389+
go func() {
1390+
wg.Wait()
1391+
close(ch)
1392+
close(errCh)
1393+
}()
1394+
1395+
var args []string
1396+
for x := range ch {
1397+
args = append(args, x)
1398+
}
1399+
1400+
// Check for any errors
1401+
for err := range errCh {
1402+
if err != nil {
1403+
return nil, err
1404+
}
1405+
}
1406+
1407+
if len(args) == 0 {
1408+
return nil, fmt.Errorf("no Cloud SQL Instances found in projects: %v", conf.Projects)
1409+
}
1410+
return args, nil
1411+
}
1412+
1413+
func parseImpersonationChain(chain string) (string, []string) {
1414+
accts := strings.Split(chain, ",")
1415+
target := accts[0]
1416+
// Assign delegates if the chain is more than one account. Delegation
1417+
// goes from last back towards target, e.g., With sa1,sa2,sa3, sa3
1418+
// delegates to sa2, which impersonates the target sa1.
1419+
var delegates []string
1420+
if l := len(accts); l > 1 {
1421+
for i := l - 1; i > 0; i-- {
1422+
delegates = append(delegates, accts[i])
1423+
}
1424+
}
1425+
return target, delegates
1426+
}

internal/proxy/proxy.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@ type Config struct {
212212
// comma-separated list of instance connection names.
213213
InstancesMetadata string
214214

215+
// Projects is a list of projects from which to connect to all
216+
// Second Generation instances.
217+
Projects []string
218+
215219
// Instances are configuration for individual instances. Instance
216220
// configuration takes precedence over global configuration.
217221
Instances []InstanceConnConfig

migration-guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ The following table lists in alphabetical order v1 flags and their v2 version.
169169
| ip_address_types | private-ip | Defaults to public. To connect to a private IP, you must add the --private-ip flag |
170170
| log_debug_stdout || v2 logs to stdout, errors to stderr by default |
171171
| max_connections | max-connections | |
172-
| projects || v2 prefers explicit connection configuration to avoid user error |
172+
| projects || Not supported as a v2 flag. v2 prefers explicit configuration. v1 -projects is supported in v2 compatibility mode. |
173173
| quiet | quiet | quiet disables all logging except errors |
174174
| quota_project | quota-project | |
175175
| refresh_config_throttle || |

0 commit comments

Comments
 (0)