Skip to content

Commit 30f9ede

Browse files
committed
cluster: add tt cluster show command
Part of #506
1 parent bff9e26 commit 30f9ede

File tree

15 files changed

+544
-10
lines changed

15 files changed

+544
-10
lines changed

.github/actions/prepare-ce-test-env/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ inputs:
1414
description: Whether to skip etcd installation
1515
type: boolean
1616
required: false
17-
default: true
17+
default: false
1818

1919
runs:
2020
using: "composite"

.github/actions/prepare-ee-test-env/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ inputs:
1212
description: Whether to skip etcd installation
1313
type: boolean
1414
required: false
15-
default: true
15+
default: false
1616

1717
runs:
1818
using: "composite"

.github/workflows/full-ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ jobs:
3838
uses: ./.github/actions/prepare-ce-test-env
3939
with:
4040
tarantool-version: '${{ matrix.tarantool-version }}'
41-
skip-etcd-install: false
4241

4342
- name: Static code check
4443
uses: ./.github/actions/static-code-check
@@ -93,7 +92,6 @@ jobs:
9392
with:
9493
sdk-version: '${{ matrix.sdk-version }}'
9594
sdk-download-token: '${{ secrets.SDK_DOWNLOAD_TOKEN }}'
96-
skip-etcd-install: false
9795

9896
- name: Static code check
9997
uses: ./.github/actions/static-code-check

.github/workflows/tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ jobs:
127127
brew install --overwrite go mage
128128
pip3 install -r test/requirements.txt
129129
130+
- name: Install etcd
131+
uses: ./.github/actions/setup-etcd
132+
130133
- name: Build tt
131134
env:
132135
TT_CLI_BUILD_SSL: 'static'

cli/cluster/etcd.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ func (collector EtcdCollector) Collect() (*Config, error) {
184184
}
185185

