From 6599f9c7dcc1da6c06b6aaf4a7723faad6957460 Mon Sep 17 00:00:00 2001 From: AlexandrLitkevich Date: Sun, 16 Feb 2025 14:05:22 +0400 Subject: [PATCH 1/2] aeon: add connection from the etcd/tcs config @TarantoolBot document Title: add connection from the etcd/tcs config Added ability to connect to gRPC server used a configuration etcd/tcs tt aeon connect URI INSTANCE_NAME Closes #1052 --- CHANGELOG.md | 1 + cli/aeon/cmd/connect.go | 4 + cli/aeon/cmd/connection.go | 81 ++++++++++++++++++ cli/cmd/aeon.go | 71 ++++++++++++++-- lib/cluster/cluster.go | 46 ++++++++++ lib/cluster/etcd.go | 66 ++++++++++++++ lib/cluster/tarantool.go | 63 ++++++++++++++ lib/connect/uri.go | 4 - test/integration/aeon/test_aeon.py | 85 +++++++++++++++++++ .../integration/aeon/test_tcs_app/config.yaml | 37 ++++++++ 10 files changed, 446 insertions(+), 12 deletions(-) create mode 100644 cli/aeon/cmd/connection.go create mode 100644 test/integration/aeon/test_tcs_app/config.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c79c7f11..92925baa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * `--path` - to show module executables. - `tt aeon connect` added tests for connect file/app. +- `tt aeon connect`: add connection from the etcd/tcs config. - `tt pack `: support `.packignore` file to specify files that should not be included in package (works the same as `.gitignore`). - `tt tcm start`: add the tcm command. diff --git a/cli/aeon/cmd/connect.go b/cli/aeon/cmd/connect.go index 047230c94..b86719ffe 100644 --- a/cli/aeon/cmd/connect.go +++ b/cli/aeon/cmd/connect.go @@ -12,6 +12,10 @@ type Ssl struct { // ConnectCtx keeps context information for aeon connection. type ConnectCtx struct { + // Username defines a username for connection. + Username string + // Password defines a password for connection. + Password string // Ssl group of paths to ssl key files. Ssl Ssl // Transport is a connection mode. diff --git a/cli/aeon/cmd/connection.go b/cli/aeon/cmd/connection.go new file mode 100644 index 000000000..d10da8b28 --- /dev/null +++ b/cli/aeon/cmd/connection.go @@ -0,0 +1,81 @@ +package cmd + +import ( + "errors" + "fmt" + + "github.com/mitchellh/mapstructure" + "github.com/tarantool/tt/cli/util" + libcluster "github.com/tarantool/tt/lib/cluster" + libconnect "github.com/tarantool/tt/lib/connect" +) + +// FillConnectCtx takes a ConnectCtx object and fills it with data from a +// collected configuration by given instanceName and libconnect.UriOpts. +// It returns an error if fails to collect a configuration, +// instantiate a cluster config or find an instance in the cluster. +func FillConnectCtx(connectCtx *ConnectCtx, uriOpts libconnect.UriOpts, + instanceName string, collectors libcluster.CollectorFactory) error { + + connOpts := libcluster.ConnectOpts{ + Username: connectCtx.Username, + Password: connectCtx.Password, + } + collector, cancel, err := libcluster.CreateCollector(collectors, + connOpts, uriOpts) + if err != nil { + return err + } + defer cancel() + + config, err := collector.Collect() + if err != nil { + return fmt.Errorf("failed to collect a configuration: %w", err) + } + + clusterConfig, err := libcluster.MakeClusterConfig(config) + if err != nil { + return err + } + + result := libcluster.Instantiate(clusterConfig, instanceName) + + dataSsl := []string{"roles_cfg", "aeon.grpc", "advertise"} + data, err := result.Get(dataSsl) + if err != nil { + return err + } + + var advertise Advertise + err = mapstructure.Decode(data, &advertise) + if err != nil { + return err + } + + if advertise.Uri == "" { + return errors.New("invalid connection url") + } + + cleanedURL, err := util.RemoveScheme(advertise.Uri) + if err != nil { + return err + } + + connectCtx.Network, connectCtx.Address = libconnect.ParseBaseURI(cleanedURL) + + if (advertise.Params.Transport != "ssl") && (advertise.Params.Transport != "plain") { + return errors.New("transport must be ssl or plain") + } + + if advertise.Params.Transport == "ssl" { + connectCtx.Transport = TransportSsl + + connectCtx.Ssl = Ssl{ + KeyFile: advertise.Params.KeyFile, + CertFile: advertise.Params.CertFile, + CaFile: advertise.Params.CaFile, + } + } + + return nil +} diff --git a/cli/cmd/aeon.go b/cli/cmd/aeon.go index da8d6e312..ee9e6d452 100644 --- a/cli/cmd/aeon.go +++ b/cli/cmd/aeon.go @@ -3,6 +3,7 @@ package cmd import ( "errors" "fmt" + "io" "os" "path/filepath" "strings" @@ -13,10 +14,12 @@ import ( aeoncmd "github.com/tarantool/tt/cli/aeon/cmd" "github.com/tarantool/tt/cli/cmdcontext" "github.com/tarantool/tt/cli/console" + "github.com/tarantool/tt/cli/modules" "github.com/tarantool/tt/cli/running" "github.com/tarantool/tt/cli/util" - "github.com/tarantool/tt/lib/cluster" + libcluster "github.com/tarantool/tt/lib/cluster" libconnect "github.com/tarantool/tt/lib/connect" + "github.com/tarantool/tt/lib/integrity" ) const ( @@ -24,22 +27,46 @@ const ( aeonHistoryLines = console.DefaultHistoryLines ) +var aoenHelp = libconnect.MakeURLHelp(map[string]any{ + "service": "etcd or tarantool config storage", + "param_key": "a target configuration key in the prefix", + "param_name": "a name of an instance in the cluster configuration", + "/prefix": "key prefix (optional)," + + "points to a “namespace” or prefix for all key operations " + + "example: If you set a key mykey, it will actually be stored as /prefix/mykey.", +}) + var connectCtx = aeoncmd.ConnectCtx{ Transport: aeoncmd.TransportPlain, } func newAeonConnectCmd() *cobra.Command { var aeonCmd = &cobra.Command{ - Use: "connect ( | | )", + Use: "connect ( | | | )", Short: "Connect to the aeon instance", Long: `Connect to the aeon instance. tt aeon connect http://localhost:50051 tt aeon connect unix:// - tt aeon connect /path/to/config INSTANCE_NAME>`, - Run: RunModuleFunc(internalAeonConnect), + tt aeon connect /path/to/config INSTANCE_NAME + tt aeon connect https://user:pass@localhost:2379/prefix INSTANCE` + "\n\n" + + aoenHelp, + PreRunE: func(cmd *cobra.Command, args []string) error { + err := aeonConnectValidateArgs(cmd, args) + util.HandleCmdErr(cmd, err) + return err + }, + Run: func(cmd *cobra.Command, args []string) { + cmdCtx.CommandName = cmd.Name() + err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo, + internalAeonConnect, args) + util.HandleCmdErr(cmd, err) + }, Args: cobra.MatchAll(cobra.RangeArgs(1, 2), aeonConnectValidateArgs), } - + aeonCmd.Flags().StringVarP(&connectCtx.Username, "username", "u", "", + "username (used as etcd credentials only)") + aeonCmd.Flags().StringVarP(&connectCtx.Password, "password", "p", "", + "password (used as etcd credentials only)") aeonCmd.Flags().StringVar(&connectCtx.Ssl.KeyFile, "sslkeyfile", "", "path to a private SSL key file") aeonCmd.Flags().StringVar(&connectCtx.Ssl.CertFile, "sslcertfile", "", @@ -82,6 +109,11 @@ func aeonConnectValidateArgs(cmd *cobra.Command, args []string) error { return err } connectCtx.Network, connectCtx.Address = libconnect.ParseBaseURI(url) + case len(args) == 2 && libconnect.IsCredentialsURI(args[0]): + err := getConfigUri(&cmdCtx, args[0], args[1]) + if err != nil { + return err + } case len(args) == 1 && !util.IsURL(args[0]): configPath, _, _, err := parseAppStr(&cmdCtx, args[0]) if err != nil { @@ -176,18 +208,18 @@ func readConfigFilePath(configPath string, instance string) error { return err } - pb := cluster.NewYamlCollector(f) + pb := libcluster.NewYamlCollector(f) config, err := pb.Collect() if err != nil { return err } - clusterConfig, err := cluster.MakeClusterConfig(config) + clusterConfig, err := libcluster.MakeClusterConfig(config) if err != nil { return err } - result := cluster.Instantiate(clusterConfig, instance) + result := libcluster.Instantiate(clusterConfig, instance) // Get SSL connection. dataSsl := []string{"roles_cfg", "aeon.grpc", "advertise"} @@ -236,3 +268,26 @@ func readConfigFilePath(configPath string, instance string) error { return nil } + +func getConfigUri(cmdCtx *cmdcontext.CmdCtx, url string, instanceName string) error { + var dataCollectors libcluster.DataCollectorFactory + checkFunc, err := integrity.GetCheckFunction(cmdCtx.Integrity) + if err == integrity.ErrNotConfigured { + dataCollectors = libcluster.NewDataCollectorFactory() + } else if err != nil { + return fmt.Errorf("failed to create collectors with integrity check: %w", err) + } else { + dataCollectors = libcluster.NewIntegrityDataCollectorFactory(checkFunc, + func(path string) (io.ReadCloser, error) { + return cmdCtx.Integrity.Repository.Read(path) + }) + } + + aeonCollectors := libcluster.NewCollectorFactory(dataCollectors) + + if uri, err := libconnect.CreateUriOpts(url); err == nil { + aeoncmd.FillConnectCtx(&connectCtx, uri, instanceName, aeonCollectors) + } + + return nil +} diff --git a/lib/cluster/cluster.go b/lib/cluster/cluster.go index ed67c00f5..a91b093bf 100644 --- a/lib/cluster/cluster.go +++ b/lib/cluster/cluster.go @@ -4,6 +4,9 @@ import ( "fmt" "time" + "github.com/tarantool/go-tarantool/v2" + libconnect "github.com/tarantool/tt/lib/connect" + clientv3 "go.etcd.io/etcd/client/v3" "gopkg.in/yaml.v3" ) @@ -348,3 +351,46 @@ func FindGroupByReplicaset(cconfig ClusterConfig, replicaset string) (string, bo } return "", false } + +func CreateCollector( + collectors CollectorFactory, + connOpts ConnectOpts, + opts libconnect.UriOpts) (Collector, func(), error) { + prefix, key, timeout := opts.Prefix, opts.Params["key"], opts.Timeout + + var ( + collector Collector + err error + closeFunc func() + ) + + tarantoolFunc := func(conn tarantool.Connector) error { + if collectors != nil { + collector, err = collectors.NewTarantool(conn, prefix, key, timeout) + if err != nil { + conn.Close() + return fmt.Errorf("failed to create tarantool config storage collector: %w", err) + } + } + closeFunc = func() { conn.Close() } + return nil + } + + etcdFunc := func(client *clientv3.Client) error { + if collectors != nil { + collector, err = collectors.NewEtcd(client, prefix, key, timeout) + if err != nil { + client.Close() + return fmt.Errorf("failed to create etcd collector: %w", err) + } + } + closeFunc = func() { client.Close() } + return nil + } + + if err := DoOnStorage(connOpts, opts, tarantoolFunc, etcdFunc); err != nil { + return nil, nil, err + } + + return collector, closeFunc, nil +} diff --git a/lib/cluster/etcd.go b/lib/cluster/etcd.go index 9a25e314f..15b19a0d0 100644 --- a/lib/cluster/etcd.go +++ b/lib/cluster/etcd.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "github.com/tarantool/go-tarantool/v2" + libconnect "github.com/tarantool/tt/lib/connect" "go.etcd.io/etcd/client/pkg/v3/transport" clientv3 "go.etcd.io/etcd/client/v3" "go.uber.org/zap" @@ -804,3 +806,67 @@ func isSameDirSymlink(f fs.DirEntry, dir string) bool { target, err := os.Readlink(filepath.Join(dir, f.Name())) return err == nil && !strings.Contains(target, "/") } + +// ConnectOpts is additional connect options specified by a user. +type ConnectOpts struct { + Username string + Password string +} + +// connectEtcd establishes a connection to etcd. +func СonnectEtcdUriOpts(uriOpts libconnect.UriOpts, connOpts ConnectOpts) (*clientv3.Client, error) { + etcdOpts := MakeEtcdOptsFromUriOpts(uriOpts) + if etcdOpts.Username == "" && etcdOpts.Password == "" { + etcdOpts.Username = connOpts.Username + etcdOpts.Password = connOpts.Password + if etcdOpts.Username == "" { + etcdOpts.Username = os.Getenv(libconnect.EtcdUsernameEnv) + } + if etcdOpts.Password == "" { + etcdOpts.Password = os.Getenv(libconnect.EtcdPasswordEnv) + } + } + + etcdcli, err := ConnectEtcd(etcdOpts) + if err != nil { + return nil, fmt.Errorf("failed to connect to etcd: %w", err) + } + return etcdcli, nil +} + +// doOnStorage determines a storage based on the opts. +func DoOnStorage(connOpts ConnectOpts, opts libconnect.UriOpts, + tarantoolFunc func(tarantool.Connector) error, etcdFunc func(*clientv3.Client) error) error { + etcdcli, errEtcd := СonnectEtcdUriOpts(opts, connOpts) + if errEtcd == nil { + return etcdFunc(etcdcli) + } + + conn, errTarantool := СonnectTarantool(opts, connOpts) + if errTarantool == nil { + return tarantoolFunc(conn) + } + + return fmt.Errorf("failed to establish a connection to tarantool or etcd: %w, %w", + errTarantool, errEtcd) +} + +// MakeEtcdOptsFromUriOpts create etcd connect options from URI options. +func MakeEtcdOptsFromUriOpts(src libconnect.UriOpts) EtcdOpts { + var endpoints []string + if src.Endpoint != "" { + endpoints = []string{src.Endpoint} + } + + return EtcdOpts{ + Endpoints: endpoints, + Username: src.Username, + Password: src.Password, + KeyFile: src.KeyFile, + CertFile: src.CertFile, + CaPath: src.CaPath, + CaFile: src.CaFile, + SkipHostVerify: src.SkipHostVerify || src.SkipPeerVerify, + Timeout: src.Timeout, + } +} diff --git a/lib/cluster/tarantool.go b/lib/cluster/tarantool.go index 58d1e3917..1f64f79e6 100644 --- a/lib/cluster/tarantool.go +++ b/lib/cluster/tarantool.go @@ -3,12 +3,15 @@ package cluster import ( "context" "fmt" + "os" "strings" "time" "github.com/mitchellh/mapstructure" "github.com/tarantool/go-tarantool/v2" + "github.com/tarantool/go-tlsdialer" + libconnect "github.com/tarantool/tt/lib/connect" ) // TarantoolAllCollector collects data from a Tarantool for a whole prefix. @@ -544,3 +547,63 @@ func tarantoolTxnGet(conn tarantool.Doer, return resp, nil } + +// MakeConnectOptsFromUriOpts create Tarantool connect options from +// URI options. +func MakeConnectOptsFromUriOpts(src libconnect.UriOpts) (tarantool.Dialer, tarantool.Opts) { + address := fmt.Sprintf("tcp://%s", src.Host) + + var dialer tarantool.Dialer + + if src.KeyFile != "" || src.CertFile != "" || src.CaFile != "" || src.Ciphers != "" { + dialer = tlsdialer.OpenSSLDialer{ + Address: address, + User: src.Username, + Password: src.Password, + SslKeyFile: src.KeyFile, + SslCertFile: src.CertFile, + SslCaFile: src.CaFile, + SslCiphers: src.Ciphers, + } + } else { + dialer = tarantool.NetDialer{ + Address: address, + User: src.Username, + Password: src.Password, + } + } + + opts := tarantool.Opts{ + Timeout: src.Timeout, + } + + return dialer, opts +} + +func СonnectTarantool(uriOpts libconnect.UriOpts, + connOpts ConnectOpts) (tarantool.Connector, error) { + if uriOpts.Username == "" && uriOpts.Password == "" { + uriOpts.Username = connOpts.Username + uriOpts.Password = connOpts.Password + if uriOpts.Username == "" { + uriOpts.Username = os.Getenv(libconnect.TarantoolUsernameEnv) + } + if uriOpts.Password == "" { + uriOpts.Password = os.Getenv(libconnect.TarantoolPasswordEnv) + } + } + + dialer, connectorOpts := MakeConnectOptsFromUriOpts(uriOpts) + + ctx := context.Background() + if connectorOpts.Timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, connectorOpts.Timeout) + defer cancel() + } + conn, err := tarantool.Connect(ctx, dialer, connectorOpts) + if err != nil { + return nil, fmt.Errorf("failed to connect to tarantool: %w", err) + } + return conn, nil +} diff --git a/lib/connect/uri.go b/lib/connect/uri.go index 29dfa23b2..d59fafa8c 100644 --- a/lib/connect/uri.go +++ b/lib/connect/uri.go @@ -71,10 +71,6 @@ type UriOpts struct { Prefix string // Tag value of #fragment URL part. Tag string - // // Key is a target key. - // Key string - // // Instance is an instance name. - // Instance string // Username is a user name for authorization Username string // Password is a password for authorization diff --git a/test/integration/aeon/test_aeon.py b/test/integration/aeon/test_aeon.py index 182a18c8e..a4cc9f6c3 100644 --- a/test/integration/aeon/test_aeon.py +++ b/test/integration/aeon/test_aeon.py @@ -5,6 +5,11 @@ import pytest +from utils import get_fixture_tcs_params, is_tarantool_ee, is_tarantool_less_3 + +fixture_tcs_params = get_fixture_tcs_params(os.path.join(os.path.dirname( + os.path.abspath(__file__)), "test_tcs_app")) + AeonConnectCommand = ("aeon", "connect") @@ -375,6 +380,86 @@ def test_cli_plain_app_success(tt_cmd, app_name, tmpdir_with_cfg, aeon_plain_fil assert tt.returncode == 0 +def test_cli_plain_tcs_success(tt_cmd, tmpdir_with_cfg, request, fixture_params, aeon_plain_file): + + if is_tarantool_less_3() or not is_tarantool_ee(): + pytest.skip() + for k, v in fixture_tcs_params.items(): + fixture_params[k] = v + instance = request.getfixturevalue("tcs") + tmpdir = tmpdir_with_cfg + + print(f"Aeon plain at: {aeon_plain_file}") + conn = instance.conn() + + source_file = os.path.join(os.path.dirname(__file__), "data/config.yml") + file = open(source_file, "r") + config = file.read() + + conn.call("config.storage.put", "/prefix/config/all", config) + creds = ( + f"{instance.connection_username}:{instance.connection_password}@" + ) + + cmd = [ + tt_cmd, + *AeonConnectCommand, + "http://" + + creds + + f"{instance.host}:{instance.port}/prefix/", + "aeon-router-002", + ] + tt = run( + cmd, + cwd=tmpdir, + capture_output=True, + input="", + text=True, + encoding="utf-8", + ) + check_tt_aeon_response(tt.stderr) + assert tt.returncode == 0 + + +def test_cli_plain_etcd_success(tt_cmd, tmpdir_with_cfg, + request, aeon_plain_file): + + instance = request.getfixturevalue("etcd") + tmpdir = tmpdir_with_cfg + + print(f"Aeon plain at: {aeon_plain_file}") + conn = instance.conn() + + source_file = os.path.join(os.path.dirname(__file__), "data/config.yml") + file = open(source_file, "r") + config = file.read() + + conn.put("/prefix/config/", config) + + creds = ( + f"{instance.connection_username}:{instance.connection_password}@" + ) + + cmd = [ + tt_cmd, + *AeonConnectCommand, + "http://" + + creds + + f"{instance.host}:{instance.port}/prefix", + "aeon-router-002", + ] + tt = run( + cmd, + cwd=tmpdir, + capture_output=True, + input="", + text=True, + encoding="utf-8", + ) + check_tt_aeon_response(tt.stderr) + assert tt.returncode == 0 + + @pytest.mark.parametrize("app_name", ["app_ssl"]) def test_cli_ssl_app_flag_success(tt_cmd, app_name, tmpdir_with_cfg, aeon_ssl, certificates): print(f"Aeon ssl at: {aeon_ssl}") diff --git a/test/integration/aeon/test_tcs_app/config.yaml b/test/integration/aeon/test_tcs_app/config.yaml new file mode 100644 index 000000000..d681f4ba1 --- /dev/null +++ b/test/integration/aeon/test_tcs_app/config.yaml @@ -0,0 +1,37 @@ +credentials: + users: + replicator: + password: 'topsecret' + roles: [replication] + client: + password: 'secret' + privileges: + - permissions: [execute] + universe: true + - permissions: [read, write] + spaces: [config_storage, config_storage_meta] + +iproto: + advertise: + peer: + login: replicator + +replication: + failover: election + +database: + use_mvcc_engine: true + +groups: + group-001: + replicasets: + replicaset-001: + roles: [config.storage] + roles_cfg: + config_storage: + status_check_interval: 3 + instances: + instance-001: + iproto: + listen: + - uri: '127.0.0.1:3303' From 95e293491030c7e412adcb7cab464c9e71aed52d Mon Sep 17 00:00:00 2001 From: AlexandrLitkevich Date: Wed, 2 Apr 2025 16:47:53 +0400 Subject: [PATCH 2/2] lib: consolidate infrastructure functions into shared modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relocated cluster functions from cluster/cmd to lib/cluster: - CreateCollector → lib/cluster/cluster.go - ConnectEtcdUriOpts → lib/cluster/etcd.go - DoOnStorage → lib/cluster/etcd.go - MakeEtcdOptsFromUriOpts → lib/cluster/etcd.go - MakeConnectOptsFromUriOpts → lib/cluster/tarantool.go - ConnectTarantool → lib/cluster/tarantool.go Established new lib/connect submodule --- CHANGELOG.md | 9 ++++ cli/cluster/cmd/common.go | 85 ++---------------------------- cli/cluster/cmd/publish.go | 2 +- cli/cluster/cmd/replicaset.go | 12 ++--- cli/cluster/cmd/show.go | 2 +- cli/cmd/aeon.go | 5 +- go.mod | 6 ++- go.sum | 4 +- lib/cluster/etcd.go | 2 +- lib/cluster/go.mod | 15 ++++-- lib/cluster/go.sum | 23 +++++--- lib/connect/go.mod | 15 ++++++ lib/connect/go.sum | 16 ++++++ lib/connect/uri.go | 20 ++++--- lib/connect/uri_test.go | 23 ++++---- test/integration/aeon/test_aeon.py | 3 +- 16 files changed, 116 insertions(+), 126 deletions(-) create mode 100644 lib/connect/go.mod create mode 100644 lib/connect/go.sum diff --git a/CHANGELOG.md b/CHANGELOG.md index 92925baa3..6a8eaed20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed +- The following functions were moved from `cluster/cmd` to `lib/cluster`: + * CreateCollector → lib/cluster/cluster.go, + * ConnectEtcdUriOpts → lib/cluster/etcd.go, + * DoOnStorage → lib/cluster/etcd.go, + * MakeEtcdOptsFromUriOpts → lib/cluster/etcd.go, + * MakeConnectOptsFromUriOpts → lib/cluster/tarantool.go, + * ConnectTarantool → lib/cluster/tarantool.go. +- Added new submodule `lib/connect`. + ### Fixed - Arguments of an internal command are not parsed if it is forced over its existent diff --git a/cli/cluster/cmd/common.go b/cli/cluster/cmd/common.go index 7f739ec7a..f171dfb60 100644 --- a/cli/cluster/cmd/common.go +++ b/cli/cluster/cmd/common.go @@ -1,17 +1,15 @@ package cmd import ( - "context" "errors" "fmt" - "os" clientv3 "go.etcd.io/etcd/client/v3" "github.com/tarantool/go-tarantool/v2" "github.com/tarantool/tt/cli/cluster" libcluster "github.com/tarantool/tt/lib/cluster" - "github.com/tarantool/tt/lib/connect" + libconnect "github.com/tarantool/tt/lib/connect" ) // printRawClusterConfig prints a raw cluster configuration or an instance @@ -137,87 +135,12 @@ func printConfig(config *libcluster.Config) { fmt.Print(config.String()) } -// connectOpts is additional connect options specified by a user. -type connectOpts struct { - Username string - Password string -} - -// connectTarantool establishes a connection to Tarantool. -func connectTarantool(uriOpts connect.UriOpts, connOpts connectOpts) (tarantool.Connector, error) { - if uriOpts.Username == "" && uriOpts.Password == "" { - uriOpts.Username = connOpts.Username - uriOpts.Password = connOpts.Password - if uriOpts.Username == "" { - uriOpts.Username = os.Getenv(connect.TarantoolUsernameEnv) - } - if uriOpts.Password == "" { - uriOpts.Password = os.Getenv(connect.TarantoolPasswordEnv) - } - } - - dialer, connectorOpts, err := MakeConnectOptsFromUriOpts(uriOpts) - if err != nil { - return nil, err - } - - ctx := context.Background() - if connectorOpts.Timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, connectorOpts.Timeout) - defer cancel() - } - conn, err := tarantool.Connect(ctx, dialer, connectorOpts) - if err != nil { - return nil, fmt.Errorf("failed to connect to tarantool: %w", err) - } - return conn, nil -} - -// connectEtcd establishes a connection to etcd. -func connectEtcd(uriOpts connect.UriOpts, connOpts connectOpts) (*clientv3.Client, error) { - etcdOpts := MakeEtcdOptsFromUriOpts(uriOpts) - if etcdOpts.Username == "" && etcdOpts.Password == "" { - etcdOpts.Username = connOpts.Username - etcdOpts.Password = connOpts.Password - if etcdOpts.Username == "" { - etcdOpts.Username = os.Getenv(connect.EtcdUsernameEnv) - } - if etcdOpts.Password == "" { - etcdOpts.Password = os.Getenv(connect.EtcdPasswordEnv) - } - } - - etcdcli, err := libcluster.ConnectEtcd(etcdOpts) - if err != nil { - return nil, fmt.Errorf("failed to connect to etcd: %w", err) - } - return etcdcli, nil -} - -// doOnStorage determines a storage based on the opts. -func doOnStorage(connOpts connectOpts, opts connect.UriOpts, - tarantoolFunc func(tarantool.Connector) error, etcdFunc func(*clientv3.Client) error) error { - etcdcli, errEtcd := connectEtcd(opts, connOpts) - if errEtcd == nil { - return etcdFunc(etcdcli) - } - - conn, errTarantool := connectTarantool(opts, connOpts) - if errTarantool == nil { - return tarantoolFunc(conn) - } - - return fmt.Errorf("failed to establish a connection to tarantool or etcd: %w, %w", - errTarantool, errEtcd) -} - // createPublisherAndCollector creates a new data publisher and collector based on UriOpts. func createPublisherAndCollector( publishers libcluster.DataPublisherFactory, collectors libcluster.CollectorFactory, - connOpts connectOpts, - opts connect.UriOpts) (libcluster.DataPublisher, libcluster.Collector, func(), error) { + connOpts libcluster.ConnectOpts, + opts libconnect.UriOpts) (libcluster.DataPublisher, libcluster.Collector, func(), error) { prefix, key, timeout := opts.Prefix, opts.Params["key"], opts.Timeout var ( @@ -265,7 +188,7 @@ func createPublisherAndCollector( return nil } - if err := doOnStorage(connOpts, opts, tarantoolFunc, etcdFunc); err != nil { + if err := libcluster.DoOnStorage(connOpts, opts, tarantoolFunc, etcdFunc); err != nil { return nil, nil, nil, err } diff --git a/cli/cluster/cmd/publish.go b/cli/cluster/cmd/publish.go index ab0dc13ea..61b2b5316 100644 --- a/cli/cluster/cmd/publish.go +++ b/cli/cluster/cmd/publish.go @@ -38,7 +38,7 @@ func PublishUri(publishCtx PublishCtx, opts connect.UriOpts) error { return err } - connOpts := connectOpts{ + connOpts := libcluster.ConnectOpts{ Username: publishCtx.Username, Password: publishCtx.Password, } diff --git a/cli/cluster/cmd/replicaset.go b/cli/cluster/cmd/replicaset.go index e0ab23b12..016b6c2e0 100644 --- a/cli/cluster/cmd/replicaset.go +++ b/cli/cluster/cmd/replicaset.go @@ -107,7 +107,7 @@ func pickPatchKey(keys []string, force bool, pathMsg string) (int, error) { func createDataCollectorAndKeyPublisher( collectors libcluster.DataCollectorFactory, publishers libcluster.DataPublisherFactory, - opts connect.UriOpts, connOpts connectOpts) ( + opts connect.UriOpts, connOpts libcluster.ConnectOpts) ( libcluster.DataCollector, replicaset.DataPublisher, func(), error) { prefix, key, timeout := opts.Prefix, opts.Params["key"], opts.Timeout var ( @@ -145,7 +145,7 @@ func createDataCollectorAndKeyPublisher( return nil } - if err := doOnStorage(connOpts, opts, tarantoolFunc, etcdFunc); err != nil { + if err := libcluster.DoOnStorage(connOpts, opts, tarantoolFunc, etcdFunc); err != nil { return nil, nil, nil, err } @@ -158,7 +158,7 @@ func Promote(url string, ctx PromoteCtx) error { if err != nil { return fmt.Errorf("invalid URL %q: %w", url, err) } - connOpts := connectOpts{ + connOpts := libcluster.ConnectOpts{ Username: ctx.Username, Password: ctx.Password, } @@ -205,7 +205,7 @@ func Demote(url string, ctx DemoteCtx) error { if err != nil { return fmt.Errorf("invalid URL %q: %w", url, err) } - connOpts := connectOpts{ + connOpts := libcluster.ConnectOpts{ Username: ctx.Username, Password: ctx.Password, } @@ -252,7 +252,7 @@ func Expel(url string, ctx ExpelCtx) error { if err != nil { return fmt.Errorf("invalid URL %q: %w", url, err) } - connOpts := connectOpts{ + connOpts := libcluster.ConnectOpts{ Username: ctx.Username, Password: ctx.Password, } @@ -306,7 +306,7 @@ func ChangeRole(url string, ctx RolesChangeCtx, action replicaset.RolesChangerAc if err != nil { return fmt.Errorf("invalid URL %q: %w", url, err) } - connOpts := connectOpts{ + connOpts := libcluster.ConnectOpts{ Username: ctx.Username, Password: ctx.Password, } diff --git a/cli/cluster/cmd/show.go b/cli/cluster/cmd/show.go index 4f858a52e..391c34d10 100644 --- a/cli/cluster/cmd/show.go +++ b/cli/cluster/cmd/show.go @@ -23,7 +23,7 @@ type ShowCtx struct { // ShowUri shows a configuration from URI. func ShowUri(showCtx ShowCtx, opts connect.UriOpts) error { - connOpts := connectOpts{ + connOpts := libcluster.ConnectOpts{ Username: showCtx.Username, Password: showCtx.Password, } diff --git a/cli/cmd/aeon.go b/cli/cmd/aeon.go index ee9e6d452..783d186cc 100644 --- a/cli/cmd/aeon.go +++ b/cli/cmd/aeon.go @@ -31,9 +31,8 @@ var aoenHelp = libconnect.MakeURLHelp(map[string]any{ "service": "etcd or tarantool config storage", "param_key": "a target configuration key in the prefix", "param_name": "a name of an instance in the cluster configuration", - "/prefix": "key prefix (optional)," + - "points to a “namespace” or prefix for all key operations " + - "example: If you set a key mykey, it will actually be stored as /prefix/mykey.", + "prefix": "key prefix (optional)," + + " points to a “namespace” or prefix for all key operations", }) var connectCtx = aeoncmd.ConnectCtx{ diff --git a/go.mod b/go.mod index 8ee32493b..4e0afa265 100644 --- a/go.mod +++ b/go.mod @@ -21,15 +21,15 @@ require ( github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c github.com/nxadm/tail v1.4.11 github.com/otiai10/copy v1.14.0 - github.com/pmezard/go-difflib v1.0.0 github.com/spf13/cobra v1.8.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/tarantool/cartridge-cli v0.0.0-20220605082730-53e6a5be9a61 github.com/tarantool/go-prompt v1.0.1 github.com/tarantool/go-tarantool v1.12.2 github.com/tarantool/go-tarantool/v2 v2.2.1 github.com/tarantool/go-tlsdialer v1.0.0 github.com/tarantool/tt/lib/cluster v0.0.0 + github.com/tarantool/tt/lib/connect v0.0.0-0 github.com/tarantool/tt/lib/integrity v0.0.0 github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/yuin/gopher-lua v1.1.1-0.20230219103905-71163b697a8f @@ -104,6 +104,7 @@ require ( github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/term v1.2.0-beta.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.11.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.30.0 // indirect @@ -162,4 +163,5 @@ replace ( github.com/tarantool/cartridge-cli => ./cli/cartridge/third_party/cartridge-cli github.com/tarantool/tt/lib/cluster => ./lib/cluster github.com/tarantool/tt/lib/integrity => ./lib/integrity + github.com/tarantool/tt/lib/connect => ./lib/connect ) diff --git a/go.sum b/go.sum index 1962c171d..986a91c4e 100644 --- a/go.sum +++ b/go.sum @@ -425,8 +425,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tarantool/go-iproto v1.1.0 h1:HULVOIHsiehI+FnHfM7wMDntuzUddO09DKqu2WnFQ5A= github.com/tarantool/go-iproto v1.1.0/go.mod h1:LNCtdyZxojUed8SbOiYHoc3v9NvaZTB7p96hUySMlIo= github.com/tarantool/go-openssl v0.0.8-0.20230307065445-720eeb389195/go.mod h1:M7H4xYSbzqpW/ZRBMyH0eyqQBsnhAMfsYk5mv0yid7A= diff --git a/lib/cluster/etcd.go b/lib/cluster/etcd.go index 15b19a0d0..1aaac2c05 100644 --- a/lib/cluster/etcd.go +++ b/lib/cluster/etcd.go @@ -834,7 +834,7 @@ func СonnectEtcdUriOpts(uriOpts libconnect.UriOpts, connOpts ConnectOpts) (*cli return etcdcli, nil } -// doOnStorage determines a storage based on the opts. +// DoOnStorage determines a storage based on the opts. func DoOnStorage(connOpts ConnectOpts, opts libconnect.UriOpts, tarantoolFunc func(tarantool.Connector) error, etcdFunc func(*clientv3.Client) error) error { etcdcli, errEtcd := СonnectEtcdUriOpts(opts, connOpts) diff --git a/lib/cluster/go.mod b/lib/cluster/go.mod index 1c0bc3cec..c6b8f6fa8 100644 --- a/lib/cluster/go.mod +++ b/lib/cluster/go.mod @@ -4,8 +4,9 @@ go 1.20 require ( github.com/mitchellh/mapstructure v1.5.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/tarantool/go-tarantool/v2 v2.2.1 + github.com/tarantool/tt/lib/connect v0.0.0-0 go.etcd.io/etcd/api/v3 v3.5.12 go.etcd.io/etcd/client/pkg/v3 v3.5.12 go.etcd.io/etcd/client/v3 v3.5.12 @@ -30,18 +31,21 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect - github.com/json-iterator/go v1.1.11 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mattn/go-pointer v0.0.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/prometheus/client_golang v1.11.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect - github.com/sirupsen/logrus v1.7.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect github.com/soheilhy/cmux v0.1.5 // indirect + github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tarantool/go-iproto v1.1.0 // indirect + github.com/tarantool/go-openssl v1.0.0 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.3.8 // indirect @@ -71,6 +75,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/tarantool/go-tlsdialer v1.0.0 github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.etcd.io/etcd/tests/v3 v3.5.12 @@ -84,3 +89,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/protobuf v1.33.0 // indirect ) + +replace github.com/tarantool/tt/lib/connect => ../connect diff --git a/lib/cluster/go.sum b/lib/cluster/go.sum index 2587a7c60..7ae5bda1d 100644 --- a/lib/cluster/go.sum +++ b/lib/cluster/go.sum @@ -102,8 +102,9 @@ github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUB github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -116,6 +117,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= +github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -124,8 +127,9 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -159,10 +163,12 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -172,12 +178,16 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tarantool/go-iproto v1.1.0 h1:HULVOIHsiehI+FnHfM7wMDntuzUddO09DKqu2WnFQ5A= github.com/tarantool/go-iproto v1.1.0/go.mod h1:LNCtdyZxojUed8SbOiYHoc3v9NvaZTB7p96hUySMlIo= +github.com/tarantool/go-openssl v1.0.0 h1:kE0XhXi8b1qWOLwmcasAS67OyQdGXn42t0Qv3NwEjWY= +github.com/tarantool/go-openssl v1.0.0/go.mod h1:M7H4xYSbzqpW/ZRBMyH0eyqQBsnhAMfsYk5mv0yid7A= github.com/tarantool/go-tarantool/v2 v2.2.1 h1:ldzMVfkmTuJl4ie3ByMIr+mmPSKDVTcSkN8XlVZEows= github.com/tarantool/go-tarantool/v2 v2.2.1/go.mod h1:hKKeZeCP8Y8+U6ZFS32ot1jHV/n4WKVP4fjRAvQznMY= +github.com/tarantool/go-tlsdialer v1.0.0 h1:UZ67erz/QiThttIvcHtUnUWo1ZJ/BlQoN088NvkVmBQ= +github.com/tarantool/go-tlsdialer v1.0.0/go.mod h1:IZuFRmnasGSBtPGZi3LMrpaSUgHYR+hDZ4ec7gR9k/0= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= @@ -278,6 +288,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/lib/connect/go.mod b/lib/connect/go.mod new file mode 100644 index 000000000..e8e3b8f42 --- /dev/null +++ b/lib/connect/go.mod @@ -0,0 +1,15 @@ +module github.com/tarantool/tt/lib/connect + +go 1.20 + +require ( + github.com/pmezard/go-difflib v1.0.0 + github.com/stretchr/testify v1.10.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/lib/connect/go.sum b/lib/connect/go.sum new file mode 100644 index 000000000..2db3f8147 --- /dev/null +++ b/lib/connect/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/connect/uri.go b/lib/connect/uri.go index d59fafa8c..9f06ffac6 100644 --- a/lib/connect/uri.go +++ b/lib/connect/uri.go @@ -10,8 +10,11 @@ import ( "strconv" "strings" "time" +) - "github.com/tarantool/tt/cli/connector" +const ( + TCPNetwork = "tcp" + UnixNetwork = "unix" ) const ( @@ -134,8 +137,11 @@ func IsCredentialsURI(str string) bool { // user:password@/path // user:password@./path pathReStr := userpassRe + `@` + systemPathPrefixRe + `[^\./].*` + // https://user:password@host:port + // https://user:password@host + httpsReStr := `(http|https)://` + userpassRe + `@([\w\.-]+(:\d+)?)(/[\w\-\./~]*)?` - uriReStr := "^((" + tcpReStr + ")|(" + unixReStr + ")|(" + pathReStr + "))$" + uriReStr := "^((" + tcpReStr + ")|(" + httpsReStr + ")|(" + unixReStr + ")|(" + pathReStr + "))$" uriRe := regexp.MustCompile(uriReStr) return uriRe.MatchString(str) } @@ -148,21 +154,21 @@ func ParseBaseURI(uri string) (string, string) { switch { case uriLen > 0 && (uri[0] == '.' || uri[0] == '/' || uri[0] == '~'): - network = connector.UnixNetwork + network = UnixNetwork address = uri case uriLen >= 7 && uri[0:7] == "unix://": - network = connector.UnixNetwork + network = UnixNetwork address = uri[7:] case uriLen >= 6 && uri[0:6] == "tcp://": - network = connector.TCPNetwork + network = TCPNetwork address = uri[6:] default: - network = connector.TCPNetwork + network = TCPNetwork address = uri } // In the case of a complex uri, shell expansion does not occur, so do it manually. - if network == connector.UnixNetwork && + if network == UnixNetwork && strings.HasPrefix(address, "~/") { if homeDir, err := os.UserHomeDir(); err == nil { address = filepath.Join(homeDir, address[2:]) diff --git a/lib/connect/uri_test.go b/lib/connect/uri_test.go index 27c8dfb7c..68eea4068 100644 --- a/lib/connect/uri_test.go +++ b/lib/connect/uri_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/stretchr/testify/assert" - "github.com/tarantool/tt/cli/connector" "github.com/tarantool/tt/lib/connect" ) @@ -45,6 +44,8 @@ var validCredentialsUris = []string{ testUserPass + "@../a", testUserPass + "@~/a", testUserPass + "@//path", + "https://" + testUserPass + "@localhost:2379/prefix", + "https://" + testUserPass + "@localhost:2379", } var invalidBaseUris = []string{ @@ -194,14 +195,14 @@ func TestParseBaseURI(t *testing.T) { network string address string }{ - {"localhost:3013", connector.TCPNetwork, "localhost:3013"}, - {"tcp://localhost:3013", connector.TCPNetwork, "localhost:3013"}, - {"./path/to/socket", connector.UnixNetwork, "./path/to/socket"}, - {"/path/to/socket", connector.UnixNetwork, "/path/to/socket"}, - {"unix:///path/to/socket", connector.UnixNetwork, "/path/to/socket"}, - {"unix://..//path/to/socket", connector.UnixNetwork, "..//path/to/socket"}, - {"..//path", connector.UnixNetwork, "..//path"}, - {"some_uri", connector.TCPNetwork, "some_uri"}, // Keeps unchanged + {"localhost:3013", connect.TCPNetwork, "localhost:3013"}, + {"tcp://localhost:3013", connect.TCPNetwork, "localhost:3013"}, + {"./path/to/socket", connect.UnixNetwork, "./path/to/socket"}, + {"/path/to/socket", connect.UnixNetwork, "/path/to/socket"}, + {"unix:///path/to/socket", connect.UnixNetwork, "/path/to/socket"}, + {"unix://..//path/to/socket", connect.UnixNetwork, "..//path/to/socket"}, + {"..//path", connect.UnixNetwork, "..//path"}, + {"some_uri", connect.TCPNetwork, "some_uri"}, // Keeps unchanged } for _, tc := range cases { @@ -215,11 +216,11 @@ func TestParseBaseURI(t *testing.T) { t.Run("starts from ~", func(t *testing.T) { homeDir, _ := os.UserHomeDir() network, address := connect.ParseBaseURI("unix://~/a/b") - assert.Equal(t, connector.UnixNetwork, network) + assert.Equal(t, connect.UnixNetwork, network) assert.Equal(t, homeDir+"/a/b", address) network, address = connect.ParseBaseURI("~/a/b") - assert.Equal(t, connector.UnixNetwork, network) + assert.Equal(t, connect.UnixNetwork, network) assert.Equal(t, homeDir+"/a/b", address) }) } diff --git a/test/integration/aeon/test_aeon.py b/test/integration/aeon/test_aeon.py index a4cc9f6c3..c01779509 100644 --- a/test/integration/aeon/test_aeon.py +++ b/test/integration/aeon/test_aeon.py @@ -406,9 +406,10 @@ def test_cli_plain_tcs_success(tt_cmd, tmpdir_with_cfg, request, fixture_params, *AeonConnectCommand, "http://" + creds - + f"{instance.host}:{instance.port}/prefix/", + + f"{instance.host}:{instance.port}/prefix", "aeon-router-002", ] + tt = run( cmd, cwd=tmpdir,