sourcegraph/dev/sg/cloudsqlproxy/cloudsqlproxy.go
Robert Lin 63403f2a16
sg: add 'sg msp postgresql connect' (#59106)
This change introduces an `mi2`-like experience for interacting with MSP databases. By default we use the new readonly-SA introduced in https://github.com/sourcegraph/sourcegraph/pull/59105, otherwise with the `--write-access` flag you can connect as the same user as the Cloud Run workload, which gives a lot more permissions.

An alias, `sg msp pg connect`, is available for those less inclined to type out the entire `postgresql` subcommand name.

Closes https://github.com/sourcegraph/managed-services/issues/207

## Test plan

Applied to `msp-testbed`, which has a PG instance and provisioned tables.
Try both service accounts:

```
sg msp pg connect --write-access msp-testbed test
sg msp pg connect msp-testbed test 
```

![image](https://github.com/sourcegraph/sourcegraph/assets/23356519/c75bcb8c-5f7d-4f3b-90ec-9effb3306ba9)

With both of the above:

```
primary=> select * from users;
 id | created_at | updated_at | deleted_at | external_id | name | avatar_url 
----+------------+------------+------------+-------------+------+------------
(0 rows)
```
2023-12-20 14:53:34 -08:00

75 lines
2.1 KiB
Go

// Initially copy-pasta from https://github.com/sourcegraph/controller/blob/main/internal/cloudsqlproxy/proxy.go
package cloudsqlproxy
import (
"context"
"fmt"
"time"
"github.com/sourcegraph/run"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
// CloudSQLProxy is a cloud-sql-proxy instance
//
// It uses the identity of the service account to connect to the database
// and the proxy can handle the authentication for the database and refresh
// the credentials as needed.
type CloudSQLProxy struct {
Token string
DBInstanceConnectionName string
ImpersonateServiceAccount string
Port int
}
func NewCloudSQLProxy(dbConnection string, iamUserEmail string, port int) (*CloudSQLProxy, error) {
return &CloudSQLProxy{
DBInstanceConnectionName: dbConnection,
ImpersonateServiceAccount: iamUserEmail,
Port: port,
}, nil
}
func (p *CloudSQLProxy) Start(ctx context.Context, timeoutSeconds int) error {
bin, err := Path()
if err != nil {
return errors.Wrap(err, "failed to get path to the cloud-sql-proxy binary")
}
proxyCmd := fmt.Sprintf("%s -i -p %d --impersonate-service-account=%s %s",
bin, p.Port, p.ImpersonateServiceAccount, p.DBInstanceConnectionName)
if timeoutSeconds > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer cancel()
std.Out.WriteWarningf("The current session will terminate in %d seconds. Use '-session.timeout' to increase the session duration.",
timeoutSeconds)
time.AfterFunc(time.Duration(timeoutSeconds)*time.Second, func() {
select {
case <-ctx.Done():
return // nothing to do
default:
std.Out.WriteAlertf("The current session has timed out after %d seconds and will be terminated.",
timeoutSeconds)
cancel()
}
})
}
err = run.Cmd(ctx, proxyCmd).Run().StreamLines(func(line string) {
std.Out.Write(" [cloud-sql-proxy] " + line)
})
if err != nil {
if ctx.Err() == context.Canceled {
return nil
}
return errors.Wrap(err, "failed to start cloud-sql-proxy")
}
return nil
}