186186
cconfig := NewConfig()
187+
if len(resp.Kvs) == 0 {
188+
return nil, fmt.Errorf("a configuration data not found in prefix %q",
189+
key)
190+
}
191+
187192
for _, kv := range resp.Kvs {
188193
if config, err := NewYamlCollector(kv.Value).Collect(); err != nil {
189194
fmtErr := "failed to decode etcd config for key %q: %w"

cli/cluster/etcd_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,8 @@ func TestEtcdConfig_Collect_empty(t *testing.T) {
240240
}
241241
config, err := cluster.NewEtcdCollector(mock, "foo", 0).Collect()
242242

243-
assert.NoError(t, err)
244-
assert.NotNil(t, config)
245-
_, err = config.Get(nil)
246243
assert.Error(t, err)
244+
assert.Nil(t, config)
247245
}
248246

249247
func TestEtcdConfig_Collect_decode_error(t *testing.T) {

cli/cluster/integration_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,7 @@ func TestEtcdCollector_empty(t *testing.T) {
344344
defer etcd.Close()
345345

346346
config, err := cluster.NewEtcdCollector(etcd, "foo", timeout).Collect()
347-
require.NoError(t, err)
348-
_, err = config.Get(nil)
347+
assert.Nil(t, config)
349348
assert.Error(t, err)
350349
}
351350

cli/cmd/cluster.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"path/filepath"
7+
"strconv"
8+
"strings"
9+
"time"
10+
11+
"github.com/spf13/cobra"
12+
13+
"github.com/tarantool/tt/cli/cluster"
14+
"github.com/tarantool/tt/cli/cmd/internal"
15+
"github.com/tarantool/tt/cli/cmdcontext"
16+
"github.com/tarantool/tt/cli/modules"
17+
"github.com/tarantool/tt/cli/running"
18+
)
19+
20+
const (
21+
defaultTimeout = 3 * time.Second
22+
configFileName = "config.yaml"
23+
)
24+
25+
func NewClusterCmd() *cobra.Command {
26+
clusterCmd := &cobra.Command{
27+
Use: "cluster",
28+
Short: "Manage cluster configuration",
29+
}
30+
31+
clusterCmd.AddCommand(&cobra.Command{
32+
Use: "show (<APP_NAME> | <APP_NAME:INSTANCE_NAME> | <URI>)",
33+
Short: "Show a cluster configuration",
34+
Long: "Show a cluster configuration for an application, instance or" +
35+
" from etcd URI.",
36+
Run: func(cmd *cobra.Command, args []string) {
37+
cmdCtx.CommandName = cmd.Name()
38+
err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo,
39+
internalClusterShowModule, args)
40+
handleCmdErr(cmd, err)
41+
},
42+
Args: cobra.ExactArgs(1),
43+
ValidArgsFunction: func(
44+
cmd *cobra.Command,
45+
args []string,
46+
toComplete string) ([]string, cobra.ShellCompDirective) {
47+
if len(args) != 0 {
48+
return nil, cobra.ShellCompDirectiveNoFileComp
49+
}
50+
return internal.ValidArgsFunction(
51+
cliOpts, &cmdCtx, cmd, toComplete,
52+
running.ExtractActiveAppNames,
53+
running.ExtractActiveInstanceNames)
54+
},
55+
DisableFlagParsing: true,
56+
})
57+
58+
return clusterCmd
59+
}
60+
61+
// internalClusterShowModule is an entrypoint for `cluster show` command.
62+
func internalClusterShowModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
63+
uri, urlErr := url.Parse(args[0])
64+
if urlErr == nil && uri.Scheme != "" {
65+
// It looks like a URL, let's get data from the etcd.
66+
return showEtcd(uri)
67+
}
68+
69+
if !isConfigExist(cmdCtx) {
70+
return fmt.Errorf("unable to resolve the application name %q: %w",
71+
args[0], errNoConfig)
72+
}
73+
74+
colonIds := strings.Index(args[0], string(running.InstanceDelimiter))
75+
instance := colonIds != -1
76+
77+
var runningCtx running.RunningCtx
78+
if err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args); err != nil {
79+
return err
80+
}
81+
82+
appDir := filepath.Dir(runningCtx.Instances[0].AppPath)
83+
path := filepath.Join(appDir, configFileName)
84+
config, err := cluster.GetClusterConfig(path)
85+
if err != nil {
86+
return fmt.Errorf("failed to get a cluster configuration: %w", err)
87+
}
88+
89+
if !instance {
90+
fmt.Print(config.RawConfig.String())
91+
return nil
92+
}
93+
94+
return printInstanceConfig(config, runningCtx.Instances[0].InstName)
95+
}
96+
97+
// showEtcd show a configuration from etcd.
98+
func showEtcd(uri *url.URL) error {
99+
etcdOpts, err := parseEtcdOpts(uri)
100+
if err != nil {
101+
return fmt.Errorf("invalid URL %q: %w", uri, err)
102+
}
103+
104+
etcdcli, err := cluster.ConnectEtcd(etcdOpts)
105+
if err != nil {
106+
return fmt.Errorf("failed to connect to etcd: %w", err)
107+
}
108+
defer etcdcli.Close()
109+
110+
collector := cluster.NewEtcdCollector(
111+
etcdcli, etcdOpts.Prefix, etcdOpts.Timeout)
112+
config, err := collector.Collect()
113+
if err != nil {
114+
return fmt.Errorf("failed to collect a configuration from etcd: %w", err)
115+
}
116+
117+
return printConfig(config, uri.Query().Get("name"))
118+
}
119+
120+
// parseEtcdOpts parses etcd options from a URL.
121+
func parseEtcdOpts(uri *url.URL) (cluster.EtcdOpts, error) {
122+
endpoint := url.URL{
123+
Scheme: uri.Scheme,
124+
Host: uri.Host,
125+
}
126+
values := uri.Query()
127+
opts := cluster.EtcdOpts{
128+
Endpoints: []string{endpoint.String()},
129+
Prefix: uri.Path,
130+
Username: uri.User.Username(),
131+
KeyFile: values.Get("ssl_key_file"),
132+
CertFile: values.Get("ssl_cert_file"),
133+
CaPath: values.Get("ssl_ca_path"),
134+
CaFile: values.Get("ssl_ca_file"),
135+
Timeout: defaultTimeout,
136+
}
137+
if password, ok := uri.User.Password(); ok {
138+
opts.Password = password
139+
}
140+
141+
verifyPeerStr := values.Get("verify_peer")
142+
verifyHostStr := values.Get("verify_host")
143+
timeoutStr := values.Get("timeout")
144+
145+
if verifyPeerStr != "" {
146+
verifyPeerStr = strings.ToLower(verifyPeerStr)
147+
if verify, err := strconv.ParseBool(verifyPeerStr); err == nil {
148+
if verify == false {
149+
opts.SkipHostVerify = true
150+
}
151+
} else {
152+
err = fmt.Errorf("invalid verify_peer, boolean expected: %w", err)
153+
return opts, err
154+
}
155+
}
156+
157+
if verifyHostStr != "" {
158+
verifyHostStr = strings.ToLower(verifyHostStr)
159+
if verify, err := strconv.ParseBool(verifyHostStr); err == nil {
160+
if verify == false {
161+
opts.SkipHostVerify = true
162+
}
163+
} else {
164+
err = fmt.Errorf("invalid verify_host, boolean expected: %w", err)
165+
return opts, err
166+
}
167+
}
168+
169+
if timeoutStr != "" {
170+
if timeout, err := strconv.ParseFloat(timeoutStr, 64); err == nil {
171+
opts.Timeout = time.Duration(timeout * float64(time.Second))
172+
} else {
173+
err = fmt.Errorf("invalid timeout, float expected: %w", err)
174+
return opts, err
175+
}
176+
}
177+
178+
return opts, nil
179+
}
180+
181+
// printConfig prints a cluster configuration or an instance configuration if
182+
// the instance name is specified.
183+
func printConfig(config *cluster.Config, instance string) error {
184+
if instance == "" {
185+
fmt.Println(config.String())
186+
return nil
187+
}
188+
189+
cconfig, err := cluster.MakeClusterConfig(config)
190+
if err != nil {
191+
return fmt.Errorf(
192+
"failed to parse a configuration data as a cluster config: %w",
193+
err)
194+
}
195+
196+
return printInstanceConfig(cconfig, instance)
197+
}
198+
199+
// printInstanceConfig prints an instance configuration in the cluster.
200+
func printInstanceConfig(config cluster.ClusterConfig, instance string) error {
201+
if !cluster.HasInstance(config, instance) {
202+
return fmt.Errorf("instance %q not found", instance)
203+
}
204+
205+
iconfig := cluster.Instantiate(config, instance)
206+
fmt.Print(iconfig.String())
207+
return nil
208+
}

cli/cmd/root.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package cmd
33
import (
44
"errors"
55
"fmt"
6-
"github.com/tarantool/tt/cli/util"
76
"os"
87
"path/filepath"
98

9+
"github.com/tarantool/tt/cli/util"
10+
1011
"github.com/apex/log"
1112
"github.com/apex/log/handlers/cli"
1213
"github.com/fatih/color"
@@ -151,6 +152,7 @@ func NewCmdRoot() *cobra.Command {
151152
NewCatCmd(),
152153
NewPlayCmd(),
153154
NewCartridgeCmd(),
155+
NewClusterCmd(),
154156
NewCoredumpCmd(),
155157
NewRunCmd(),
156158
NewSearchCmd(),

0 commit comments

Comments
 (0)