-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #1048
- Loading branch information
Showing
9 changed files
with
366 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,17 @@ | ||
package cmd | ||
|
||
// Ssl structure groups paths to ssl key files. | ||
type Ssl struct { | ||
// KeyFile path to the private SSL key file (optional) | ||
KeyFile string | ||
// CertFile path to the SSL certificate file (optional). | ||
CertFile string | ||
// CaFile path to the trusted certificate authorities (CA) file (optional) | ||
CaFile string | ||
} | ||
|
||
// ConnectCtx keeps context information for aeon connection. | ||
type ConnectCtx struct { | ||
Ssl Ssl | ||
TransportMode Transport | ||
} |
This file contains 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,52 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"slices" | ||
|
||
"golang.org/x/exp/maps" | ||
) | ||
|
||
// Transport is a type, with a restriction on the list of supported connection modes. | ||
type Transport string | ||
|
||
// String is used both by fmt.Print and by Cobra in help text. | ||
func (t Transport) String() string { | ||
return string(t) | ||
} | ||
|
||
// Type is only used in Cobra help text. | ||
func (t Transport) Type() string { | ||
return "MODE" | ||
} | ||
|
||
const ( | ||
// TransportPlain used as a default insecure transport mode. | ||
TransportPlain Transport = "plain" | ||
|
||
// TransportSsl used for encrypted connection mode. | ||
TransportSsl Transport = "ssl" | ||
) | ||
|
||
// ValidTransport is a list of supported transports with its Cobra helping information. | ||
var ValidTransport = map[Transport]string{ | ||
TransportPlain: "unsafe connection mode", | ||
TransportSsl: "secure encrypted connection", | ||
} | ||
|
||
// Set ensures valid value is applied. | ||
func (t *Transport) Set(v string) error { | ||
_, ok := ValidTransport[Transport(v)] | ||
if !ok { | ||
return fmt.Errorf(`must be %s`, ListValidTransports()) | ||
} | ||
*t = Transport(v) | ||
return nil | ||
} | ||
|
||
// ListValidTransports returns string representation with list of supported transport modes. | ||
func ListValidTransports() string { | ||
ks := maps.Keys(ValidTransport) | ||
slices.Sort(ks) | ||
return fmt.Sprintf("%v", ks) | ||
} |
This file contains 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,49 @@ | ||
package cmd_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"github.com/tarantool/tt/cli/aeon/cmd" | ||
) | ||
|
||
func TestTransport_Set(t *testing.T) { | ||
tests := []struct { | ||
val string | ||
want cmd.Transport | ||
wantErr bool | ||
}{ | ||
{"plain", cmd.Transport("plain"), false}, | ||
{"ssl", cmd.Transport("ssl"), false}, | ||
{"", cmd.Transport(""), true}, | ||
{"mode", cmd.Transport(""), true}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(string(tt.val), func(t *testing.T) { | ||
var tr cmd.Transport | ||
if err := tr.Set(tt.val); (err != nil) != tt.wantErr { | ||
t.Errorf("Transport.Set() error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestTransport_Type(t *testing.T) { | ||
tests := []cmd.Transport{ | ||
"plain", | ||
"ssl", | ||
"", | ||
} | ||
for _, tt := range tests { | ||
t.Run(string(tt), func(t *testing.T) { | ||
if got := tt.Type(); got != "MODE" { | ||
t.Errorf("Transport.Type() = %v, want MODE", got) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestListValidTransports(t *testing.T) { | ||
ts := cmd.ListValidTransports() | ||
require.Equal(t, "[plain ssl]", ts) | ||
} |
This file contains 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,106 @@ | ||
package cmd | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/spf13/cobra" | ||
aeon "github.com/tarantool/tt/cli/aeon/cmd" | ||
"github.com/tarantool/tt/cli/cmdcontext" | ||
"github.com/tarantool/tt/cli/modules" | ||
"github.com/tarantool/tt/cli/util" | ||
libconnect "github.com/tarantool/tt/lib/connect" | ||
) | ||
|
||
var aeonConnectCtx = aeon.ConnectCtx{ | ||
TransportMode: aeon.TransportPlain, | ||
} | ||
|
||
func newAeonConnectCmd() *cobra.Command { | ||
var aeonCmd = &cobra.Command{ | ||
Use: "connect URI", | ||
Short: "Connect to the aeon instance", | ||
Long: "Connect to the aeon instance.\n\n" + | ||
libconnect.EnvCredentialsHelp + "\n\n" + | ||
`tt aeon connect user:pass@localhost:3013`, | ||
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) | ||
}, | ||
} | ||
aeonCmd.Flags().StringVar(&aeonConnectCtx.Ssl.KeyFile, "sslkeyfile", "", | ||
"path to a private SSL key file") | ||
aeonCmd.Flags().StringVar(&aeonConnectCtx.Ssl.CertFile, "sslcertfile", "", | ||
"path to a SSL certificate file") | ||
aeonCmd.Flags().StringVar(&aeonConnectCtx.Ssl.CaFile, "sslcafile", "", | ||
"path to a trusted certificate authorities (CA) file") | ||
|
||
aeonCmd.Flags().Var(&aeonConnectCtx.TransportMode, "transport", | ||
fmt.Sprintf("allowed %s", aeon.ListValidTransports())) | ||
aeonCmd.RegisterFlagCompletionFunc("transport", aeonTransportCompletion) | ||
|
||
return aeonCmd | ||
} | ||
|
||
func aeonTransportCompletion(cmd *cobra.Command, args []string, toComplete string) ( | ||
[]string, cobra.ShellCompDirective) { | ||
suggest := make([]string, 0, len(aeon.ValidTransport)) | ||
for k, v := range aeon.ValidTransport { | ||
suggest = append(suggest, string(k)+"\t"+v) | ||
} | ||
return suggest, cobra.ShellCompDirectiveDefault | ||
} | ||
|
||
// NewAeonCmd() create new aeon command. | ||
func NewAeonCmd() *cobra.Command { | ||
var aeonCmd = &cobra.Command{ | ||
Use: "aeon", | ||
Short: "Manage aeon application", | ||
} | ||
aeonCmd.AddCommand( | ||
newAeonConnectCmd(), | ||
) | ||
return aeonCmd | ||
} | ||
|
||
func aeonConnectValidateArgs(cmd *cobra.Command, args []string) error { | ||
if !cmd.Flags().Changed("transport") && (aeonConnectCtx.Ssl.KeyFile != "" || | ||
aeonConnectCtx.Ssl.CertFile != "" || aeonConnectCtx.Ssl.CaFile != "") { | ||
aeonConnectCtx.TransportMode = aeon.TransportSsl | ||
} | ||
|
||
checkFile := func(path string) bool { | ||
return path == "" || util.IsRegularFile(path) | ||
} | ||
|
||
if aeonConnectCtx.TransportMode != aeon.TransportPlain { | ||
if cmd.Flags().Changed("sslkeyfile") != cmd.Flags().Changed("sslcertfile") { | ||
return errors.New("files Key and Cert must be specified both") | ||
} | ||
|
||
if !checkFile(aeonConnectCtx.Ssl.KeyFile) { | ||
return fmt.Errorf("not valid path to a private SSL key file=%q", | ||
aeonConnectCtx.Ssl.KeyFile) | ||
} | ||
if !checkFile(aeonConnectCtx.Ssl.CertFile) { | ||
return fmt.Errorf("not valid path to an SSL certificate file=%q", | ||
aeonConnectCtx.Ssl.CertFile) | ||
} | ||
if !checkFile(aeonConnectCtx.Ssl.CaFile) { | ||
return fmt.Errorf("not valid path to trusted certificate authorities (CA) file=%q", | ||
aeonConnectCtx.Ssl.CaFile) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func internalAeonConnect(cmdCtx *cmdcontext.CmdCtx, args []string) error { | ||
return nil | ||
} |
This file contains 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 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,138 @@ | ||
#!/usr/bin/env python3 | ||
from pathlib import Path | ||
from subprocess import PIPE, STDOUT, run | ||
|
||
import pytest | ||
|
||
AeonConnectCommand = ("aeon", "connect") | ||
|
||
FormatData = { | ||
"testdata": Path(__file__).parent / "testdata", | ||
} | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"args", | ||
[ | ||
(), | ||
("--transport", "plain"), | ||
("--transport=plain"), | ||
# "plain" mode ignores any ssl flags values. | ||
("--transport", "plain", "--sslkeyfile", "not-exits.key"), | ||
("--transport", "plain", "--sslcertfile", "not-exits.key"), | ||
("--transport", "plain", "--sslcafile", "not-exits.key"), | ||
("--transport", "plain", "--sslkeyfile", "{testdata}/private.key"), | ||
("--transport", "plain", "--sslcertfile", "{testdata}/certfile.key"), | ||
("--transport", "plain", "--sslcafile", "{testdata}/ca.key"), | ||
( | ||
"--sslkeyfile={testdata}/private.key", | ||
"--sslcertfile={testdata}/certfile.key", | ||
), | ||
( | ||
# "ssl" mode require existed path to files. | ||
"--transport=ssl", | ||
"--sslkeyfile={testdata}/private.key", | ||
"--sslcertfile={testdata}/certfile.key", | ||
"--sslcafile={testdata}/ca.key", | ||
), | ||
], | ||
) | ||
def test_cli_arguments_success(tt_cmd, args): | ||
args = (a.format(**FormatData) for a in args) | ||
result = run((tt_cmd, *AeonConnectCommand, *args)) | ||
assert result.returncode == 0 | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"args, error", | ||
[ | ||
( | ||
("--transport", "mode"), | ||
'Error: invalid argument "mode" for "--transport" flag', | ||
), | ||
( | ||
( | ||
"--transport=ssl", | ||
"--sslkeyfile=not-exits.key", | ||
"--sslcertfile={testdata}/certfile.key", | ||
"--sslcafile={testdata}/ca.key", | ||
), | ||
'not valid path to a private SSL key file="not-exits.key"', | ||
), | ||
( | ||
( | ||
"--transport=ssl", | ||
"--sslkeyfile={testdata}/private.key", | ||
"--sslcertfile=not-exits.key", | ||
"--sslcafile={testdata}/ca.key", | ||
), | ||
'not valid path to an SSL certificate file="not-exits.key"', | ||
), | ||
( | ||
( | ||
"--transport=ssl", | ||
"--sslkeyfile={testdata}/private.key", | ||
"--sslcertfile={testdata}/certfile.key", | ||
"--sslcafile=not-exits.key", | ||
), | ||
'not valid path to trusted certificate authorities (CA) file="not-exits.key"', | ||
), | ||
( | ||
("--sslcafile=not-exits.key",), | ||
'not valid path to trusted certificate authorities (CA) file="not-exits.key"', | ||
), | ||
( | ||
( | ||
"--transport=ssl", | ||
"--sslcertfile={testdata}/certfile.key", | ||
"--sslcafile={testdata}/ca.key", | ||
), | ||
"files Key and Cert must be specified both", | ||
), | ||
( | ||
( | ||
"--transport=ssl", | ||
"--sslkeyfile={testdata}/private.key", | ||
"--sslcafile={testdata}/ca.key", | ||
), | ||
"files Key and Cert must be specified both", | ||
), | ||
( | ||
( | ||
"--transport=ssl", | ||
"--sslcertfile={testdata}/certfile.key", | ||
"--sslcafile={testdata}/ca.key", | ||
"--sslkeyfile", | ||
), | ||
"flag needs an argument: --sslkeyfile", | ||
), | ||
( | ||
( | ||
"--transport=ssl", | ||
"--sslkeyfile={testdata}/private.key", | ||
"--sslcafile={testdata}/ca.key", | ||
"--sslcertfile", | ||
), | ||
"flag needs an argument: --sslcertfile", | ||
), | ||
( | ||
( | ||
"--transport=ssl", | ||
"--sslkeyfile={testdata}/private.key", | ||
"--sslcertfile={testdata}/certfile.key", | ||
"--sslcafile", | ||
), | ||
"flag needs an argument: --sslcafile", | ||
), | ||
], | ||
) | ||
def test_cli_arguments_fail(tt_cmd, args, error): | ||
args = (a.format(**FormatData) for a in args) | ||
result = run( | ||
(tt_cmd, *AeonConnectCommand, *args), | ||
stderr=STDOUT, | ||
stdout=PIPE, | ||
text=True, | ||
) | ||
assert result.returncode != 0 | ||
assert error in result.stdout |
This file contains 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 @@ | ||
# trusted certificate authorities (CA) file |
This file contains 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 @@ | ||
# SSL certificate file |
This file contains 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 @@ | ||
# private SSL key file |