-
Notifications
You must be signed in to change notification settings - Fork 294
feat: add MySQL command support #5749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 7 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
e4ff752
feat: add MySQL command support
enbiyagoral 8f7765b
Fix MySQL connection according to review feedback
enbiyagoral 7683a9a
Update internal/cmd/commands/connect/mysql.go
enbiyagoral 8ebca68
Update internal/cmd/commands/connect/mysql.go
enbiyagoral 913d814
Update internal/cmd/commands/connect/mysql.go
enbiyagoral b0794f4
feat: add MySQL support to boundary connect
enbiyagoral 5bd4d10
docs: add MySQL to navigation menu
enbiyagoral abeae9f
Update testing/internal/e2e/tests/base_plus/target_tcp_connect_mysql_…
enbiyagoral b75f195
test: add containerized MySQL connection test for target functionality
enbiyagoral 13af50e
Remove MySQL environment variables from e2e test config
enbiyagoral 64945e4
test: move MySQL e2e test from base_plus to base folder
enbiyagoral 4e980fd
Fix MySQL test to support both MySQL and MariaDB clients
enbiyagoral 7f7110e
Fix MySQL test to support both MySQL and MariaDB clients
enbiyagoral File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package connect | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"github.com/hashicorp/boundary/api/proxy" | ||
"github.com/hashicorp/boundary/internal/cmd/base" | ||
"github.com/posener/complete" | ||
) | ||
|
||
const ( | ||
mysqlSynopsis = "Authorize a session against a target and invoke a MySQL client to connect" | ||
) | ||
|
||
func mysqlOptions(c *Command, set *base.FlagSets) { | ||
f := set.NewFlagSet("MySQL Options") | ||
|
||
f.StringVar(&base.StringVar{ | ||
Name: "style", | ||
Target: &c.flagMySQLStyle, | ||
EnvVar: "BOUNDARY_CONNECT_MYSQL_STYLE", | ||
Completion: complete.PredictSet("mysql"), | ||
Default: "mysql", | ||
Usage: `Specifies how the CLI will attempt to invoke a MySQL client. This will also set a suitable default for -exec if a value was not specified. Currently-understood values are "mysql".`, | ||
}) | ||
|
||
f.StringVar(&base.StringVar{ | ||
Name: "username", | ||
Target: &c.flagUsername, | ||
EnvVar: "BOUNDARY_CONNECT_USERNAME", | ||
Completion: complete.PredictNothing, | ||
Usage: `Specifies the username to pass through to the client. May be overridden by credentials sourced from a credential store.`, | ||
}) | ||
|
||
f.StringVar(&base.StringVar{ | ||
Name: "dbname", | ||
Target: &c.flagDbname, | ||
EnvVar: "BOUNDARY_CONNECT_DBNAME", | ||
Completion: complete.PredictNothing, | ||
Usage: `Specifies the database name to pass through to the client.`, | ||
}) | ||
} | ||
|
||
type mysqlFlags struct { | ||
flagMySQLStyle string | ||
} | ||
|
||
func (m *mysqlFlags) defaultExec() string { | ||
return strings.ToLower(m.flagMySQLStyle) | ||
} | ||
|
||
func (m *mysqlFlags) buildArgs(c *Command, port, ip, _ string, creds proxy.Credentials) (args, envs []string, retCreds proxy.Credentials, retErr error) { | ||
var username, password string | ||
|
||
retCreds = creds | ||
if len(retCreds.UsernamePassword) > 0 { | ||
// Mark credential as consumed so it is not printed to user | ||
retCreds.UsernamePassword[0].Consumed = true | ||
|
||
// For now just grab the first username password credential brokered | ||
username = retCreds.UsernamePassword[0].Username | ||
password = retCreds.UsernamePassword[0].Password | ||
} | ||
|
||
switch m.flagMySQLStyle { | ||
case "mysql": | ||
// Handle password first - defaults-file must be the first argument | ||
if password != "" { | ||
passfile, err := os.CreateTemp("", "*") | ||
if err != nil { | ||
return nil, nil, proxy.Credentials{}, fmt.Errorf("Error saving MySQL password to tmp file: %w", err) | ||
} | ||
c.cleanupFuncs = append(c.cleanupFuncs, func() error { | ||
if err := os.Remove(passfile.Name()); err != nil { | ||
return fmt.Errorf("Error removing temporary password file; consider removing %s manually: %w", passfile.Name(), err) | ||
} | ||
return nil | ||
}) | ||
_, err = passfile.Write([]byte("[client]\npassword=" + password)) | ||
if err != nil { | ||
_ = passfile.Close() | ||
return nil, nil, proxy.Credentials{}, fmt.Errorf("Error writing password file to %s: %w", passfile.Name(), err) | ||
} | ||
if err := passfile.Close(); err != nil { | ||
return nil, nil, proxy.Credentials{}, fmt.Errorf("Error closing password file after writing to %s: %w", passfile.Name(), err) | ||
} | ||
// --defaults-file must be the first argument | ||
args = append([]string{"--defaults-file=" + passfile.Name()}, args...) | ||
|
||
if c.flagDbname == "" { | ||
c.UI.Warn("Credentials are being brokered but no -dbname parameter provided. mysql may misinterpret another parameter as the database name.") | ||
} | ||
} else { | ||
// If no password provided, add -p to prompt for password | ||
args = append(args, "-p") | ||
} | ||
|
||
if port != "" { | ||
args = append(args, "-P", port) | ||
} | ||
args = append(args, "-h", ip) | ||
|
||
if c.flagDbname != "" { | ||
args = append(args, "-D", c.flagDbname) | ||
} | ||
|
||
switch { | ||
case username != "": | ||
args = append(args, "-u", username) | ||
case c.flagUsername != "": | ||
args = append(args, "-u", c.flagUsername) | ||
} | ||
} | ||
return | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
testing/internal/e2e/tests/base_plus/target_tcp_connect_mysql_test.go
enbiyagoral marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package base_plus_test | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"io" | ||
"os/exec" | ||
"testing" | ||
|
||
"github.com/creack/pty" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/hashicorp/boundary/internal/target" | ||
"github.com/hashicorp/boundary/testing/internal/e2e" | ||
"github.com/hashicorp/boundary/testing/internal/e2e/boundary" | ||
enbiyagoral marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
// TestCliTcpTargetConnectMysql uses the boundary cli to connect to a | ||
// target using `connect mysql` | ||
func TestCliTcpTargetConnectMysql(t *testing.T) { | ||
enbiyagoral marked this conversation as resolved.
Show resolved
Hide resolved
|
||
e2e.MaybeSkipTest(t) | ||
c, err := loadTestConfig() | ||
require.NoError(t, err) | ||
|
||
ctx := context.Background() | ||
boundary.AuthenticateAdminCli(t, ctx) | ||
orgId, err := boundary.CreateOrgCli(t, ctx) | ||
require.NoError(t, err) | ||
t.Cleanup(func() { | ||
ctx := context.Background() | ||
boundary.AuthenticateAdminCli(t, ctx) | ||
output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("scopes", "delete", "-id", orgId)) | ||
require.NoError(t, output.Err, string(output.Stderr)) | ||
}) | ||
projectId, err := boundary.CreateProjectCli(t, ctx, orgId) | ||
require.NoError(t, err) | ||
targetId, err := boundary.CreateTargetCli( | ||
t, | ||
ctx, | ||
projectId, | ||
c.TargetPort, | ||
target.WithAddress(c.TargetAddress), | ||
) | ||
require.NoError(t, err) | ||
storeId, err := boundary.CreateCredentialStoreStaticCli(t, ctx, projectId) | ||
require.NoError(t, err) | ||
credentialId, err := boundary.CreateStaticCredentialPasswordCli( | ||
t, | ||
ctx, | ||
storeId, | ||
c.MysqlUser, | ||
c.MysqlPassword, | ||
) | ||
require.NoError(t, err) | ||
err = boundary.AddBrokeredCredentialSourceToTargetCli(t, ctx, targetId, credentialId) | ||
require.NoError(t, err) | ||
|
||
var cmd *exec.Cmd | ||
cmd = exec.CommandContext(ctx, | ||
"boundary", | ||
"connect", "mysql", | ||
"-target-id", targetId, | ||
"-dbname", c.MysqlDbName, | ||
) | ||
f, err := pty.Start(cmd) | ||
require.NoError(t, err) | ||
t.Cleanup(func() { | ||
err := f.Close() | ||
require.NoError(t, err) | ||
}) | ||
|
||
_, err = f.Write([]byte("SHOW TABLES;\n")) // list all tables | ||
require.NoError(t, err) | ||
_, err = f.Write([]byte("SELECT DATABASE();\n")) // show current database | ||
require.NoError(t, err) | ||
_, err = f.Write([]byte("EXIT;\n")) // exit mysql session | ||
require.NoError(t, err) | ||
_, err = f.Write([]byte{4}) // EOT (End of Transmission - marks end of file stream) | ||
require.NoError(t, err) | ||
|
||
var buf bytes.Buffer | ||
_, _ = io.Copy(&buf, f) | ||
require.Contains(t, buf.String(), "mysql>", "Session did not return expected MySQL prompt") | ||
require.Contains(t, buf.String(), c.MysqlDbName, "Session did not return expected output") | ||
t.Log("Successfully connected to MySQL target") | ||
} | ||
enbiyagoral marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.