From d68bc44e8367749aa433f9fc5014db6b72693d3b Mon Sep 17 00:00:00 2001 From: Dirkjan Bussink Date: Wed, 9 Oct 2024 13:53:36 +0200 Subject: [PATCH] Switch shell / connect to use caching_sha2_password by default This switches up both connect and shell to use `caching_sha2_password` by default. `connect` gets an option to also select the legacy `mysql_native_password` option. `shell` selects based on the `mysql --version` returns. For MySQL 8.x and later, it now uses `caching_sha2_password`. Signed-off-by: Dirkjan Bussink --- Dockerfile | 2 +- Makefile | 2 +- docker-compose.yml | 2 +- docker/Dockerfile.goreleaser | 2 +- go.mod | 8 +++--- go.sum | 8 +++--- internal/cmd/connect/connect.go | 19 ++++++++++++- internal/cmd/database/dump.go | 7 ++++- internal/cmd/database/restore.go | 4 ++- internal/cmd/shell/shell.go | 4 +-- internal/cmdutil/cmdutil.go | 47 ++++++++++++++++++++++++++------ internal/mock/keyspace.go | 24 ++++++++++++++++ 12 files changed, 104 insertions(+), 25 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3d92ac98..6faff363 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23.1 as build +FROM golang:1.23.2 as build WORKDIR /app COPY . . diff --git a/Makefile b/Makefile index fed30c9d..3faaa3dd 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ endif REPO=planetscale NAME=pscale BUILD_PKG=github.com/planetscale/cli/cmd/pscale -GORELEASE_CROSS_VERSION ?= v1.23.1 +GORELEASE_CROSS_VERSION ?= v1.23.2 SYFT_VERSION ?= 1.9.0 .PHONY: all diff --git a/docker-compose.yml b/docker-compose.yml index c526cc4f..d52dd0f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '2' services: app: - image: golang:1.23.1 + image: golang:1.23.2 volumes: - .:/work working_dir: /work diff --git a/docker/Dockerfile.goreleaser b/docker/Dockerfile.goreleaser index 5ac38fe8..21c83d62 100644 --- a/docker/Dockerfile.goreleaser +++ b/docker/Dockerfile.goreleaser @@ -1,4 +1,4 @@ -ARG GORELEASE_CROSS_VERSION=v1.23.1 +ARG GORELEASE_CROSS_VERSION=v1.23.2 FROM ghcr.io/goreleaser/goreleaser-cross:${GORELEASE_CROSS_VERSION} RUN apt-get update && apt-get install -y openssh-client diff --git a/go.mod b/go.mod index f3c92742..3dfdb532 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/planetscale/cli -go 1.23.1 +go 1.23.2 require ( github.com/99designs/keyring v1.2.2 @@ -23,9 +23,9 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pkg/errors v0.9.1 - github.com/planetscale/planetscale-go v0.108.0 + github.com/planetscale/planetscale-go v0.110.0 github.com/planetscale/psdb v0.0.0-20240109164348-6848e728f6e7 - github.com/planetscale/psdbproxy v0.0.0-20240927190836-61feaf3c8bdb + github.com/planetscale/psdbproxy v0.0.0-20241009145102-7fdfa92ae3ca github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 @@ -39,6 +39,7 @@ require ( golang.org/x/sys v0.26.0 golang.org/x/text v0.19.0 gopkg.in/yaml.v2 v2.4.0 + vitess.io/vitess v0.10.3-0.20240927074858-3e5371377b43 ) require ( @@ -91,7 +92,6 @@ require ( gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - vitess.io/vitess v0.10.3-0.20240927074858-3e5371377b43 // indirect ) replace github.com/golang/glog => github.com/planetscale/noglog v0.2.1-0.20210421230640-bea75fcd2e8e diff --git a/go.sum b/go.sum index c2b85298..02231147 100644 --- a/go.sum +++ b/go.sum @@ -114,12 +114,12 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/noglog v0.2.1-0.20210421230640-bea75fcd2e8e h1:MZ8D+Z3m2vvqGZLvoQfpaGg/j1fNDr4j03s3PRz4rVY= github.com/planetscale/noglog v0.2.1-0.20210421230640-bea75fcd2e8e/go.mod h1:hwAsSPQdvPa3WcfKfzTXxtEq/HlqwLjQasfO6QbGo4Q= -github.com/planetscale/planetscale-go v0.108.0 h1:KwLhKsntOO2yokjYxfBtUwHN9/n+c6PnYPbsibZ8jmE= -github.com/planetscale/planetscale-go v0.108.0/go.mod h1:2s0/iqbBEBEL5k+3t0+eNztAU3bYxUynLQs4yLrKNDI= +github.com/planetscale/planetscale-go v0.110.0 h1:iaS/4pbP/efBmLtzr+cH8gjhLy1OjAovOgSqL+Gbobk= +github.com/planetscale/planetscale-go v0.110.0/go.mod h1:ldGffCLckkR8fjGDjDFs4WcjlDr8uqg2qRUZhRYBEMI= github.com/planetscale/psdb v0.0.0-20240109164348-6848e728f6e7 h1:dxdoFKWVDlV1gq8UQC8NWCofLjCEjEHw47gfeojgs28= github.com/planetscale/psdb v0.0.0-20240109164348-6848e728f6e7/go.mod h1:WZmi4gw3rOK+ryd1inGxgfKwoFV04O7xBCqzWzv0/0U= -github.com/planetscale/psdbproxy v0.0.0-20240927190836-61feaf3c8bdb h1:eJqW2GfcbKOkUCNHBmHOIWrX4sIaYUJ9xljnlNYtVPQ= -github.com/planetscale/psdbproxy v0.0.0-20240927190836-61feaf3c8bdb/go.mod h1:qxC4twwQwRjHb9lt2DB/Ny0CuhJs1rrxskPbBLDljgo= +github.com/planetscale/psdbproxy v0.0.0-20241009145102-7fdfa92ae3ca h1:E5E1yyZ03FzA/6styZr5C2L3DdqhnkZsKSJy5q4JnuU= +github.com/planetscale/psdbproxy v0.0.0-20241009145102-7fdfa92ae3ca/go.mod h1:qxC4twwQwRjHb9lt2DB/Ny0CuhJs1rrxskPbBLDljgo= github.com/planetscale/vitess-types v0.0.0-20231211191709-770e14433716 h1:FD6vnHCirVPeyn+E6Z0HyXoAqXzjfTcRpgss/63im6w= github.com/planetscale/vitess-types v0.0.0-20231211191709-770e14433716/go.mod h1:8KWsIjuUBs+xlbfn9wBCxicFZqV8BCPIFoqlOSs+60I= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= diff --git a/internal/cmd/connect/connect.go b/internal/cmd/connect/connect.go index b2400200..a6bf269d 100644 --- a/internal/cmd/connect/connect.go +++ b/internal/cmd/connect/connect.go @@ -21,6 +21,8 @@ import ( "github.com/mattn/go-shellwords" "github.com/spf13/cobra" + + "vitess.io/vitess/go/mysql" ) func ConnectCmd(ch *cmdutil.Helper) *cobra.Command { @@ -34,6 +36,7 @@ func ConnectCmd(ch *cmdutil.Helper) *cobra.Command { role string noRandom bool replica bool + authMethod string } cmd := &cobra.Command{ @@ -95,6 +98,18 @@ argument: role = cmdutil.ReaderRole } + authMethod := mysql.CachingSha2Password + if flags.authMethod != "" { + switch flags.authMethod { + case "caching_sha2_password": + authMethod = mysql.CachingSha2Password + case "mysql_native_password": + authMethod = mysql.MysqlNativePassword + default: + return fmt.Errorf("unsupported auth method: %s", flags.authMethod) + } + } + // check whether database and branch exist dbBranch, err := client.DatabaseBranches.Get(ctx, &planetscale.GetDatabaseBranchRequest{ Organization: ch.Config.Organization, @@ -156,7 +171,7 @@ argument: errCh := make(chan error, 1) go func() { - errCh <- proxy.Serve(l) + errCh <- proxy.Serve(l, authMethod) }() go func() { @@ -215,6 +230,8 @@ argument: cmd.PersistentFlags().StringVar(&flags.role, "role", "", "Role defines the access level, allowed values are: reader, writer, readwriter, admin. Defaults to 'reader' for replica passwords, otherwise defaults to 'admin'.") cmd.Flags().BoolVar(&flags.replica, "replica", false, "When enabled, the password will route all reads to the branch's primary replicas and all read-only regions.") + cmd.PersistentFlags().StringVar(&flags.authMethod, "mysql-auth-method", + "", "MySQL auth method defines the authentication method returned for the MySQL protocol. Allowed values are: caching_sha2_password, mysql_native_password. Defaults to 'caching_sha2_password'.") return cmd } diff --git a/internal/cmd/database/dump.go b/internal/cmd/database/dump.go index add08868..c5d3f789 100644 --- a/internal/cmd/database/dump.go +++ b/internal/cmd/database/dump.go @@ -21,6 +21,8 @@ import ( _ "github.com/go-sql-driver/mysql" "github.com/spf13/cobra" + + "vitess.io/vitess/go/mysql" ) type dumpFlags struct { @@ -173,7 +175,10 @@ func dump(ch *cmdutil.Helper, cmd *cobra.Command, flags *dumpFlags, args []strin defer l.Close() go func() { - if err := proxy.Serve(l); err != nil { + // We have to use mysql.MysqlNativePassword here because we still end + // up using https://github.com/xelabs/go-mysqlstack which is unmaintained + // and doesn't support caching_sha2_password. + if err := proxy.Serve(l, mysql.MysqlNativePassword); err != nil { ch.Printer.Println("proxy error: ", err) } }() diff --git a/internal/cmd/database/restore.go b/internal/cmd/database/restore.go index c35410f0..ee44d60e 100644 --- a/internal/cmd/database/restore.go +++ b/internal/cmd/database/restore.go @@ -15,6 +15,8 @@ import ( ps "github.com/planetscale/planetscale-go/planetscale" "github.com/spf13/cobra" + + "vitess.io/vitess/go/mysql" ) type restoreFlags struct { @@ -127,7 +129,7 @@ func restore(ch *cmdutil.Helper, cmd *cobra.Command, flags *restoreFlags, args [ defer l.Close() go func() { - if err := proxy.Serve(l); err != nil { + if err := proxy.Serve(l, mysql.MysqlNativePassword); err != nil { ch.Printer.Println("proxy error: ", err) } }() diff --git a/internal/cmd/shell/shell.go b/internal/cmd/shell/shell.go index 1c382169..baec28da 100644 --- a/internal/cmd/shell/shell.go +++ b/internal/cmd/shell/shell.go @@ -63,7 +63,7 @@ second argument: runForeground = false } - mysqlPath, err := cmdutil.MySQLClientPath() + mysqlPath, authMethod, err := cmdutil.MySQLClientPath() if err != nil { return err } @@ -210,7 +210,7 @@ second argument: errCh := make(chan error, 1) go func() { - errCh <- proxy.Serve(l) + errCh <- proxy.Serve(l, authMethod) }() go func() { diff --git a/internal/cmdutil/cmdutil.go b/internal/cmdutil/cmdutil.go index 3e0e16d0..3052fc24 100644 --- a/internal/cmdutil/cmdutil.go +++ b/internal/cmdutil/cmdutil.go @@ -5,7 +5,9 @@ import ( "fmt" "os" "path/filepath" + "regexp" "runtime" + "strconv" "strings" "time" @@ -18,6 +20,8 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" exec "golang.org/x/sys/execabs" + + "vitess.io/vitess/go/mysql" ) const WarnAuthMessage = "not authenticated yet. Please run 'pscale auth login'" @@ -166,10 +170,12 @@ func HasHomebrew() bool { return err == nil } +var versionRegex = regexp.MustCompile(`Ver ([0-9]+)\.([0-9]+)\.([0-9]+)`) + // MySQLClientPath checks whether the 'mysql' client exists and returns the // path to the binary. The returned error contains instructions to install the // client. -func MySQLClientPath() (string, error) { +func MySQLClientPath() (string, mysql.AuthMethodDescription, error) { // 'brew install mysql-client' installs the client into an unusual path // https://docs.brew.sh/FAQ#why-should-i-install-homebrew-in-the-default-location var homebrewPrefix string @@ -183,8 +189,11 @@ func MySQLClientPath() (string, error) { homebrewPrefix = "/home/linuxbrew/.linuxbrew" } + authMethod := mysql.CachingSha2Password oldpath := os.Getenv("PATH") - newpath := homebrewPrefix + "/opt/mysql-client/bin/" + string(os.PathListSeparator) + oldpath + newpath := homebrewPrefix + "/opt/mysql-client/bin/" + + homebrewPrefix + "/opt/mysql/bin/" + + string(os.PathListSeparator) + oldpath defer func() { if err := os.Setenv("PATH", oldpath); err != nil { fmt.Println("failed to restore PATH", err) @@ -192,21 +201,43 @@ func MySQLClientPath() (string, error) { }() if err := os.Setenv("PATH", newpath); err != nil { - return "", err + return "", authMethod, err } path, err := exec.LookPath("mysql") - if err == nil { - return path, nil + if err != nil { + return installInstructions("couldn't find the 'mysql' command-line tool required to run this command.") + } + + cmd := exec.Command("mysql", "--version") + out, err := cmd.Output() + if err != nil { + return "", authMethod, fmt.Errorf("failed to run 'mysql --version': %w", err) + } + + v := versionRegex.FindStringSubmatch(string(out)) + if len(v) != 4 { + return "", authMethod, fmt.Errorf("could not parse server version from: %s", string(out)) + } + major, err := strconv.Atoi(v[1]) + if err != nil { + return "", authMethod, fmt.Errorf("could not parse server version from: %s", string(out)) } - msg := "couldn't find the 'mysql' command-line tool required to run this command." + if major < 8 { + authMethod = mysql.MysqlNativePassword + } + + return path, authMethod, nil +} + +func installInstructions(msg string) (string, mysql.AuthMethodDescription, error) { installURL := "https://planetscale.com/docs/reference/planetscale-environment-setup" switch runtime.GOOS { case "darwin": if HasHomebrew() { - return "", fmt.Errorf("%s\nTo install, run: brew install mysql-client", msg) + return "", mysql.CachingSha2Password, fmt.Errorf("%s\nTo install, run: brew install mysql-client@8.4", msg) } installURL = "https://planetscale.com/docs/reference/planetscale-environment-setup#macos-instructions" @@ -216,7 +247,7 @@ func MySQLClientPath() (string, error) { installURL = "https://planetscale.com/docs/reference/planetscale-environment-setup#windows-instructions" } - return "", fmt.Errorf("%s\nTo install, follow the instructions: %s", msg, installURL) + return "", mysql.CachingSha2Password, fmt.Errorf("%s\nTo install, follow the instructions: %s", msg, installURL) } func ParseSSLMode(sslMode string) ps.ExternalDataSourceSSLVerificationMode { diff --git a/internal/mock/keyspace.go b/internal/mock/keyspace.go index 5994e31f..e3bde5c1 100644 --- a/internal/mock/keyspace.go +++ b/internal/mock/keyspace.go @@ -21,6 +21,15 @@ type BranchKeyspacesService struct { CreateFn func(context.Context, *ps.CreateBranchKeyspaceRequest) (*ps.Keyspace, error) CreateFnInvoked bool + + ResizeFn func(context.Context, *ps.ResizeKeyspaceRequest) (*ps.KeyspaceResizeRequest, error) + ResizeInvoked bool + + CancelResizeFn func(context.Context, *ps.CancelKeyspaceResizeRequest) error + CancelResizeInvoked bool + + ResizeStatusFn func(context.Context, *ps.KeyspaceResizeStatusRequest) (*ps.KeyspaceResizeRequest, error) + ResizeStatusInvoked bool } func (s *BranchKeyspacesService) List(ctx context.Context, req *ps.ListBranchKeyspacesRequest) ([]*ps.Keyspace, error) { @@ -48,3 +57,18 @@ func (s *BranchKeyspacesService) Create(ctx context.Context, req *ps.CreateBranc s.CreateFnInvoked = true return s.CreateFn(ctx, req) } + +func (s *BranchKeyspacesService) Resize(ctx context.Context, req *ps.ResizeKeyspaceRequest) (*ps.KeyspaceResizeRequest, error) { + s.ResizeInvoked = true + return s.ResizeFn(ctx, req) +} + +func (s *BranchKeyspacesService) CancelResize(ctx context.Context, req *ps.CancelKeyspaceResizeRequest) error { + s.CancelResizeInvoked = true + return s.CancelResizeFn(ctx, req) +} + +func (s *BranchKeyspacesService) ResizeStatus(ctx context.Context, req *ps.KeyspaceResizeStatusRequest) (*ps.KeyspaceResizeRequest, error) { + s.ResizeStatusInvoked = true + return s.ResizeStatusFn(ctx, req) +}