diff --git a/go/cmd/vtctlclient/main.go b/go/cmd/vtctlclient/main.go index e7311a5cffa..bd8871851f7 100644 --- a/go/cmd/vtctlclient/main.go +++ b/go/cmd/vtctlclient/main.go @@ -67,6 +67,10 @@ func main() { logutil.LogEvent(logger, e) }) if err != nil { + if strings.Contains(err.Error(), "flag: help requested") { + return + } + errStr := strings.Replace(err.Error(), "remote error: ", "", -1) fmt.Printf("%s Error: %s\n", flag.Arg(0), errStr) log.Error(err) diff --git a/go/cmd/vtctldclient/internal/command/legacy_shim.go b/go/cmd/vtctldclient/internal/command/legacy_shim.go new file mode 100644 index 00000000000..f4036fd26ae --- /dev/null +++ b/go/cmd/vtctldclient/internal/command/legacy_shim.go @@ -0,0 +1,104 @@ +/* +Copyright 2021 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package command + +import ( + "context" + "flag" + "fmt" + "strings" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/cmd/vtctldclient/cli" + "vitess.io/vitess/go/vt/log" + "vitess.io/vitess/go/vt/logutil" + "vitess.io/vitess/go/vt/vtctl/vtctlclient" + + logutilpb "vitess.io/vitess/go/vt/proto/logutil" +) + +var ( + // LegacyVtctlCommand provides a shim to make legacy ExecuteVtctlCommand + // RPCs. This allows users to use a single binary to make RPCs against both + // the new and old vtctld gRPC APIs. + LegacyVtctlCommand = &cobra.Command{ + Use: "LegacyVtctlCommand -- [flags ...] [args ...]", + Short: "Invoke a legacy vtctlclient command. Flag parsing is best effort.", + Args: cobra.ArbitraryArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cli.FinishedParsing(cmd) + return runLegacyCommand(args) + }, + Long: strings.TrimSpace(` +LegacyVtctlCommand uses the legacy vtctl grpc client to make an ExecuteVtctlCommand +rpc to a vtctld. + +This command exists to support a smooth transition of any scripts that relied on +vtctlclient during the migration to the new vtctldclient, and will be removed, +following the Vitess project's standard deprecation cycle, once all commands +have been migrated to the new VtctldServer api. + +To see the list of available legacy commands, run "LegacyVtctlCommand -- help". +Note that, as with the old client, this requires a running server, as the flag +parsing and help/usage text generation, is done server-side. + +Also note that, in order to defer that flag parsing to the server side, you must +use the double-dash ("--") after the LegacyVtctlCommand subcommand string, or +the client-side flag parsing library we are using will attempt to parse those +flags (and fail). +`), + Example: strings.TrimSpace(` +LegacyVtctlCommand help # displays this help message +LegacyVtctlCommand -- help # displays help for supported legacy vtctl commands + +# When using legacy command that take arguments, a double dash must be used +# before the first flag argument, like in the first example. The double dash may +# be used, however, at any point after the "LegacyVtctlCommand" string, as in +# the second example. +LegacyVtctlCommand AddCellInfo -- -server_address "localhost:1234" -root "/vitess/cell1" +LegacyVtctlCommand -- AddCellInfo -server_address "localhost:5678" -root "/vitess/cell1"`), + } +) + +func runLegacyCommand(args []string) error { + // Duplicated (mostly) from go/cmd/vtctlclient/main.go. + logger := logutil.NewConsoleLogger() + + ctx, cancel := context.WithTimeout(context.Background(), actionTimeout) + defer cancel() + + err := vtctlclient.RunCommandAndWait(ctx, server, args, func(e *logutilpb.Event) { + logutil.LogEvent(logger, e) + }) + if err != nil { + if strings.Contains(err.Error(), "flag: help requested") { + // Help is caught by SetHelpFunc, so we don't want to indicate this as an error. + return nil + } + + errStr := strings.Replace(err.Error(), "remote error: ", "", -1) + fmt.Printf("%s Error: %s\n", flag.Arg(0), errStr) + log.Error(err) + } + + return err +} + +func init() { + Root.AddCommand(LegacyVtctlCommand) +} diff --git a/go/cmd/vtctldclient/internal/command/root.go b/go/cmd/vtctldclient/internal/command/root.go index 7243c8836e5..8335710f449 100644 --- a/go/cmd/vtctldclient/internal/command/root.go +++ b/go/cmd/vtctldclient/internal/command/root.go @@ -25,7 +25,6 @@ import ( "github.com/spf13/cobra" "vitess.io/vitess/go/trace" - "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/vt/vtctl/vtctldclient" ) @@ -44,9 +43,7 @@ var ( // command context for every command. PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { traceCloser = trace.StartTracing("vtctldclient") - if server == "" { - err = errors.New("please specify -server to specify the vtctld server to connect to") - log.Error(err) + if err := ensureServerArg(); err != nil { return err } @@ -75,6 +72,17 @@ var ( } ) +var errNoServer = errors.New("please specify -server to specify the vtctld server to connect to") + +// ensureServerArg validates that --server was passed to the CLI. +func ensureServerArg() error { + if server == "" { + return errNoServer + } + + return nil +} + func init() { Root.PersistentFlags().StringVar(&server, "server", "", "server to use for connection") Root.PersistentFlags().DurationVar(&actionTimeout, "action_timeout", time.Hour, "timeout for the total command") diff --git a/go/cmd/vtctldclient/plugin_grpcvtctlclient.go b/go/cmd/vtctldclient/plugin_grpcvtctlclient.go new file mode 100644 index 00000000000..48c631a8baa --- /dev/null +++ b/go/cmd/vtctldclient/plugin_grpcvtctlclient.go @@ -0,0 +1,23 @@ +/* +Copyright 2021 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +// Imports and registers the gRPC vtctl client. + +import ( + _ "vitess.io/vitess/go/vt/vtctl/grpcvtctlclient" +) diff --git a/go/vt/vtctl/grpcclientcommon/dial_option.go b/go/vt/vtctl/grpcclientcommon/dial_option.go new file mode 100644 index 00000000000..7a69bcf9638 --- /dev/null +++ b/go/vt/vtctl/grpcclientcommon/dial_option.go @@ -0,0 +1,41 @@ +/* +Copyright 2021 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package grpcclientcommon defines the flags shared by both grpcvtctlclient and +// grpcvtctldclient. +package grpcclientcommon + +import ( + "flag" + + "google.golang.org/grpc" + + "vitess.io/vitess/go/vt/grpcclient" +) + +var ( + cert = flag.String("vtctld_grpc_cert", "", "the cert to use to connect") + key = flag.String("vtctld_grpc_key", "", "the key to use to connect") + ca = flag.String("vtctld_grpc_ca", "", "the server ca to use to validate servers when connecting") + name = flag.String("vtctld_grpc_server_name", "", "the server name to use to validate server certificate") +) + +// SecureDialOption returns a grpc.DialOption configured to use TLS (or +// insecure if no flags were set) based on the vtctld_grpc_* flags declared by +// this package. +func SecureDialOption() (grpc.DialOption, error) { + return grpcclient.SecureDialOption(*cert, *key, *ca, *name) +} diff --git a/go/vt/vtctl/grpcvtctlclient/client.go b/go/vt/vtctl/grpcvtctlclient/client.go index 5a61d7dc793..f0fe94ca330 100644 --- a/go/vt/vtctl/grpcvtctlclient/client.go +++ b/go/vt/vtctl/grpcvtctlclient/client.go @@ -18,7 +18,6 @@ limitations under the License. package grpcvtctlclient import ( - "flag" "time" "context" @@ -27,6 +26,7 @@ import ( "vitess.io/vitess/go/vt/grpcclient" "vitess.io/vitess/go/vt/logutil" + "vitess.io/vitess/go/vt/vtctl/grpcclientcommon" "vitess.io/vitess/go/vt/vtctl/vtctlclient" logutilpb "vitess.io/vitess/go/vt/proto/logutil" @@ -34,20 +34,13 @@ import ( vtctlservicepb "vitess.io/vitess/go/vt/proto/vtctlservice" ) -var ( - cert = flag.String("vtctld_grpc_cert", "", "the cert to use to connect") - key = flag.String("vtctld_grpc_key", "", "the key to use to connect") - ca = flag.String("vtctld_grpc_ca", "", "the server ca to use to validate servers when connecting") - name = flag.String("vtctld_grpc_server_name", "", "the server name to use to validate server certificate") -) - type gRPCVtctlClient struct { cc *grpc.ClientConn c vtctlservicepb.VtctlClient } func gRPCVtctlClientFactory(addr string) (vtctlclient.VtctlClient, error) { - opt, err := grpcclient.SecureDialOption(*cert, *key, *ca, *name) + opt, err := grpcclientcommon.SecureDialOption() if err != nil { return nil, err } diff --git a/go/vt/vtctl/grpcvtctldclient/client.go b/go/vt/vtctl/grpcvtctldclient/client.go index a0f5a14ef77..45e19ff88e8 100644 --- a/go/vt/vtctl/grpcvtctldclient/client.go +++ b/go/vt/vtctl/grpcvtctldclient/client.go @@ -19,11 +19,10 @@ limitations under the License. package grpcvtctldclient import ( - "flag" - "google.golang.org/grpc" "vitess.io/vitess/go/vt/grpcclient" + "vitess.io/vitess/go/vt/vtctl/grpcclientcommon" "vitess.io/vitess/go/vt/vtctl/vtctldclient" vtctlservicepb "vitess.io/vitess/go/vt/proto/vtctlservice" @@ -31,22 +30,6 @@ import ( const connClosedMsg = "grpc: the client connection is closed" -// (TODO:@amason) - These flags match exactly the flags used in grpcvtctlclient. -// If a program attempts to import both of these packages, it will panic during -// startup due to the duplicate flags. -// -// For everything else I've been doing a sed s/vtctl/vtctld, but I cannot do -// that here, since these flags are already "vtctld_*". My other options are to -// name them "vtctld2_*" or to omit them completely. -// -// Not to pitch project ideas in comments, but a nice project to solve -var ( - cert = flag.String("vtctld_grpc_cert", "", "the cert to use to connect") - key = flag.String("vtctld_grpc_key", "", "the key to use to connect") - ca = flag.String("vtctld_grpc_ca", "", "the server ca to use to validate servers when connecting") - name = flag.String("vtctld_grpc_server_name", "", "the server name to use to validate server certificate") -) - type gRPCVtctldClient struct { cc *grpc.ClientConn c vtctlservicepb.VtctldClient @@ -56,7 +39,7 @@ type gRPCVtctldClient struct { //go:generate grpcvtctldclient -out client_gen.go func gRPCVtctldClientFactory(addr string) (vtctldclient.VtctldClient, error) { - opt, err := grpcclient.SecureDialOption(*cert, *key, *ca, *name) + opt, err := grpcclientcommon.SecureDialOption() if err != nil { return nil, err }