mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-05 07:45:27 -04:00
Replace mutiple args with flags
AB#1955
This commit is contained in:
parent
469b2ff46c
commit
1189078c5a
10 changed files with 336 additions and 587 deletions
|
@ -4,8 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/cli/azure"
|
"github.com/edgelesssys/constellation/cli/azure"
|
||||||
"github.com/edgelesssys/constellation/cli/cloud/cloudcmd"
|
"github.com/edgelesssys/constellation/cli/cloud/cloudcmd"
|
||||||
|
@ -20,42 +18,39 @@ import (
|
||||||
|
|
||||||
func newCreateCmd() *cobra.Command {
|
func newCreateCmd() *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "create {aws|gcp|azure} C_COUNT W_COUNT TYPE",
|
Use: "create {aws|gcp|azure}",
|
||||||
Short: "Create instances on a cloud platform for your Constellation.",
|
Short: "Create instances on a cloud platform for your Constellation.",
|
||||||
Long: `Create instances on a cloud platform for your Constellation.
|
Long: "Create instances on a cloud platform for your Constellation.",
|
||||||
A Constellation with C_COUNT control-plane nodes and W_COUNT workers is created.
|
|
||||||
TYPE is the instance type used for all instances.`,
|
|
||||||
Args: cobra.MatchAll(
|
Args: cobra.MatchAll(
|
||||||
cobra.ExactArgs(4),
|
cobra.ExactArgs(1),
|
||||||
isIntGreaterZeroArg(1),
|
isCloudProvider(0),
|
||||||
isIntGreaterZeroArg(2),
|
|
||||||
isInstanceTypeForProvider(3, 0),
|
|
||||||
warnAWS(0),
|
warnAWS(0),
|
||||||
),
|
),
|
||||||
ValidArgsFunction: createCompletion,
|
ValidArgsFunction: createCompletion,
|
||||||
RunE: runCreate,
|
RunE: runCreate,
|
||||||
}
|
}
|
||||||
cmd.Flags().String("name", "constell", "Set this flag to create the Constellation with the specified name.")
|
cmd.Flags().String("name", "constell", "Create the Constellation cluster with the specified name.")
|
||||||
cmd.Flags().BoolP("yes", "y", false, "Set this flag to create the Constellation without further confirmation.")
|
cmd.Flags().BoolP("yes", "y", false, "Create the Constellation cluster without further confirmation.")
|
||||||
|
cmd.Flags().IntP("control-plane-nodes", "c", 1, "Number of control-plane nodes.")
|
||||||
|
must(cobra.MarkFlagRequired(cmd.Flags(), "control-plane-nodes"))
|
||||||
|
cmd.Flags().IntP("worker-nodes", "w", 1, "Number of worker nodes.")
|
||||||
|
must(cobra.MarkFlagRequired(cmd.Flags(), "worker-nodes"))
|
||||||
|
cmd.Flags().StringP("instance-type", "t", "", "Instance type of cluster nodes.")
|
||||||
|
must(cmd.RegisterFlagCompletionFunc("instance-type", instanceTypeCompletion))
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreate(cmd *cobra.Command, args []string) error {
|
func runCreate(cmd *cobra.Command, args []string) error {
|
||||||
provider := cloudprovider.FromString(args[0])
|
provider := cloudprovider.FromString(args[0])
|
||||||
countCoord, _ := strconv.Atoi(args[1]) // err checked in args validation
|
|
||||||
countNode, _ := strconv.Atoi(args[2]) // err checked in args validation
|
|
||||||
insType := strings.ToLower(args[3])
|
|
||||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||||
creator := cloudcmd.NewCreator(cmd.OutOrStdout())
|
creator := cloudcmd.NewCreator(cmd.OutOrStdout())
|
||||||
|
|
||||||
return create(cmd, creator, fileHandler, countCoord, countNode, provider, insType)
|
return create(cmd, creator, fileHandler, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler,
|
func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler, provider cloudprovider.Provider,
|
||||||
countCoord, countNode int, provider cloudprovider.Provider, insType string,
|
|
||||||
) (retErr error) {
|
) (retErr error) {
|
||||||
flags, err := parseCreateFlags(cmd)
|
flags, err := parseCreateFlags(cmd, provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -72,8 +67,8 @@ func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler,
|
||||||
if !flags.yes {
|
if !flags.yes {
|
||||||
// Ask user to confirm action.
|
// Ask user to confirm action.
|
||||||
cmd.Printf("The following Constellation will be created:\n")
|
cmd.Printf("The following Constellation will be created:\n")
|
||||||
cmd.Printf("%d control-planes nodes of type %s will be created.\n", countCoord, insType)
|
cmd.Printf("%d control-planes nodes of type %s will be created.\n", flags.controllerCount, flags.insType)
|
||||||
cmd.Printf("%d worker nodes of type %s will be created.\n", countNode, insType)
|
cmd.Printf("%d worker nodes of type %s will be created.\n", flags.workerCount, flags.insType)
|
||||||
ok, err := askToConfirm(cmd, "Do you want to create this Constellation?")
|
ok, err := askToConfirm(cmd, "Do you want to create this Constellation?")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -84,7 +79,7 @@ func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := creator.Create(cmd.Context(), provider, config, flags.name, insType, countCoord, countNode)
|
state, err := creator.Create(cmd.Context(), provider, config, flags.name, flags.insType, flags.controllerCount, flags.workerCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -98,7 +93,34 @@ func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler,
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseCreateFlags parses the flags of the create command.
|
// parseCreateFlags parses the flags of the create command.
|
||||||
func parseCreateFlags(cmd *cobra.Command) (createFlags, error) {
|
func parseCreateFlags(cmd *cobra.Command, provider cloudprovider.Provider) (createFlags, error) {
|
||||||
|
controllerCount, err := cmd.Flags().GetInt("control-plane-nodes")
|
||||||
|
if err != nil {
|
||||||
|
return createFlags{}, err
|
||||||
|
}
|
||||||
|
if controllerCount < constants.MinControllerCount {
|
||||||
|
return createFlags{}, fmt.Errorf("number of control-plane nodes must be at least %d", constants.MinControllerCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
workerCount, err := cmd.Flags().GetInt("worker-nodes")
|
||||||
|
if err != nil {
|
||||||
|
return createFlags{}, err
|
||||||
|
}
|
||||||
|
if workerCount < constants.MinWorkerCount {
|
||||||
|
return createFlags{}, fmt.Errorf("number of worker nodes must be at least %d", constants.MinWorkerCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
insType, err := cmd.Flags().GetString("instance-type")
|
||||||
|
if err != nil {
|
||||||
|
return createFlags{}, err
|
||||||
|
}
|
||||||
|
if insType == "" {
|
||||||
|
insType = defaultInstanceType(provider)
|
||||||
|
}
|
||||||
|
if err := validInstanceTypeForProvider(insType, provider); err != nil {
|
||||||
|
return createFlags{}, err
|
||||||
|
}
|
||||||
|
|
||||||
name, err := cmd.Flags().GetString("name")
|
name, err := cmd.Flags().GetString("name")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return createFlags{}, err
|
return createFlags{}, err
|
||||||
|
@ -109,16 +131,21 @@ func parseCreateFlags(cmd *cobra.Command) (createFlags, error) {
|
||||||
constellationNameLength, len(name), name,
|
constellationNameLength, len(name), name,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
yes, err := cmd.Flags().GetBool("yes")
|
yes, err := cmd.Flags().GetBool("yes")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return createFlags{}, err
|
return createFlags{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
devConfigPath, err := cmd.Flags().GetString("dev-config")
|
devConfigPath, err := cmd.Flags().GetString("dev-config")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return createFlags{}, err
|
return createFlags{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return createFlags{
|
return createFlags{
|
||||||
|
controllerCount: controllerCount,
|
||||||
|
workerCount: workerCount,
|
||||||
|
insType: insType,
|
||||||
name: name,
|
name: name,
|
||||||
devConfigPath: devConfigPath,
|
devConfigPath: devConfigPath,
|
||||||
yes: yes,
|
yes: yes,
|
||||||
|
@ -127,11 +154,26 @@ func parseCreateFlags(cmd *cobra.Command) (createFlags, error) {
|
||||||
|
|
||||||
// createFlags contains the parsed flags of the create command.
|
// createFlags contains the parsed flags of the create command.
|
||||||
type createFlags struct {
|
type createFlags struct {
|
||||||
|
controllerCount int
|
||||||
|
workerCount int
|
||||||
|
insType string
|
||||||
name string
|
name string
|
||||||
devConfigPath string
|
devConfigPath string
|
||||||
yes bool
|
yes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defaultInstanceType returns the default instance type for the given provider.
|
||||||
|
func defaultInstanceType(provider cloudprovider.Provider) string {
|
||||||
|
switch provider {
|
||||||
|
case cloudprovider.GCP:
|
||||||
|
return gcp.InstanceTypes[0]
|
||||||
|
case cloudprovider.Azure:
|
||||||
|
return azure.InstanceTypes[0]
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// checkDirClean checks if files of a previous Constellation are left in the current working dir.
|
// checkDirClean checks if files of a previous Constellation are left in the current working dir.
|
||||||
func checkDirClean(fileHandler file.Handler) error {
|
func checkDirClean(fileHandler file.Handler) error {
|
||||||
if _, err := fileHandler.Stat(constants.StateFilename); !errors.Is(err, fs.ErrNotExist) {
|
if _, err := fileHandler.Stat(constants.StateFilename); !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
@ -153,27 +195,20 @@ func createCompletion(cmd *cobra.Command, args []string, toComplete string) ([]s
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
return []string{"aws", "gcp", "azure"}, cobra.ShellCompDirectiveNoFileComp
|
return []string{"aws", "gcp", "azure"}, cobra.ShellCompDirectiveNoFileComp
|
||||||
case 1:
|
default:
|
||||||
return []string{}, cobra.ShellCompDirectiveNoFileComp
|
return []string{}, cobra.ShellCompDirectiveError
|
||||||
case 2:
|
}
|
||||||
return []string{}, cobra.ShellCompDirectiveNoFileComp
|
}
|
||||||
case 3:
|
|
||||||
var instanceTypeList []string
|
func instanceTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
switch args[0] {
|
if len(args) != 1 {
|
||||||
case "aws":
|
return []string{}, cobra.ShellCompDirectiveError
|
||||||
instanceTypeList = []string{
|
}
|
||||||
"4xlarge",
|
switch args[0] {
|
||||||
"8xlarge",
|
case "gcp":
|
||||||
"12xlarge",
|
return gcp.InstanceTypes, cobra.ShellCompDirectiveNoFileComp
|
||||||
"16xlarge",
|
case "azure":
|
||||||
"24xlarge",
|
return azure.InstanceTypes, cobra.ShellCompDirectiveNoFileComp
|
||||||
}
|
|
||||||
case "gcp":
|
|
||||||
instanceTypeList = gcp.InstanceTypes
|
|
||||||
case "azure":
|
|
||||||
instanceTypeList = azure.InstanceTypes
|
|
||||||
}
|
|
||||||
return instanceTypeList, cobra.ShellCompDirectiveNoFileComp
|
|
||||||
default:
|
default:
|
||||||
return []string{}, cobra.ShellCompDirectiveError
|
return []string{}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -23,29 +24,10 @@ func TestCreateArgumentValidation(t *testing.T) {
|
||||||
args []string
|
args []string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
"gcp valid create 1": {[]string{"gcp", "3", "3", "n2d-standard-2"}, false},
|
"gcp": {[]string{"gcp"}, false},
|
||||||
"gcp valid create 2": {[]string{"gcp", "3", "7", "n2d-standard-16"}, false},
|
"azure": {[]string{"azure"}, false},
|
||||||
"gcp valid create 3": {[]string{"gcp", "1", "2", "n2d-standard-96"}, false},
|
"aws waring": {[]string{"aws"}, true},
|
||||||
"gcp invalid too many arguments": {[]string{"gcp", "3", "2", "n2d-standard-2", "n2d-standard-2"}, true},
|
"too many args": {[]string{"gcp", "1", "2"}, true},
|
||||||
"gcp invalid too many arguments 2": {[]string{"gcp", "3", "2", "n2d-standard-2", "2"}, true},
|
|
||||||
"gcp invalid no control planes": {[]string{"gcp", "0", "1", "n2d-standard-2"}, true},
|
|
||||||
"gcp invalid no workers": {[]string{"gcp", "1", "0", "n2d-standard-2"}, true},
|
|
||||||
"gcp invalid first is no int": {[]string{"gcp", "n2d-standard-2", "1", "n2d-standard-2"}, true},
|
|
||||||
"gcp invalid second is no int": {[]string{"gcp", "3", "n2d-standard-2", "n2d-standard-2"}, true},
|
|
||||||
"gcp invalid third is no size": {[]string{"gcp", "2", "2", "2"}, true},
|
|
||||||
"gcp invalid wrong order": {[]string{"gcp", "n2d-standard-2", "2", "2"}, true},
|
|
||||||
"azure valid create 1": {[]string{"azure", "3", "3", "Standard_DC2as_v5"}, false},
|
|
||||||
"azure valid create 2": {[]string{"azure", "3", "7", "Standard_DC4as_v5"}, false},
|
|
||||||
"azure valid create 3": {[]string{"azure", "1", "2", "Standard_DC8as_v5"}, false},
|
|
||||||
"azure invalid to many arguments": {[]string{"azure", "3", "2", "Standard_DC2as_v5", "Standard_DC2as_v5"}, true},
|
|
||||||
"azure invalid to many arguments 2": {[]string{"azure", "3", "2", "Standard_DC2as_v5", "2"}, true},
|
|
||||||
"azure invalid no control planes": {[]string{"azure", "0", "1", "Standard_DC2as_v5"}, true},
|
|
||||||
"azure invalid no workers": {[]string{"azure", "1", "0", "Standard_DC2as_v5"}, true},
|
|
||||||
"azure invalid first is no int": {[]string{"azure", "Standard_DC2as_v5", "1", "Standard_DC2as_v5"}, true},
|
|
||||||
"azure invalid second is no int": {[]string{"azure", "1", "Standard_DC2as_v5", "Standard_DC2as_v5"}, true},
|
|
||||||
"azure invalid third is no size": {[]string{"azure", "2", "2", "2"}, true},
|
|
||||||
"azure invalid wrong order": {[]string{"azure", "Standard_DC2as_v5", "2", "2"}, true},
|
|
||||||
"aws waring": {[]string{"aws", "1", "2", "4xlarge"}, true},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range testCases {
|
for name, tc := range testCases {
|
||||||
|
@ -72,6 +54,9 @@ func TestCreate(t *testing.T) {
|
||||||
creator *stubCloudCreator
|
creator *stubCloudCreator
|
||||||
provider cloudprovider.Provider
|
provider cloudprovider.Provider
|
||||||
yesFlag bool
|
yesFlag bool
|
||||||
|
controllerCountFlag *int
|
||||||
|
workerCountFlag *int
|
||||||
|
insTypeFlag string
|
||||||
devConfigFlag string
|
devConfigFlag string
|
||||||
nameFlag string
|
nameFlag string
|
||||||
stdin string
|
stdin string
|
||||||
|
@ -87,7 +72,7 @@ func TestCreate(t *testing.T) {
|
||||||
"interactive": {
|
"interactive": {
|
||||||
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
creator: &stubCloudCreator{state: testState},
|
creator: &stubCloudCreator{state: testState},
|
||||||
provider: cloudprovider.GCP,
|
provider: cloudprovider.Azure,
|
||||||
stdin: "yes\n",
|
stdin: "yes\n",
|
||||||
},
|
},
|
||||||
"interactive abort": {
|
"interactive abort": {
|
||||||
|
@ -111,6 +96,43 @@ func TestCreate(t *testing.T) {
|
||||||
nameFlag: strings.Repeat("a", constellationNameLength+1),
|
nameFlag: strings.Repeat("a", constellationNameLength+1),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
"flag control-plane-count invalid": {
|
||||||
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
|
creator: &stubCloudCreator{},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
controllerCountFlag: intPtr(0),
|
||||||
|
workerCountFlag: intPtr(3),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"flag worker-count invalid": {
|
||||||
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
|
creator: &stubCloudCreator{},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
controllerCountFlag: intPtr(3),
|
||||||
|
workerCountFlag: intPtr(-1),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"flag control-plane-count missing": {
|
||||||
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
|
creator: &stubCloudCreator{},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
workerCountFlag: intPtr(3),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"flag worker-count missing": {
|
||||||
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
|
creator: &stubCloudCreator{},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
controllerCountFlag: intPtr(3),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"flag invalid instance-type": {
|
||||||
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
|
creator: &stubCloudCreator{},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
insTypeFlag: "invalid",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
"old state in directory": {
|
"old state in directory": {
|
||||||
setupFs: func(require *require.Assertions) afero.Fs {
|
setupFs: func(require *require.Assertions) afero.Fs {
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
|
@ -180,10 +202,10 @@ func TestCreate(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
cmd := newCreateCmd()
|
cmd := newCreateCmd()
|
||||||
cmd.Flags().String("dev-config", "", "") // register persisten flag manually
|
|
||||||
cmd.SetOut(&bytes.Buffer{})
|
cmd.SetOut(&bytes.Buffer{})
|
||||||
cmd.SetErr(&bytes.Buffer{})
|
cmd.SetErr(&bytes.Buffer{})
|
||||||
cmd.SetIn(bytes.NewBufferString(tc.stdin))
|
cmd.SetIn(bytes.NewBufferString(tc.stdin))
|
||||||
|
cmd.Flags().String("dev-config", "", "") // register persisten flag manually
|
||||||
if tc.yesFlag {
|
if tc.yesFlag {
|
||||||
require.NoError(cmd.Flags().Set("yes", "true"))
|
require.NoError(cmd.Flags().Set("yes", "true"))
|
||||||
}
|
}
|
||||||
|
@ -193,9 +215,19 @@ func TestCreate(t *testing.T) {
|
||||||
if tc.devConfigFlag != "" {
|
if tc.devConfigFlag != "" {
|
||||||
require.NoError(cmd.Flags().Set("dev-config", tc.devConfigFlag))
|
require.NoError(cmd.Flags().Set("dev-config", tc.devConfigFlag))
|
||||||
}
|
}
|
||||||
|
if tc.controllerCountFlag != nil {
|
||||||
|
require.NoError(cmd.Flags().Set("control-plane-nodes", fmt.Sprint(*tc.controllerCountFlag)))
|
||||||
|
}
|
||||||
|
if tc.workerCountFlag != nil {
|
||||||
|
require.NoError(cmd.Flags().Set("worker-nodes", fmt.Sprint(*tc.workerCountFlag)))
|
||||||
|
}
|
||||||
|
if tc.insTypeFlag != "" {
|
||||||
|
require.NoError(cmd.Flags().Set("instance-type", tc.insTypeFlag))
|
||||||
|
}
|
||||||
|
|
||||||
fileHandler := file.NewHandler(tc.setupFs(require))
|
fileHandler := file.NewHandler(tc.setupFs(require))
|
||||||
|
|
||||||
err := create(cmd, tc.creator, fileHandler, 3, 3, tc.provider, "type")
|
err := create(cmd, tc.creator, fileHandler, tc.provider)
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
|
@ -277,38 +309,7 @@ func TestCreateCompletion(t *testing.T) {
|
||||||
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
||||||
},
|
},
|
||||||
"second arg": {
|
"second arg": {
|
||||||
args: []string{"gcp"},
|
args: []string{"gcp", "foo"},
|
||||||
wantResult: []string{},
|
|
||||||
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
"third arg": {
|
|
||||||
args: []string{"gcp", "1"},
|
|
||||||
wantResult: []string{},
|
|
||||||
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
"fourth arg aws": {
|
|
||||||
args: []string{"aws", "1", "2"},
|
|
||||||
wantResult: []string{
|
|
||||||
"4xlarge",
|
|
||||||
"8xlarge",
|
|
||||||
"12xlarge",
|
|
||||||
"16xlarge",
|
|
||||||
"24xlarge",
|
|
||||||
},
|
|
||||||
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
"fourth arg gcp": {
|
|
||||||
args: []string{"gcp", "1", "2"},
|
|
||||||
wantResult: gcp.InstanceTypes,
|
|
||||||
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
"fourth arg azure": {
|
|
||||||
args: []string{"azure", "1", "2"},
|
|
||||||
wantResult: azure.InstanceTypes,
|
|
||||||
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
"fifth arg": {
|
|
||||||
args: []string{"aws", "1", "2", "4xlarge"},
|
|
||||||
wantResult: []string{},
|
wantResult: []string{},
|
||||||
wantShellCD: cobra.ShellCompDirectiveError,
|
wantShellCD: cobra.ShellCompDirectiveError,
|
||||||
},
|
},
|
||||||
|
@ -325,3 +326,48 @@ func TestCreateCompletion(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInstanceTypeCompletion(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
args []string
|
||||||
|
wantResult []string
|
||||||
|
wantShellCD cobra.ShellCompDirective
|
||||||
|
}{
|
||||||
|
"azure": {
|
||||||
|
args: []string{"azure"},
|
||||||
|
wantResult: azure.InstanceTypes,
|
||||||
|
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
||||||
|
},
|
||||||
|
"gcp": {
|
||||||
|
args: []string{"gcp"},
|
||||||
|
wantResult: gcp.InstanceTypes,
|
||||||
|
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
||||||
|
},
|
||||||
|
"empty args": {
|
||||||
|
args: []string{},
|
||||||
|
wantResult: []string{},
|
||||||
|
wantShellCD: cobra.ShellCompDirectiveError,
|
||||||
|
},
|
||||||
|
"unknown provider": {
|
||||||
|
args: []string{"foo"},
|
||||||
|
wantResult: []string{},
|
||||||
|
wantShellCD: cobra.ShellCompDirectiveError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
cmd := &cobra.Command{}
|
||||||
|
result, shellCD := instanceTypeCompletion(cmd, tc.args, "")
|
||||||
|
|
||||||
|
assert.Equal(tc.wantResult, result)
|
||||||
|
assert.Equal(tc.wantShellCD, shellCD)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func intPtr(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ func newInitCmd() *cobra.Command {
|
||||||
cmd.Flags().String("privatekey", "", "path to your private key.")
|
cmd.Flags().String("privatekey", "", "path to your private key.")
|
||||||
cmd.Flags().String("master-secret", "", "path to base64 encoded master secret.")
|
cmd.Flags().String("master-secret", "", "path to base64 encoded master secret.")
|
||||||
cmd.Flags().Bool("wg-autoconfig", false, "enable automatic configuration of WireGuard interface")
|
cmd.Flags().Bool("wg-autoconfig", false, "enable automatic configuration of WireGuard interface")
|
||||||
|
must(cmd.Flags().MarkHidden("wg-autoconfig"))
|
||||||
cmd.Flags().Bool("autoscale", false, "enable Kubernetes cluster-autoscaler")
|
cmd.Flags().Bool("autoscale", false, "enable Kubernetes cluster-autoscaler")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,9 @@ func newRecoverCmd() *cobra.Command {
|
||||||
RunE: runRecover,
|
RunE: runRecover,
|
||||||
}
|
}
|
||||||
cmd.Flags().String("ip", "", "Instance IP address.")
|
cmd.Flags().String("ip", "", "Instance IP address.")
|
||||||
_ = cmd.MarkFlagRequired("ip")
|
must(cmd.MarkFlagRequired("ip"))
|
||||||
cmd.Flags().String("disk-uuid", "", "Disk UUID of the encrypted state disk.")
|
cmd.Flags().String("disk-uuid", "", "Disk UUID of the encrypted state disk.")
|
||||||
_ = cmd.MarkFlagRequired("disk-uuid")
|
must(cmd.MarkFlagRequired("disk-uuid"))
|
||||||
cmd.Flags().String("master-secret", "", "Path to base64 encoded master secret.")
|
cmd.Flags().String("master-secret", "", "Path to base64 encoded master secret.")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,8 @@ func init() {
|
||||||
cobra.EnableCommandSorting = false
|
cobra.EnableCommandSorting = false
|
||||||
// Set output of cmd.Print to stdout. (By default, it's stderr.)
|
// Set output of cmd.Print to stdout. (By default, it's stderr.)
|
||||||
rootCmd.SetOut(os.Stdout)
|
rootCmd.SetOut(os.Stdout)
|
||||||
rootCmd.PersistentFlags().String("dev-config", "", "Set this flag to create the Constellation using settings from a development config.")
|
rootCmd.PersistentFlags().String("dev-config", "", "Set this flag to create the Constellation cluster using settings from a development config.")
|
||||||
|
must(rootCmd.MarkPersistentFlagFilename("dev-config", "json"))
|
||||||
rootCmd.AddCommand(newCreateCmd())
|
rootCmd.AddCommand(newCreateCmd())
|
||||||
rootCmd.AddCommand(newInitCmd())
|
rootCmd.AddCommand(newInitCmd())
|
||||||
rootCmd.AddCommand(newVerifyCmd())
|
rootCmd.AddCommand(newVerifyCmd())
|
||||||
|
@ -62,3 +63,9 @@ func init() {
|
||||||
rootCmd.AddCommand(newTerminateCmd())
|
rootCmd.AddCommand(newTerminateCmd())
|
||||||
rootCmd.AddCommand(newVersionCmd())
|
rootCmd.AddCommand(newVersionCmd())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func must(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,46 +3,13 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/cli/azure"
|
"github.com/edgelesssys/constellation/cli/azure"
|
||||||
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/cli/ec2"
|
|
||||||
"github.com/edgelesssys/constellation/cli/gcp"
|
"github.com/edgelesssys/constellation/cli/gcp"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isIntArg checks if argument at position arg is an integer.
|
|
||||||
func isIntArg(arg int) cobra.PositionalArgs {
|
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
|
||||||
if _, err := strconv.Atoi(args[arg]); err != nil {
|
|
||||||
return fmt.Errorf("argument %d must be an integer", arg)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isIntGreaterArg checks if argument at position arg is an integer and greater i.
|
|
||||||
func isIntGreaterArg(arg int, i int) cobra.PositionalArgs {
|
|
||||||
return cobra.MatchAll(isIntArg(arg), func(cmd *cobra.Command, args []string) error {
|
|
||||||
if v, _ := strconv.Atoi(args[arg]); v <= i {
|
|
||||||
return fmt.Errorf("argument %d must be greater %d, but it's %d", arg, i, v)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIntLessArg(arg int, i int) cobra.PositionalArgs {
|
|
||||||
return cobra.MatchAll(isIntArg(arg), func(cmd *cobra.Command, args []string) error {
|
|
||||||
if v, _ := strconv.Atoi(args[arg]); v >= i {
|
|
||||||
return fmt.Errorf("argument %d must be less %d, but it's %d", arg, i, v)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// warnAWS warns that AWS isn't supported.
|
// warnAWS warns that AWS isn't supported.
|
||||||
func warnAWS(providerPos int) cobra.PositionalArgs {
|
func warnAWS(providerPos int) cobra.PositionalArgs {
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
return func(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -53,60 +20,6 @@ func warnAWS(providerPos int) cobra.PositionalArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isIntGreaterZeroArg checks if argument at position arg is a positive non zero integer.
|
|
||||||
func isIntGreaterZeroArg(arg int) cobra.PositionalArgs {
|
|
||||||
return isIntGreaterArg(arg, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPort(arg int) cobra.PositionalArgs {
|
|
||||||
return cobra.MatchAll(
|
|
||||||
isIntGreaterArg(arg, -1),
|
|
||||||
isIntLessArg(arg, 65536),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIP(arg int) cobra.PositionalArgs {
|
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
|
||||||
if ip := net.ParseIP(args[arg]); ip == nil {
|
|
||||||
return fmt.Errorf("argument %s isn't a valid IP address", args[arg])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isEC2InstanceType checks if argument at position arg is a key in m.
|
|
||||||
// The argument will always be converted to lower case letters.
|
|
||||||
func isEC2InstanceType(arg int) cobra.PositionalArgs {
|
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
|
||||||
if _, ok := ec2.InstanceTypes[strings.ToLower(args[arg])]; !ok {
|
|
||||||
return fmt.Errorf("'%s' isn't an AWS EC2 instance type", args[arg])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGCPInstanceType(arg int) cobra.PositionalArgs {
|
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
|
||||||
for _, instanceType := range gcp.InstanceTypes {
|
|
||||||
if args[arg] == instanceType {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("argument %s isn't a valid GCP instance type", args[arg])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAzureInstanceType(arg int) cobra.PositionalArgs {
|
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
|
||||||
for _, instanceType := range azure.InstanceTypes {
|
|
||||||
if args[arg] == instanceType {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("argument %s isn't a valid Azure instance type", args[arg])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isCloudProvider(arg int) cobra.PositionalArgs {
|
func isCloudProvider(arg int) cobra.PositionalArgs {
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
return func(cmd *cobra.Command, args []string) error {
|
||||||
if provider := cloudprovider.FromString(args[arg]); provider == cloudprovider.Unknown {
|
if provider := cloudprovider.FromString(args[arg]); provider == cloudprovider.Unknown {
|
||||||
|
@ -116,34 +29,23 @@ func isCloudProvider(arg int) cobra.PositionalArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isInstanceTypeForProvider returns a argument validation function that checks if the argument
|
func validInstanceTypeForProvider(insType string, provider cloudprovider.Provider) error {
|
||||||
// at position typePos is a valid instance type for the cloud provider string at position
|
switch provider {
|
||||||
// providerPos.
|
|
||||||
func isInstanceTypeForProvider(typePos, providerPos int) cobra.PositionalArgs {
|
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 2 {
|
|
||||||
return fmt.Errorf("requires 2 arguments, but only %d are provided", len(args))
|
|
||||||
}
|
|
||||||
if len(args) <= typePos {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"%d arguments provided, but index %d of typePos is out of bound",
|
|
||||||
len(args), typePos,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if len(args) <= providerPos {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"%d arguments provided, but index %d of providerPos is out of bound",
|
|
||||||
len(args), providerPos,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch cloudprovider.FromString(args[providerPos]) {
|
|
||||||
case cloudprovider.GCP:
|
case cloudprovider.GCP:
|
||||||
return isGCPInstanceType(typePos)(cmd, args)
|
for _, instanceType := range gcp.InstanceTypes {
|
||||||
case cloudprovider.Azure:
|
if insType == instanceType {
|
||||||
return isAzureInstanceType(typePos)(cmd, args)
|
return nil
|
||||||
default:
|
|
||||||
return fmt.Errorf("argument %s isn't a valid cloud platform", args[providerPos])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return fmt.Errorf("%s isn't a valid GCP instance type", insType)
|
||||||
|
case cloudprovider.Azure:
|
||||||
|
for _, instanceType := range azure.InstanceTypes {
|
||||||
|
if insType == instanceType {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s isn't a valid Azure instance type", insType)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%s isn't a valid cloud platform", provider)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,243 +7,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsIntArg(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
args []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"valid int 1": {[]string{"1"}, false},
|
|
||||||
"valid int 2": {[]string{"42"}, false},
|
|
||||||
"valid int 3": {[]string{"987987498"}, false},
|
|
||||||
"valid int and other args": {[]string{"3", "hello"}, false},
|
|
||||||
"valid int and other args 2": {[]string{"3", "4"}, false},
|
|
||||||
"invalid 1": {[]string{"hello world"}, true},
|
|
||||||
"invalid 2": {[]string{"98798d749f8"}, true},
|
|
||||||
"invalid 3": {[]string{"three"}, true},
|
|
||||||
"invalid 4": {[]string{"0.3"}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testCmd := &cobra.Command{Args: isIntArg(0)}
|
|
||||||
|
|
||||||
err := testCmd.ValidateArgs(tc.args)
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsIntGreaterArg(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
args []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"valid int 1": {[]string{"13"}, false},
|
|
||||||
"valid int 2": {[]string{"42"}, false},
|
|
||||||
"valid int 3": {[]string{"987987498"}, false},
|
|
||||||
"invalid int 1": {[]string{"1"}, true},
|
|
||||||
"invalid int and other args": {[]string{"3", "hello"}, true},
|
|
||||||
"invalid int and other args 2": {[]string{"-14", "4"}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testCmd := &cobra.Command{Args: isIntGreaterArg(0, 12)}
|
|
||||||
|
|
||||||
err := testCmd.ValidateArgs(tc.args)
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsIntGreaterZeroArg(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
args []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"valid int 1": {[]string{"13"}, false},
|
|
||||||
"valid int 2": {[]string{"42"}, false},
|
|
||||||
"valid int 3": {[]string{"987987498"}, false},
|
|
||||||
"invalid": {[]string{"0"}, true},
|
|
||||||
"invalid int 1": {[]string{"-42", "hello"}, true},
|
|
||||||
"invalid int and other args": {[]string{"-9487239847", "4"}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testCmd := &cobra.Command{Args: isIntGreaterZeroArg(0)}
|
|
||||||
|
|
||||||
err := testCmd.ValidateArgs(tc.args)
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsPort(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
args []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"valid port 1": {[]string{"80"}, false},
|
|
||||||
"valid port 2": {[]string{"8080"}, false},
|
|
||||||
"valid port 3": {[]string{"65535"}, false},
|
|
||||||
"invalid port 1": {[]string{"foo"}, true},
|
|
||||||
"invalid port 2": {[]string{"65536"}, true},
|
|
||||||
"invalid port 3": {[]string{"-1"}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testCmd := &cobra.Command{Args: isPort(0)}
|
|
||||||
|
|
||||||
err := testCmd.ValidateArgs(tc.args)
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsIP(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
args []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"valid ip 1": {[]string{"192.168.0.2"}, false},
|
|
||||||
"valid ip 2": {[]string{"127.0.0.1"}, false},
|
|
||||||
"valid ip 3": {[]string{"8.8.8.8"}, false},
|
|
||||||
"invalid ip 1": {[]string{"foo"}, true},
|
|
||||||
"invalid ip 2": {[]string{"foo.bar.baz.1"}, true},
|
|
||||||
"invalid ip 3": {[]string{"800.800.800.800"}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testCmd := &cobra.Command{Args: isIP(0)}
|
|
||||||
|
|
||||||
err := testCmd.ValidateArgs(tc.args)
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsEC2InstanceType(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
args []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"is instance type 1": {[]string{"4xl"}, false},
|
|
||||||
"is instance type 2": {[]string{"12xlarge", "something else"}, false},
|
|
||||||
"isn't instance type 1": {[]string{"notAnInstanceType"}, true},
|
|
||||||
"isn't instance type 2": {[]string{"Hello World!"}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testCmd := &cobra.Command{Args: isEC2InstanceType(0)}
|
|
||||||
|
|
||||||
err := testCmd.ValidateArgs(tc.args)
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsGCPInstanceType(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
args []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"is instance type 1": {[]string{"n2d-standard-4"}, false},
|
|
||||||
"is instance type 2": {[]string{"n2d-standard-16", "something else"}, false},
|
|
||||||
"isn't instance type 1": {[]string{"notAnInstanceType"}, true},
|
|
||||||
"isn't instance type 2": {[]string{"Hello World!"}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testCmd := &cobra.Command{Args: isGCPInstanceType(0)}
|
|
||||||
|
|
||||||
err := testCmd.ValidateArgs(tc.args)
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsAzureInstanceType(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
args []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"is instance type 1": {[]string{"Standard_DC2as_v5"}, false},
|
|
||||||
"is instance type 2": {[]string{"Standard_DC8as_v5", "something else"}, false},
|
|
||||||
"isn't instance type 1": {[]string{"notAnInstanceType"}, true},
|
|
||||||
"isn't instance type 2": {[]string{"Hello World!"}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testCmd := &cobra.Command{Args: isAzureInstanceType(0)}
|
|
||||||
|
|
||||||
err := testCmd.ValidateArgs(tc.args)
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsCloudProvider(t *testing.T) {
|
func TestIsCloudProvider(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
pos int
|
pos int
|
||||||
|
@ -273,41 +36,3 @@ func TestIsCloudProvider(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsInstanceTypeForProvider(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
typePos int
|
|
||||||
providerPos int
|
|
||||||
args []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"valid gcp type 1": {1, 0, []string{"gcp", "n2d-standard-4"}, false},
|
|
||||||
"valid gcp type 2": {1, 0, []string{"gcp", "n2d-standard-16", "foo"}, false},
|
|
||||||
"valid azure type 1": {1, 0, []string{"azure", "Standard_DC2as_v5"}, false},
|
|
||||||
"valid azure type 2": {1, 0, []string{"azure", "Standard_DC8as_v5", "foo"}, false},
|
|
||||||
"mixed order 1": {0, 3, []string{"n2d-standard-4", "", "foo", "gcp"}, false},
|
|
||||||
"mixed order 2": {2, 1, []string{"", "gcp", "n2d-standard-4", "foo", "bar"}, false},
|
|
||||||
"invalid gcp type": {1, 0, []string{"gcp", "foo"}, true},
|
|
||||||
"invalid azure type": {1, 0, []string{"azure", "foo"}, true},
|
|
||||||
"args to short": {2, 0, []string{"foo"}, true},
|
|
||||||
"provider position invalid": {1, 2, []string{"gcp", "n2d-standard-4"}, true},
|
|
||||||
"type position invalid": {2, 0, []string{"gcp", "n2d-standard-4"}, true},
|
|
||||||
"unknown provider": {1, 0, []string{"foo", "n2d-standard-4"}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testCmd := &cobra.Command{Args: isInstanceTypeForProvider(tc.typePos, tc.providerPos)}
|
|
||||||
|
|
||||||
err := testCmd.ValidateArgs(tc.args)
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,12 +4,16 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/cli/cloud/cloudcmd"
|
"github.com/edgelesssys/constellation/cli/cloud/cloudcmd"
|
||||||
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/cli/file"
|
"github.com/edgelesssys/constellation/cli/file"
|
||||||
"github.com/edgelesssys/constellation/cli/proto"
|
"github.com/edgelesssys/constellation/cli/proto"
|
||||||
"github.com/edgelesssys/constellation/internal/config"
|
"github.com/edgelesssys/constellation/internal/config"
|
||||||
|
"github.com/edgelesssys/constellation/internal/constants"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
rpcStatus "google.golang.org/grpc/status"
|
rpcStatus "google.golang.org/grpc/status"
|
||||||
|
@ -17,33 +21,32 @@ import (
|
||||||
|
|
||||||
func newVerifyCmd() *cobra.Command {
|
func newVerifyCmd() *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "verify {azure|gcp} IP PORT",
|
Use: "verify {aws|azure|gcp}",
|
||||||
Short: "Verify the confidential properties of your Constellation.",
|
Short: "Verify the confidential properties of your Constellation cluster.",
|
||||||
Long: "Verify the confidential properties of your Constellation.",
|
Long: "Verify the confidential properties of your Constellation cluster.",
|
||||||
Args: cobra.MatchAll(
|
Args: cobra.MatchAll(
|
||||||
cobra.ExactArgs(3),
|
cobra.ExactArgs(1),
|
||||||
isCloudProvider(0),
|
isCloudProvider(0),
|
||||||
isIP(1),
|
warnAWS(0),
|
||||||
isPort(2),
|
|
||||||
),
|
),
|
||||||
RunE: runVerify,
|
RunE: runVerify,
|
||||||
}
|
}
|
||||||
cmd.Flags().String("owner-id", "", "verify the Constellation using the owner identity derived from the master secret.")
|
cmd.Flags().String("owner-id", "", "Verify using the owner identity derived from the master secret.")
|
||||||
cmd.Flags().String("unique-id", "", "verify the Constellation using the unique cluster identity.")
|
cmd.Flags().String("unique-id", "", "Verify using the unique cluster identity.")
|
||||||
|
cmd.Flags().StringP("node-endpoint", "e", "", "Endpoint of the node to verify. Form: HOST[:PORT]")
|
||||||
|
must(cmd.MarkFlagRequired("node-endpoint"))
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVerify(cmd *cobra.Command, args []string) error {
|
func runVerify(cmd *cobra.Command, args []string) error {
|
||||||
provider := cloudprovider.FromString(args[0])
|
provider := cloudprovider.FromString(args[0])
|
||||||
ip := args[1]
|
|
||||||
port := args[2]
|
|
||||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||||
protoClient := &proto.Client{}
|
protoClient := &proto.Client{}
|
||||||
defer protoClient.Close()
|
defer protoClient.Close()
|
||||||
return verify(cmd.Context(), cmd, provider, ip, port, fileHandler, protoClient)
|
return verify(cmd.Context(), cmd, provider, fileHandler, protoClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func verify(ctx context.Context, cmd *cobra.Command, provider cloudprovider.Provider, ip, port string, fileHandler file.Handler, protoClient protoClient) error {
|
func verify(ctx context.Context, cmd *cobra.Command, provider cloudprovider.Provider, fileHandler file.Handler, protoClient protoClient) error {
|
||||||
flags, err := parseVerifyFlags(cmd)
|
flags, err := parseVerifyFlags(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -66,7 +69,7 @@ func verify(ctx context.Context, cmd *cobra.Command, provider cloudprovider.Prov
|
||||||
cmd.Print(validators.Warnings())
|
cmd.Print(validators.Warnings())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := protoClient.Connect(ip, port, validators.V()); err != nil {
|
if err := protoClient.Connect(flags.nodeHost, flags.nodePort, validators.V()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := protoClient.GetState(ctx); err != nil {
|
if _, err := protoClient.GetState(ctx); err != nil {
|
||||||
|
@ -93,12 +96,27 @@ func parseVerifyFlags(cmd *cobra.Command) (verifyFlags, error) {
|
||||||
return verifyFlags{}, errors.New("neither owner ID nor unique ID provided to verify the Constellation")
|
return verifyFlags{}, errors.New("neither owner ID nor unique ID provided to verify the Constellation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
endpoint, err := cmd.Flags().GetString("node-endpoint")
|
||||||
|
if err != nil {
|
||||||
|
return verifyFlags{}, err
|
||||||
|
}
|
||||||
|
host, port, err := net.SplitHostPort(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(err.Error(), "missing port in address") {
|
||||||
|
return verifyFlags{}, err
|
||||||
|
}
|
||||||
|
host = endpoint
|
||||||
|
port = strconv.Itoa(constants.CoordinatorPort)
|
||||||
|
}
|
||||||
|
|
||||||
devConfigPath, err := cmd.Flags().GetString("dev-config")
|
devConfigPath, err := cmd.Flags().GetString("dev-config")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verifyFlags{}, err
|
return verifyFlags{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return verifyFlags{
|
return verifyFlags{
|
||||||
|
nodeHost: host,
|
||||||
|
nodePort: port,
|
||||||
devConfigPath: devConfigPath,
|
devConfigPath: devConfigPath,
|
||||||
ownerID: ownerID,
|
ownerID: ownerID,
|
||||||
clusterID: clusterID,
|
clusterID: clusterID,
|
||||||
|
@ -106,6 +124,8 @@ func parseVerifyFlags(cmd *cobra.Command) (verifyFlags, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type verifyFlags struct {
|
type verifyFlags struct {
|
||||||
|
nodeHost string
|
||||||
|
nodePort string
|
||||||
ownerID string
|
ownerID string
|
||||||
clusterID string
|
clusterID string
|
||||||
devConfigPath string
|
devConfigPath string
|
||||||
|
@ -117,8 +137,6 @@ func verifyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]s
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
return []string{"gcp", "azure"}, cobra.ShellCompDirectiveNoFileComp
|
return []string{"gcp", "azure"}, cobra.ShellCompDirectiveNoFileComp
|
||||||
case 1, 2:
|
|
||||||
return []string{}, cobra.ShellCompDirectiveNoFileComp
|
|
||||||
default:
|
default:
|
||||||
return []string{}, cobra.ShellCompDirectiveError
|
return []string{}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,9 @@ func TestVerifyCmdArgumentValidation(t *testing.T) {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
"no args": {[]string{}, true},
|
"no args": {[]string{}, true},
|
||||||
"valid azure": {[]string{"azure", "192.0.2.1", "1234"}, false},
|
"valid azure": {[]string{"azure"}, false},
|
||||||
"valid gcp": {[]string{"gcp", "192.0.2.1", "1234"}, false},
|
"valid gcp": {[]string{"gcp"}, false},
|
||||||
"invalid provider": {[]string{"invalid", "192.0.2.1", "1234"}, true},
|
"invalid provider": {[]string{"invalid", "192.0.2.1", "1234"}, true},
|
||||||
"invalid ip": {[]string{"gcp", "invalid", "1234"}, true},
|
|
||||||
"invalid port": {[]string{"gcp", "192.0.2.1", "invalid"}, true},
|
|
||||||
"invalid port 2": {[]string{"gcp", "192.0.2.1", "65536"}, true},
|
|
||||||
"not enough arguments": {[]string{"gcp", "192.0.2.1"}, true},
|
|
||||||
"too many arguments": {[]string{"gcp", "192.0.2.1", "1234", "5678"}, true},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range testCases {
|
for name, tc := range testCases {
|
||||||
|
@ -57,6 +52,7 @@ func TestVerify(t *testing.T) {
|
||||||
setupFs func(*require.Assertions) afero.Fs
|
setupFs func(*require.Assertions) afero.Fs
|
||||||
provider cloudprovider.Provider
|
provider cloudprovider.Provider
|
||||||
protoClient protoClient
|
protoClient protoClient
|
||||||
|
nodeEndpointFlag string
|
||||||
devConfigFlag string
|
devConfigFlag string
|
||||||
ownerIDFlag string
|
ownerIDFlag string
|
||||||
clusterIDFlag string
|
clusterIDFlag string
|
||||||
|
@ -65,30 +61,50 @@ func TestVerify(t *testing.T) {
|
||||||
"gcp": {
|
"gcp": {
|
||||||
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
provider: cloudprovider.GCP,
|
provider: cloudprovider.GCP,
|
||||||
|
nodeEndpointFlag: "192.0.2.1:1234",
|
||||||
ownerIDFlag: zeroBase64,
|
ownerIDFlag: zeroBase64,
|
||||||
protoClient: &stubProtoClient{},
|
protoClient: &stubProtoClient{},
|
||||||
},
|
},
|
||||||
"azure": {
|
"azure": {
|
||||||
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
provider: cloudprovider.Azure,
|
provider: cloudprovider.Azure,
|
||||||
|
nodeEndpointFlag: "192.0.2.1:1234",
|
||||||
ownerIDFlag: zeroBase64,
|
ownerIDFlag: zeroBase64,
|
||||||
protoClient: &stubProtoClient{},
|
protoClient: &stubProtoClient{},
|
||||||
},
|
},
|
||||||
|
"default port": {
|
||||||
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
nodeEndpointFlag: "192.0.2.1",
|
||||||
|
ownerIDFlag: zeroBase64,
|
||||||
|
protoClient: &stubProtoClient{},
|
||||||
|
},
|
||||||
|
"invalid endpoint": {
|
||||||
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
nodeEndpointFlag: ":::::",
|
||||||
|
ownerIDFlag: zeroBase64,
|
||||||
|
protoClient: &stubProtoClient{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
"neither owner id nor cluster id set": {
|
"neither owner id nor cluster id set": {
|
||||||
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
provider: cloudprovider.GCP,
|
provider: cloudprovider.GCP,
|
||||||
|
nodeEndpointFlag: "192.0.2.1:1234",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"dev config file not existing": {
|
"dev config file not existing": {
|
||||||
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
provider: cloudprovider.GCP,
|
provider: cloudprovider.GCP,
|
||||||
ownerIDFlag: zeroBase64,
|
ownerIDFlag: zeroBase64,
|
||||||
|
nodeEndpointFlag: "192.0.2.1:1234",
|
||||||
devConfigFlag: "./file",
|
devConfigFlag: "./file",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"error protoClient Connect": {
|
"error protoClient Connect": {
|
||||||
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
provider: cloudprovider.Azure,
|
provider: cloudprovider.Azure,
|
||||||
|
nodeEndpointFlag: "192.0.2.1:1234",
|
||||||
ownerIDFlag: zeroBase64,
|
ownerIDFlag: zeroBase64,
|
||||||
protoClient: &stubProtoClient{connectErr: someErr},
|
protoClient: &stubProtoClient{connectErr: someErr},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
@ -96,6 +112,7 @@ func TestVerify(t *testing.T) {
|
||||||
"error protoClient GetState": {
|
"error protoClient GetState": {
|
||||||
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
provider: cloudprovider.Azure,
|
provider: cloudprovider.Azure,
|
||||||
|
nodeEndpointFlag: "192.0.2.1:1234",
|
||||||
ownerIDFlag: zeroBase64,
|
ownerIDFlag: zeroBase64,
|
||||||
protoClient: &stubProtoClient{getStateErr: rpcStatus.Error(codes.Internal, "failed")},
|
protoClient: &stubProtoClient{getStateErr: rpcStatus.Error(codes.Internal, "failed")},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
@ -103,6 +120,7 @@ func TestVerify(t *testing.T) {
|
||||||
"error protoClient GetState not rpc": {
|
"error protoClient GetState not rpc": {
|
||||||
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() },
|
||||||
provider: cloudprovider.Azure,
|
provider: cloudprovider.Azure,
|
||||||
|
nodeEndpointFlag: "192.0.2.1:1234",
|
||||||
ownerIDFlag: zeroBase64,
|
ownerIDFlag: zeroBase64,
|
||||||
protoClient: &stubProtoClient{getStateErr: someErr},
|
protoClient: &stubProtoClient{getStateErr: someErr},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
@ -128,10 +146,13 @@ func TestVerify(t *testing.T) {
|
||||||
if tc.clusterIDFlag != "" {
|
if tc.clusterIDFlag != "" {
|
||||||
require.NoError(cmd.Flags().Set("cluster-id", tc.clusterIDFlag))
|
require.NoError(cmd.Flags().Set("cluster-id", tc.clusterIDFlag))
|
||||||
}
|
}
|
||||||
|
if tc.nodeEndpointFlag != "" {
|
||||||
|
require.NoError(cmd.Flags().Set("node-endpoint", tc.nodeEndpointFlag))
|
||||||
|
}
|
||||||
fileHandler := file.NewHandler(tc.setupFs(require))
|
fileHandler := file.NewHandler(tc.setupFs(require))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
err := verify(ctx, cmd, tc.provider, "192.0.2.1", "1234", fileHandler, tc.protoClient)
|
err := verify(ctx, cmd, tc.provider, fileHandler, tc.protoClient)
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
|
@ -156,21 +177,8 @@ func TestVerifyCompletion(t *testing.T) {
|
||||||
wantResult: []string{"gcp", "azure"},
|
wantResult: []string{"gcp", "azure"},
|
||||||
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
||||||
},
|
},
|
||||||
"second arg": {
|
|
||||||
args: []string{"gcp"},
|
|
||||||
toComplete: "192.0.2.1",
|
|
||||||
wantResult: []string{},
|
|
||||||
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
"third arg": {
|
|
||||||
args: []string{"gcp", "192.0.2.1"},
|
|
||||||
toComplete: "443",
|
|
||||||
wantResult: []string{},
|
|
||||||
wantShellCD: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
"additional arg": {
|
"additional arg": {
|
||||||
args: []string{"gcp", "192.0.2.1", "443"},
|
args: []string{"gcp", "foo"},
|
||||||
toComplete: "./file",
|
|
||||||
wantResult: []string{},
|
wantResult: []string{},
|
||||||
wantShellCD: cobra.ShellCompDirectiveError,
|
wantShellCD: cobra.ShellCompDirectiveError,
|
||||||
},
|
},
|
||||||
|
|
|
@ -32,6 +32,13 @@ const (
|
||||||
// Cryptographic constants.
|
// Cryptographic constants.
|
||||||
//
|
//
|
||||||
StateDiskKeyLength = 32
|
StateDiskKeyLength = 32
|
||||||
|
|
||||||
|
//
|
||||||
|
// CLI.
|
||||||
|
//
|
||||||
|
|
||||||
|
MinControllerCount = 1
|
||||||
|
MinWorkerCount = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// CliVersion is the version of the CLI. Left as a separate variable to allow override during build.
|
// CliVersion is the version of the CLI. Left as a separate variable to allow override during build.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue