Skip to content

Commit

Permalink
aeon: connect dummy implementation
Browse files Browse the repository at this point in the history
Closes #1048
  • Loading branch information
dmyger committed Dec 11, 2024
1 parent 96db068 commit ba43ee0
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 0 deletions.
17 changes: 17 additions & 0 deletions cli/aeon/cmd/connect.go
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
}
52 changes: 52 additions & 0 deletions cli/aeon/cmd/transport.go
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)
}
49 changes: 49 additions & 0 deletions cli/aeon/cmd/transport_test.go
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)
}
106 changes: 106 additions & 0 deletions cli/cmd/aeon.go
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
}
1 change: 1 addition & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ After that tt will be able to manage the application using 'replicaset_example'
NewKillCmd(),
NewLogCmd(),
NewEnableCmd(),
NewAeonCmd(),
)
if err := injectCmds(rootCmd); err != nil {
panic(err.Error())
Expand Down
138 changes: 138 additions & 0 deletions test/integration/aeon/test_aeon.py
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
1 change: 1 addition & 0 deletions test/integration/aeon/testdata/ca.key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# trusted certificate authorities (CA) file
1 change: 1 addition & 0 deletions test/integration/aeon/testdata/certfile.key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# SSL certificate file
1 change: 1 addition & 0 deletions test/integration/aeon/testdata/private.key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# private SSL key file

0 comments on commit ba43ee0

Please sign in to comment.