Skip to content

Commit

Permalink
Teleport Connect: Add SetGatewayTargetSubresourceName RPC (#13476)
Browse files Browse the repository at this point in the history
* clusters.CreateGateway: Trim spaces when building a CLI command

If cliCommand.Env is empty, the returned command has an empty space at
the front.

* Do not pass TargetSubresourceName when fetching certs for gateway

Since we want to be able to freely specify the db name after the gateway
creation, let's not pass the db name when creating the gateway to show
the intent more clearly.

* Make CLICommand a method on Gateway rather than a field

* Extract getting cluster by resource URI to Storage
  • Loading branch information
ravicious authored Jun 27, 2022
1 parent 8a27614 commit a7760a4
Show file tree
Hide file tree
Showing 17 changed files with 1,186 additions and 465 deletions.
61 changes: 35 additions & 26 deletions lib/client/db/dbcmd/dbcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ const (
snowsqlBin = "snowsql"
)

// execer is an abstraction of Go's exec module, as this one doesn't specify any interfaces.
// Execer is an abstraction of Go's exec module, as this one doesn't specify any interfaces.
// This interface exists only to enable mocking.
type execer interface {
type Execer interface {
// RunCommand runs a system command.
RunCommand(name string, arg ...string) ([]byte, error)
// LookPath returns a full path to a binary if this one is found in system PATH,
Expand All @@ -73,21 +73,21 @@ type execer interface {
Command(name string, arg ...string) *exec.Cmd
}

// systemExecer implements execer interface by using Go exec module.
type systemExecer struct{}
// SystemExecer implements execer interface by using Go exec module.
type SystemExecer struct{}

// RunCommand is a wrapper for exec.Command(...).Output()
func (s systemExecer) RunCommand(name string, arg ...string) ([]byte, error) {
func (s SystemExecer) RunCommand(name string, arg ...string) ([]byte, error) {
return exec.Command(name, arg...).Output()
}

// LookPath is a wrapper for exec.LookPath(...)
func (s systemExecer) LookPath(file string) (string, error) {
func (s SystemExecer) LookPath(file string) (string, error) {
return exec.LookPath(file)
}

// Command is a wrapper for exec.Command(...)
func (s systemExecer) Command(name string, arg ...string) *exec.Cmd {
func (s SystemExecer) Command(name string, arg ...string) *exec.Cmd {
return exec.Command(name, arg...)
}

Expand All @@ -103,8 +103,6 @@ type CLICommandBuilder struct {
port int
options connectionCommandOpts
uid utils.UID

exe execer
}

func NewCmdBuilder(tc *client.TeleportClient, profile *client.ProfileStatus,
Expand All @@ -126,6 +124,10 @@ func NewCmdBuilder(tc *client.TeleportClient, profile *client.ProfileStatus,
options.log = logrus.NewEntry(logrus.StandardLogger())
}

if options.exe == nil {
options.exe = &SystemExecer{}
}

return &CLICommandBuilder{
tc: tc,
profile: profile,
Expand All @@ -135,8 +137,6 @@ func NewCmdBuilder(tc *client.TeleportClient, profile *client.ProfileStatus,
options: options,
rootCluster: rootClusterName,
uid: utils.NewRealUID(),

exe: &systemExecer{},
}
}

Expand Down Expand Up @@ -198,17 +198,17 @@ func (c *CLICommandBuilder) GetConnectCommandNoAbsPath() (*exec.Cmd, error) {
}

func (c *CLICommandBuilder) getPostgresCommand() *exec.Cmd {
return c.exe.Command(postgresBin, c.getPostgresConnString())
return c.options.exe.Command(postgresBin, c.getPostgresConnString())
}

func (c *CLICommandBuilder) getCockroachCommand() *exec.Cmd {
// If cockroach CLI client is not available, fallback to psql.
if _, err := c.exe.LookPath(cockroachBin); err != nil {
if _, err := c.options.exe.LookPath(cockroachBin); err != nil {
c.options.log.Debugf("Couldn't find %q client in PATH, falling back to %q: %v.",
cockroachBin, postgresBin, err)
return c.getPostgresCommand()
}
return c.exe.Command(cockroachBin, "sql", "--url", c.getPostgresConnString())
return c.options.exe.Command(cockroachBin, "sql", "--url", c.getPostgresConnString())
}

// getPostgresConnString returns the connection string for postgres.
Expand Down Expand Up @@ -274,7 +274,7 @@ func (c *CLICommandBuilder) getMySQLOracleCommand() *exec.Cmd {
args := c.getMySQLCommonCmdOpts()

if c.options.noTLS {
return c.exe.Command(mysqlBin, args...)
return c.options.exe.Command(mysqlBin, args...)
}

// defaults-group-suffix must be first.
Expand All @@ -286,7 +286,7 @@ func (c *CLICommandBuilder) getMySQLOracleCommand() *exec.Cmd {
args = append(args, fmt.Sprintf("--ssl-mode=%s", mysql.MySQLSSLModeVerifyCA))
}

return c.exe.Command(mysqlBin, args...)
return c.options.exe.Command(mysqlBin, args...)
}

// getMySQLCommand returns mariadb command if the binary is on the path. Otherwise,
Expand All @@ -295,7 +295,7 @@ func (c *CLICommandBuilder) getMySQLCommand() (*exec.Cmd, error) {
// Check if mariadb client is available. Prefer it over mysql client even if connecting to MySQL server.
if c.isMariaDBBinAvailable() {
args := c.getMariaDBArgs()
return c.exe.Command(mariadbBin, args...), nil
return c.options.exe.Command(mariadbBin, args...), nil
}

// Check for mysql binary. In case the caller doesn't tolerate a missing CLI client, return with
Expand All @@ -313,7 +313,7 @@ func (c *CLICommandBuilder) getMySQLCommand() (*exec.Cmd, error) {
mySQLMariaDBFlavor, err := c.isMySQLBinMariaDBFlavor()
if mySQLMariaDBFlavor && err == nil {
args := c.getMariaDBArgs()
return c.exe.Command(mysqlBin, args...), nil
return c.options.exe.Command(mysqlBin, args...), nil
}

// Either we failed to check the flavor or binary comes from Oracle. Regardless return mysql/Oracle command.
Expand All @@ -322,27 +322,27 @@ func (c *CLICommandBuilder) getMySQLCommand() (*exec.Cmd, error) {

// isMariaDBBinAvailable returns true if "mariadb" binary is found in the system PATH.
func (c *CLICommandBuilder) isMariaDBBinAvailable() bool {
_, err := c.exe.LookPath(mariadbBin)
_, err := c.options.exe.LookPath(mariadbBin)
return err == nil
}

// isMySQLBinAvailable returns true if "mysql" binary is found in the system PATH.
func (c *CLICommandBuilder) isMySQLBinAvailable() bool {
_, err := c.exe.LookPath(mysqlBin)
_, err := c.options.exe.LookPath(mysqlBin)
return err == nil
}

// isMongoshBinAvailable returns true if "mongosh" binary is found in the system PATH.
func (c *CLICommandBuilder) isMongoshBinAvailable() bool {
_, err := c.exe.LookPath(mongoshBin)
_, err := c.options.exe.LookPath(mongoshBin)
return err == nil
}

// isMySQLBinMariaDBFlavor checks if mysql binary comes from Oracle or MariaDB.
// true is returned when binary comes from MariaDB, false when from Oracle.
func (c *CLICommandBuilder) isMySQLBinMariaDBFlavor() (bool, error) {
// Check if mysql comes from Oracle or MariaDB
mysqlVer, err := c.exe.RunCommand(mysqlBin, "--version")
mysqlVer, err := c.options.exe.RunCommand(mysqlBin, "--version")
if err != nil {
// Looks like incorrect mysql installation.
return false, trace.Wrap(err)
Expand Down Expand Up @@ -405,11 +405,11 @@ func (c *CLICommandBuilder) getMongoCommand() *exec.Cmd {

// use `mongosh` if available
if hasMongosh {
return c.exe.Command(mongoshBin, args...)
return c.options.exe.Command(mongoshBin, args...)
}

// fall back to `mongo` if `mongosh` isn't found
return c.exe.Command(mongoBin, args...)
return c.options.exe.Command(mongoBin, args...)
}

func (c *CLICommandBuilder) getMongoAddress() string {
Expand Down Expand Up @@ -467,7 +467,7 @@ func (c *CLICommandBuilder) getRedisCommand() *exec.Cmd {
args = append(args, []string{"-n", c.db.Database}...)
}

return c.exe.Command(redisBin, args...)
return c.options.exe.Command(redisBin, args...)
}

func (c *CLICommandBuilder) getSQLServerCommand() *exec.Cmd {
Expand All @@ -484,7 +484,7 @@ func (c *CLICommandBuilder) getSQLServerCommand() *exec.Cmd {
args = append(args, "-d", c.db.Database)
}

return c.exe.Command(mssqlBin, args...)
return c.options.exe.Command(mssqlBin, args...)
}

func (c *CLICommandBuilder) getSnowflakeCommand() *exec.Cmd {
Expand Down Expand Up @@ -513,6 +513,7 @@ type connectionCommandOpts struct {
printFormat bool
tolerateMissingCLIClient bool
log *logrus.Entry
exe Execer
}

// ConnectCommandFunc is a type for functions returned by the "With*" functions in this package.
Expand Down Expand Up @@ -586,6 +587,14 @@ func WithTolerateMissingCLIClient() ConnectCommandFunc {
}
}

// WithExecer allows to provide a different Execer than the default SystemExecer. Useful in contexts
// where there's a place that wants to use dbcmd with the ability to mock out SystemExecer in tests.
func WithExecer(exe Execer) ConnectCommandFunc {
return func(opts *connectionCommandOpts) {
opts.exe = exe
}
}

const (
// envVarMongoServerSelectionTimeoutMS is the environment variable that
// controls the server selection timeout used for MongoDB clients.
Expand Down
6 changes: 3 additions & 3 deletions lib/client/db/dbcmd/dbcmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,11 +494,11 @@ func TestCLICommandBuilderGetConnectCommand(t *testing.T) {

opts := append([]ConnectCommandFunc{
WithLocalProxy("localhost", 12345, ""),
WithExecer(tt.execer),
}, tt.opts...)

c := NewCmdBuilder(tc, profile, database, "root", opts...)
c.uid = utils.NewFakeUID()
c.exe = tt.execer
got, err := c.GetConnectCommand()
if tt.wantErr {
if err == nil {
Expand Down Expand Up @@ -541,11 +541,11 @@ func TestGetConnectCommandNoAbsPathConvertsAbsolutePathToRelative(t *testing.T)
opts := []ConnectCommandFunc{
WithLocalProxy("localhost", 12345, ""),
WithNoTLS(),
WithExecer(&fakeExec{commandPathBehavior: forceAbsolutePath}),
}

c := NewCmdBuilder(tc, profile, database, "root", opts...)
c.uid = utils.NewFakeUID()
c.exe = &fakeExec{commandPathBehavior: forceAbsolutePath}

got, err := c.GetConnectCommandNoAbsPath()
require.NoError(t, err)
Expand Down Expand Up @@ -580,11 +580,11 @@ func TestGetConnectCommandNoAbsPathIsNoopWhenGivenRelativePath(t *testing.T) {
opts := []ConnectCommandFunc{
WithLocalProxy("localhost", 12345, ""),
WithNoTLS(),
WithExecer(&fakeExec{commandPathBehavior: forceBasePath}),
}

c := NewCmdBuilder(tc, profile, database, "root", opts...)
c.uid = utils.NewFakeUID()
c.exe = &fakeExec{commandPathBehavior: forceBasePath}

got, err := c.GetConnectCommandNoAbsPath()
require.NoError(t, err)
Expand Down
20 changes: 16 additions & 4 deletions lib/teleterm/api/proto/v1/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,33 @@ service TerminalService {
rpc ListDatabases(ListDatabasesRequest) returns (ListDatabasesResponse);
// ListDatabaseUsers lists allowed users for the given database based on the role set.
rpc ListDatabaseUsers(ListDatabaseUsersRequest) returns (ListDatabaseUsersResponse);
// ListGateways lists gateways
rpc ListGateways(ListGatewaysRequest) returns (ListGatewaysResponse);
// ListServers lists servers
rpc ListServers(ListServersRequest) returns (ListServersResponse);
// ListKubes list kubes
rpc ListKubes(ListKubesRequest) returns (ListKubesResponse);
// ListApps list apps
rpc ListApps(ListAppsRequest) returns (ListAppsResponse);
// CreateGateway creates a gateway
rpc CreateGateway(CreateGatewayRequest) returns (Gateway);
// AddCluster adds a cluster to profile
rpc AddCluster(AddClusterRequest) returns (Cluster);
// RemoveCluster removes a cluster from profile
rpc RemoveCluster(RemoveClusterRequest) returns (EmptyResponse);

// ListGateways lists gateways
rpc ListGateways(ListGatewaysRequest) returns (ListGatewaysResponse);
// CreateGateway creates a gateway
rpc CreateGateway(CreateGatewayRequest) returns (Gateway);
// RemoveGateway removes a gateway
rpc RemoveGateway(RemoveGatewayRequest) returns (EmptyResponse);
// RestartGateway stops a gateway and starts a new with identical parameters, keeping the
// original URI. A temporary workaround until it's possible to refresh certs in a running
// database proxy.
rpc RestartGateway(RestartGatewayRequest) returns (EmptyResponse);
// SetGatewayTargetSubresourceName changes the TargetSubresourceName field of gateway.Gateway
// and returns the updated version of gateway.Gateway.
//
// In Connect this is used to update the db name of a db connection along with the CLI command.
rpc SetGatewayTargetSubresourceName(SetGatewayTargetSubresourceNameRequest) returns (Gateway);

// GetAuthSettings returns cluster auth settigns
rpc GetAuthSettings(GetAuthSettingsRequest) returns (AuthSettings);
// GetCluster returns a cluster
Expand Down Expand Up @@ -142,6 +149,11 @@ message RemoveGatewayRequest { string gateway_uri = 1; }

message RestartGatewayRequest { string gateway_uri = 1; }

message SetGatewayTargetSubresourceNameRequest {
string gateway_uri = 1;
string target_subresource_name = 2;
}

message ListServersRequest { string cluster_uri = 1; }

message ListServersResponse { repeated Server servers = 1; }
Expand Down
Loading

0 comments on commit a7760a4

Please sign in to comment.