From 4d9b6a86385b330e3f5834618ef92e6fa83be2aa Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Fri, 28 Jun 2024 15:26:07 -0700 Subject: [PATCH 01/26] WIP --- cmd/drop.go | 23 ----- cmd/dropIndex.go | 118 ---------------------- cmd/flags/client.go | 11 ++- cmd/flags/constants.go | 7 ++ cmd/{create.go => index.go} | 10 +- cmd/{createIndex.go => indexCreate.go} | 132 ++++++++++++------------- cmd/indexDrop.go | 114 +++++++++++++++++++++ cmd/{listIndex.go => indexList.go} | 44 ++++----- cmd/list.go | 23 ----- cmd/role.go | 21 ++++ cmd/rolesList.go | 94 ++++++++++++++++++ cmd/root.go | 4 + cmd/user.go | 21 ++++ cmd/userCreate.go | 110 +++++++++++++++++++++ cmd/userDrop.go | 93 +++++++++++++++++ cmd/userGrant.go | 99 +++++++++++++++++++ cmd/userList.go | 86 ++++++++++++++++ cmd/userNewPassword.go | 109 ++++++++++++++++++++ cmd/userRevoke.go | 108 ++++++++++++++++++++ cmd/utils.go | 26 +++-- cmd/view.go | 28 ++++++ cmd/writers/roleList.go | 35 +++++++ cmd/writers/userList.go | 36 +++++++ 23 files changed, 1082 insertions(+), 270 deletions(-) delete mode 100644 cmd/drop.go delete mode 100644 cmd/dropIndex.go rename cmd/{create.go => index.go} (60%) rename cmd/{createIndex.go => indexCreate.go} (71%) create mode 100644 cmd/indexDrop.go rename cmd/{listIndex.go => indexList.go} (63%) delete mode 100644 cmd/list.go create mode 100644 cmd/role.go create mode 100644 cmd/rolesList.go create mode 100644 cmd/user.go create mode 100644 cmd/userCreate.go create mode 100644 cmd/userDrop.go create mode 100644 cmd/userGrant.go create mode 100644 cmd/userList.go create mode 100644 cmd/userNewPassword.go create mode 100644 cmd/userRevoke.go create mode 100644 cmd/writers/roleList.go create mode 100644 cmd/writers/userList.go diff --git a/cmd/drop.go b/cmd/drop.go deleted file mode 100644 index a5648d9..0000000 --- a/cmd/drop.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright © 2024 NAME HERE -*/ -package cmd - -import ( - "github.com/spf13/cobra" -) - -// dropCmd represents the drop command -var dropCmd = &cobra.Command{ - Use: "drop", - Short: "A parent command for dropping resources", - Long: `A parent command for dropping resources. It currently only supports dropping indexes. - For example: - export ASVEC_HOST=:5000 - asvec drop index -i myindex -n test - `, -} - -func init() { - rootCmd.AddCommand(dropCmd) -} diff --git a/cmd/dropIndex.go b/cmd/dropIndex.go deleted file mode 100644 index d0c6306..0000000 --- a/cmd/dropIndex.go +++ /dev/null @@ -1,118 +0,0 @@ -/* -Copyright © 2024 NAME HERE -*/ -package cmd - -import ( - "asvec/cmd/flags" - "context" - "fmt" - "log/slog" - "time" - - commonFlags "github.com/aerospike/tools-common-go/flags" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" -) - -//nolint:govet // Padding not a concern for a CLI -var dropIndexFlags = &struct { - clientFlags flags.ClientFlags - namespace string - sets []string - indexName string - timeout time.Duration -}{ - clientFlags: *flags.NewClientFlags(), -} - -func newDropIndexFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} - flagSet.StringVarP(&dropIndexFlags.namespace, flags.Namespace, "n", "", commonFlags.DefaultWrapHelpString("The namespace for the index.")) //nolint:lll // For readability - flagSet.StringSliceVarP(&dropIndexFlags.sets, flags.Sets, "s", nil, commonFlags.DefaultWrapHelpString("The sets for the index.")) //nolint:lll // For readability - flagSet.StringVarP(&dropIndexFlags.indexName, flags.IndexName, "i", "", commonFlags.DefaultWrapHelpString("The name of the index.")) //nolint:lll // For readability - flagSet.DurationVar(&dropIndexFlags.timeout, flags.Timeout, time.Second*5, commonFlags.DefaultWrapHelpString("The distance metric for the index.")) //nolint:lll // For readability - flagSet.AddFlagSet(dropIndexFlags.clientFlags.NewClientFlagSet()) - - return flagSet -} - -var dropIndexRequiredFlags = []string{ - flags.Namespace, - flags.IndexName, -} - -// dropIndexCmd represents the dropIndex command -func newDropIndexCommand() *cobra.Command { - return &cobra.Command{ - Use: "index", - Short: "A command for dropping indexes", - Long: `A command for dropping indexes. Deleting an index will free up - storage but will also disable vector search on your data. - - For example: - export ASVEC_HOST=:5000 - asvec drop index -i myindex -n test - `, - PreRunE: func(_ *cobra.Command, _ []string) error { - if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { - return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) - } - - return nil - }, - RunE: func(_ *cobra.Command, _ []string) error { - logger.Debug("parsed flags", - append(dropIndexFlags.clientFlags.NewSLogAttr(), - slog.String(flags.Namespace, dropIndexFlags.namespace), - slog.Any(flags.Sets, dropIndexFlags.sets), - slog.String(flags.IndexName, dropIndexFlags.indexName), - slog.Duration(flags.Timeout, dropIndexFlags.timeout), - )..., - ) - - adminClient, err := createClientFromFlags(&dropIndexFlags.clientFlags, dropIndexFlags.timeout) - if err != nil { - return err - } - defer adminClient.Close() - - if !confirm(fmt.Sprintf( - "Are you sure you want to drop the index %s on field %s?", - nsAndSetString( - createIndexFlags.namespace, - createIndexFlags.sets, - ), - createIndexFlags.vectorField, - )) { - return nil - } - - ctx, cancel := context.WithTimeout(context.Background(), dropIndexFlags.timeout) - defer cancel() - - err = adminClient.IndexDrop(ctx, dropIndexFlags.namespace, dropIndexFlags.indexName) - if err != nil { - logger.Error("unable to drop index", slog.Any("error", err)) - return err - } - - view.Printf("Successfully dropped index %s.%s", dropIndexFlags.namespace, dropIndexFlags.indexName) - return nil - }, - } -} - -func init() { - dropIndexCmd := newDropIndexCommand() - dropCmd.AddCommand(dropIndexCmd) - dropIndexCmd.Flags().AddFlagSet(newDropIndexFlagSet()) - - for _, flag := range dropIndexRequiredFlags { - err := dropIndexCmd.MarkFlagRequired(flag) - if err != nil { - panic(err) - } - } -} diff --git a/cmd/flags/client.go b/cmd/flags/client.go index 45d410e..bea7a98 100644 --- a/cmd/flags/client.go +++ b/cmd/flags/client.go @@ -3,6 +3,7 @@ package flags import ( "fmt" "log/slog" + "time" commonFlags "github.com/aerospike/tools-common-go/flags" "github.com/spf13/pflag" @@ -14,6 +15,7 @@ type ClientFlags struct { ListenerName StringOptionalFlag User StringOptionalFlag Password commonFlags.PasswordFlag + Timeout time.Duration TLSFlags } @@ -32,21 +34,28 @@ func (cf *ClientFlags) NewClientFlagSet() *pflag.FlagSet { flagSet.VarP(&cf.ListenerName, ListenerName, "l", commonFlags.DefaultWrapHelpString("The listener to ask the AVS server for as configured in the AVS server. Likely required for cloud deployments.")) //nolint:lll // For readability flagSet.VarP(&cf.User, User, "U", commonFlags.DefaultWrapHelpString("The AVS user to authenticate with.")) //nolint:lll // For readability flagSet.VarP(&cf.Password, Password, "P", commonFlags.DefaultWrapHelpString("The AVS password for the specified user.")) //nolint:lll // For readability + flagSet.DurationVar(&cf.Timeout, Timeout, time.Second*5, commonFlags.DefaultWrapHelpString("The timeout to use for each request to AVS")) //nolint:lll // For readability flagSet.AddFlagSet(cf.NewTLSFlagSet(commonFlags.DefaultWrapHelpString)) return flagSet } func (cf *ClientFlags) NewSLogAttr() []any { + logPass := "" + if cf.Password.String() != "" { + logPass = "*" + } + return []any{slog.String(Host, cf.Host.String()), slog.String(Seeds, cf.Seeds.String()), slog.String(ListenerName, cf.ListenerName.String()), slog.String(User, cf.User.String()), - slog.String(Password, cf.Password.String()), + slog.String(Password, logPass), slog.Bool(TLSCaFile, cf.TLSRootCAFile != nil), slog.Bool(TLSCaPath, cf.TLSRootCAPath != nil), slog.Bool(TLSCertFile, cf.TLSCertFile != nil), slog.Bool(TLSKeyFile, cf.TLSKeyFile != nil), slog.Bool(TLSKeyFilePass, cf.TLSKeyFilePass != nil), + slog.Duration(Timeout, cf.Timeout), } } diff --git a/cmd/flags/constants.go b/cmd/flags/constants.go index 888f67b..0b3161d 100644 --- a/cmd/flags/constants.go +++ b/cmd/flags/constants.go @@ -7,6 +7,13 @@ const ( ListenerName = "listener-name" User = "user" Password = "password" + Username = "name" + NewUser = "create-user" + DropUser = "drop-user" + GrantUser = "grant-user" + RevokeUser = "revoke-user" + NewPassword = "new-password" + Roles = "roles" Namespace = "namespace" Sets = "sets" IndexName = "index-name" diff --git a/cmd/create.go b/cmd/index.go similarity index 60% rename from cmd/create.go rename to cmd/index.go index aee73bc..2f900d2 100644 --- a/cmd/create.go +++ b/cmd/index.go @@ -7,17 +7,17 @@ import ( "github.com/spf13/cobra" ) -// createCmd represents the create command -var createCmd = &cobra.Command{ - Use: "create", +// indexCmd represents the create command +var indexCmd = &cobra.Command{ + Use: "index", Short: "A parent command for creating resources", Long: `A parent command for creating resources. It currently only supports creating indexes. For example: export ASVEC_HOST=:5000 - asvec create index -i myindex -n test -s testset -f vector-field -d 256 -m COSINE + asvec index ---help `, } func init() { - rootCmd.AddCommand(createCmd) + rootCmd.AddCommand(indexCmd) } diff --git a/cmd/createIndex.go b/cmd/indexCreate.go similarity index 71% rename from cmd/createIndex.go rename to cmd/indexCreate.go index d178b20..a57c8c9 100644 --- a/cmd/createIndex.go +++ b/cmd/indexCreate.go @@ -9,7 +9,6 @@ import ( "fmt" "log/slog" "strings" - "time" "github.com/aerospike/avs-client-go/protos" commonFlags "github.com/aerospike/tools-common-go/flags" @@ -19,7 +18,7 @@ import ( ) //nolint:govet // Padding not a concern for a CLI -var createIndexFlags = &struct { +var indexCreateFlags = &struct { clientFlags flags.ClientFlags namespace string sets []string @@ -36,7 +35,6 @@ var createIndexFlags = &struct { hnswBatchMaxRecords flags.Uint32OptionalFlag hnswBatchInterval flags.Uint32OptionalFlag hnswBatchEnabled flags.BoolOptionalFlag - timeout time.Duration }{ clientFlags: *flags.NewClientFlags(), storageNamespace: flags.StringOptionalFlag{}, @@ -49,30 +47,29 @@ var createIndexFlags = &struct { hnswBatchEnabled: flags.BoolOptionalFlag{}, } -func newCreateIndexFlagSet() *pflag.FlagSet { +func newIndexCreateFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} //nolint:lll // For readability - flagSet.StringVarP(&createIndexFlags.namespace, flags.Namespace, "n", "", commonFlags.DefaultWrapHelpString("The namespace for the index.")) //nolint:lll // For readability - flagSet.StringSliceVarP(&createIndexFlags.sets, flags.Sets, "s", nil, commonFlags.DefaultWrapHelpString("The sets for the index.")) //nolint:lll // For readability - flagSet.StringVarP(&createIndexFlags.indexName, flags.IndexName, "i", "", commonFlags.DefaultWrapHelpString("The name of the index.")) //nolint:lll // For readability - flagSet.StringVarP(&createIndexFlags.vectorField, flags.VectorField, "f", "", commonFlags.DefaultWrapHelpString("The name of the vector field.")) //nolint:lll // For readability - flagSet.Uint32VarP(&createIndexFlags.dimensions, flags.Dimension, "d", 0, commonFlags.DefaultWrapHelpString("The dimension of the vector field.")) //nolint:lll // For readability - flagSet.VarP(&createIndexFlags.distanceMetric, flags.DistanceMetric, "m", commonFlags.DefaultWrapHelpString(fmt.Sprintf("The distance metric for the index. Valid values: %s", strings.Join(flags.DistanceMetricEnum(), ", ")))) //nolint:lll // For readability - flagSet.StringToStringVar(&createIndexFlags.indexMeta, flags.IndexMeta, nil, commonFlags.DefaultWrapHelpString("The distance metric for the index.")) //nolint:lll // For readability - flagSet.DurationVar(&createIndexFlags.timeout, flags.Timeout, time.Second*5, commonFlags.DefaultWrapHelpString("The distance metric for the index.")) //nolint:lll // For readability - flagSet.Var(&createIndexFlags.storageNamespace, flags.StorageNamespace, commonFlags.DefaultWrapHelpString("Optional storage namespace where the index is stored. Defaults to the index namespace.")) //nolint:lll // For readability //nolint:lll // For readability - flagSet.Var(&createIndexFlags.storageSet, flags.StorageSet, commonFlags.DefaultWrapHelpString("Optional storage set where the index is stored. Defaults to the index name.")) //nolint:lll // For readability //nolint:lll // For readability - flagSet.Var(&createIndexFlags.hnswMaxEdges, flags.MaxEdges, commonFlags.DefaultWrapHelpString("Maximum number bi-directional links per HNSW vertex. Greater values of 'm' in general provide better recall for data with high dimensionality, while lower values work well for data with lower dimensionality. The storage space required for the index increases proportionally with 'm'")) //nolint:lll // For readability - flagSet.Var(&createIndexFlags.hnswConstructionEf, flags.ConstructionEf, commonFlags.DefaultWrapHelpString("The number of candidate nearest neighbors shortlisted during index creation. Larger values provide better recall at the cost of longer index update times. The default is 100.")) //nolint:lll // For readability - flagSet.Var(&createIndexFlags.hnswEf, flags.Ef, commonFlags.DefaultWrapHelpString("The default number of candidate nearest neighbors shortlisted during search. Larger values provide better recall at the cost of longer search times. The default is 100.")) //nolint:lll // For readability - flagSet.Var(&createIndexFlags.hnswBatchMaxRecords, flags.BatchMaxRecords, commonFlags.DefaultWrapHelpString("Maximum number of records to fit in a batch. The default value is 10000.")) //nolint:lll // For readability - flagSet.Var(&createIndexFlags.hnswBatchInterval, flags.BatchInterval, commonFlags.DefaultWrapHelpString("The maximum amount of time in milliseconds to wait before finalizing a batch. The default value is 10000.")) //nolint:lll // For readability - flagSet.Var(&createIndexFlags.hnswBatchEnabled, flags.BatchEnabled, commonFlags.DefaultWrapHelpString("Enables batching for index updates. Default is true meaning batching is enabled.")) //nolint:lll // For readability - flagSet.AddFlagSet(createIndexFlags.clientFlags.NewClientFlagSet()) + flagSet.StringVarP(&indexCreateFlags.namespace, flags.Namespace, "n", "", commonFlags.DefaultWrapHelpString("The namespace for the index.")) //nolint:lll // For readability + flagSet.StringSliceVarP(&indexCreateFlags.sets, flags.Sets, "s", nil, commonFlags.DefaultWrapHelpString("The sets for the index.")) //nolint:lll // For readability + flagSet.StringVarP(&indexCreateFlags.indexName, flags.IndexName, "i", "", commonFlags.DefaultWrapHelpString("The name of the index.")) //nolint:lll // For readability + flagSet.StringVarP(&indexCreateFlags.vectorField, flags.VectorField, "f", "", commonFlags.DefaultWrapHelpString("The name of the vector field.")) //nolint:lll // For readability + flagSet.Uint32VarP(&indexCreateFlags.dimensions, flags.Dimension, "d", 0, commonFlags.DefaultWrapHelpString("The dimension of the vector field.")) //nolint:lll // For readability + flagSet.VarP(&indexCreateFlags.distanceMetric, flags.DistanceMetric, "m", commonFlags.DefaultWrapHelpString(fmt.Sprintf("The distance metric for the index. Valid values: %s", strings.Join(flags.DistanceMetricEnum(), ", ")))) //nolint:lll // For readability + flagSet.StringToStringVar(&indexCreateFlags.indexMeta, flags.IndexMeta, nil, commonFlags.DefaultWrapHelpString("The distance metric for the index.")) //nolint:lll // For readability //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.storageNamespace, flags.StorageNamespace, commonFlags.DefaultWrapHelpString("Optional storage namespace where the index is stored. Defaults to the index namespace.")) //nolint:lll // For readability //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.storageSet, flags.StorageSet, commonFlags.DefaultWrapHelpString("Optional storage set where the index is stored. Defaults to the index name.")) //nolint:lll // For readability //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswMaxEdges, flags.MaxEdges, commonFlags.DefaultWrapHelpString("Maximum number bi-directional links per HNSW vertex. Greater values of 'm' in general provide better recall for data with high dimensionality, while lower values work well for data with lower dimensionality. The storage space required for the index increases proportionally with 'm'")) //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswConstructionEf, flags.ConstructionEf, commonFlags.DefaultWrapHelpString("The number of candidate nearest neighbors shortlisted during index creation. Larger values provide better recall at the cost of longer index update times. The default is 100.")) //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswEf, flags.Ef, commonFlags.DefaultWrapHelpString("The default number of candidate nearest neighbors shortlisted during search. Larger values provide better recall at the cost of longer search times. The default is 100.")) //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswBatchMaxRecords, flags.BatchMaxRecords, commonFlags.DefaultWrapHelpString("Maximum number of records to fit in a batch. The default value is 10000.")) //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswBatchInterval, flags.BatchInterval, commonFlags.DefaultWrapHelpString("The maximum amount of time in milliseconds to wait before finalizing a batch. The default value is 10000.")) //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswBatchEnabled, flags.BatchEnabled, commonFlags.DefaultWrapHelpString("Enables batching for index updates. Default is true meaning batching is enabled.")) //nolint:lll // For readability + flagSet.AddFlagSet(indexCreateFlags.clientFlags.NewClientFlagSet()) return flagSet } -var createIndexRequiredFlags = []string{ +var indexCreateRequiredFlags = []string{ flags.Namespace, flags.IndexName, flags.VectorField, @@ -81,9 +78,9 @@ var createIndexRequiredFlags = []string{ } // createIndexCmd represents the createIndex command -func newCreateIndexCmd() *cobra.Command { +func newIndexCreateCmd() *cobra.Command { return &cobra.Command{ - Use: "index", + Use: "create", Short: "A command for creating indexes", Long: `A command for creating indexes. An index is required to enable vector search on your data. The index tells AVS where your data is located, @@ -93,7 +90,7 @@ func newCreateIndexCmd() *cobra.Command { For example: export ASVEC_HOST=:5000 - asvec create index -i myindex -n test -s testset -d 256 -m COSINE --vector-field vector \ + asvec index create -i myindex -n test -s testset -d 256 -m COSINE --vector-field vector \ --storage-namespace test --hnsw-batch-enabled false `, PreRunE: func(_ *cobra.Command, _ []string) error { @@ -105,27 +102,26 @@ func newCreateIndexCmd() *cobra.Command { }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", - append(createIndexFlags.clientFlags.NewSLogAttr(), - slog.String(flags.Namespace, createIndexFlags.namespace), - slog.Any(flags.Sets, createIndexFlags.sets), - slog.String(flags.IndexName, createIndexFlags.indexName), - slog.String(flags.VectorField, createIndexFlags.vectorField), - slog.Uint64(flags.Dimension, uint64(createIndexFlags.dimensions)), - slog.Any(flags.IndexMeta, createIndexFlags.indexMeta), - slog.String(flags.DistanceMetric, createIndexFlags.distanceMetric.String()), - slog.Duration(flags.Timeout, createIndexFlags.timeout), - slog.Any(flags.StorageNamespace, createIndexFlags.storageNamespace.String()), - slog.Any(flags.StorageSet, createIndexFlags.storageSet.String()), - slog.Any(flags.MaxEdges, createIndexFlags.hnswMaxEdges.String()), - slog.Any(flags.Ef, createIndexFlags.hnswEf), - slog.Any(flags.ConstructionEf, createIndexFlags.hnswConstructionEf.String()), - slog.Any(flags.BatchMaxRecords, createIndexFlags.hnswBatchMaxRecords.String()), - slog.Any(flags.BatchInterval, createIndexFlags.hnswBatchInterval.String()), - slog.Any(flags.BatchEnabled, createIndexFlags.hnswBatchEnabled.String()), + append(indexCreateFlags.clientFlags.NewSLogAttr(), + slog.String(flags.Namespace, indexCreateFlags.namespace), + slog.Any(flags.Sets, indexCreateFlags.sets), + slog.String(flags.IndexName, indexCreateFlags.indexName), + slog.String(flags.VectorField, indexCreateFlags.vectorField), + slog.Uint64(flags.Dimension, uint64(indexCreateFlags.dimensions)), + slog.Any(flags.IndexMeta, indexCreateFlags.indexMeta), + slog.String(flags.DistanceMetric, indexCreateFlags.distanceMetric.String()), + slog.Any(flags.StorageNamespace, indexCreateFlags.storageNamespace.String()), + slog.Any(flags.StorageSet, indexCreateFlags.storageSet.String()), + slog.Any(flags.MaxEdges, indexCreateFlags.hnswMaxEdges.String()), + slog.Any(flags.Ef, indexCreateFlags.hnswEf), + slog.Any(flags.ConstructionEf, indexCreateFlags.hnswConstructionEf.String()), + slog.Any(flags.BatchMaxRecords, indexCreateFlags.hnswBatchMaxRecords.String()), + slog.Any(flags.BatchInterval, indexCreateFlags.hnswBatchInterval.String()), + slog.Any(flags.BatchEnabled, indexCreateFlags.hnswBatchEnabled.String()), )..., ) - adminClient, err := createClientFromFlags(&createIndexFlags.clientFlags, createIndexFlags.timeout) + adminClient, err := createClientFromFlags(&indexCreateFlags.clientFlags) if err != nil { return err } @@ -133,23 +129,23 @@ func newCreateIndexCmd() *cobra.Command { // Inverted to make it easier to understand var hnswBatchDisabled *bool - if createIndexFlags.hnswBatchEnabled.Val != nil { - bd := !(*createIndexFlags.hnswBatchEnabled.Val) + if indexCreateFlags.hnswBatchEnabled.Val != nil { + bd := !(*indexCreateFlags.hnswBatchEnabled.Val) hnswBatchDisabled = &bd } indexStorage := &protos.IndexStorage{ - Namespace: createIndexFlags.storageNamespace.Val, - Set: createIndexFlags.storageSet.Val, + Namespace: indexCreateFlags.storageNamespace.Val, + Set: indexCreateFlags.storageSet.Val, } hnswParams := &protos.HnswParams{ - M: createIndexFlags.hnswMaxEdges.Val, - Ef: createIndexFlags.hnswEf.Val, - EfConstruction: createIndexFlags.hnswConstructionEf.Val, + M: indexCreateFlags.hnswMaxEdges.Val, + Ef: indexCreateFlags.hnswEf.Val, + EfConstruction: indexCreateFlags.hnswConstructionEf.Val, BatchingParams: &protos.HnswBatchingParams{ - MaxRecords: createIndexFlags.hnswBatchMaxRecords.Val, - Interval: createIndexFlags.hnswBatchInterval.Val, + MaxRecords: indexCreateFlags.hnswBatchMaxRecords.Val, + Interval: indexCreateFlags.hnswBatchInterval.Val, Disabled: hnswBatchDisabled, }, } @@ -157,27 +153,27 @@ func newCreateIndexCmd() *cobra.Command { if !confirm(fmt.Sprintf( "Are you sure you want to create the index %s field %s?", nsAndSetString( - createIndexFlags.namespace, - createIndexFlags.sets, + indexCreateFlags.namespace, + indexCreateFlags.sets, ), - createIndexFlags.vectorField, + indexCreateFlags.vectorField, )) { return nil } - ctx, cancel := context.WithTimeout(context.Background(), createIndexFlags.timeout) + ctx, cancel := context.WithTimeout(context.Background(), indexCreateFlags.clientFlags.Timeout) defer cancel() err = adminClient.IndexCreate( ctx, - createIndexFlags.namespace, - createIndexFlags.sets, - createIndexFlags.indexName, - createIndexFlags.vectorField, - createIndexFlags.dimensions, - protos.VectorDistanceMetric(protos.VectorDistanceMetric_value[createIndexFlags.distanceMetric.String()]), + indexCreateFlags.namespace, + indexCreateFlags.sets, + indexCreateFlags.indexName, + indexCreateFlags.vectorField, + indexCreateFlags.dimensions, + protos.VectorDistanceMetric(protos.VectorDistanceMetric_value[indexCreateFlags.distanceMetric.String()]), hnswParams, - createIndexFlags.indexMeta, + indexCreateFlags.indexMeta, indexStorage, ) if err != nil { @@ -185,23 +181,23 @@ func newCreateIndexCmd() *cobra.Command { return err } - view.Printf("Successfully created index %s.%s", createIndexFlags.namespace, createIndexFlags.indexName) + view.Printf("Successfully created index %s.%s", indexCreateFlags.namespace, indexCreateFlags.indexName) return nil }, } } func init() { - createIndexCmd := newCreateIndexCmd() - createCmd.AddCommand(createIndexCmd) + createIndexCmd := newIndexCreateCmd() + indexCmd.AddCommand(createIndexCmd) // TODO: Add custom template for usage to take into account terminal width // Ex: https://github.com/sigstore/cosign/pull/3011/files - flagSet := newCreateIndexFlagSet() + flagSet := newIndexCreateFlagSet() createIndexCmd.Flags().AddFlagSet(flagSet) - for _, flag := range createIndexRequiredFlags { + for _, flag := range indexCreateRequiredFlags { err := createIndexCmd.MarkFlagRequired(flag) if err != nil { panic(err) diff --git a/cmd/indexDrop.go b/cmd/indexDrop.go new file mode 100644 index 0000000..f25c7e4 --- /dev/null +++ b/cmd/indexDrop.go @@ -0,0 +1,114 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "asvec/cmd/flags" + "context" + "fmt" + "log/slog" + + commonFlags "github.com/aerospike/tools-common-go/flags" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +//nolint:govet // Padding not a concern for a CLI +var indexDropFlags = &struct { + clientFlags flags.ClientFlags + namespace string + sets []string + indexName string +}{ + clientFlags: *flags.NewClientFlags(), +} + +func newIndexDropFlagSet() *pflag.FlagSet { + flagSet := &pflag.FlagSet{} + flagSet.StringVarP(&indexDropFlags.namespace, flags.Namespace, "n", "", commonFlags.DefaultWrapHelpString("The namespace for the index.")) //nolint:lll // For readability + flagSet.StringSliceVarP(&indexDropFlags.sets, flags.Sets, "s", nil, commonFlags.DefaultWrapHelpString("The sets for the index.")) //nolint:lll // For readability + flagSet.StringVarP(&indexDropFlags.indexName, flags.IndexName, "i", "", commonFlags.DefaultWrapHelpString("The name of the index.")) //nolint:lll // For readability + flagSet.AddFlagSet(indexDropFlags.clientFlags.NewClientFlagSet()) + + return flagSet +} + +var indexDropRequiredFlags = []string{ + flags.Namespace, + flags.IndexName, +} + +// dropIndexCmd represents the dropIndex command +func newIndexDropCommand() *cobra.Command { + return &cobra.Command{ + Use: "drop", + Short: "A command for dropping indexes", + Long: `A command for dropping indexes. Deleting an index will free up + storage but will also disable vector search on your data. + + For example: + export ASVEC_HOST=:5000 + asvec index drop -i myindex -n test + `, + PreRunE: func(_ *cobra.Command, _ []string) error { + if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { + return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) + } + + return nil + }, + RunE: func(_ *cobra.Command, _ []string) error { + logger.Debug("parsed flags", + append(indexDropFlags.clientFlags.NewSLogAttr(), + slog.String(flags.Namespace, indexDropFlags.namespace), + slog.Any(flags.Sets, indexDropFlags.sets), + slog.String(flags.IndexName, indexDropFlags.indexName), + )..., + ) + + adminClient, err := createClientFromFlags(&indexDropFlags.clientFlags) + if err != nil { + return err + } + defer adminClient.Close() + + if !confirm(fmt.Sprintf( + "Are you sure you want to drop the index %s on field %s?", + nsAndSetString( + indexCreateFlags.namespace, + indexCreateFlags.sets, + ), + indexCreateFlags.vectorField, + )) { + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), indexDropFlags.clientFlags.Timeout) + defer cancel() + + err = adminClient.IndexDrop(ctx, indexDropFlags.namespace, indexDropFlags.indexName) + if err != nil { + logger.Error("unable to drop index", slog.Any("error", err)) + return err + } + + view.Printf("Successfully dropped index %s.%s", indexDropFlags.namespace, indexDropFlags.indexName) + return nil + }, + } +} + +func init() { + indexDropCmd := newIndexDropCommand() + indexCmd.AddCommand(indexDropCmd) + indexDropCmd.Flags().AddFlagSet(newIndexDropFlagSet()) + + for _, flag := range indexDropRequiredFlags { + err := indexDropCmd.MarkFlagRequired(flag) + if err != nil { + panic(err) + } + } +} diff --git a/cmd/listIndex.go b/cmd/indexList.go similarity index 63% rename from cmd/listIndex.go rename to cmd/indexList.go index 5bd334e..13ec41c 100644 --- a/cmd/listIndex.go +++ b/cmd/indexList.go @@ -9,7 +9,6 @@ import ( "fmt" "log/slog" "sync" - "time" "github.com/aerospike/avs-client-go/protos" commonFlags "github.com/aerospike/tools-common-go/flags" @@ -18,36 +17,34 @@ import ( "github.com/spf13/viper" ) -var listIndexFlags = &struct { +var indexListFlags = &struct { clientFlags flags.ClientFlags verbose bool - timeout time.Duration }{ clientFlags: *flags.NewClientFlags(), } -func newListIndexFlagSet() *pflag.FlagSet { +func newIndexListFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} - flagSet.BoolVarP(&listIndexFlags.verbose, flags.Verbose, "v", false, commonFlags.DefaultWrapHelpString("Print detailed index information.")) //nolint:lll // For readability - flagSet.DurationVar(&listIndexFlags.timeout, flags.Timeout, time.Second*5, commonFlags.DefaultWrapHelpString("The distance metric for the index.")) //nolint:lll // For readability - flagSet.AddFlagSet(listIndexFlags.clientFlags.NewClientFlagSet()) + flagSet.BoolVarP(&indexListFlags.verbose, flags.Verbose, "v", false, commonFlags.DefaultWrapHelpString("Print detailed index information.")) //nolint:lll // For readability + flagSet.AddFlagSet(indexListFlags.clientFlags.NewClientFlagSet()) return flagSet } -var listIndexRequiredFlags = []string{} +var indexListRequiredFlags = []string{} // listIndexCmd represents the listIndex command -func newListIndexCmd() *cobra.Command { +func newIndexListCmd() *cobra.Command { return &cobra.Command{ - Use: "index", - Aliases: []string{"indexes"}, + Use: "list", + Aliases: []string{"ls"}, Short: "A command for listing indexes", Long: fmt.Sprintf(`A command for displaying useful information about AVS indexes. To display additional index information use the --%s flag. For example: export ASVEC_HOST=:5000 - asvec list index + asvec index list `, flags.Verbose), PreRunE: func(_ *cobra.Command, _ []string) error { if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { @@ -58,19 +55,18 @@ func newListIndexCmd() *cobra.Command { }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", - append(listIndexFlags.clientFlags.NewSLogAttr(), - slog.Bool(flags.Verbose, listIndexFlags.verbose), - slog.Duration(flags.Timeout, listIndexFlags.timeout), + append(indexListFlags.clientFlags.NewSLogAttr(), + slog.Bool(flags.Verbose, indexListFlags.verbose), )..., ) - adminClient, err := createClientFromFlags(&listIndexFlags.clientFlags, listIndexFlags.timeout) + adminClient, err := createClientFromFlags(&indexListFlags.clientFlags) if err != nil { return err } defer adminClient.Close() - ctx, cancel := context.WithTimeout(context.Background(), listIndexFlags.timeout) + ctx, cancel := context.WithTimeout(context.Background(), indexListFlags.clientFlags.Timeout) defer cancel() indexList, err := adminClient.IndexList(ctx) @@ -83,7 +79,7 @@ func newListIndexCmd() *cobra.Command { cancel() - ctx, cancel = context.WithTimeout(context.Background(), listIndexFlags.timeout) + ctx, cancel = context.WithTimeout(context.Background(), indexListFlags.clientFlags.Timeout) defer cancel() wg := sync.WaitGroup{} @@ -110,7 +106,7 @@ func newListIndexCmd() *cobra.Command { logger.Debug("server index list", slog.String("response", indexList.String())) - view.PrintIndexes(indexList, indexStatusList, listIndexFlags.verbose) + view.PrintIndexes(indexList, indexStatusList, indexListFlags.verbose) return nil }, @@ -118,13 +114,13 @@ func newListIndexCmd() *cobra.Command { } func init() { - listIndexCmd := newListIndexCmd() + indexListCmd := newIndexListCmd() - listCmd.AddCommand(listIndexCmd) - listIndexCmd.Flags().AddFlagSet(newListIndexFlagSet()) + indexCmd.AddCommand(indexListCmd) + indexListCmd.Flags().AddFlagSet(newIndexListFlagSet()) - for _, flag := range listIndexRequiredFlags { - err := listIndexCmd.MarkFlagRequired(flag) + for _, flag := range indexListRequiredFlags { + err := indexListCmd.MarkFlagRequired(flag) if err != nil { panic(err) } diff --git a/cmd/list.go b/cmd/list.go deleted file mode 100644 index 7f649df..0000000 --- a/cmd/list.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright © 2024 NAME HERE -*/ -package cmd - -import ( - "github.com/spf13/cobra" -) - -// listCmd represents the list command -var listCmd = &cobra.Command{ - Use: "list", - Short: "A parent command for listing resources", - Long: `A parent command for listings resources. It currently only supports listing indexes. - For example: - export ASVEC_HOST=:5000 - asvec list index - `, -} - -func init() { - rootCmd.AddCommand(listCmd) -} diff --git a/cmd/role.go b/cmd/role.go new file mode 100644 index 0000000..a2bfcc2 --- /dev/null +++ b/cmd/role.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// userCmd represents the create command +var roleCmd = &cobra.Command{ + Use: "role", + Aliases: []string{"roles"}, + Short: "A parent command for viewing roles.", + Long: `A parent command for listing, creating, dropping, and granting roles to users. + For example: + export ASVEC_HOST=:5000 + asvec user list + `, +} + +func init() { + rootCmd.AddCommand(roleCmd) +} diff --git a/cmd/rolesList.go b/cmd/rolesList.go new file mode 100644 index 0000000..5c0711b --- /dev/null +++ b/cmd/rolesList.go @@ -0,0 +1,94 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "asvec/cmd/flags" + "context" + "fmt" + "log/slog" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +var rolesListFlags = &struct { + clientFlags flags.ClientFlags +}{ + clientFlags: *flags.NewClientFlags(), +} + +func newRoleListFlagSet() *pflag.FlagSet { + flagSet := &pflag.FlagSet{} + + flagSet.AddFlagSet(rolesListFlags.clientFlags.NewClientFlagSet()) + + return flagSet +} + +var roleListRequiredFlags = []string{} + +// listIndexCmd represents the listIndex command +func newRoleListCmd() *cobra.Command { + return &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "A command for listing users", + Long: `A command for displaying useful information about AVS users. + For example: + export ASVEC_HOST=:5000 + asvec user list + `, + PreRunE: func(_ *cobra.Command, _ []string) error { + if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { + return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) + } + + return nil + }, + RunE: func(_ *cobra.Command, _ []string) error { + logger.Debug("parsed flags", + rolesListFlags.clientFlags.NewSLogAttr()..., + ) + + adminClient, err := createClientFromFlags(&rolesListFlags.clientFlags) + if err != nil { + return err + } + defer adminClient.Close() + + ctx, cancel := context.WithTimeout(context.Background(), rolesListFlags.clientFlags.Timeout) + defer cancel() + + userList, err := adminClient.ListRoles(ctx) + if err != nil { + logger.Error("failed to list roles", slog.Any("error", err)) + return err + } + + cancel() + + logger.Debug("server role list", slog.String("response", userList.String())) + + view.PrintRoles(userList) + + return nil + }, + } +} + +func init() { + roleListCmd := newRoleListCmd() + + roleCmd.AddCommand(roleListCmd) + roleListCmd.Flags().AddFlagSet(newRoleListFlagSet()) + + for _, flag := range roleListRequiredFlags { + err := roleListCmd.MarkFlagRequired(flag) + if err != nil { + panic(err) + } + } +} diff --git a/cmd/root.go b/cmd/root.go index 4b38a74..990abb7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -48,6 +48,10 @@ var rootCmd = &cobra.Command{ handler.Enabled(context.Background(), lvl.Level()) } + if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { + return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) + } + cmd.SilenceUsage = true if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { diff --git a/cmd/user.go b/cmd/user.go new file mode 100644 index 0000000..1cda286 --- /dev/null +++ b/cmd/user.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// userCmd represents the create command +var userCmd = &cobra.Command{ + Use: "user", + Aliases: []string{"users"}, + Short: "A parent command for viewing and configuring users.", + Long: `A parent command for listing, creating, dropping, and granting roles to users. + For example: + export ASVEC_HOST=:5000 + asvec user list + `, +} + +func init() { + rootCmd.AddCommand(userCmd) +} diff --git a/cmd/userCreate.go b/cmd/userCreate.go new file mode 100644 index 0000000..bf6b40f --- /dev/null +++ b/cmd/userCreate.go @@ -0,0 +1,110 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "asvec/cmd/flags" + "context" + "log/slog" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +//nolint:govet // Padding not a concern for a CLI +var userCreateFlags = &struct { + clientFlags flags.ClientFlags + newUsername string + newPassword string + roles []string +}{ + clientFlags: *flags.NewClientFlags(), +} + +func newUserCreateFlagSet() *pflag.FlagSet { + flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability + flagSet.AddFlagSet(userCreateFlags.clientFlags.NewClientFlagSet()) + flagSet.StringVar(&userCreateFlags.newUsername, flags.NewUser, "", "TODO") + flagSet.StringVar(&userCreateFlags.newPassword, flags.NewPassword, "", "TODO") + flagSet.StringSliceVar(&userCreateFlags.roles, flags.Roles, []string{}, "TODO") + + return flagSet +} + +var userCreateRequiredFlags = []string{ + flags.NewUser, + flags.Roles, +} + +// createUserCmd represents the createIndex command +func newUserCreateCmd() *cobra.Command { + return &cobra.Command{ + Use: "create", + Short: "A command for creating users", + Long: `A command for creating users. TODO + + For example: + export ASVEC_HOST=127.0.0.1:5000 ASVEC_USER=admin + asvec user create --new-user foo --roles read-write + `, + RunE: func(_ *cobra.Command, _ []string) error { + logger.Debug("parsed flags", + append( + userCreateFlags.clientFlags.NewSLogAttr(), + slog.String(flags.NewUser, userCreateFlags.newUsername), + slog.Any(flags.Roles, userCreateFlags.roles), + )..., + ) + + adminClient, err := createClientFromFlags(&userCreateFlags.clientFlags) + if err != nil { + return err + } + defer adminClient.Close() + + if userCreateFlags.newPassword == "" { + userCreateFlags.newPassword, err = passwordPrompt("New User Password: ") + if err != nil { + logger.Error("failed to read new password", slog.Any("error", err)) + return err + } + } + + ctx, cancel := context.WithTimeout(context.Background(), userCreateFlags.clientFlags.Timeout) + defer cancel() + + err = adminClient.CreateUser( + ctx, + userCreateFlags.newUsername, + userCreateFlags.newPassword, + userCreateFlags.roles, + ) + if err != nil { + logger.Error("unable to create user", slog.String("user", userCreateFlags.newUsername), slog.Any("error", err)) + return err + } + + view.Printf("Successfully created user %s", userCreateFlags.newUsername) + return nil + }, + } +} + +func init() { + userCreateCmd := newUserCreateCmd() + userCmd.AddCommand(userCreateCmd) + + // TODO: Add custom template for usage to take into account terminal width + // Ex: https://github.com/sigstore/cosign/pull/3011/files + + flagSet := newUserCreateFlagSet() + userCreateCmd.Flags().AddFlagSet(flagSet) + + for _, flag := range userCreateRequiredFlags { + err := userCreateCmd.MarkFlagRequired(flag) + if err != nil { + panic(err) + } + } +} diff --git a/cmd/userDrop.go b/cmd/userDrop.go new file mode 100644 index 0000000..26e185c --- /dev/null +++ b/cmd/userDrop.go @@ -0,0 +1,93 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "asvec/cmd/flags" + "context" + "log/slog" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +//nolint:govet // Padding not a concern for a CLI +var userDropFlags = &struct { + clientFlags flags.ClientFlags + dropUser string +}{ + clientFlags: *flags.NewClientFlags(), +} + +func newUserDropFlagSet() *pflag.FlagSet { + flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability + flagSet.AddFlagSet(userDropFlags.clientFlags.NewClientFlagSet()) + flagSet.StringVar(&userDropFlags.dropUser, flags.DropUser, "", "TODO") + + return flagSet +} + +var userDropRequiredFlags = []string{ + flags.DropUser, +} + +func newUserDropCmd() *cobra.Command { + return &cobra.Command{ + Use: "drop", + Short: "A command for dropping users", + Long: `A command for dropping users. TODO + + For example: + export ASVEC_HOST=127.0.0.1:5000 ASVEC_USER=admin + asvec user drop --new-user foo --roles read-write + `, + RunE: func(_ *cobra.Command, _ []string) error { + logger.Debug("parsed flags", + append( + userDropFlags.clientFlags.NewSLogAttr(), + slog.String(flags.NewUser, userDropFlags.dropUser), + )..., + ) + + adminClient, err := createClientFromFlags(&userDropFlags.clientFlags) + if err != nil { + return err + } + defer adminClient.Close() + + ctx, cancel := context.WithTimeout(context.Background(), userDropFlags.clientFlags.Timeout) + defer cancel() + + err = adminClient.DropUser( + ctx, + userDropFlags.dropUser, + ) + if err != nil { + logger.Error("unable to create user", slog.String("user", userDropFlags.dropUser), slog.Any("error", err)) + return err + } + + view.Printf("Successfully dropped user %s", userDropFlags.dropUser) + return nil + }, + } +} + +func init() { + userDropCmd := newUserDropCmd() + userCmd.AddCommand(userDropCmd) + + // TODO: Add custom template for usage to take into account terminal width + // Ex: https://github.com/sigstore/cosign/pull/3011/files + + flagSet := newUserDropFlagSet() + userDropCmd.Flags().AddFlagSet(flagSet) + + for _, flag := range userDropRequiredFlags { + err := userDropCmd.MarkFlagRequired(flag) + if err != nil { + panic(err) + } + } +} diff --git a/cmd/userGrant.go b/cmd/userGrant.go new file mode 100644 index 0000000..10445fa --- /dev/null +++ b/cmd/userGrant.go @@ -0,0 +1,99 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "asvec/cmd/flags" + "context" + "log/slog" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +//nolint:govet // Padding not a concern for a CLI +var userGrantFlags = &struct { + clientFlags flags.ClientFlags + grantUser string + roles []string +}{ + clientFlags: *flags.NewClientFlags(), +} + +func newUserGrantFlagSet() *pflag.FlagSet { + flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability + flagSet.AddFlagSet(userGrantFlags.clientFlags.NewClientFlagSet()) + flagSet.StringVar(&userGrantFlags.grantUser, flags.GrantUser, "", "TODO") + flagSet.StringSliceVar(&userGrantFlags.roles, flags.Roles, []string{}, "TODO") + + return flagSet +} + +var userGrantRequiredFlags = []string{ + flags.GrantUser, + flags.Roles, +} + +func newUserGrantCmd() *cobra.Command { + return &cobra.Command{ + Use: "grant", + Short: "A command for granting users roles", + Long: `A command for creating users. TODO + + For example: + export ASVEC_HOST=127.0.0.1:5000 ASVEC_USER=admin + asvec user grant --grant-user foo --roles admin + `, + RunE: func(_ *cobra.Command, _ []string) error { + logger.Debug("parsed flags", + append( + userGrantFlags.clientFlags.NewSLogAttr(), + slog.String(flags.NewUser, userGrantFlags.grantUser), + slog.Any(flags.Roles, userGrantFlags.roles), + )..., + ) + + adminClient, err := createClientFromFlags(&userGrantFlags.clientFlags) + if err != nil { + return err + } + defer adminClient.Close() + + ctx, cancel := context.WithTimeout(context.Background(), userGrantFlags.clientFlags.Timeout) + defer cancel() + + err = adminClient.GrantRoles( + ctx, + userGrantFlags.grantUser, + userGrantFlags.roles, + ) + if err != nil { + logger.Error("unable to grant user roles", slog.String("user", userGrantFlags.grantUser), slog.Any("roles", userGrantFlags.roles), slog.Any("error", err)) + return err + } + + view.Printf("Successfully granted user %s roles %s", userGrantFlags.grantUser, strings.Join(userGrantFlags.roles, ", ")) + return nil + }, + } +} + +func init() { + userGrantCmd := newUserGrantCmd() + userCmd.AddCommand(userGrantCmd) + + // TODO: Add custom template for usage to take into account terminal width + // Ex: https://github.com/sigstore/cosign/pull/3011/files + + flagSet := newUserGrantFlagSet() + userGrantCmd.Flags().AddFlagSet(flagSet) + + for _, flag := range userGrantRequiredFlags { + err := userGrantCmd.MarkFlagRequired(flag) + if err != nil { + panic(err) + } + } +} diff --git a/cmd/userList.go b/cmd/userList.go new file mode 100644 index 0000000..b6e523e --- /dev/null +++ b/cmd/userList.go @@ -0,0 +1,86 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "asvec/cmd/flags" + "context" + "log/slog" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var userListFlags = &struct { + clientFlags flags.ClientFlags +}{ + clientFlags: *flags.NewClientFlags(), +} + +func newUserListFlagSet() *pflag.FlagSet { + flagSet := &pflag.FlagSet{} + + flagSet.AddFlagSet(userListFlags.clientFlags.NewClientFlagSet()) + + return flagSet +} + +var userListRequiredFlags = []string{} + +// listIndexCmd represents the listIndex command +func newUserListCmd() *cobra.Command { + return &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "A command for listing users", + Long: `A command for displaying useful information about AVS users. + For example: + export ASVEC_HOST=:5000 + asvec user list + `, + RunE: func(_ *cobra.Command, _ []string) error { + logger.Debug("parsed flags", + userListFlags.clientFlags.NewSLogAttr()..., + ) + + adminClient, err := createClientFromFlags(&userListFlags.clientFlags) + if err != nil { + return err + } + defer adminClient.Close() + + ctx, cancel := context.WithTimeout(context.Background(), userListFlags.clientFlags.Timeout) + defer cancel() + + userList, err := adminClient.ListUsers(ctx) + if err != nil { + logger.Error("failed to list users", slog.Any("error", err)) + return err + } + + cancel() + + logger.Debug("server user list", slog.String("response", userList.String())) + + view.PrintUsers(userList) + view.Print("Use 'role list' to view available roles") + + return nil + }, + } +} + +func init() { + userListCmd := newUserListCmd() + + userCmd.AddCommand(userListCmd) + userListCmd.Flags().AddFlagSet(newUserListFlagSet()) + + for _, flag := range userListRequiredFlags { + err := userListCmd.MarkFlagRequired(flag) + if err != nil { + panic(err) + } + } +} diff --git a/cmd/userNewPassword.go b/cmd/userNewPassword.go new file mode 100644 index 0000000..7e267c0 --- /dev/null +++ b/cmd/userNewPassword.go @@ -0,0 +1,109 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "asvec/cmd/flags" + "context" + "log/slog" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +//nolint:govet // Padding not a concern for a CLI +var userNewPassFlags = &struct { + clientFlags flags.ClientFlags + username string + password string + roles []string +}{ + clientFlags: *flags.NewClientFlags(), +} + +func newUserNewPassFlagSet() *pflag.FlagSet { + flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability + flagSet.AddFlagSet(userNewPassFlags.clientFlags.NewClientFlagSet()) + flagSet.StringVar(&userNewPassFlags.username, flags.Username, "", "TODO") + flagSet.StringVar(&userNewPassFlags.password, flags.NewPassword, "", "TODO") + + return flagSet +} + +var userNewPassRequiredFlags = []string{ + flags.Username, +} + +// createUserCmd represents the createIndex command +func newUserNewPasswordCmd() *cobra.Command { + return &cobra.Command{ + Use: "new-password", + Aliases: []string{"new-pass"}, + Short: "A command for creating users", + Long: `A command for creating users. TODO + + For example: + export ASVEC_HOST=127.0.0.1:5000 ASVEC_USER=admin + asvec user new-password --name + `, + + RunE: func(_ *cobra.Command, _ []string) error { + logger.Debug("parsed flags", + append( + userNewPassFlags.clientFlags.NewSLogAttr(), + slog.String(flags.NewUser, userNewPassFlags.username), + slog.Any(flags.Roles, userNewPassFlags.roles), + )..., + ) + + adminClient, err := createClientFromFlags(&userNewPassFlags.clientFlags) + if err != nil { + return err + } + defer adminClient.Close() + + if userNewPassFlags.password == "" { + userNewPassFlags.password, err = passwordPrompt("New Password: ") + if err != nil { + logger.Error("failed to read new password", slog.Any("error", err)) + return err + } + } + + ctx, cancel := context.WithTimeout(context.Background(), userNewPassFlags.clientFlags.Timeout) + defer cancel() + + err = adminClient.UpdateCredentials( + ctx, + userNewPassFlags.username, + userNewPassFlags.password, + ) + if err != nil { + logger.Error("unable to update user credentials", slog.String("user", userNewPassFlags.username), slog.Any("error", err)) + return err + } + + view.Printf("Successfully updated user %s's credentials", userNewPassFlags.username) + return nil + }, + } +} + +func init() { + userNewPassCmd := newUserNewPasswordCmd() + userCmd.AddCommand(userNewPassCmd) + + // TODO: Add custom template for usage to take into account terminal width + // Ex: https://github.com/sigstore/cosign/pull/3011/files + + flagSet := newUserNewPassFlagSet() + userNewPassCmd.Flags().AddFlagSet(flagSet) + + for _, flag := range userNewPassRequiredFlags { + err := userNewPassCmd.MarkFlagRequired(flag) + if err != nil { + panic(err) + } + } +} diff --git a/cmd/userRevoke.go b/cmd/userRevoke.go new file mode 100644 index 0000000..586e4af --- /dev/null +++ b/cmd/userRevoke.go @@ -0,0 +1,108 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "asvec/cmd/flags" + "context" + "fmt" + "log/slog" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +//nolint:govet // Padding not a concern for a CLI +var userRevokeFlags = &struct { + clientFlags flags.ClientFlags + revokeUser string + roles []string +}{ + clientFlags: *flags.NewClientFlags(), +} + +func newUserRevokeFlagSet() *pflag.FlagSet { + flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability + flagSet.AddFlagSet(userRevokeFlags.clientFlags.NewClientFlagSet()) + flagSet.StringVar(&userRevokeFlags.revokeUser, flags.RevokeUser, "", "TODO") + flagSet.StringSliceVar(&userRevokeFlags.roles, flags.Roles, []string{}, "TODO") + + return flagSet +} + +var userRevokeRequiredFlags = []string{ + flags.RevokeUser, + flags.Roles, +} + +func newUserRevokeCmd() *cobra.Command { + return &cobra.Command{ + Use: "revoke", + Short: "A command for revoking users roles", + Long: `A command for revoking users roles. TODO + + For example: + export ASVEC_HOST=127.0.0.1:5000 ASVEC_USER=admin + asvec user revoke --revoke-user foo --roles admin + `, + PreRunE: func(_ *cobra.Command, _ []string) error { + if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { + return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) + } + + return nil + }, + RunE: func(_ *cobra.Command, _ []string) error { + logger.Debug("parsed flags", + append( + userRevokeFlags.clientFlags.NewSLogAttr(), + slog.String(flags.NewUser, userRevokeFlags.revokeUser), + slog.Any(flags.Roles, userRevokeFlags.roles), + )..., + ) + + adminClient, err := createClientFromFlags(&userRevokeFlags.clientFlags) + if err != nil { + return err + } + defer adminClient.Close() + + ctx, cancel := context.WithTimeout(context.Background(), userRevokeFlags.clientFlags.Timeout) + defer cancel() + + err = adminClient.RevokeRoles( + ctx, + userRevokeFlags.revokeUser, + userRevokeFlags.roles, + ) + if err != nil { + logger.Error("unable to revoke user roles", slog.String("user", userRevokeFlags.revokeUser), slog.Any("roles", userRevokeFlags.roles), slog.Any("error", err)) + return err + } + + view.Printf("Successfully revoked user %s's roles %s", userRevokeFlags.revokeUser, strings.Join(userRevokeFlags.roles, ", ")) + return nil + }, + } +} + +func init() { + userRevokeCmd := newUserRevokeCmd() + userCmd.AddCommand(userRevokeCmd) + + // TODO: Add custom template for usage to take into account terminal width + // Ex: https://github.com/sigstore/cosign/pull/3011/files + + flagSet := newUserRevokeFlagSet() + userRevokeCmd.Flags().AddFlagSet(flagSet) + + for _, flag := range userRevokeRequiredFlags { + err := userRevokeCmd.MarkFlagRequired(flag) + if err != nil { + panic(err) + } + } +} diff --git a/cmd/utils.go b/cmd/utils.go index 71a062b..c6220c5 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -7,17 +7,29 @@ import ( "log/slog" "os" "strings" - "time" "golang.org/x/term" avs "github.com/aerospike/avs-client-go" ) -func createClientFromFlags(clientFlags *flags.ClientFlags, connectTimeout time.Duration) (*avs.AdminClient, error) { +func passwordPrompt(prompt string) (string, error) { + fmt.Print(prompt) + + bytePassword, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + return "", err + } + + fmt.Println() + + return string(bytePassword), nil +} + +func createClientFromFlags(clientFlags *flags.ClientFlags) (*avs.AdminClient, error) { hosts, isLoadBalancer := parseBothHostSeedsFlag(clientFlags.Seeds, clientFlags.Host) - ctx, cancel := context.WithTimeout(context.Background(), connectTimeout) + ctx, cancel := context.WithTimeout(context.Background(), clientFlags.Timeout) defer cancel() tlsConfig, err := clientFlags.NewTLSConfig() @@ -32,15 +44,13 @@ func createClientFromFlags(clientFlags *flags.ClientFlags, connectTimeout time.D strPass := clientFlags.Password.String() password = &strPass } else { - fmt.Print("Enter Password: ") - bytePassword, err := term.ReadPassword(int(os.Stdin.Fd())) + pass, err := passwordPrompt("Enter Password: ") if err != nil { logger.Error("failed to read password", slog.Any("error", err)) return nil, err } - fmt.Println() // Print a newline after the password input - strPass := string(bytePassword) - password = &strPass + + password = &pass } } diff --git a/cmd/view.go b/cmd/view.go index c3ccc59..2520555 100644 --- a/cmd/view.go +++ b/cmd/view.go @@ -66,3 +66,31 @@ func (v *View) PrintIndexes( t.Render() } + +func (v *View) getUserListWriter() *writers.UserTableWriter { + return writers.NewUserTableWriter(v.writer, v.logger) +} + +func (v *View) PrintUsers(usersList *protos.ListUsersResponse) { + t := v.getUserListWriter() + + for _, user := range usersList.GetUsers() { + t.AppendUserRow(user) + } + + t.Render() +} + +func (v *View) getRoleListWriter() *writers.RoleTableWriter { + return writers.NewRoleTableWriter(v.writer, v.logger) +} + +func (v *View) PrintRoles(usersList *protos.ListRolesResponse) { + t := v.getRoleListWriter() + + for _, role := range usersList.GetRoles() { + t.AppendRoleRow(role) + } + + t.Render() +} diff --git a/cmd/writers/roleList.go b/cmd/writers/roleList.go new file mode 100644 index 0000000..3a46ce1 --- /dev/null +++ b/cmd/writers/roleList.go @@ -0,0 +1,35 @@ +package writers + +import ( + "io" + "log/slog" + + "github.com/aerospike/avs-client-go/protos" + "github.com/jedib0t/go-pretty/v6/table" +) + +type RoleTableWriter struct { + table.Writer + logger *slog.Logger +} + +func NewRoleTableWriter(writer io.Writer, logger *slog.Logger) *RoleTableWriter { + t := RoleTableWriter{NewDefaultWriter(writer), logger} + + t.AppendHeader(table.Row{"Roles"}, rowConfigAutoMerge) + + // t.SetTitle("Roles") + t.SetAutoIndex(true) + t.SortBy([]table.SortBy{ + {Name: "Roles", Mode: table.Asc}, + {Name: "User", Mode: table.Asc}, + }) + + // t.Style().Options.SeparateRows = true + + return &t +} + +func (itw *RoleTableWriter) AppendRoleRow(role *protos.Role) { + itw.AppendRow(table.Row{role.GetId()}) +} diff --git a/cmd/writers/userList.go b/cmd/writers/userList.go new file mode 100644 index 0000000..b5c9512 --- /dev/null +++ b/cmd/writers/userList.go @@ -0,0 +1,36 @@ +package writers + +import ( + "io" + "log/slog" + "strings" + + "github.com/aerospike/avs-client-go/protos" + "github.com/jedib0t/go-pretty/v6/table" +) + +type UserTableWriter struct { + table.Writer + logger *slog.Logger +} + +func NewUserTableWriter(writer io.Writer, logger *slog.Logger) *UserTableWriter { + t := UserTableWriter{NewDefaultWriter(writer), logger} + + t.AppendHeader(table.Row{"User", "Roles"}, rowConfigAutoMerge) + + t.SetTitle("Users") + t.SetAutoIndex(true) + t.SortBy([]table.SortBy{ + {Name: "Roles", Mode: table.Asc}, + {Name: "User", Mode: table.Asc}, + }) + + t.Style().Options.SeparateRows = true + + return &t +} + +func (itw *UserTableWriter) AppendUserRow(user *protos.User) { + itw.AppendRow(table.Row{user.GetUsername(), strings.Join(user.GetRoles(), ", ")}) +} From 5b280593519b1c2e975373580c416dec8cebb0e8 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Mon, 1 Jul 2024 14:10:37 -0700 Subject: [PATCH 02/26] Update to 0.5.1 for all tests, use --name for user cmds, add --yes --- cmd/flags/client.go | 8 +- cmd/flags/constants.go | 9 +- cmd/indexCreate.go | 7 +- cmd/indexDrop.go | 5 +- cmd/root.go | 2 +- cmd/userCreate.go | 6 +- cmd/userDrop.go | 6 +- cmd/userGrant.go | 6 +- cmd/userNewPassword.go | 2 +- cmd/userRevoke.go | 6 +- docker/auth/docker-compose.yml | 2 +- docker/config/aerospike-proximus.yml | 10 +- docker/docker-compose.yml | 2 +- docker/mtls/docker-compose.yml | 2 +- docker/tls/docker-compose.yml | 2 +- e2e_test.go | 336 ++++++++++++++++++++++----- 16 files changed, 315 insertions(+), 96 deletions(-) diff --git a/cmd/flags/client.go b/cmd/flags/client.go index bea7a98..c60c132 100644 --- a/cmd/flags/client.go +++ b/cmd/flags/client.go @@ -32,8 +32,8 @@ func (cf *ClientFlags) NewClientFlagSet() *pflag.FlagSet { flagSet.VarP(cf.Host, Host, "h", commonFlags.DefaultWrapHelpString(fmt.Sprintf("The AVS host to connect to. If cluster discovery is needed use --%s", Seeds))) //nolint:lll // For readability flagSet.Var(cf.Seeds, Seeds, commonFlags.DefaultWrapHelpString(fmt.Sprintf("The AVS seeds to use for cluster discovery. If no cluster discovery is needed (i.e. load-balancer) then use --%s", Host))) //nolint:lll // For readability flagSet.VarP(&cf.ListenerName, ListenerName, "l", commonFlags.DefaultWrapHelpString("The listener to ask the AVS server for as configured in the AVS server. Likely required for cloud deployments.")) //nolint:lll // For readability - flagSet.VarP(&cf.User, User, "U", commonFlags.DefaultWrapHelpString("The AVS user to authenticate with.")) //nolint:lll // For readability - flagSet.VarP(&cf.Password, Password, "P", commonFlags.DefaultWrapHelpString("The AVS password for the specified user.")) //nolint:lll // For readability + flagSet.VarP(&cf.User, AuthUser, "U", commonFlags.DefaultWrapHelpString("The AVS user to authenticate with.")) //nolint:lll // For readability + flagSet.VarP(&cf.Password, AuthPassword, "P", commonFlags.DefaultWrapHelpString("The AVS password for the specified user.")) //nolint:lll // For readability flagSet.DurationVar(&cf.Timeout, Timeout, time.Second*5, commonFlags.DefaultWrapHelpString("The timeout to use for each request to AVS")) //nolint:lll // For readability flagSet.AddFlagSet(cf.NewTLSFlagSet(commonFlags.DefaultWrapHelpString)) @@ -49,8 +49,8 @@ func (cf *ClientFlags) NewSLogAttr() []any { return []any{slog.String(Host, cf.Host.String()), slog.String(Seeds, cf.Seeds.String()), slog.String(ListenerName, cf.ListenerName.String()), - slog.String(User, cf.User.String()), - slog.String(Password, logPass), + slog.String(AuthUser, cf.User.String()), + slog.String(AuthPassword, logPass), slog.Bool(TLSCaFile, cf.TLSRootCAFile != nil), slog.Bool(TLSCaPath, cf.TLSRootCAPath != nil), slog.Bool(TLSCertFile, cf.TLSCertFile != nil), diff --git a/cmd/flags/constants.go b/cmd/flags/constants.go index 0b3161d..bc134a2 100644 --- a/cmd/flags/constants.go +++ b/cmd/flags/constants.go @@ -5,17 +5,14 @@ const ( Seeds = "seeds" Host = "host" ListenerName = "listener-name" - User = "user" - Password = "password" + AuthUser = "user" + AuthPassword = "password" Username = "name" - NewUser = "create-user" - DropUser = "drop-user" - GrantUser = "grant-user" - RevokeUser = "revoke-user" NewPassword = "new-password" Roles = "roles" Namespace = "namespace" Sets = "sets" + Yes = "yes" IndexName = "index-name" VectorField = "vector-field" Dimension = "dimension" diff --git a/cmd/indexCreate.go b/cmd/indexCreate.go index a57c8c9..c8a5668 100644 --- a/cmd/indexCreate.go +++ b/cmd/indexCreate.go @@ -20,6 +20,7 @@ import ( //nolint:govet // Padding not a concern for a CLI var indexCreateFlags = &struct { clientFlags flags.ClientFlags + yes bool namespace string sets []string indexName string @@ -48,7 +49,8 @@ var indexCreateFlags = &struct { } func newIndexCreateFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} //nolint:lll // For readability + flagSet := &pflag.FlagSet{} + flagSet.BoolVarP(&indexCreateFlags.yes, flags.Yes, "y", false, commonFlags.DefaultWrapHelpString("When true do not prompt for confirmation.")) //nolint:lll // For readability flagSet.StringVarP(&indexCreateFlags.namespace, flags.Namespace, "n", "", commonFlags.DefaultWrapHelpString("The namespace for the index.")) //nolint:lll // For readability flagSet.StringSliceVarP(&indexCreateFlags.sets, flags.Sets, "s", nil, commonFlags.DefaultWrapHelpString("The sets for the index.")) //nolint:lll // For readability flagSet.StringVarP(&indexCreateFlags.indexName, flags.IndexName, "i", "", commonFlags.DefaultWrapHelpString("The name of the index.")) //nolint:lll // For readability @@ -103,6 +105,7 @@ func newIndexCreateCmd() *cobra.Command { RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append(indexCreateFlags.clientFlags.NewSLogAttr(), + slog.Bool(flags.Yes, indexCreateFlags.yes), slog.String(flags.Namespace, indexCreateFlags.namespace), slog.Any(flags.Sets, indexCreateFlags.sets), slog.String(flags.IndexName, indexCreateFlags.indexName), @@ -150,7 +153,7 @@ func newIndexCreateCmd() *cobra.Command { }, } - if !confirm(fmt.Sprintf( + if !indexCreateFlags.yes && !confirm(fmt.Sprintf( "Are you sure you want to create the index %s field %s?", nsAndSetString( indexCreateFlags.namespace, diff --git a/cmd/indexDrop.go b/cmd/indexDrop.go index f25c7e4..315d9fc 100644 --- a/cmd/indexDrop.go +++ b/cmd/indexDrop.go @@ -18,6 +18,7 @@ import ( //nolint:govet // Padding not a concern for a CLI var indexDropFlags = &struct { clientFlags flags.ClientFlags + yes bool namespace string sets []string indexName string @@ -27,6 +28,7 @@ var indexDropFlags = &struct { func newIndexDropFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} + flagSet.BoolVarP(&indexDropFlags.yes, flags.Yes, "y", false, commonFlags.DefaultWrapHelpString("When true do not prompt for confirmation.")) flagSet.StringVarP(&indexDropFlags.namespace, flags.Namespace, "n", "", commonFlags.DefaultWrapHelpString("The namespace for the index.")) //nolint:lll // For readability flagSet.StringSliceVarP(&indexDropFlags.sets, flags.Sets, "s", nil, commonFlags.DefaultWrapHelpString("The sets for the index.")) //nolint:lll // For readability flagSet.StringVarP(&indexDropFlags.indexName, flags.IndexName, "i", "", commonFlags.DefaultWrapHelpString("The name of the index.")) //nolint:lll // For readability @@ -62,6 +64,7 @@ func newIndexDropCommand() *cobra.Command { RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append(indexDropFlags.clientFlags.NewSLogAttr(), + slog.Bool(flags.Yes, indexDropFlags.yes), slog.String(flags.Namespace, indexDropFlags.namespace), slog.Any(flags.Sets, indexDropFlags.sets), slog.String(flags.IndexName, indexDropFlags.indexName), @@ -74,7 +77,7 @@ func newIndexDropCommand() *cobra.Command { } defer adminClient.Close() - if !confirm(fmt.Sprintf( + if !indexDropFlags.yes && !confirm(fmt.Sprintf( "Are you sure you want to drop the index %s on field %s?", nsAndSetString( indexCreateFlags.namespace, diff --git a/cmd/root.go b/cmd/root.go index 990abb7..7e4bd4f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -98,7 +98,7 @@ func init() { common.SetupRoot(rootCmd, "aerospike-vector-search", "0.0.0") // TODO: Handle version viper.SetEnvPrefix("ASVEC") - bindEnvs := []string{flags.Host, flags.Seeds, flags.User, flags.Password} + bindEnvs := []string{flags.Host, flags.Seeds, flags.AuthUser, flags.AuthPassword} // Bind specified flags to ASVEC_* for _, env := range bindEnvs { diff --git a/cmd/userCreate.go b/cmd/userCreate.go index bf6b40f..b2b9e8c 100644 --- a/cmd/userCreate.go +++ b/cmd/userCreate.go @@ -25,7 +25,7 @@ var userCreateFlags = &struct { func newUserCreateFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability flagSet.AddFlagSet(userCreateFlags.clientFlags.NewClientFlagSet()) - flagSet.StringVar(&userCreateFlags.newUsername, flags.NewUser, "", "TODO") + flagSet.StringVar(&userCreateFlags.newUsername, flags.Username, "", "TODO") flagSet.StringVar(&userCreateFlags.newPassword, flags.NewPassword, "", "TODO") flagSet.StringSliceVar(&userCreateFlags.roles, flags.Roles, []string{}, "TODO") @@ -33,7 +33,7 @@ func newUserCreateFlagSet() *pflag.FlagSet { } var userCreateRequiredFlags = []string{ - flags.NewUser, + flags.Username, flags.Roles, } @@ -52,7 +52,7 @@ func newUserCreateCmd() *cobra.Command { logger.Debug("parsed flags", append( userCreateFlags.clientFlags.NewSLogAttr(), - slog.String(flags.NewUser, userCreateFlags.newUsername), + slog.String(flags.Username, userCreateFlags.newUsername), slog.Any(flags.Roles, userCreateFlags.roles), )..., ) diff --git a/cmd/userDrop.go b/cmd/userDrop.go index 26e185c..defc3b0 100644 --- a/cmd/userDrop.go +++ b/cmd/userDrop.go @@ -23,13 +23,13 @@ var userDropFlags = &struct { func newUserDropFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability flagSet.AddFlagSet(userDropFlags.clientFlags.NewClientFlagSet()) - flagSet.StringVar(&userDropFlags.dropUser, flags.DropUser, "", "TODO") + flagSet.StringVar(&userDropFlags.dropUser, flags.Username, "", "TODO") return flagSet } var userDropRequiredFlags = []string{ - flags.DropUser, + flags.Username, } func newUserDropCmd() *cobra.Command { @@ -46,7 +46,7 @@ func newUserDropCmd() *cobra.Command { logger.Debug("parsed flags", append( userDropFlags.clientFlags.NewSLogAttr(), - slog.String(flags.NewUser, userDropFlags.dropUser), + slog.String(flags.Username, userDropFlags.dropUser), )..., ) diff --git a/cmd/userGrant.go b/cmd/userGrant.go index 10445fa..69accfb 100644 --- a/cmd/userGrant.go +++ b/cmd/userGrant.go @@ -25,14 +25,14 @@ var userGrantFlags = &struct { func newUserGrantFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability flagSet.AddFlagSet(userGrantFlags.clientFlags.NewClientFlagSet()) - flagSet.StringVar(&userGrantFlags.grantUser, flags.GrantUser, "", "TODO") + flagSet.StringVar(&userGrantFlags.grantUser, flags.Username, "", "TODO") flagSet.StringSliceVar(&userGrantFlags.roles, flags.Roles, []string{}, "TODO") return flagSet } var userGrantRequiredFlags = []string{ - flags.GrantUser, + flags.Username, flags.Roles, } @@ -50,7 +50,7 @@ func newUserGrantCmd() *cobra.Command { logger.Debug("parsed flags", append( userGrantFlags.clientFlags.NewSLogAttr(), - slog.String(flags.NewUser, userGrantFlags.grantUser), + slog.String(flags.Username, userGrantFlags.grantUser), slog.Any(flags.Roles, userGrantFlags.roles), )..., ) diff --git a/cmd/userNewPassword.go b/cmd/userNewPassword.go index 7e267c0..ab5a248 100644 --- a/cmd/userNewPassword.go +++ b/cmd/userNewPassword.go @@ -52,7 +52,7 @@ func newUserNewPasswordCmd() *cobra.Command { logger.Debug("parsed flags", append( userNewPassFlags.clientFlags.NewSLogAttr(), - slog.String(flags.NewUser, userNewPassFlags.username), + slog.String(flags.Username, userNewPassFlags.username), slog.Any(flags.Roles, userNewPassFlags.roles), )..., ) diff --git a/cmd/userRevoke.go b/cmd/userRevoke.go index 586e4af..24b08c4 100644 --- a/cmd/userRevoke.go +++ b/cmd/userRevoke.go @@ -27,14 +27,14 @@ var userRevokeFlags = &struct { func newUserRevokeFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability flagSet.AddFlagSet(userRevokeFlags.clientFlags.NewClientFlagSet()) - flagSet.StringVar(&userRevokeFlags.revokeUser, flags.RevokeUser, "", "TODO") + flagSet.StringVar(&userRevokeFlags.revokeUser, flags.Username, "", "TODO") flagSet.StringSliceVar(&userRevokeFlags.roles, flags.Roles, []string{}, "TODO") return flagSet } var userRevokeRequiredFlags = []string{ - flags.RevokeUser, + flags.Username, flags.Roles, } @@ -59,7 +59,7 @@ func newUserRevokeCmd() *cobra.Command { logger.Debug("parsed flags", append( userRevokeFlags.clientFlags.NewSLogAttr(), - slog.String(flags.NewUser, userRevokeFlags.revokeUser), + slog.String(flags.Username, userRevokeFlags.revokeUser), slog.Any(flags.Roles, userRevokeFlags.roles), )..., ) diff --git a/docker/auth/docker-compose.yml b/docker/auth/docker-compose.yml index 30a3af1..4d568ad 100644 --- a/docker/auth/docker-compose.yml +++ b/docker/auth/docker-compose.yml @@ -11,7 +11,7 @@ services: - "--config-file" - "/opt/aerospike/etc/aerospike/aerospike.conf" avs: - image: aerospike.jfrog.io/docker/aerospike/aerospike-proximus-private:0.5.0-SNAPSHOT + image: aerospike.jfrog.io/docker/aerospike/aerospike-proximus-private:0.5.1-SNAPSHOT ports: - "10000:10000" networks: diff --git a/docker/config/aerospike-proximus.yml b/docker/config/aerospike-proximus.yml index ac73f06..18e64f4 100644 --- a/docker/config/aerospike-proximus.yml +++ b/docker/config/aerospike-proximus.yml @@ -9,11 +9,11 @@ cluster: # The Proximus service listening ports, TLS and network interface. service: ports: - 10000: {} - advertised-listeners: - default: - address: 127.0.0.1 - port: 10000 + 10000: + advertised-listeners: + default: + address: 127.0.0.1 + port: 10000 # Management API listening ports, TLS and network interface. manage: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 9870a90..4d568ad 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -11,7 +11,7 @@ services: - "--config-file" - "/opt/aerospike/etc/aerospike/aerospike.conf" avs: - image: aerospike/aerospike-proximus:0.4.0 + image: aerospike.jfrog.io/docker/aerospike/aerospike-proximus-private:0.5.1-SNAPSHOT ports: - "10000:10000" networks: diff --git a/docker/mtls/docker-compose.yml b/docker/mtls/docker-compose.yml index 30a3af1..4d568ad 100644 --- a/docker/mtls/docker-compose.yml +++ b/docker/mtls/docker-compose.yml @@ -11,7 +11,7 @@ services: - "--config-file" - "/opt/aerospike/etc/aerospike/aerospike.conf" avs: - image: aerospike.jfrog.io/docker/aerospike/aerospike-proximus-private:0.5.0-SNAPSHOT + image: aerospike.jfrog.io/docker/aerospike/aerospike-proximus-private:0.5.1-SNAPSHOT ports: - "10000:10000" networks: diff --git a/docker/tls/docker-compose.yml b/docker/tls/docker-compose.yml index 30a3af1..4d568ad 100644 --- a/docker/tls/docker-compose.yml +++ b/docker/tls/docker-compose.yml @@ -11,7 +11,7 @@ services: - "--config-file" - "/opt/aerospike/etc/aerospike/aerospike.conf" avs: - image: aerospike.jfrog.io/docker/aerospike/aerospike-proximus-private:0.5.0-SNAPSHOT + image: aerospike.jfrog.io/docker/aerospike/aerospike-proximus-private:0.5.1-SNAPSHOT ports: - "10000:10000" networks: diff --git a/e2e_test.go b/e2e_test.go index 3b636e2..41fcbbd 100644 --- a/e2e_test.go +++ b/e2e_test.go @@ -85,7 +85,7 @@ func TestCmdSuite(t *testing.T) { logger.Error("Failed to read cert") } - certificates, err := GetCertificates("docker/mtls/config/tls/localhost.crt", "docker/mtls/config/tls/localhost.key") + _, err = GetCertificates("docker/mtls/config/tls/localhost.crt", "docker/mtls/config/tls/localhost.key") if err != nil { t.Fatalf("unable to read certificates %v", err) t.FailNow() @@ -93,44 +93,44 @@ func TestCmdSuite(t *testing.T) { } logger.Info("%v", slog.Any("cert", rootCA)) - suite.Run(t, &CmdTestSuite{ - composeFile: "docker/docker-compose.yml", // vanilla - suiteFlags: []string{"--log-level debug"}, - avsIP: "localhost", - }) - suite.Run(t, &CmdTestSuite{ - composeFile: "docker/tls/docker-compose.yml", // tls - suiteFlags: []string{ - "--log-level debug", - createFlagStr(flags.TLSCaFile, "docker/tls/config/tls/ca.aerospike.com.crt"), - }, - avsTLSConfig: &tls.Config{ - Certificates: nil, - RootCAs: rootCA, - }, - avsIP: "localhost", - }) - suite.Run(t, &CmdTestSuite{ - composeFile: "docker/mtls/docker-compose.yml", // mutual tls - suiteFlags: []string{ - "--log-level debug", - createFlagStr(flags.TLSCaFile, "docker/mtls/config/tls/ca.aerospike.com.crt"), - createFlagStr(flags.TLSCertFile, "docker/mtls/config/tls/localhost.crt"), - createFlagStr(flags.TLSKeyFile, "docker/mtls/config/tls/localhost.key"), - }, - avsTLSConfig: &tls.Config{ - Certificates: certificates, - RootCAs: rootCA, - }, - avsIP: "localhost", - }) + // suite.Run(t, &CmdTestSuite{ + // composeFile: "docker/docker-compose.yml", // vanilla + // suiteFlags: []string{"--log-level debug"}, + // avsIP: "localhost", + // }) + // suite.Run(t, &CmdTestSuite{ + // composeFile: "docker/tls/docker-compose.yml", // tls + // suiteFlags: []string{ + // "--log-level debug", + // createFlagStr(flags.TLSCaFile, "docker/tls/config/tls/ca.aerospike.com.crt"), + // }, + // avsTLSConfig: &tls.Config{ + // Certificates: nil, + // RootCAs: rootCA, + // }, + // avsIP: "localhost", + // }) + // suite.Run(t, &CmdTestSuite{ + // composeFile: "docker/mtls/docker-compose.yml", // mutual tls + // suiteFlags: []string{ + // "--log-level debug", + // createFlagStr(flags.TLSCaFile, "docker/mtls/config/tls/ca.aerospike.com.crt"), + // createFlagStr(flags.TLSCertFile, "docker/mtls/config/tls/localhost.crt"), + // createFlagStr(flags.TLSKeyFile, "docker/mtls/config/tls/localhost.key"), + // }, + // avsTLSConfig: &tls.Config{ + // Certificates: certificates, + // RootCAs: rootCA, + // }, + // avsIP: "localhost", + // }) suite.Run(t, &CmdTestSuite{ composeFile: "docker/auth/docker-compose.yml", // tls + auth (auth requires tls) suiteFlags: []string{ "--log-level debug", createFlagStr(flags.TLSCaFile, "docker/auth/config/tls/ca.aerospike.com.crt"), - createFlagStr(flags.User, "admin"), - createFlagStr(flags.Password, "admin"), + createFlagStr(flags.AuthUser, "admin"), + createFlagStr(flags.AuthPassword, "admin"), }, avsUser: getStrPtr("admin"), avsPassword: getStrPtr("admin"), @@ -241,7 +241,7 @@ func (suite *CmdTestSuite) TestSuccessfulCreateIndexCmd() { "test with storage config", "index1", "test", - fmt.Sprintf("create index --host %s -n test -i index1 -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), + fmt.Sprintf("index create -y --host %s -n test -i index1 -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), NewIndexDefinitionBuilder("index1", "test", 256, protos.VectorDistanceMetric_SQUARED_EUCLIDEAN, "vector1"). WithStorageNamespace("bar"). WithStorageSet("testbar"). @@ -251,7 +251,7 @@ func (suite *CmdTestSuite) TestSuccessfulCreateIndexCmd() { "test with hnsw params and seeds", "index2", "test", - fmt.Sprintf("create index --timeout 10s --seeds %s -n test -i index2 -d 256 -m HAMMING --vector-field vector2 --hnsw-max-edges 10 --hnsw-ef 11 --hnsw-ef-construction 12", suite.avsHostPort.String()), + fmt.Sprintf("index create -y --timeout 10s --seeds %s -n test -i index2 -d 256 -m HAMMING --vector-field vector2 --hnsw-max-edges 10 --hnsw-ef 11 --hnsw-ef-construction 12", suite.avsHostPort.String()), NewIndexDefinitionBuilder("index2", "test", 256, protos.VectorDistanceMetric_HAMMING, "vector2"). WithHnswM(10). WithHnswEf(11). @@ -262,7 +262,7 @@ func (suite *CmdTestSuite) TestSuccessfulCreateIndexCmd() { "test with hnsw batch params", "index3", "test", - fmt.Sprintf("create index --timeout 10s --host %s -n test -i index3 -d 256 -m COSINE --vector-field vector3 --hnsw-batch-enabled false --hnsw-batch-interval 50 --hnsw-batch-max-records 100", suite.avsHostPort.String()), + fmt.Sprintf("index create -y --timeout 10s --host %s -n test -i index3 -d 256 -m COSINE --vector-field vector3 --hnsw-batch-enabled false --hnsw-batch-interval 50 --hnsw-batch-max-records 100", suite.avsHostPort.String()), NewIndexDefinitionBuilder("index3", "test", 256, protos.VectorDistanceMetric_COSINE, "vector3"). WithHnswBatchingMaxRecord(100). WithHnswBatchingInterval(50). @@ -277,7 +277,7 @@ func (suite *CmdTestSuite) TestSuccessfulCreateIndexCmd() { if err != nil { suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) - suite.FailNow("unable to create index") + suite.FailNow("unable to index create") } actual, err := suite.avsClient.IndexGet(context.Background(), tc.indexNamespace, tc.indexName) @@ -292,10 +292,10 @@ func (suite *CmdTestSuite) TestSuccessfulCreateIndexCmd() { } func (suite *CmdTestSuite) TestCreateIndexFailsAlreadyExistsCmd() { - lines, err := suite.runCmd(strings.Split(fmt.Sprintf("create index --host %s -n test -i exists -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), " ")...) + lines, err := suite.runCmd(strings.Split(fmt.Sprintf("index create -y --host %s -n test -i exists -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), " ")...) suite.Assert().NoError(err, "index should have NOT existed on first call. error: %s, stdout/err: %s", err, lines) - lines, err = suite.runCmd(strings.Split(fmt.Sprintf("create index --host %s -n test -i exists -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), " ")...) + lines, err = suite.runCmd(strings.Split(fmt.Sprintf("index create -y --host %s -n test -i exists -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), " ")...) suite.Assert().Error(err, "index should HAVE existed on first call. error: %s, stdout/err: %s", err, lines) suite.Assert().Contains(lines[0], "AlreadyExists") @@ -314,7 +314,7 @@ func (suite *CmdTestSuite) TestSuccessfulDropIndexCmd() { "indexdrop1", "test", nil, - fmt.Sprintf("drop index --seeds %s -n test -i indexdrop1 --timeout 10s", suite.avsHostPort.String()), + fmt.Sprintf("index drop -y --seeds %s -n test -i indexdrop1 --timeout 10s", suite.avsHostPort.String()), }, { "test with set", @@ -323,7 +323,7 @@ func (suite *CmdTestSuite) TestSuccessfulDropIndexCmd() { []string{ "testset", }, - fmt.Sprintf("drop index --host %s -n test -s testset -i indexdrop2 --timeout 10s", suite.avsHostPort.String()), + fmt.Sprintf("index drop -y --host %s -n test -s testset -i indexdrop2 --timeout 10s", suite.avsHostPort.String()), }, } @@ -331,7 +331,7 @@ func (suite *CmdTestSuite) TestSuccessfulDropIndexCmd() { suite.Run(tc.name, func() { err := suite.avsClient.IndexCreate(context.Background(), tc.indexNamespace, tc.indexSet, tc.indexName, "vector", 1, protos.VectorDistanceMetric_COSINE, nil, nil, nil) if err != nil { - suite.FailNowf("unable to create index", "%v", err) + suite.FailNowf("unable to index create", "%v", err) } time.Sleep(time.Second * 3) @@ -340,7 +340,7 @@ func (suite *CmdTestSuite) TestSuccessfulDropIndexCmd() { suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) if err != nil { - suite.FailNow("unable to drop index") + suite.FailNow("unable to index drop") } _, err = suite.avsClient.IndexGet(context.Background(), tc.indexNamespace, tc.indexName) @@ -355,7 +355,7 @@ func (suite *CmdTestSuite) TestSuccessfulDropIndexCmd() { } func (suite *CmdTestSuite) TestDropIndexFailsDoesNotExistCmd() { - lines, err := suite.runCmd(strings.Split(fmt.Sprintf("drop index --seeds %s -n test -i DNE --timeout 10s", suite.avsHostPort.String()), " ")...) + lines, err := suite.runCmd(strings.Split(fmt.Sprintf("index drop -y --seeds %s -n test -i DNE --timeout 10s", suite.avsHostPort.String()), " ")...) suite.Assert().Error(err, "index should have NOT existed. stdout/err: %s", lines) suite.Assert().Contains(lines[0], "server error") @@ -392,7 +392,7 @@ func (suite *CmdTestSuite) TestSuccessfulListIndexCmd() { "list", "test", 256, protos.VectorDistanceMetric_COSINE, "vector", ).Build(), }, - fmt.Sprintf("list index -h %s", suite.avsHostPort.String()), + fmt.Sprintf("index list -h %s", suite.avsHostPort.String()), `╭─────────────────────────────────────────────────────────────────────────╮ │ Indexes │ ├───┬──────┬───────────┬────────┬────────────┬─────────────────┬──────────┤ @@ -412,7 +412,7 @@ func (suite *CmdTestSuite) TestSuccessfulListIndexCmd() { "list2", "bar", 256, protos.VectorDistanceMetric_HAMMING, "vector", ).WithSet("barset").Build(), }, - fmt.Sprintf("list index -h %s", suite.avsHostPort.String()), + fmt.Sprintf("index list -h %s", suite.avsHostPort.String()), `╭───────────────────────────────────────────────────────────────────────────────────╮ │ Indexes │ ├───┬───────┬───────────┬────────┬────────┬────────────┬─────────────────┬──────────┤ @@ -434,7 +434,7 @@ func (suite *CmdTestSuite) TestSuccessfulListIndexCmd() { "list2", "bar", 256, protos.VectorDistanceMetric_HAMMING, "vector", ).WithSet("barset").Build(), }, - fmt.Sprintf("list index -h %s --verbose", suite.avsHostPort.String()), + fmt.Sprintf("index list -h %s --verbose", suite.avsHostPort.String()), `╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Indexes │ ├───┬───────┬───────────┬────────┬────────┬────────────┬─────────────────┬──────────┬───────────────────────┬────────────────────────────────┤ @@ -508,6 +508,222 @@ func (suite *CmdTestSuite) TestSuccessfulListIndexCmd() { } } +func (suite *CmdTestSuite) TestSuccessfulUserCreateCmd() { + if suite.avsUser == nil { + suite.T().Skip("authentication is disabled. skipping test") + } + + testCases := []struct { + name string + cmd string + expectedUser *protos.User + }{ + { + "create user with comma sep roles", + fmt.Sprintf("users create --host %s --timeout 10s --name foo1 --new-password foo --roles admin,read-write", suite.avsHostPort.String()), + &protos.User{ + Username: "foo1", + Roles: []string{ + "admin", + "read-write", + }, + }, + }, + { + "create user with comma multiple roles", + fmt.Sprintf("users create --host %s --timeout 10s --name foo2 --new-password foo --roles admin --roles read-write", suite.avsHostPort.String()), + &protos.User{ + Username: "foo2", + Roles: []string{ + "admin", + "read-write", + }, + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) + + if err != nil { + suite.FailNow("failed") + } + + time.Sleep(time.Second * 1) + + actualUser, err := suite.avsClient.GetUser(context.Background(), tc.expectedUser.Username) + suite.Assert().NoError(err, "error: %s", err) + + suite.Assert().EqualExportedValues(tc.expectedUser, actualUser) + }) + + } +} + +func (suite *CmdTestSuite) TestSuccessfulUserDropCmd() { + if suite.avsUser == nil { + suite.T().Skip("authentication is disabled. skipping test") + } + + testCases := []struct { + name string + user string + cmd string + }{ + { + "drop user", + "drop0", + fmt.Sprintf("users drop --host %s --timeout 10s --name drop0", suite.avsHostPort.String()), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := suite.avsClient.CreateUser(context.Background(), tc.user, tc.user, []string{"admin"}) + suite.Assert().NoError(err, "we were not able to create the user before we try to drop it", err) + + lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) + + if err != nil { + suite.FailNow("failed") + } + + _, err = suite.avsClient.GetUser(context.Background(), tc.user) + suite.Assert().Error(err, "we should not have retrieved the dropped user") + }) + } +} + +// Server treats non-existing users as a no-op in drop cmd +// +// func (suite *CmdTestSuite) TestFailedUserDropCmd() { + +// if suite.avsUser == nil { +// suite.T().Skip("authentication is disabled. skipping test") +// } + +// lines, err := suite.runCmd(strings.Split(fmt.Sprintf("users drop --host %s --timeout 10s --name DNE", suite.avsHostPort.String()), " ")...) +// suite.Assert().Error(err, "error: %s, stdout/err: %s", err, lines) +// suite.Assert().Contains(lines[0], "server error") +// } + +func (suite *CmdTestSuite) TestSuccessfulUserGrantCmd() { + if suite.avsUser == nil { + suite.T().Skip("authentication is disabled. skipping test") + } + + testCases := []struct { + name string + user string + cmd string + expectedUser *protos.User + }{ + { + "grant user", + "grant0", + fmt.Sprintf("users grant --host %s --timeout 10s --name grant0 --roles read-write", suite.avsHostPort.String()), + &protos.User{ + Username: "grant0", + Roles: []string{"read-write", "admin"}, + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := suite.avsClient.CreateUser(context.Background(), tc.user, "foo", []string{"admin"}) + suite.Assert().NoError(err, "we were not able to create the user before we try to grant it", err) + + lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) + + if err != nil { + suite.FailNow("failed") + } + + actualUser, err := suite.avsClient.GetUser(context.Background(), tc.user) + suite.Assert().NoError(err, "error: %s", err) + + suite.Assert().EqualExportedValues(tc.expectedUser, actualUser) + }) + } +} + +func (suite *CmdTestSuite) TestSuccessfulListUsersCmd() { + if suite.avsUser == nil { + suite.T().Skip("authentication is disabled. skipping test") + } + + testCases := []struct { + name string + cmd string + expectedTable string + }{ + { + "users list", + fmt.Sprintf("users list --seeds %s --timeout 10s", suite.avsHostPort.String()), + `╭───────────────────────────────╮ +│ Users │ +├───┬───────┬───────────────────┤ +│ │ USER │ ROLES │ +├───┼───────┼───────────────────┤ +│ 1 │ admin │ admin, read-write │ +╰───┴───────┴───────────────────╯ +Use 'role list' to view available roles +`, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) + + actualTable := removeANSICodes(strings.Join(lines, "\n")) + + suite.Assert().Equal(tc.expectedTable, actualTable) + }) + } +} + +func (suite *CmdTestSuite) TestSuccessfulListRolesCmd() { + if suite.avsUser == nil { + suite.T().Skip("authentication is disabled. skipping test") + } + + testCases := []struct { + name string + cmd string + expectedTable string + }{ + { + "roles list", + fmt.Sprintf("role list --seeds %s --timeout 10s", suite.avsHostPort.String()), + `╭───┬────────────╮ +│ │ ROLES │ +├───┼────────────┤ +│ 1 │ admin │ +│ 2 │ read-write │ +╰───┴────────────╯ +`, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) + + actualTable := removeANSICodes(strings.Join(lines, "\n")) + + suite.Assert().Equal(tc.expectedTable, actualTable) + }) + } +} + func (suite *CmdTestSuite) TestFailInvalidArg() { testCases := []struct { name string @@ -516,62 +732,62 @@ func (suite *CmdTestSuite) TestFailInvalidArg() { }{ { "use seeds and hosts together", - fmt.Sprintf("create index --seeds %s --host 1.1.1.1:3001 -n test -i index1 -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), + fmt.Sprintf("index create -y --seeds %s --host 1.1.1.1:3001 -n test -i index1 -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), "Error: only --seeds or --host allowed", }, { "use seeds and hosts together", - fmt.Sprintf("list index --seeds %s --host 1.1.1.1:3001", suite.avsHostPort.String()), + fmt.Sprintf("index list --seeds %s --host 1.1.1.1:3001", suite.avsHostPort.String()), "Error: only --seeds or --host allowed", }, { "use seeds and hosts together", - fmt.Sprintf("drop index --seeds %s --host 1.1.1.1:3001 -n test -i index1", suite.avsHostPort.String()), + fmt.Sprintf("index drop -y --seeds %s --host 1.1.1.1:3001 -n test -i index1", suite.avsHostPort.String()), "Error: only --seeds or --host allowed", }, { "test with bad dimension", - "create index --host 1.1.1.1:3001 -n test -i index1 -d -1 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", + "index create -y --host 1.1.1.1:3001 -n test -i index1 -d -1 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", "Error: invalid argument \"-1\" for \"-d, --dimension\"", }, { "test with bad distance metric", - "create index --host 1.1.1.1:3001 -n test -i index1 -d 10 -m BAD --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", + "index create -y --host 1.1.1.1:3001 -n test -i index1 -d 10 -m BAD --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", "Error: invalid argument \"BAD\" for \"-m, --distance-metric\"", }, { "test with bad timeout", - "create index --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", "Error: invalid argument \"10\" for \"--timeout\"", }, { "test with bad hnsw-batch-enabled", - "create index --hnsw-batch-enabled foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-batch-enabled foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", "Error: invalid argument \"foo\" for \"--hnsw-batch-enabled\"", }, { "test with bad hnsw-batch-interval", - "create index --hnsw-batch-interval foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-batch-interval foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", "Error: invalid argument \"foo\" for \"--hnsw-batch-interval\"", }, { "test with bad hnsw-batch-max-records", - "create index --hnsw-batch-max-records foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-batch-max-records foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", "Error: invalid argument \"foo\" for \"--hnsw-batch-max-records\"", }, { "test with bad hnsw-ef", - "create index --hnsw-ef foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-ef foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", "Error: invalid argument \"foo\" for \"--hnsw-ef\"", }, { "test with bad hnsw-ef-construction", - "create index --hnsw-ef-construction foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-ef-construction foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", "Error: invalid argument \"foo\" for \"--hnsw-ef-construction\"", }, { "test with bad hnsw-max-edges", - "create index --hnsw-max-edges foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-max-edges foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", "Error: invalid argument \"foo\" for \"--hnsw-max-edges\"", }, } From 146b5275532fb1121b02c75cd050af78782615b2 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Tue, 2 Jul 2024 09:43:31 -0700 Subject: [PATCH 03/26] add acl management tests --- .github/workflows/tests.yml | 5 +- .../config/aerospike-proximus.yml | 0 docker/{ => vanilla}/config/aerospike.conf | 0 docker/{ => vanilla}/docker-compose.yml | 0 e2e_test.go | 359 ++++++++++++++---- 5 files changed, 294 insertions(+), 70 deletions(-) rename docker/{ => vanilla}/config/aerospike-proximus.yml (100%) rename docker/{ => vanilla}/config/aerospike.conf (100%) rename docker/{ => vanilla}/docker-compose.yml (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9a82f7a..9bec926 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,10 @@ jobs: FEATURES_CONF : ${{secrets.FEATURES_CONF}} run: | - echo "$FEATURES_CONF" > docker/config/features.conf + echo "$FEATURES_CONF" > docker/vanilla/config/features.conf + echo "$FEATURES_CONF" > docker/tls/config/features.conf + echo "$FEATURES_CONF" > docker/mtls/config/features.conf + echo "$FEATURES_CONF" > docker/auth/config/features.conf - name: Run tests run: | make coverage diff --git a/docker/config/aerospike-proximus.yml b/docker/vanilla/config/aerospike-proximus.yml similarity index 100% rename from docker/config/aerospike-proximus.yml rename to docker/vanilla/config/aerospike-proximus.yml diff --git a/docker/config/aerospike.conf b/docker/vanilla/config/aerospike.conf similarity index 100% rename from docker/config/aerospike.conf rename to docker/vanilla/config/aerospike.conf diff --git a/docker/docker-compose.yml b/docker/vanilla/docker-compose.yml similarity index 100% rename from docker/docker-compose.yml rename to docker/vanilla/docker-compose.yml diff --git a/e2e_test.go b/e2e_test.go index 41fcbbd..6498dac 100644 --- a/e2e_test.go +++ b/e2e_test.go @@ -85,7 +85,7 @@ func TestCmdSuite(t *testing.T) { logger.Error("Failed to read cert") } - _, err = GetCertificates("docker/mtls/config/tls/localhost.crt", "docker/mtls/config/tls/localhost.key") + certificates, err := GetCertificates("docker/mtls/config/tls/localhost.crt", "docker/mtls/config/tls/localhost.key") if err != nil { t.Fatalf("unable to read certificates %v", err) t.FailNow() @@ -93,41 +93,44 @@ func TestCmdSuite(t *testing.T) { } logger.Info("%v", slog.Any("cert", rootCA)) - // suite.Run(t, &CmdTestSuite{ - // composeFile: "docker/docker-compose.yml", // vanilla - // suiteFlags: []string{"--log-level debug"}, - // avsIP: "localhost", - // }) - // suite.Run(t, &CmdTestSuite{ - // composeFile: "docker/tls/docker-compose.yml", // tls - // suiteFlags: []string{ - // "--log-level debug", - // createFlagStr(flags.TLSCaFile, "docker/tls/config/tls/ca.aerospike.com.crt"), - // }, - // avsTLSConfig: &tls.Config{ - // Certificates: nil, - // RootCAs: rootCA, - // }, - // avsIP: "localhost", - // }) - // suite.Run(t, &CmdTestSuite{ - // composeFile: "docker/mtls/docker-compose.yml", // mutual tls - // suiteFlags: []string{ - // "--log-level debug", - // createFlagStr(flags.TLSCaFile, "docker/mtls/config/tls/ca.aerospike.com.crt"), - // createFlagStr(flags.TLSCertFile, "docker/mtls/config/tls/localhost.crt"), - // createFlagStr(flags.TLSKeyFile, "docker/mtls/config/tls/localhost.key"), - // }, - // avsTLSConfig: &tls.Config{ - // Certificates: certificates, - // RootCAs: rootCA, - // }, - // avsIP: "localhost", - // }) + suite.Run(t, &CmdTestSuite{ + composeFile: "docker/vanilla/docker-compose.yml", // vanilla + suiteFlags: []string{"--log-level debug", "--timeout 10s"}, + avsIP: "localhost", + }) + suite.Run(t, &CmdTestSuite{ + composeFile: "docker/tls/docker-compose.yml", // tls + suiteFlags: []string{ + "--log-level debug", + "--timeout 10s", + createFlagStr(flags.TLSCaFile, "docker/tls/config/tls/ca.aerospike.com.crt"), + }, + avsTLSConfig: &tls.Config{ + Certificates: nil, + RootCAs: rootCA, + }, + avsIP: "localhost", + }) + suite.Run(t, &CmdTestSuite{ + composeFile: "docker/mtls/docker-compose.yml", // mutual tls + suiteFlags: []string{ + "--log-level debug", + "--timeout 10s", + createFlagStr(flags.TLSCaFile, "docker/mtls/config/tls/ca.aerospike.com.crt"), + createFlagStr(flags.TLSCertFile, "docker/mtls/config/tls/localhost.crt"), + createFlagStr(flags.TLSKeyFile, "docker/mtls/config/tls/localhost.key"), + }, + avsTLSConfig: &tls.Config{ + Certificates: certificates, + RootCAs: rootCA, + }, + avsIP: "localhost", + }) suite.Run(t, &CmdTestSuite{ composeFile: "docker/auth/docker-compose.yml", // tls + auth (auth requires tls) suiteFlags: []string{ "--log-level debug", + "--timeout 10s", createFlagStr(flags.TLSCaFile, "docker/auth/config/tls/ca.aerospike.com.crt"), createFlagStr(flags.AuthUser, "admin"), createFlagStr(flags.AuthPassword, "admin"), @@ -209,9 +212,15 @@ func (suite *CmdTestSuite) TearDownSuite() { } } -func (suite *CmdTestSuite) runCmd(asvecCmd ...string) ([]string, error) { +// All this does is append the suite flags to args because certain runs (e.g. +// flag parse error tests) should not append this flags +func (suite *CmdTestSuite) runSuiteCmd(asvecCmd ...string) ([]string, error) { suiteFlags := strings.Split(strings.Join(suite.suiteFlags, " "), " ") asvecCmd = append(suiteFlags, asvecCmd...) + return suite.runCmd(asvecCmd...) +} + +func (suite *CmdTestSuite) runCmd(asvecCmd ...string) ([]string, error) { logger.Info("running command", slog.String("cmd", strings.Join(asvecCmd, " "))) cmd := exec.Command(suite.app, asvecCmd...) cmd.Env = []string{"GOCOVERDIR=" + os.Getenv("COVERAGE_DIR")} @@ -241,7 +250,7 @@ func (suite *CmdTestSuite) TestSuccessfulCreateIndexCmd() { "test with storage config", "index1", "test", - fmt.Sprintf("index create -y --host %s -n test -i index1 -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), + fmt.Sprintf("index create -y --host %s -n test -i index1 -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar s", suite.avsHostPort.String()), NewIndexDefinitionBuilder("index1", "test", 256, protos.VectorDistanceMetric_SQUARED_EUCLIDEAN, "vector1"). WithStorageNamespace("bar"). WithStorageSet("testbar"). @@ -251,7 +260,7 @@ func (suite *CmdTestSuite) TestSuccessfulCreateIndexCmd() { "test with hnsw params and seeds", "index2", "test", - fmt.Sprintf("index create -y --timeout 10s --seeds %s -n test -i index2 -d 256 -m HAMMING --vector-field vector2 --hnsw-max-edges 10 --hnsw-ef 11 --hnsw-ef-construction 12", suite.avsHostPort.String()), + fmt.Sprintf("index create -y s --seeds %s -n test -i index2 -d 256 -m HAMMING --vector-field vector2 --hnsw-max-edges 10 --hnsw-ef 11 --hnsw-ef-construction 12", suite.avsHostPort.String()), NewIndexDefinitionBuilder("index2", "test", 256, protos.VectorDistanceMetric_HAMMING, "vector2"). WithHnswM(10). WithHnswEf(11). @@ -262,7 +271,7 @@ func (suite *CmdTestSuite) TestSuccessfulCreateIndexCmd() { "test with hnsw batch params", "index3", "test", - fmt.Sprintf("index create -y --timeout 10s --host %s -n test -i index3 -d 256 -m COSINE --vector-field vector3 --hnsw-batch-enabled false --hnsw-batch-interval 50 --hnsw-batch-max-records 100", suite.avsHostPort.String()), + fmt.Sprintf("index create -y s --host %s -n test -i index3 -d 256 -m COSINE --vector-field vector3 --hnsw-batch-enabled false --hnsw-batch-interval 50 --hnsw-batch-max-records 100", suite.avsHostPort.String()), NewIndexDefinitionBuilder("index3", "test", 256, protos.VectorDistanceMetric_COSINE, "vector3"). WithHnswBatchingMaxRecord(100). WithHnswBatchingInterval(50). @@ -273,7 +282,7 @@ func (suite *CmdTestSuite) TestSuccessfulCreateIndexCmd() { for _, tc := range testCases { suite.Run(tc.name, func() { - lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) if err != nil { suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) @@ -292,10 +301,10 @@ func (suite *CmdTestSuite) TestSuccessfulCreateIndexCmd() { } func (suite *CmdTestSuite) TestCreateIndexFailsAlreadyExistsCmd() { - lines, err := suite.runCmd(strings.Split(fmt.Sprintf("index create -y --host %s -n test -i exists -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), " ")...) + lines, err := suite.runSuiteCmd(strings.Split(fmt.Sprintf("index create -y --host %s -n test -i exists -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar s", suite.avsHostPort.String()), " ")...) suite.Assert().NoError(err, "index should have NOT existed on first call. error: %s, stdout/err: %s", err, lines) - lines, err = suite.runCmd(strings.Split(fmt.Sprintf("index create -y --host %s -n test -i exists -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), " ")...) + lines, err = suite.runSuiteCmd(strings.Split(fmt.Sprintf("index create -y --host %s -n test -i exists -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar s", suite.avsHostPort.String()), " ")...) suite.Assert().Error(err, "index should HAVE existed on first call. error: %s, stdout/err: %s", err, lines) suite.Assert().Contains(lines[0], "AlreadyExists") @@ -314,7 +323,7 @@ func (suite *CmdTestSuite) TestSuccessfulDropIndexCmd() { "indexdrop1", "test", nil, - fmt.Sprintf("index drop -y --seeds %s -n test -i indexdrop1 --timeout 10s", suite.avsHostPort.String()), + fmt.Sprintf("index drop -y --seeds %s -n test -i indexdrop1 s", suite.avsHostPort.String()), }, { "test with set", @@ -323,7 +332,7 @@ func (suite *CmdTestSuite) TestSuccessfulDropIndexCmd() { []string{ "testset", }, - fmt.Sprintf("index drop -y --host %s -n test -s testset -i indexdrop2 --timeout 10s", suite.avsHostPort.String()), + fmt.Sprintf("index drop -y --host %s -n test -s testset -i indexdrop2 s", suite.avsHostPort.String()), }, } @@ -336,7 +345,7 @@ func (suite *CmdTestSuite) TestSuccessfulDropIndexCmd() { time.Sleep(time.Second * 3) - lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) if err != nil { @@ -355,7 +364,7 @@ func (suite *CmdTestSuite) TestSuccessfulDropIndexCmd() { } func (suite *CmdTestSuite) TestDropIndexFailsDoesNotExistCmd() { - lines, err := suite.runCmd(strings.Split(fmt.Sprintf("index drop -y --seeds %s -n test -i DNE --timeout 10s", suite.avsHostPort.String()), " ")...) + lines, err := suite.runSuiteCmd(strings.Split(fmt.Sprintf("index drop -y --seeds %s -n test -i DNE s", suite.avsHostPort.String()), " ")...) suite.Assert().Error(err, "index should have NOT existed. stdout/err: %s", lines) suite.Assert().Contains(lines[0], "server error") @@ -497,7 +506,7 @@ func (suite *CmdTestSuite) TestSuccessfulListIndexCmd() { ) } - lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) actualTable := removeANSICodes(strings.Join(lines, "\n")) @@ -520,7 +529,7 @@ func (suite *CmdTestSuite) TestSuccessfulUserCreateCmd() { }{ { "create user with comma sep roles", - fmt.Sprintf("users create --host %s --timeout 10s --name foo1 --new-password foo --roles admin,read-write", suite.avsHostPort.String()), + fmt.Sprintf("users create --host %s s --name foo1 --new-password foo --roles admin,read-write", suite.avsHostPort.String()), &protos.User{ Username: "foo1", Roles: []string{ @@ -531,7 +540,7 @@ func (suite *CmdTestSuite) TestSuccessfulUserCreateCmd() { }, { "create user with comma multiple roles", - fmt.Sprintf("users create --host %s --timeout 10s --name foo2 --new-password foo --roles admin --roles read-write", suite.avsHostPort.String()), + fmt.Sprintf("users create --host %s s --name foo2 --new-password foo --roles admin --roles read-write", suite.avsHostPort.String()), &protos.User{ Username: "foo2", Roles: []string{ @@ -544,7 +553,7 @@ func (suite *CmdTestSuite) TestSuccessfulUserCreateCmd() { for _, tc := range testCases { suite.Run(tc.name, func() { - lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) if err != nil { @@ -562,6 +571,33 @@ func (suite *CmdTestSuite) TestSuccessfulUserCreateCmd() { } } +func (suite *CmdTestSuite) TestFailUserCreateCmd() { + if suite.avsUser == nil { + suite.T().Skip("authentication is disabled. skipping test") + } + + testCases := []struct { + name string + cmd string + expectedErr string + }{ + { + "fail to create user with invalid role", + fmt.Sprintf("users create --host %s s --name foo1 --new-password foo --roles invalid", suite.avsHostPort.String()), + "unknown roles [invalid]", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) + suite.Assert().Error(err, "error: %s, stdout/err: %s", err, lines) + suite.Assert().Contains(lines[0], tc.expectedErr) + }) + + } +} + func (suite *CmdTestSuite) TestSuccessfulUserDropCmd() { if suite.avsUser == nil { suite.T().Skip("authentication is disabled. skipping test") @@ -575,7 +611,7 @@ func (suite *CmdTestSuite) TestSuccessfulUserDropCmd() { { "drop user", "drop0", - fmt.Sprintf("users drop --host %s --timeout 10s --name drop0", suite.avsHostPort.String()), + fmt.Sprintf("users drop --host %s s --name drop0", suite.avsHostPort.String()), }, } @@ -584,7 +620,7 @@ func (suite *CmdTestSuite) TestSuccessfulUserDropCmd() { err := suite.avsClient.CreateUser(context.Background(), tc.user, tc.user, []string{"admin"}) suite.Assert().NoError(err, "we were not able to create the user before we try to drop it", err) - lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) if err != nil { @@ -605,7 +641,7 @@ func (suite *CmdTestSuite) TestSuccessfulUserDropCmd() { // suite.T().Skip("authentication is disabled. skipping test") // } -// lines, err := suite.runCmd(strings.Split(fmt.Sprintf("users drop --host %s --timeout 10s --name DNE", suite.avsHostPort.String()), " ")...) +// lines, err := suite.runCmd(strings.Split(fmt.Sprintf("users drop --host %s s --name DNE", suite.avsHostPort.String()), " ")...) // suite.Assert().Error(err, "error: %s, stdout/err: %s", err, lines) // suite.Assert().Contains(lines[0], "server error") // } @@ -624,7 +660,7 @@ func (suite *CmdTestSuite) TestSuccessfulUserGrantCmd() { { "grant user", "grant0", - fmt.Sprintf("users grant --host %s --timeout 10s --name grant0 --roles read-write", suite.avsHostPort.String()), + fmt.Sprintf("users grant --host %s s --name grant0 --roles read-write", suite.avsHostPort.String()), &protos.User{ Username: "grant0", Roles: []string{"read-write", "admin"}, @@ -637,7 +673,49 @@ func (suite *CmdTestSuite) TestSuccessfulUserGrantCmd() { err := suite.avsClient.CreateUser(context.Background(), tc.user, "foo", []string{"admin"}) suite.Assert().NoError(err, "we were not able to create the user before we try to grant it", err) - lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) + suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) + + if err != nil { + suite.FailNow("failed") + } + + actualUser, err := suite.avsClient.GetUser(context.Background(), tc.user) + suite.Assert().NoError(err, "error: %s", err) + + suite.Assert().EqualExportedValues(tc.expectedUser, actualUser) + }) + } +} + +func (suite *CmdTestSuite) TestSuccessfulUserRevokeCmd() { + if suite.avsUser == nil { + suite.T().Skip("authentication is disabled. skipping test") + } + + testCases := []struct { + name string + user string + cmd string + expectedUser *protos.User + }{ + { + "revoke user", + "revoke0", + fmt.Sprintf("users revoke --host %s s --name revoke0 --roles read-write", suite.avsHostPort.String()), + &protos.User{ + Username: "revoke0", + Roles: []string{"admin"}, + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := suite.avsClient.CreateUser(context.Background(), tc.user, "foo", []string{"admin", "read-write"}) + suite.Assert().NoError(err, "we were not able to create the user before we try to revoke it", err) + + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) if err != nil { @@ -652,6 +730,55 @@ func (suite *CmdTestSuite) TestSuccessfulUserGrantCmd() { } } +func (suite *CmdTestSuite) TestSuccessfulUsersNewPasswordCmd() { + if suite.avsUser == nil { + suite.T().Skip("authentication is disabled. skipping test") + } + + testCases := []struct { + name string + user string + newPassword string + cmd string + }{ + { + "change password", + "password0", + "foo", + fmt.Sprintf("users new-password --host %s s --name password0 --new-password foo", suite.avsHostPort.String()), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := suite.avsClient.CreateUser(context.Background(), tc.user, "oldpass", []string{"admin"}) + suite.Assert().NoError(err, "we were not able to create the user before we try to change password", err) + + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) + suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) + + if err != nil { + suite.FailNow("failed") + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + _, err = avs.NewAdminClient( + ctx, + avs.HostPortSlice{suite.avsHostPort}, + nil, + true, + &tc.user, + &tc.newPassword, + suite.avsTLSConfig, + logger, + ) + suite.Assert().NoError(err, "error: %s", err) + }) + } +} + func (suite *CmdTestSuite) TestSuccessfulListUsersCmd() { if suite.avsUser == nil { suite.T().Skip("authentication is disabled. skipping test") @@ -664,7 +791,7 @@ func (suite *CmdTestSuite) TestSuccessfulListUsersCmd() { }{ { "users list", - fmt.Sprintf("users list --seeds %s --timeout 10s", suite.avsHostPort.String()), + fmt.Sprintf("users list --seeds %s s", suite.avsHostPort.String()), `╭───────────────────────────────╮ │ Users │ ├───┬───────┬───────────────────┤ @@ -679,7 +806,7 @@ Use 'role list' to view available roles for _, tc := range testCases { suite.Run(tc.name, func() { - lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) actualTable := removeANSICodes(strings.Join(lines, "\n")) @@ -689,6 +816,70 @@ Use 'role list' to view available roles } } +func (suite *CmdTestSuite) TestFailUserCmdsWithInvalidUser() { + if suite.avsUser == nil { + suite.T().Skip("authentication is disabled. skipping test") + } + + testCases := []struct { + name string + cmd string + expectedErr string + }{ + { + "fail to revoke user to invalid user", + fmt.Sprintf("users revoke --host %s s --name foo1 --roles admin", suite.avsHostPort.String()), + "failed to revoke user roles: server error: NotFound", + }, + { + "fail to grant user to invalid user", + fmt.Sprintf("users grant --host %s s --name foo1 --roles admin", suite.avsHostPort.String()), + "failed to grant user roles: server error: NotFound", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) + suite.Assert().Error(err, "error: %s, stdout/err: %s", err, lines) + suite.Assert().Contains(lines[0], tc.expectedErr) + }) + + } +} + +func (suite *CmdTestSuite) TestFailUserCmdsWithInvalidRoles() { + if suite.avsUser == nil { + suite.T().Skip("authentication is disabled. skipping test") + } + + testCases := []struct { + name string + cmd string + expectedErr string + }{ + { + "fail to grant user with invalid role", + fmt.Sprintf("users grant --host %s s --name foo1 --roles invalid", suite.avsHostPort.String()), + "unknown roles [invalid]", + }, + { + "fail to revoke user with invalid role", + fmt.Sprintf("users revoke --host %s s --name foo1 --roles invalid", suite.avsHostPort.String()), + "unknown roles [invalid]", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) + suite.Assert().Error(err, "error: %s, stdout/err: %s", err, lines) + suite.Assert().Contains(lines[0], tc.expectedErr) + }) + + } +} + func (suite *CmdTestSuite) TestSuccessfulListRolesCmd() { if suite.avsUser == nil { suite.T().Skip("authentication is disabled. skipping test") @@ -701,7 +892,7 @@ func (suite *CmdTestSuite) TestSuccessfulListRolesCmd() { }{ { "roles list", - fmt.Sprintf("role list --seeds %s --timeout 10s", suite.avsHostPort.String()), + fmt.Sprintf("role list --seeds %s s", suite.avsHostPort.String()), `╭───┬────────────╮ │ │ ROLES │ ├───┼────────────┤ @@ -714,7 +905,7 @@ func (suite *CmdTestSuite) TestSuccessfulListRolesCmd() { for _, tc := range testCases { suite.Run(tc.name, func() { - lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) + lines, err := suite.runSuiteCmd(strings.Split(tc.cmd, " ")...) suite.Assert().NoError(err, "error: %s, stdout/err: %s", err, lines) actualTable := removeANSICodes(strings.Join(lines, "\n")) @@ -726,13 +917,13 @@ func (suite *CmdTestSuite) TestSuccessfulListRolesCmd() { func (suite *CmdTestSuite) TestFailInvalidArg() { testCases := []struct { - name string - cmd string - errStr string + name string + cmd string + expectedErrStr string }{ { "use seeds and hosts together", - fmt.Sprintf("index create -y --seeds %s --host 1.1.1.1:3001 -n test -i index1 -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", suite.avsHostPort.String()), + fmt.Sprintf("index create -y --seeds %s --host 1.1.1.1:3001 -n test -i index1 -d 256 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar s", suite.avsHostPort.String()), "Error: only --seeds or --host allowed", }, { @@ -747,12 +938,12 @@ func (suite *CmdTestSuite) TestFailInvalidArg() { }, { "test with bad dimension", - "index create -y --host 1.1.1.1:3001 -n test -i index1 -d -1 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", + "index create -y --host 1.1.1.1:3001 -n test -i index1 -d -1 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar s", "Error: invalid argument \"-1\" for \"-d, --dimension\"", }, { "test with bad distance metric", - "index create -y --host 1.1.1.1:3001 -n test -i index1 -d 10 -m BAD --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10s", + "index create -y --host 1.1.1.1:3001 -n test -i index1 -d 10 -m BAD --vector-field vector1 --storage-namespace bar --storage-set testbar s", "Error: invalid argument \"BAD\" for \"-m, --distance-metric\"", }, { @@ -762,34 +953,64 @@ func (suite *CmdTestSuite) TestFailInvalidArg() { }, { "test with bad hnsw-batch-enabled", - "index create -y --hnsw-batch-enabled foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-batch-enabled foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar", "Error: invalid argument \"foo\" for \"--hnsw-batch-enabled\"", }, { "test with bad hnsw-batch-interval", - "index create -y --hnsw-batch-interval foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-batch-interval foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar ", "Error: invalid argument \"foo\" for \"--hnsw-batch-interval\"", }, { "test with bad hnsw-batch-max-records", - "index create -y --hnsw-batch-max-records foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-batch-max-records foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar ", "Error: invalid argument \"foo\" for \"--hnsw-batch-max-records\"", }, { "test with bad hnsw-ef", - "index create -y --hnsw-ef foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-ef foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar ", "Error: invalid argument \"foo\" for \"--hnsw-ef\"", }, { "test with bad hnsw-ef-construction", - "index create -y --hnsw-ef-construction foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-ef-construction foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar ", "Error: invalid argument \"foo\" for \"--hnsw-ef-construction\"", }, { "test with bad hnsw-max-edges", - "index create -y --hnsw-max-edges foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar --timeout 10", + "index create -y --hnsw-max-edges foo --host 1.1.1.1:3001 -n test -i index1 -d 10 -m SQUARED_EUCLIDEAN --vector-field vector1 --storage-namespace bar --storage-set testbar ", "Error: invalid argument \"foo\" for \"--hnsw-max-edges\"", }, + { + "test with bad password", + "user create --password file:blah --name foo --roles admin", + "blah: no such file or directory", + }, + { + "test with bad tls-cafile", + "user create --tls-cafile blah --name foo --roles admin", + "blah: no such file or directory", + }, + { + "test with bad tls-capath", + "user create --tls-capath blah --name foo --roles admin", + "blah: no such file or directory", + }, + { + "test with bad tls-certfile", + "user create --tls-certfile blah --name foo --roles admin", + "blah: no such file or directory", + }, + { + "test with bad tls-keyfile", + "user create --tls-keyfile blah --name foo --roles admin", + "blah: no such file or directory", + }, + { + "test with bad tls-keyfile-password", + "user create --tls-keyfile-password b64:bla65asdf54r345123!@#$h --name foo --roles admin", + "Error: invalid argument \"b64:bla65asdf54r345123!@#$h\"", + }, } for _, tc := range testCases { @@ -797,7 +1018,7 @@ func (suite *CmdTestSuite) TestFailInvalidArg() { lines, err := suite.runCmd(strings.Split(tc.cmd, " ")...) suite.Assert().Error(err, "error: %s, stdout/err: %s", err, lines) - suite.Assert().Contains(lines[0], tc.errStr) + suite.Assert().Contains(lines[0], tc.expectedErrStr) }) } } From 2f5ddbfdfa80b5d50119209cbdeac5e13f76135c Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Tue, 2 Jul 2024 14:24:44 -0700 Subject: [PATCH 04/26] fix help, finish TODOs --- .github/workflows/create-prerelease.yml | 2 +- ...eadme-check-links.yml => link-checker.yml} | 0 Makefile | 28 +++++----- README.md | 2 +- VERSION.md | 2 +- cmd/constants.go | 5 ++ cmd/flags/constants.go | 2 +- cmd/index.go | 11 ++-- cmd/indexCreate.go | 55 ++++++++++--------- cmd/indexDrop.go | 13 +++-- cmd/indexList.go | 18 +++--- cmd/role.go | 14 +++-- cmd/rolesList.go | 18 +++--- cmd/root.go | 17 +++--- cmd/user.go | 7 ++- cmd/userCreate.go | 29 ++++++---- cmd/userDrop.go | 21 ++++--- cmd/userGrant.go | 32 +++++++---- cmd/userList.go | 16 +++--- cmd/userNewPassword.go | 25 +++++---- cmd/userRevoke.go | 24 ++++---- 21 files changed, 192 insertions(+), 149 deletions(-) rename .github/workflows/{readme-check-links.yml => link-checker.yml} (100%) create mode 100644 cmd/constants.go diff --git a/.github/workflows/create-prerelease.yml b/.github/workflows/create-prerelease.yml index 3bfe314..16b0df8 100644 --- a/.github/workflows/create-prerelease.yml +++ b/.github/workflows/create-prerelease.yml @@ -14,7 +14,7 @@ on: push: branches: - - "VEC-189-tls" # remove before merge into main + - "VEC-210-acl" # remove before merge into main jobs: build-and-release: runs-on: macos-13 diff --git a/.github/workflows/readme-check-links.yml b/.github/workflows/link-checker.yml similarity index 100% rename from .github/workflows/readme-check-links.yml rename to .github/workflows/link-checker.yml diff --git a/Makefile b/Makefile index 737fb92..b8892f5 100644 --- a/Makefile +++ b/Makefile @@ -155,9 +155,11 @@ clean: ## actual code +VERSION = $(shell git describe --tags --always) +GO_LDFLAGS="-X 'asvec/cmd.Version=$(VERSION)' -s -w" OS := $(shell uname -o) CPU := $(shell uname -m) -ver:=$(shell V=$$(git branch --show-current); if [[ $$V == v* ]]; then printf $${V:1} > ./VERSION.md; fi; cat ./VERSION.md) +ver:=$(shell V=$$(git describe --tags --always); then printf $$V > ./VERSION.md; fi; cat ./VERSION.md) define _amddebscript ver=$(cat ./VERSION.md) cat < ./bin/deb/DEBIAN/control @@ -241,14 +243,14 @@ prep: .PHONY: compile_linux_wip_amd64 compile_linux_wip_amd64: - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o asvec-linux-amd64-wip + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-linux-amd64-wip ifneq (, $(shell which upx)) upx asvec-linux-amd64-wip endif .PHONY: compile_linux_wip_arm64 compile_linux_wip_arm64: - env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags="-s -w" -o asvec-linux-arm64-wip + env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-linux-arm64-wip ifneq (, $(shell which upx)) upx asvec-linux-arm64-wip endif @@ -256,53 +258,53 @@ endif .PHONY: compile_linux_amd64 compile_linux_amd64: printf "package main\n\nimport _ \"embed\"\n\nvar nLinuxBinaryX64 []byte\n\n//go:embed asvec-linux-arm64-wip\nvar nLinuxBinaryArm64 []byte\n" > embed_linux.go - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o asvec-linux-amd64 + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-linux-amd64 mv asvec-linux-amd64 $(BIN_DIR)/ .PHONY: compile_linux_arm64 compile_linux_arm64: printf "package main\n\nimport _ \"embed\"\n\n//go:embed asvec-linux-amd64-wip\nvar nLinuxBinaryX64 []byte\n\nvar nLinuxBinaryArm64 []byte\n" > embed_linux.go - env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags="-s -w" -o asvec-linux-arm64 + env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-linux-arm64 mv asvec-linux-arm64 $(BIN_DIR)/ .PHONY: compile_darwin compile_darwin: printf "package main\n\nimport _ \"embed\"\n\n//go:embed asvec-linux-amd64-wip\nvar nLinuxBinaryX64 []byte\n\n//go:embed asvec-linux-arm64-wip\nvar nLinuxBinaryArm64 []byte" > embed_darwin.go - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o asvec-macos-amd64 - env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath -ldflags="-s -w" -o asvec-macos-arm64 + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-macos-amd64 + env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-macos-arm64 mv asvec-macos-amd64 $(BIN_DIR)/ mv asvec-macos-arm64 $(BIN_DIR)/ .PHONY: compile_darwin_amd64 compile_darwin_amd64: printf "package main\n\nimport _ \"embed\"\n\n//go:embed asvec-linux-amd64-wip\nvar nLinuxBinaryX64 []byte\n\n//go:embed asvec-linux-arm64-wip\nvar nLinuxBinaryArm64 []byte" > embed_darwin.go - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o asvec-macos-amd64 + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-macos-amd64 mv asvec-macos-amd64 $(BIN_DIR)/ .PHONY: compile_darwin_arm64 compile_darwin_arm64: printf "package main\n\nimport _ \"embed\"\n\n//go:embed asvec-linux-amd64-wip\nvar nLinuxBinaryX64 []byte\n\n//go:embed asvec-linux-arm64-wip\nvar nLinuxBinaryArm64 []byte" > embed_darwin.go - env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath -ldflags="-s -w" -o asvec-macos-arm64 + env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-macos-arm64 mv asvec-macos-arm64 $(BIN_DIR)/ .PHONY: compile_windows compile_windows: printf "package main\n\nimport _ \"embed\"\n\n//go:embed asvec-linux-amd64-wip\nvar nLinuxBinaryX64 []byte\n\n//go:embed asvec-linux-arm64-wip\nvar nLinuxBinaryArm64 []byte" > embed_windows.go - env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o asvec-windows-amd64.exe - env CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -trimpath -ldflags="-s -w" -o asvec-windows-arm64.exe + env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-windows-amd64.exe + env CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-windows-arm64.exe mv asvec-windows-amd64.exe $(BIN_DIR)/ mv asvec-windows-arm64.exe $(BIN_DIR)/ .PHONY: compile_windows_amd64 compile_windows_amd64: printf "package main\n\nimport _ \"embed\"\n\n//go:embed asvec-linux-amd64-wip\nvar nLinuxBinaryX64 []byte\n\n//go:embed asvec-linux-arm64-wip\nvar nLinuxBinaryArm64 []byte" > embed_windows.go - env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o asvec-windows-amd64.exe + env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-windows-amd64.exe mv asvec-windows-amd64.exe $(BIN_DIR)/ .PHONY: compile_windows_arm64 compile_windows_arm64: printf "package main\n\nimport _ \"embed\"\n\n//go:embed asvec-linux-amd64-wip\nvar nLinuxBinaryX64 []byte\n\n//go:embed asvec-linux-arm64-wip\nvar nLinuxBinaryArm64 []byte" > embed_windows.go - env CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -trimpath -ldflags="-s -w" -o asvec-windows-arm64.exe + env CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -trimpath -ldflags=$(GO_LDFLAGS) -o asvec-windows-arm64.exe mv asvec-windows-arm64.exe $(BIN_DIR)/ .PHONY: official diff --git a/README.md b/README.md index 082b2e6..c521619 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ efficiency and productivity for users getting started with vector search. Ensure you have an AVS instance up and running for `asvec` to connect to. Checkout out the [AVS documentation](https://aerospike.com/docs/vector) for -options on getting started. +instructions on getting started. ### Installation diff --git a/VERSION.md b/VERSION.md index 6c6aa7c..a6fdb54 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -0.1.0 \ No newline at end of file +0.1.0-1ff8255-7-g146b6db \ No newline at end of file diff --git a/cmd/constants.go b/cmd/constants.go new file mode 100644 index 0000000..d5f230b --- /dev/null +++ b/cmd/constants.go @@ -0,0 +1,5 @@ +package cmd + +const ( + HelpTxtSetupEnv = "export ASVEC_HOST=:5000 ASVEC_USER= ASVEC_PASSWORD=" +) diff --git a/cmd/flags/constants.go b/cmd/flags/constants.go index bc134a2..41f133a 100644 --- a/cmd/flags/constants.go +++ b/cmd/flags/constants.go @@ -7,7 +7,7 @@ const ( ListenerName = "listener-name" AuthUser = "user" AuthPassword = "password" - Username = "name" + Name = "name" NewPassword = "new-password" Roles = "roles" Namespace = "namespace" diff --git a/cmd/index.go b/cmd/index.go index 2f900d2..6b8903f 100644 --- a/cmd/index.go +++ b/cmd/index.go @@ -10,11 +10,12 @@ import ( // indexCmd represents the create command var indexCmd = &cobra.Command{ Use: "index", - Short: "A parent command for creating resources", - Long: `A parent command for creating resources. It currently only supports creating indexes. - For example: - export ASVEC_HOST=:5000 - asvec index ---help + Short: "A parent command viewing, creating, and removing indexes.", + Long: `A parent command viewing, creating, and removing indexes. + +For example: + + asvec index --help `, } diff --git a/cmd/indexCreate.go b/cmd/indexCreate.go index c8a5668..09df994 100644 --- a/cmd/indexCreate.go +++ b/cmd/indexCreate.go @@ -50,22 +50,22 @@ var indexCreateFlags = &struct { func newIndexCreateFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} - flagSet.BoolVarP(&indexCreateFlags.yes, flags.Yes, "y", false, commonFlags.DefaultWrapHelpString("When true do not prompt for confirmation.")) //nolint:lll // For readability - flagSet.StringVarP(&indexCreateFlags.namespace, flags.Namespace, "n", "", commonFlags.DefaultWrapHelpString("The namespace for the index.")) //nolint:lll // For readability - flagSet.StringSliceVarP(&indexCreateFlags.sets, flags.Sets, "s", nil, commonFlags.DefaultWrapHelpString("The sets for the index.")) //nolint:lll // For readability - flagSet.StringVarP(&indexCreateFlags.indexName, flags.IndexName, "i", "", commonFlags.DefaultWrapHelpString("The name of the index.")) //nolint:lll // For readability - flagSet.StringVarP(&indexCreateFlags.vectorField, flags.VectorField, "f", "", commonFlags.DefaultWrapHelpString("The name of the vector field.")) //nolint:lll // For readability - flagSet.Uint32VarP(&indexCreateFlags.dimensions, flags.Dimension, "d", 0, commonFlags.DefaultWrapHelpString("The dimension of the vector field.")) //nolint:lll // For readability - flagSet.VarP(&indexCreateFlags.distanceMetric, flags.DistanceMetric, "m", commonFlags.DefaultWrapHelpString(fmt.Sprintf("The distance metric for the index. Valid values: %s", strings.Join(flags.DistanceMetricEnum(), ", ")))) //nolint:lll // For readability - flagSet.StringToStringVar(&indexCreateFlags.indexMeta, flags.IndexMeta, nil, commonFlags.DefaultWrapHelpString("The distance metric for the index.")) //nolint:lll // For readability //nolint:lll // For readability - flagSet.Var(&indexCreateFlags.storageNamespace, flags.StorageNamespace, commonFlags.DefaultWrapHelpString("Optional storage namespace where the index is stored. Defaults to the index namespace.")) //nolint:lll // For readability //nolint:lll // For readability - flagSet.Var(&indexCreateFlags.storageSet, flags.StorageSet, commonFlags.DefaultWrapHelpString("Optional storage set where the index is stored. Defaults to the index name.")) //nolint:lll // For readability //nolint:lll // For readability - flagSet.Var(&indexCreateFlags.hnswMaxEdges, flags.MaxEdges, commonFlags.DefaultWrapHelpString("Maximum number bi-directional links per HNSW vertex. Greater values of 'm' in general provide better recall for data with high dimensionality, while lower values work well for data with lower dimensionality. The storage space required for the index increases proportionally with 'm'")) //nolint:lll // For readability - flagSet.Var(&indexCreateFlags.hnswConstructionEf, flags.ConstructionEf, commonFlags.DefaultWrapHelpString("The number of candidate nearest neighbors shortlisted during index creation. Larger values provide better recall at the cost of longer index update times. The default is 100.")) //nolint:lll // For readability - flagSet.Var(&indexCreateFlags.hnswEf, flags.Ef, commonFlags.DefaultWrapHelpString("The default number of candidate nearest neighbors shortlisted during search. Larger values provide better recall at the cost of longer search times. The default is 100.")) //nolint:lll // For readability - flagSet.Var(&indexCreateFlags.hnswBatchMaxRecords, flags.BatchMaxRecords, commonFlags.DefaultWrapHelpString("Maximum number of records to fit in a batch. The default value is 10000.")) //nolint:lll // For readability - flagSet.Var(&indexCreateFlags.hnswBatchInterval, flags.BatchInterval, commonFlags.DefaultWrapHelpString("The maximum amount of time in milliseconds to wait before finalizing a batch. The default value is 10000.")) //nolint:lll // For readability - flagSet.Var(&indexCreateFlags.hnswBatchEnabled, flags.BatchEnabled, commonFlags.DefaultWrapHelpString("Enables batching for index updates. Default is true meaning batching is enabled.")) //nolint:lll // For readability + flagSet.BoolVarP(&indexCreateFlags.yes, flags.Yes, "y", false, commonFlags.DefaultWrapHelpString("When true do not prompt for confirmation.")) //nolint:lll // For readability + flagSet.StringVarP(&indexCreateFlags.namespace, flags.Namespace, "n", "", commonFlags.DefaultWrapHelpString("The namespace for the index.")) //nolint:lll // For readability + flagSet.StringSliceVarP(&indexCreateFlags.sets, flags.Sets, "s", nil, commonFlags.DefaultWrapHelpString("The sets for the index.")) //nolint:lll // For readability + flagSet.StringVarP(&indexCreateFlags.indexName, flags.IndexName, "i", "", commonFlags.DefaultWrapHelpString("The name of the index.")) //nolint:lll // For readability + flagSet.StringVarP(&indexCreateFlags.vectorField, flags.VectorField, "f", "", commonFlags.DefaultWrapHelpString("The name of the vector field.")) //nolint:lll // For readability + flagSet.Uint32VarP(&indexCreateFlags.dimensions, flags.Dimension, "d", 0, commonFlags.DefaultWrapHelpString("The dimension of the vector field.")) //nolint:lll // For readability + flagSet.VarP(&indexCreateFlags.distanceMetric, flags.DistanceMetric, "m", commonFlags.DefaultWrapHelpString(fmt.Sprintf("The distance metric for the index. Valid values: %s", strings.Join(flags.DistanceMetricEnum(), ", ")))) //nolint:lll // For readability + flagSet.StringToStringVar(&indexCreateFlags.indexMeta, flags.IndexMeta, nil, commonFlags.DefaultWrapHelpString("The distance metric for the index.")) //nolint:lll // For readability //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.storageNamespace, flags.StorageNamespace, commonFlags.DefaultWrapHelpString("Optional storage namespace where the index is stored. Defaults to the index namespace.")) //nolint:lll // For readability //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.storageSet, flags.StorageSet, commonFlags.DefaultWrapHelpString("Optional storage set where the index is stored. Defaults to the index name.")) //nolint:lll // For readability //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswMaxEdges, flags.MaxEdges, commonFlags.DefaultWrapHelpString("Maximum number bi-directional links per HNSW vertex. Greater values of 'm' in general provide better recall for data with high dimensionality, while lower values work well for data with lower dimensionality. The storage space required for the index increases proportionally with 'm'.")) //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswConstructionEf, flags.ConstructionEf, commonFlags.DefaultWrapHelpString("The number of candidate nearest neighbors shortlisted during index creation. Larger values provide better recall at the cost of longer index update times. The default is 100.")) //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswEf, flags.Ef, commonFlags.DefaultWrapHelpString("The default number of candidate nearest neighbors shortlisted during search. Larger values provide better recall at the cost of longer search times. The default is 100.")) //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswBatchMaxRecords, flags.BatchMaxRecords, commonFlags.DefaultWrapHelpString("Maximum number of records to fit in a batch. The default value is 10000.")) //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswBatchInterval, flags.BatchInterval, commonFlags.DefaultWrapHelpString("The maximum amount of time in milliseconds to wait before finalizing a batch. The default value is 10000.")) //nolint:lll // For readability + flagSet.Var(&indexCreateFlags.hnswBatchEnabled, flags.BatchEnabled, commonFlags.DefaultWrapHelpString("Enables batching for index updates. Default is true meaning batching is enabled.")) //nolint:lll // For readability flagSet.AddFlagSet(indexCreateFlags.clientFlags.NewClientFlagSet()) return flagSet @@ -84,17 +84,18 @@ func newIndexCreateCmd() *cobra.Command { return &cobra.Command{ Use: "create", Short: "A command for creating indexes", - Long: `A command for creating indexes. An index is required to enable vector - search on your data. The index tells AVS where your data is located, - what your vectors look like, and how vectors should be compared to each other. - Optionally, you can tweak where your index is stored and how the HNSW algorithm - behaves. For more information see: https://aerospike.com/docs/vector - - For example: - export ASVEC_HOST=:5000 - asvec index create -i myindex -n test -s testset -d 256 -m COSINE --vector-field vector \ - --storage-namespace test --hnsw-batch-enabled false - `, + Long: fmt.Sprintf(`A command for creating indexes. An index is required to enable vector +search on your data. The index tells AVS where your data is located, +what your vectors look like, and how vectors should be compared to each other. +Optionally, you can tweak where your index is stored and how the HNSW algorithm +behaves. For more information see: https://aerospike.com/docs/vector + +For example: + +%s +asvec index create -i myindex -n test -s testset -d 256 -m COSINE --%s vector \ + --%s test --%s false + `, HelpTxtSetupEnv, flags.VectorField, flags.StorageNamespace, flags.BatchEnabled), PreRunE: func(_ *cobra.Command, _ []string) error { if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) diff --git a/cmd/indexDrop.go b/cmd/indexDrop.go index 315d9fc..d68f728 100644 --- a/cmd/indexDrop.go +++ b/cmd/indexDrop.go @@ -47,13 +47,14 @@ func newIndexDropCommand() *cobra.Command { return &cobra.Command{ Use: "drop", Short: "A command for dropping indexes", - Long: `A command for dropping indexes. Deleting an index will free up - storage but will also disable vector search on your data. + Long: fmt.Sprintf(`A command for dropping indexes. Deleting an index will free up +storage but will also disable vector search on your data. - For example: - export ASVEC_HOST=:5000 - asvec index drop -i myindex -n test - `, +For example: + +%s +asvec index drop -i myindex -n test + `, HelpTxtSetupEnv), PreRunE: func(_ *cobra.Command, _ []string) error { if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) diff --git a/cmd/indexList.go b/cmd/indexList.go index 13ec41c..ca0dc32 100644 --- a/cmd/indexList.go +++ b/cmd/indexList.go @@ -37,15 +37,17 @@ var indexListRequiredFlags = []string{} // listIndexCmd represents the listIndex command func newIndexListCmd() *cobra.Command { return &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, + Use: "ls", + Aliases: []string{"list"}, Short: "A command for listing indexes", - Long: fmt.Sprintf(`A command for displaying useful information about AVS indexes. To display additional - index information use the --%s flag. - For example: - export ASVEC_HOST=:5000 - asvec index list - `, flags.Verbose), + Long: fmt.Sprintf(`A command for listing useful information about AVS indexes. To display additional +index information use the --%s flag. + +For example: + +%s +asvec index ls + `, flags.Verbose, HelpTxtSetupEnv), PreRunE: func(_ *cobra.Command, _ []string) error { if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) diff --git a/cmd/role.go b/cmd/role.go index a2bfcc2..f2b295d 100644 --- a/cmd/role.go +++ b/cmd/role.go @@ -8,12 +8,14 @@ import ( var roleCmd = &cobra.Command{ Use: "role", Aliases: []string{"roles"}, - Short: "A parent command for viewing roles.", - Long: `A parent command for listing, creating, dropping, and granting roles to users. - For example: - export ASVEC_HOST=:5000 - asvec user list - `, + Short: "A parent command for listing roles.", + Long: `A parent command for listing roles. Other sub-commands will be added +in the future. + +For example: + +asvec role --help + `, } func init() { diff --git a/cmd/rolesList.go b/cmd/rolesList.go index 5c0711b..326bf9d 100644 --- a/cmd/rolesList.go +++ b/cmd/rolesList.go @@ -33,14 +33,16 @@ var roleListRequiredFlags = []string{} // listIndexCmd represents the listIndex command func newRoleListCmd() *cobra.Command { return &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "A command for listing users", - Long: `A command for displaying useful information about AVS users. - For example: - export ASVEC_HOST=:5000 - asvec user list - `, + Use: "ls", + Aliases: []string{"list"}, + Short: "A command for listing roles", + Long: fmt.Sprintf(`A command for listing roles. + +For example: + +%s +asvec role ls + `, HelpTxtSetupEnv), PreRunE: func(_ *cobra.Command, _ []string) error { if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) diff --git a/cmd/root.go b/cmd/root.go index 7e4bd4f..df0b9fd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( @@ -20,6 +17,7 @@ import ( var lvl = new(slog.LevelVar) var logger = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: lvl})) var view = NewView(os.Stdout, logger) +var Version = "development" // Overwritten at build time by ld_flags var rootFlags = &struct { logLevel flags.LogLevelFlag @@ -30,9 +28,14 @@ var rootCmd = &cobra.Command{ Use: "asvec", Short: "Aerospike Vector Search CLI", Long: `Welcome to the AVS Deployment Manager CLI Tool! - To start using this tool, please consult the detailed documentation available at https://aerospike.com/docs/vector. - Should you encounter any issues or have questions, feel free to report them by creating a GitHub issue. - Enterprise customers requiring support should contact Aerospike Support directly at https://aerospike.com/support.`, +To start using this tool, please consult the detailed documentation available at https://aerospike.com/docs/vector. +Should you encounter any issues or have questions, feel free to report them by creating a GitHub issue. +Enterprise customers requiring support should contact Aerospike Support directly at https://aerospike.com/support. + +For example: + +asvec --help + `, PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { if rootFlags.logLevel.NotSet() { lvl.Set(slog.LevelError + 1) // disable all logging @@ -95,7 +98,7 @@ func init() { flags.LogLevel, common.DefaultWrapHelpString(fmt.Sprintf("Log level for additional details and debugging. Valid values: %s", strings.Join(flags.LogLevelEnum(), ", "))), //nolint:lll // For readability ) - common.SetupRoot(rootCmd, "aerospike-vector-search", "0.0.0") // TODO: Handle version + common.SetupRoot(rootCmd, "aerospike-vector-search", Version) // TODO: Handle version viper.SetEnvPrefix("ASVEC") bindEnvs := []string{flags.Host, flags.Seeds, flags.AuthUser, flags.AuthPassword} diff --git a/cmd/user.go b/cmd/user.go index 1cda286..19a7c1f 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -10,9 +10,10 @@ var userCmd = &cobra.Command{ Aliases: []string{"users"}, Short: "A parent command for viewing and configuring users.", Long: `A parent command for listing, creating, dropping, and granting roles to users. - For example: - export ASVEC_HOST=:5000 - asvec user list + +For example: + +asvec user --help `, } diff --git a/cmd/userCreate.go b/cmd/userCreate.go index b2b9e8c..51d0f2f 100644 --- a/cmd/userCreate.go +++ b/cmd/userCreate.go @@ -6,8 +6,10 @@ package cmd import ( "asvec/cmd/flags" "context" + "fmt" "log/slog" + commonFlags "github.com/aerospike/tools-common-go/flags" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -23,17 +25,17 @@ var userCreateFlags = &struct { } func newUserCreateFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability + flagSet := &pflag.FlagSet{} //nolint:lll // For readability flagSet.AddFlagSet(userCreateFlags.clientFlags.NewClientFlagSet()) - flagSet.StringVar(&userCreateFlags.newUsername, flags.Username, "", "TODO") - flagSet.StringVar(&userCreateFlags.newPassword, flags.NewPassword, "", "TODO") - flagSet.StringSliceVar(&userCreateFlags.roles, flags.Roles, []string{}, "TODO") + flagSet.StringVar(&userCreateFlags.newUsername, flags.Name, "", commonFlags.DefaultWrapHelpString("The name of the new user.")) //nolint:lll // For readability + flagSet.StringVar(&userCreateFlags.newPassword, flags.NewPassword, "", commonFlags.DefaultWrapHelpString("The password for the new user. If a new password is not provided you you will be prompted to enter a new password.")) //nolint:lll // For readability + flagSet.StringSliceVar(&userCreateFlags.roles, flags.Roles, []string{}, commonFlags.DefaultWrapHelpString("The roles to assign to the new user. To see valid roles run 'asvec role ls'.")) //nolint:lll // For readability return flagSet } var userCreateRequiredFlags = []string{ - flags.Username, + flags.Name, flags.Roles, } @@ -41,18 +43,21 @@ var userCreateRequiredFlags = []string{ func newUserCreateCmd() *cobra.Command { return &cobra.Command{ Use: "create", - Short: "A command for creating users", - Long: `A command for creating users. TODO + Short: "A command for creating new users", + Long: fmt.Sprintf(`A command for creating new users. Users are assigned +roles which have certain privileges. Users should be have the minimum number of +roles necessary to perform their tasks. - For example: - export ASVEC_HOST=127.0.0.1:5000 ASVEC_USER=admin - asvec user create --new-user foo --roles read-write - `, +For example: + +%s +asvec user create --%s foo --%s read-write + `, HelpTxtSetupEnv, flags.Name, flags.Roles), RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( userCreateFlags.clientFlags.NewSLogAttr(), - slog.String(flags.Username, userCreateFlags.newUsername), + slog.String(flags.Name, userCreateFlags.newUsername), slog.Any(flags.Roles, userCreateFlags.roles), )..., ) diff --git a/cmd/userDrop.go b/cmd/userDrop.go index defc3b0..efccb76 100644 --- a/cmd/userDrop.go +++ b/cmd/userDrop.go @@ -6,8 +6,10 @@ package cmd import ( "asvec/cmd/flags" "context" + "fmt" "log/slog" + commonFlags "github.com/aerospike/tools-common-go/flags" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -21,32 +23,33 @@ var userDropFlags = &struct { } func newUserDropFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability + flagSet := &pflag.FlagSet{} //nolint:lll // For readability flagSet.AddFlagSet(userDropFlags.clientFlags.NewClientFlagSet()) - flagSet.StringVar(&userDropFlags.dropUser, flags.Username, "", "TODO") + flagSet.StringVar(&userDropFlags.dropUser, flags.Name, "", commonFlags.DefaultWrapHelpString("The name of the user to drop.")) //nolint:lll // For readability return flagSet } var userDropRequiredFlags = []string{ - flags.Username, + flags.Name, } func newUserDropCmd() *cobra.Command { return &cobra.Command{ Use: "drop", Short: "A command for dropping users", - Long: `A command for dropping users. TODO + Long: fmt.Sprintf(`A command for dropping users. - For example: - export ASVEC_HOST=127.0.0.1:5000 ASVEC_USER=admin - asvec user drop --new-user foo --roles read-write - `, +For example: + +%s +asvec user drop --%s foo + `, HelpTxtSetupEnv, flags.Name, flags.Roles), RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( userDropFlags.clientFlags.NewSLogAttr(), - slog.String(flags.Username, userDropFlags.dropUser), + slog.String(flags.Name, userDropFlags.dropUser), )..., ) diff --git a/cmd/userGrant.go b/cmd/userGrant.go index 69accfb..c9c0f86 100644 --- a/cmd/userGrant.go +++ b/cmd/userGrant.go @@ -6,9 +6,11 @@ package cmd import ( "asvec/cmd/flags" "context" + "fmt" "log/slog" "strings" + commonFlags "github.com/aerospike/tools-common-go/flags" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -23,34 +25,35 @@ var userGrantFlags = &struct { } func newUserGrantFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability + flagSet := &pflag.FlagSet{} //nolint:lll // For readability flagSet.AddFlagSet(userGrantFlags.clientFlags.NewClientFlagSet()) - flagSet.StringVar(&userGrantFlags.grantUser, flags.Username, "", "TODO") - flagSet.StringSliceVar(&userGrantFlags.roles, flags.Roles, []string{}, "TODO") + flagSet.StringVar(&userGrantFlags.grantUser, flags.Name, "", commonFlags.DefaultWrapHelpString("The existing user to grant new roles")) //nolint:lll // For readability + flagSet.StringSliceVar(&userGrantFlags.roles, flags.Roles, []string{}, commonFlags.DefaultWrapHelpString("The roles to grant the existing user. New roles are added to a users existing roles.")) //nolint:lll // For readability return flagSet } var userGrantRequiredFlags = []string{ - flags.Username, + flags.Name, flags.Roles, } func newUserGrantCmd() *cobra.Command { return &cobra.Command{ Use: "grant", - Short: "A command for granting users roles", - Long: `A command for creating users. TODO + Short: "A command for granting roles to an existing users.", + Long: fmt.Sprintf(`A command for granting roles to an existing users. - For example: - export ASVEC_HOST=127.0.0.1:5000 ASVEC_USER=admin - asvec user grant --grant-user foo --roles admin - `, +For example: + +%s +asvec user grant --%s foo --%s admin + `, HelpTxtSetupEnv, flags.Name, flags.Roles), RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( userGrantFlags.clientFlags.NewSLogAttr(), - slog.String(flags.Username, userGrantFlags.grantUser), + slog.String(flags.Name, userGrantFlags.grantUser), slog.Any(flags.Roles, userGrantFlags.roles), )..., ) @@ -70,7 +73,12 @@ func newUserGrantCmd() *cobra.Command { userGrantFlags.roles, ) if err != nil { - logger.Error("unable to grant user roles", slog.String("user", userGrantFlags.grantUser), slog.Any("roles", userGrantFlags.roles), slog.Any("error", err)) + logger.Error( + "unable to grant user roles", + slog.String("user", userGrantFlags.grantUser), + slog.Any("roles", userGrantFlags.roles), + slog.Any("error", err), + ) return err } diff --git a/cmd/userList.go b/cmd/userList.go index b6e523e..cbb392b 100644 --- a/cmd/userList.go +++ b/cmd/userList.go @@ -6,6 +6,7 @@ package cmd import ( "asvec/cmd/flags" "context" + "fmt" "log/slog" "github.com/spf13/cobra" @@ -31,14 +32,15 @@ var userListRequiredFlags = []string{} // listIndexCmd represents the listIndex command func newUserListCmd() *cobra.Command { return &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, + Use: "ls", + Aliases: []string{"list"}, Short: "A command for listing users", - Long: `A command for displaying useful information about AVS users. - For example: - export ASVEC_HOST=:5000 - asvec user list - `, + Long: fmt.Sprintf(`A command for listing useful information about AVS users. +For example: + +%s +asvec user ls + `, HelpTxtSetupEnv), RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", userListFlags.clientFlags.NewSLogAttr()..., diff --git a/cmd/userNewPassword.go b/cmd/userNewPassword.go index ab5a248..de6d6aa 100644 --- a/cmd/userNewPassword.go +++ b/cmd/userNewPassword.go @@ -6,8 +6,10 @@ package cmd import ( "asvec/cmd/flags" "context" + "fmt" "log/slog" + commonFlags "github.com/aerospike/tools-common-go/flags" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -23,16 +25,16 @@ var userNewPassFlags = &struct { } func newUserNewPassFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability + flagSet := &pflag.FlagSet{} flagSet.AddFlagSet(userNewPassFlags.clientFlags.NewClientFlagSet()) - flagSet.StringVar(&userNewPassFlags.username, flags.Username, "", "TODO") - flagSet.StringVar(&userNewPassFlags.password, flags.NewPassword, "", "TODO") + flagSet.StringVar(&userNewPassFlags.username, flags.Name, "", commonFlags.DefaultWrapHelpString("The name of the user.")) //nolint:lll // For readability + flagSet.StringVar(&userNewPassFlags.password, flags.NewPassword, "", commonFlags.DefaultWrapHelpString("The new password for the user. If a new password is not provided you you will be prompted to enter a new password.")) //nolint:lll // For readability return flagSet } var userNewPassRequiredFlags = []string{ - flags.Username, + flags.Name, } // createUserCmd represents the createIndex command @@ -40,19 +42,20 @@ func newUserNewPasswordCmd() *cobra.Command { return &cobra.Command{ Use: "new-password", Aliases: []string{"new-pass"}, - Short: "A command for creating users", - Long: `A command for creating users. TODO + Short: "Change the password for a user", + Long: fmt.Sprintf(`A command for changing the password for an existing user. - For example: - export ASVEC_HOST=127.0.0.1:5000 ASVEC_USER=admin - asvec user new-password --name - `, +For example: + +%s +asvec user new-password --%s foo + `, HelpTxtSetupEnv, flags.Name), RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( userNewPassFlags.clientFlags.NewSLogAttr(), - slog.String(flags.Username, userNewPassFlags.username), + slog.String(flags.Name, userNewPassFlags.username), slog.Any(flags.Roles, userNewPassFlags.roles), )..., ) diff --git a/cmd/userRevoke.go b/cmd/userRevoke.go index 24b08c4..f3178e6 100644 --- a/cmd/userRevoke.go +++ b/cmd/userRevoke.go @@ -10,6 +10,7 @@ import ( "log/slog" "strings" + commonFlags "github.com/aerospike/tools-common-go/flags" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -25,29 +26,30 @@ var userRevokeFlags = &struct { } func newUserRevokeFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} //nolint:lll // For readability //nolint:lll // For readability + flagSet := &pflag.FlagSet{} //nolint:lll // For readability flagSet.AddFlagSet(userRevokeFlags.clientFlags.NewClientFlagSet()) - flagSet.StringVar(&userRevokeFlags.revokeUser, flags.Username, "", "TODO") - flagSet.StringSliceVar(&userRevokeFlags.roles, flags.Roles, []string{}, "TODO") + flagSet.StringVar(&userRevokeFlags.revokeUser, flags.Name, "", commonFlags.DefaultWrapHelpString("The existing user to grant new roles.")) //nolint:lll // For readability + flagSet.StringSliceVar(&userRevokeFlags.roles, flags.Roles, []string{}, commonFlags.DefaultWrapHelpString("The roles to revoke from the user. Roles are removed from a user's existing roles.")) //nolint:lll // For readability return flagSet } var userRevokeRequiredFlags = []string{ - flags.Username, + flags.Name, flags.Roles, } func newUserRevokeCmd() *cobra.Command { return &cobra.Command{ Use: "revoke", - Short: "A command for revoking users roles", - Long: `A command for revoking users roles. TODO + Short: "A command for revoking roles from an existing user.", + Long: fmt.Sprintf(`A command for revoking roles from an existing user. - For example: - export ASVEC_HOST=127.0.0.1:5000 ASVEC_USER=admin - asvec user revoke --revoke-user foo --roles admin - `, +For example: + +%s +asvec user revoke --%s foo --%s admin + `, HelpTxtSetupEnv, flags.Name, flags.Roles), PreRunE: func(_ *cobra.Command, _ []string) error { if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) @@ -59,7 +61,7 @@ func newUserRevokeCmd() *cobra.Command { logger.Debug("parsed flags", append( userRevokeFlags.clientFlags.NewSLogAttr(), - slog.String(flags.Username, userRevokeFlags.revokeUser), + slog.String(flags.Name, userRevokeFlags.revokeUser), slog.Any(flags.Roles, userRevokeFlags.roles), )..., ) From d67b9a8f196474a2b473c873f0d8255cac946f87 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Tue, 2 Jul 2024 15:06:55 -0700 Subject: [PATCH 05/26] try to fix pre-release --- .github/workflows/create-prerelease.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/create-prerelease.yml b/.github/workflows/create-prerelease.yml index 16b0df8..1edb868 100644 --- a/.github/workflows/create-prerelease.yml +++ b/.github/workflows/create-prerelease.yml @@ -21,6 +21,8 @@ jobs: steps: - name: "Git checkout" uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: "Install Homebrew" run: /bin/bash -c "NONINTERACTIVE=1 $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - name: "Install Dependencies" From a2a52404b3c021cc8bff697374c60b4f7f0cc71b Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Tue, 2 Jul 2024 15:14:06 -0700 Subject: [PATCH 06/26] revert --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b8892f5..1d667c3 100644 --- a/Makefile +++ b/Makefile @@ -159,7 +159,7 @@ VERSION = $(shell git describe --tags --always) GO_LDFLAGS="-X 'asvec/cmd.Version=$(VERSION)' -s -w" OS := $(shell uname -o) CPU := $(shell uname -m) -ver:=$(shell V=$$(git describe --tags --always); then printf $$V > ./VERSION.md; fi; cat ./VERSION.md) +ver:=$(shell V=$$(git branch --show-current); if [[ $$V == v* ]]; then printf $${V:1} > ../VERSION.md; fi; cat ../VERSION.md) define _amddebscript ver=$(cat ./VERSION.md) cat < ./bin/deb/DEBIAN/control From b2d63db7ee002a3da7f5f19bcdcf2e2650c59256 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Tue, 2 Jul 2024 15:21:44 -0700 Subject: [PATCH 07/26] again --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1d667c3..7188a12 100644 --- a/Makefile +++ b/Makefile @@ -155,11 +155,10 @@ clean: ## actual code -VERSION = $(shell git describe --tags --always) -GO_LDFLAGS="-X 'asvec/cmd.Version=$(VERSION)' -s -w" OS := $(shell uname -o) CPU := $(shell uname -m) ver:=$(shell V=$$(git branch --show-current); if [[ $$V == v* ]]; then printf $${V:1} > ../VERSION.md; fi; cat ../VERSION.md) +GO_LDFLAGS="-X 'asvec/cmd.Version=$(ver)' -s -w" define _amddebscript ver=$(cat ./VERSION.md) cat < ./bin/deb/DEBIAN/control From 6072363e274594030022e04c3e2a4d8ad610f628 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Tue, 2 Jul 2024 15:41:45 -0700 Subject: [PATCH 08/26] try without --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7188a12..778f046 100644 --- a/Makefile +++ b/Makefile @@ -158,7 +158,7 @@ clean: OS := $(shell uname -o) CPU := $(shell uname -m) ver:=$(shell V=$$(git branch --show-current); if [[ $$V == v* ]]; then printf $${V:1} > ../VERSION.md; fi; cat ../VERSION.md) -GO_LDFLAGS="-X 'asvec/cmd.Version=$(ver)' -s -w" +# GO_LDFLAGS="-X 'asvec/cmd.Version=$(ver)' -s -w" define _amddebscript ver=$(cat ./VERSION.md) cat < ./bin/deb/DEBIAN/control From e62233c54726802be04840ea8bd722d898299bbb Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Tue, 2 Jul 2024 15:50:58 -0700 Subject: [PATCH 09/26] again --- VERSION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.md b/VERSION.md index a6fdb54..6c6aa7c 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -0.1.0-1ff8255-7-g146b6db \ No newline at end of file +0.1.0 \ No newline at end of file From 04f111ff78d6b7f7703e56af249e87dbdcab702c Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 10:21:31 -0700 Subject: [PATCH 10/26] linter errors --- cmd/flags/client.go | 1 + cmd/flags/constants.go | 2 +- cmd/flags/tls.go | 1 + cmd/indexDrop.go | 8 ++++---- cmd/userCreate.go | 3 +-- cmd/userDrop.go | 4 ++-- cmd/userGrant.go | 10 +++++++--- cmd/userNewPassword.go | 7 +++++-- cmd/userRevoke.go | 17 +++++++++++++---- cmd/utils.go | 10 ++++++---- cmd/writers/roleList.go | 4 +--- 11 files changed, 42 insertions(+), 25 deletions(-) diff --git a/cmd/flags/client.go b/cmd/flags/client.go index c60c132..e9280f7 100644 --- a/cmd/flags/client.go +++ b/cmd/flags/client.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/pflag" ) +//nolint:govet // Padding not a concern for a CLI type ClientFlags struct { Host *HostPortFlag Seeds *SeedsSliceFlag diff --git a/cmd/flags/constants.go b/cmd/flags/constants.go index 41f133a..17e09d9 100644 --- a/cmd/flags/constants.go +++ b/cmd/flags/constants.go @@ -33,5 +33,5 @@ const ( TLSCaPath = "tls-capath" TLSCertFile = "tls-certfile" TLSKeyFile = "tls-keyfile" - TLSKeyFilePass = "tls-keyfile-password" + TLSKeyFilePass = "tls-keyfile-password" //nolint:gosec // Not a credential ) diff --git a/cmd/flags/tls.go b/cmd/flags/tls.go index 837f2c7..ea571dd 100644 --- a/cmd/flags/tls.go +++ b/cmd/flags/tls.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/pflag" ) +//nolint:govet // Padding not a concern for a CLI type TLSFlags struct { TLSProtocols commonFlags.TLSProtocolsFlag TLSRootCAFile commonFlags.CertFlag diff --git a/cmd/indexDrop.go b/cmd/indexDrop.go index d68f728..9d16b4b 100644 --- a/cmd/indexDrop.go +++ b/cmd/indexDrop.go @@ -28,10 +28,10 @@ var indexDropFlags = &struct { func newIndexDropFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} - flagSet.BoolVarP(&indexDropFlags.yes, flags.Yes, "y", false, commonFlags.DefaultWrapHelpString("When true do not prompt for confirmation.")) - flagSet.StringVarP(&indexDropFlags.namespace, flags.Namespace, "n", "", commonFlags.DefaultWrapHelpString("The namespace for the index.")) //nolint:lll // For readability - flagSet.StringSliceVarP(&indexDropFlags.sets, flags.Sets, "s", nil, commonFlags.DefaultWrapHelpString("The sets for the index.")) //nolint:lll // For readability - flagSet.StringVarP(&indexDropFlags.indexName, flags.IndexName, "i", "", commonFlags.DefaultWrapHelpString("The name of the index.")) //nolint:lll // For readability + flagSet.BoolVarP(&indexDropFlags.yes, flags.Yes, "y", false, commonFlags.DefaultWrapHelpString("When true do not prompt for confirmation.")) //nolint:lll // For readability + flagSet.StringVarP(&indexDropFlags.namespace, flags.Namespace, "n", "", commonFlags.DefaultWrapHelpString("The namespace for the index.")) //nolint:lll // For readability + flagSet.StringSliceVarP(&indexDropFlags.sets, flags.Sets, "s", nil, commonFlags.DefaultWrapHelpString("The sets for the index.")) //nolint:lll // For readability + flagSet.StringVarP(&indexDropFlags.indexName, flags.IndexName, "i", "", commonFlags.DefaultWrapHelpString("The name of the index.")) //nolint:lll // For readability flagSet.AddFlagSet(indexDropFlags.clientFlags.NewClientFlagSet()) return flagSet diff --git a/cmd/userCreate.go b/cmd/userCreate.go index 51d0f2f..df38793 100644 --- a/cmd/userCreate.go +++ b/cmd/userCreate.go @@ -14,7 +14,6 @@ import ( "github.com/spf13/pflag" ) -//nolint:govet // Padding not a concern for a CLI var userCreateFlags = &struct { clientFlags flags.ClientFlags newUsername string @@ -25,7 +24,7 @@ var userCreateFlags = &struct { } func newUserCreateFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} //nolint:lll // For readability + flagSet := &pflag.FlagSet{} flagSet.AddFlagSet(userCreateFlags.clientFlags.NewClientFlagSet()) flagSet.StringVar(&userCreateFlags.newUsername, flags.Name, "", commonFlags.DefaultWrapHelpString("The name of the new user.")) //nolint:lll // For readability flagSet.StringVar(&userCreateFlags.newPassword, flags.NewPassword, "", commonFlags.DefaultWrapHelpString("The password for the new user. If a new password is not provided you you will be prompted to enter a new password.")) //nolint:lll // For readability diff --git a/cmd/userDrop.go b/cmd/userDrop.go index efccb76..aef0ec8 100644 --- a/cmd/userDrop.go +++ b/cmd/userDrop.go @@ -23,7 +23,7 @@ var userDropFlags = &struct { } func newUserDropFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} //nolint:lll // For readability + flagSet := &pflag.FlagSet{} flagSet.AddFlagSet(userDropFlags.clientFlags.NewClientFlagSet()) flagSet.StringVar(&userDropFlags.dropUser, flags.Name, "", commonFlags.DefaultWrapHelpString("The name of the user to drop.")) //nolint:lll // For readability @@ -44,7 +44,7 @@ For example: %s asvec user drop --%s foo - `, HelpTxtSetupEnv, flags.Name, flags.Roles), + `, HelpTxtSetupEnv, flags.Name), RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( diff --git a/cmd/userGrant.go b/cmd/userGrant.go index c9c0f86..a7e663d 100644 --- a/cmd/userGrant.go +++ b/cmd/userGrant.go @@ -15,7 +15,6 @@ import ( "github.com/spf13/pflag" ) -//nolint:govet // Padding not a concern for a CLI var userGrantFlags = &struct { clientFlags flags.ClientFlags grantUser string @@ -25,7 +24,7 @@ var userGrantFlags = &struct { } func newUserGrantFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} //nolint:lll // For readability + flagSet := &pflag.FlagSet{} flagSet.AddFlagSet(userGrantFlags.clientFlags.NewClientFlagSet()) flagSet.StringVar(&userGrantFlags.grantUser, flags.Name, "", commonFlags.DefaultWrapHelpString("The existing user to grant new roles")) //nolint:lll // For readability flagSet.StringSliceVar(&userGrantFlags.roles, flags.Roles, []string{}, commonFlags.DefaultWrapHelpString("The roles to grant the existing user. New roles are added to a users existing roles.")) //nolint:lll // For readability @@ -49,6 +48,7 @@ For example: %s asvec user grant --%s foo --%s admin `, HelpTxtSetupEnv, flags.Name, flags.Roles), + //nolint:dupl // Ignore code duplication RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( @@ -82,7 +82,11 @@ asvec user grant --%s foo --%s admin return err } - view.Printf("Successfully granted user %s roles %s", userGrantFlags.grantUser, strings.Join(userGrantFlags.roles, ", ")) + view.Printf( + "Successfully granted user %s roles %s", + userGrantFlags.grantUser, + strings.Join(userGrantFlags.roles, ", "), + ) return nil }, } diff --git a/cmd/userNewPassword.go b/cmd/userNewPassword.go index de6d6aa..c61cd7e 100644 --- a/cmd/userNewPassword.go +++ b/cmd/userNewPassword.go @@ -14,7 +14,6 @@ import ( "github.com/spf13/pflag" ) -//nolint:govet // Padding not a concern for a CLI var userNewPassFlags = &struct { clientFlags flags.ClientFlags username string @@ -83,7 +82,11 @@ asvec user new-password --%s foo userNewPassFlags.password, ) if err != nil { - logger.Error("unable to update user credentials", slog.String("user", userNewPassFlags.username), slog.Any("error", err)) + logger.Error( + "unable to update user credentials", + slog.String("user", userNewPassFlags.username), + slog.Any("error", err), + ) return err } diff --git a/cmd/userRevoke.go b/cmd/userRevoke.go index f3178e6..aeae5c0 100644 --- a/cmd/userRevoke.go +++ b/cmd/userRevoke.go @@ -16,7 +16,6 @@ import ( "github.com/spf13/viper" ) -//nolint:govet // Padding not a concern for a CLI var userRevokeFlags = &struct { clientFlags flags.ClientFlags revokeUser string @@ -26,7 +25,7 @@ var userRevokeFlags = &struct { } func newUserRevokeFlagSet() *pflag.FlagSet { - flagSet := &pflag.FlagSet{} //nolint:lll // For readability + flagSet := &pflag.FlagSet{} flagSet.AddFlagSet(userRevokeFlags.clientFlags.NewClientFlagSet()) flagSet.StringVar(&userRevokeFlags.revokeUser, flags.Name, "", commonFlags.DefaultWrapHelpString("The existing user to grant new roles.")) //nolint:lll // For readability flagSet.StringSliceVar(&userRevokeFlags.roles, flags.Roles, []string{}, commonFlags.DefaultWrapHelpString("The roles to revoke from the user. Roles are removed from a user's existing roles.")) //nolint:lll // For readability @@ -57,6 +56,7 @@ asvec user revoke --%s foo --%s admin return nil }, + //nolint:dupl // Ignore code duplication RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( @@ -81,11 +81,20 @@ asvec user revoke --%s foo --%s admin userRevokeFlags.roles, ) if err != nil { - logger.Error("unable to revoke user roles", slog.String("user", userRevokeFlags.revokeUser), slog.Any("roles", userRevokeFlags.roles), slog.Any("error", err)) + logger.Error( + "unable to revoke user roles", + slog.String("user", userRevokeFlags.revokeUser), + slog.Any("roles", userRevokeFlags.roles), + slog.Any("error", err), + ) return err } - view.Printf("Successfully revoked user %s's roles %s", userRevokeFlags.revokeUser, strings.Join(userRevokeFlags.roles, ", ")) + view.Printf( + "Successfully revoked user %s's roles %s", + userRevokeFlags.revokeUser, + strings.Join(userRevokeFlags.roles, ", "), + ) return nil }, } diff --git a/cmd/utils.go b/cmd/utils.go index c6220c5..ea4776e 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -39,6 +39,7 @@ func createClientFromFlags(clientFlags *flags.ClientFlags) (*avs.AdminClient, er } var password *string + if clientFlags.User.Val != nil { if len(clientFlags.Password) != 0 { strPass := clientFlags.Password.String() @@ -86,11 +87,12 @@ func parseBothHostSeedsFlag(seeds *flags.SeedsSliceFlag, host *flags.HostPortFla func nsAndSetString(namespace string, sets []string) string { var setStr string - if len(sets) == 0 { + switch len(sets) { + case 0: setStr = "*" - } else if len(sets) == 1 { + case 1: setStr = sets[0] - } else { + default: setStr = fmt.Sprintf("%v", sets) } @@ -103,5 +105,5 @@ func confirm(prompt string) bool { fmt.Print(prompt + " (y/n): ") fmt.Scanln(&confirm) - return strings.ToLower(confirm) == "y" + return strings.EqualFold(confirm, "y") } diff --git a/cmd/writers/roleList.go b/cmd/writers/roleList.go index 3a46ce1..304cd9e 100644 --- a/cmd/writers/roleList.go +++ b/cmd/writers/roleList.go @@ -18,15 +18,13 @@ func NewRoleTableWriter(writer io.Writer, logger *slog.Logger) *RoleTableWriter t.AppendHeader(table.Row{"Roles"}, rowConfigAutoMerge) - // t.SetTitle("Roles") + // t.SetTitle("Roles") //nolint:gocritic // Add back when we add more fields to Roles table t.SetAutoIndex(true) t.SortBy([]table.SortBy{ {Name: "Roles", Mode: table.Asc}, {Name: "User", Mode: table.Asc}, }) - // t.Style().Options.SeparateRows = true - return &t } From fe3fadb374eb2ad8db111415856deadf1b20d09a Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 10:25:06 -0700 Subject: [PATCH 11/26] fix lint --- cmd/writers/roleList.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/writers/roleList.go b/cmd/writers/roleList.go index 304cd9e..cc276d8 100644 --- a/cmd/writers/roleList.go +++ b/cmd/writers/roleList.go @@ -17,8 +17,6 @@ func NewRoleTableWriter(writer io.Writer, logger *slog.Logger) *RoleTableWriter t := RoleTableWriter{NewDefaultWriter(writer), logger} t.AppendHeader(table.Row{"Roles"}, rowConfigAutoMerge) - - // t.SetTitle("Roles") //nolint:gocritic // Add back when we add more fields to Roles table t.SetAutoIndex(true) t.SortBy([]table.SortBy{ {Name: "Roles", Mode: table.Asc}, From c797943494cb5dcc020e4c732fc430cb6d2be606 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 10:50:49 -0700 Subject: [PATCH 12/26] fix build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 778f046..f3a1bb7 100644 --- a/Makefile +++ b/Makefile @@ -157,7 +157,7 @@ clean: OS := $(shell uname -o) CPU := $(shell uname -m) -ver:=$(shell V=$$(git branch --show-current); if [[ $$V == v* ]]; then printf $${V:1} > ../VERSION.md; fi; cat ../VERSION.md) +ver:=$(shell V=$$(git branch --show-current); if [[ $$V == v* ]]; then printf $${V:1} > ./VERSION.md; fi; cat ./VERSION.md) # GO_LDFLAGS="-X 'asvec/cmd.Version=$(ver)' -s -w" define _amddebscript ver=$(cat ./VERSION.md) From d3822c3aedc33f8394bd441c4bc1003e98632ed5 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 11:22:55 -0700 Subject: [PATCH 13/26] try again with git describe --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f3a1bb7..ca7ddfe 100644 --- a/Makefile +++ b/Makefile @@ -157,8 +157,8 @@ clean: OS := $(shell uname -o) CPU := $(shell uname -m) -ver:=$(shell V=$$(git branch --show-current); if [[ $$V == v* ]]; then printf $${V:1} > ./VERSION.md; fi; cat ./VERSION.md) -# GO_LDFLAGS="-X 'asvec/cmd.Version=$(ver)' -s -w" +ver:=$(shell V=$$(git describe --tags --always); printf $${V} > ./VERSION.md; cat ./VERSION.md) +GO_LDFLAGS="-X 'asvec/cmd.Version=$(ver)' -s -w" define _amddebscript ver=$(cat ./VERSION.md) cat < ./bin/deb/DEBIAN/control From b27761f5d80594bc335c6481563f0c2bea09f8f5 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 12:52:40 -0700 Subject: [PATCH 14/26] add docker login to jfrog --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9bec926..3f15481 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,6 +25,9 @@ jobs: echo "$FEATURES_CONF" > docker/tls/config/features.conf echo "$FEATURES_CONF" > docker/mtls/config/features.conf echo "$FEATURES_CONF" > docker/auth/config/features.conf + - name: Login to Aerospike Jfrog + run: | + docker login aeropike.jfrog.com --password ${{ secrets.JFROG_ACCESS_TOKEN }} - name: Run tests run: | make coverage From eb3551d08a490dcbb0043a86658fbf760fce9536 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 12:57:42 -0700 Subject: [PATCH 15/26] add username --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3f15481..a92328a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,7 @@ jobs: echo "$FEATURES_CONF" > docker/auth/config/features.conf - name: Login to Aerospike Jfrog run: | - docker login aeropike.jfrog.com --password ${{ secrets.JFROG_ACCESS_TOKEN }} + docker login aeropike.jfrog.com --username ${{ secrets.JFROG_USERNAME }} --password ${{ secrets.JFROG_ACCESS_TOKEN }} - name: Run tests run: | make coverage From 600f018127c7ec45dc2cfe34e4027a713ce95103 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 14:11:03 -0700 Subject: [PATCH 16/26] change .com to .io --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a92328a..e142521 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,7 @@ jobs: echo "$FEATURES_CONF" > docker/auth/config/features.conf - name: Login to Aerospike Jfrog run: | - docker login aeropike.jfrog.com --username ${{ secrets.JFROG_USERNAME }} --password ${{ secrets.JFROG_ACCESS_TOKEN }} + docker login aeropike.jfrog.io --username ${{ secrets.JFROG_USERNAME }} --password ${{ secrets.JFROG_ACCESS_TOKEN }} - name: Run tests run: | make coverage From 86c41371a7e217e05f3c4ebe7cc7660cdfe1001f Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 14:21:11 -0700 Subject: [PATCH 17/26] again --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e142521..32df685 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,7 @@ jobs: echo "$FEATURES_CONF" > docker/auth/config/features.conf - name: Login to Aerospike Jfrog run: | - docker login aeropike.jfrog.io --username ${{ secrets.JFROG_USERNAME }} --password ${{ secrets.JFROG_ACCESS_TOKEN }} + docker login aerospike.jfrog.io --username ${{ secrets.JFROG_USERNAME }} --password ${{ secrets.JFROG_ACCESS_TOKEN }} - name: Run tests run: | make coverage From 85a33b13ab5ecd38f257ff32a26becf1bb1a9fc6 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 14:28:09 -0700 Subject: [PATCH 18/26] try with longer sleep between tests --- e2e_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e_test.go b/e2e_test.go index 6498dac..56e626b 100644 --- a/e2e_test.go +++ b/e2e_test.go @@ -152,6 +152,9 @@ func (suite *CmdTestSuite) SetupSuite() { suite.avsHostPort = avs.NewHostPort(suite.avsIP, suite.avsPort) err := docker_compose_up(suite.composeFile) + + time.Sleep(5) + if err != nil { suite.FailNowf("unable to start docker compose up", "%v", err) } From a11b9e6069f55a1a1be4e086524bd71ab98663f7 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 14:42:40 -0700 Subject: [PATCH 19/26] add timeout between docker compose --- e2e_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e_test.go b/e2e_test.go index 56e626b..67acc3f 100644 --- a/e2e_test.go +++ b/e2e_test.go @@ -153,7 +153,7 @@ func (suite *CmdTestSuite) SetupSuite() { err := docker_compose_up(suite.composeFile) - time.Sleep(5) + time.Sleep(time.Second * 10) if err != nil { suite.FailNowf("unable to start docker compose up", "%v", err) From 3772fa8c27aac043550cdca3b57b29f53e4755f1 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 14:52:46 -0700 Subject: [PATCH 20/26] replace with rpm_ver --- Makefile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index ca7ddfe..9a89742 100644 --- a/Makefile +++ b/Makefile @@ -158,6 +158,7 @@ clean: OS := $(shell uname -o) CPU := $(shell uname -m) ver:=$(shell V=$$(git describe --tags --always); printf $${V} > ./VERSION.md; cat ./VERSION.md) +rpm_ver := $(ver:-=_) GO_LDFLAGS="-X 'asvec/cmd.Version=$(ver)' -s -w" define _amddebscript ver=$(cat ./VERSION.md) @@ -389,23 +390,23 @@ pkg-zip: pkg-zip-amd64 pkg-zip-arm64 pkg-rpm-amd64: rm -rf $(BIN_DIR)/asvec-rpm-centos cp -a $(BIN_DIR)/asvecrpm $(BIN_DIR)/asvec-rpm-centos - sed -i.bak "s/VERSIONHERE/${ver}/g" $(BIN_DIR)/asvec-rpm-centos/asvec.spec + sed -i.bak "s/VERSIONHERE/${rpm_ver}/g" $(BIN_DIR)/asvec-rpm-centos/asvec.spec cp $(BIN_DIR)/asvec-linux-amd64 $(BIN_DIR)/asvec-rpm-centos/usr/local/aerospike/bin/asvec rm -f $(BIN_DIR)/asvec-linux-x86_64.rpm bash -ce "cd $(BIN_DIR) && rpmbuild --target=x86_64-redhat-linux --buildroot \$$(pwd)/asvec-rpm-centos -bb asvec-rpm-centos/asvec.spec" - rm -f $(BIN_DIR)/packages/asvec-linux-amd64-${ver}.rpm - mv $(BIN_DIR)/asvec-linux-x86_64.rpm $(BIN_DIR)/packages/asvec-linux-amd64-${ver}.rpm + rm -f $(BIN_DIR)/packages/asvec-linux-amd64-${rpm_ver}.rpm + mv $(BIN_DIR)/asvec-linux-x86_64.rpm $(BIN_DIR)/packages/asvec-linux-amd64-${rpm_ver}.rpm .PHONY: pkg-rpm-arm64 pkg-rpm-arm64: rm -rf $(BIN_DIR)/asvec-rpm-centos cp -a $(BIN_DIR)/asvecrpm $(BIN_DIR)/asvec-rpm-centos - sed -i.bak "s/VERSIONHERE/${ver}/g" $(BIN_DIR)/asvec-rpm-centos/asvec.spec + sed -i.bak "s/VERSIONHERE/${rpm_ver}/g" $(BIN_DIR)/asvec-rpm-centos/asvec.spec cp $(BIN_DIR)/asvec-linux-arm64 $(BIN_DIR)/asvec-rpm-centos/usr/local/aerospike/bin/asvec rm -f $(BIN_DIR)/asvec-linux-arm64.rpm bash -ce "cd $(BIN_DIR) && rpmbuild --target=arm64-redhat-linux --buildroot \$$(pwd)/asvec-rpm-centos -bb asvec-rpm-centos/asvec.spec" - rm -f $(BIN_DIR)/packages/asvec-linux-arm64-${ver}.rpm - mv $(BIN_DIR)/asvec-linux-arm64.rpm $(BIN_DIR)/packages/asvec-linux-arm64-${ver}.rpm + rm -f $(BIN_DIR)/packages/asvec-linux-arm64-${rpm_ver}.rpm + mv $(BIN_DIR)/asvec-linux-arm64.rpm $(BIN_DIR)/packages/asvec-linux-arm64-${rpm_ver}.rpm .PHONY: pkg-rpm pkg-rpm: pkg-rpm-amd64 pkg-rpm-arm64 From 0a7ca4cde93fd10437a3e8c4ec1e7fa61148fc46 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 15:23:16 -0700 Subject: [PATCH 21/26] again --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 9a89742..b140321 100644 --- a/Makefile +++ b/Makefile @@ -159,6 +159,7 @@ OS := $(shell uname -o) CPU := $(shell uname -m) ver:=$(shell V=$$(git describe --tags --always); printf $${V} > ./VERSION.md; cat ./VERSION.md) rpm_ver := $(ver:-=_) +$(info ver is $(ver) and rpm_ver is $(rpm_ver)) GO_LDFLAGS="-X 'asvec/cmd.Version=$(ver)' -s -w" define _amddebscript ver=$(cat ./VERSION.md) From a694b31f7937c60e3fb0a1a1388c19c49804422d Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 15:28:53 -0700 Subject: [PATCH 22/26] again --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b140321..f1cde34 100644 --- a/Makefile +++ b/Makefile @@ -158,7 +158,7 @@ clean: OS := $(shell uname -o) CPU := $(shell uname -m) ver:=$(shell V=$$(git describe --tags --always); printf $${V} > ./VERSION.md; cat ./VERSION.md) -rpm_ver := $(ver:-=_) +rpm_ver := $(shell echo $(ver) | sed 's/-/_/g') $(info ver is $(ver) and rpm_ver is $(rpm_ver)) GO_LDFLAGS="-X 'asvec/cmd.Version=$(ver)' -s -w" define _amddebscript From b58f6db5453fd6b08387bec2f08e01ef8449830f Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 15:48:23 -0700 Subject: [PATCH 23/26] review changes --- cmd/index.go | 3 --- cmd/indexCreate.go | 11 ----------- cmd/indexDrop.go | 11 ----------- cmd/indexList.go | 11 ----------- cmd/rolesList.go | 13 ------------- cmd/userCreate.go | 5 +---- cmd/userDrop.go | 3 --- cmd/userGrant.go | 5 +---- cmd/userList.go | 5 ----- cmd/userNewPassword.go | 6 +----- cmd/userRevoke.go | 15 ++------------- main.go | 3 --- 12 files changed, 5 insertions(+), 86 deletions(-) diff --git a/cmd/index.go b/cmd/index.go index 6b8903f..c8b6287 100644 --- a/cmd/index.go +++ b/cmd/index.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( diff --git a/cmd/indexCreate.go b/cmd/indexCreate.go index 09df994..d87669f 100644 --- a/cmd/indexCreate.go +++ b/cmd/indexCreate.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( @@ -14,7 +11,6 @@ import ( commonFlags "github.com/aerospike/tools-common-go/flags" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/spf13/viper" ) //nolint:govet // Padding not a concern for a CLI @@ -96,13 +92,6 @@ For example: asvec index create -i myindex -n test -s testset -d 256 -m COSINE --%s vector \ --%s test --%s false `, HelpTxtSetupEnv, flags.VectorField, flags.StorageNamespace, flags.BatchEnabled), - PreRunE: func(_ *cobra.Command, _ []string) error { - if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { - return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) - } - - return nil - }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append(indexCreateFlags.clientFlags.NewSLogAttr(), diff --git a/cmd/indexDrop.go b/cmd/indexDrop.go index 9d16b4b..57bb5cf 100644 --- a/cmd/indexDrop.go +++ b/cmd/indexDrop.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( @@ -12,7 +9,6 @@ import ( commonFlags "github.com/aerospike/tools-common-go/flags" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/spf13/viper" ) //nolint:govet // Padding not a concern for a CLI @@ -55,13 +51,6 @@ For example: %s asvec index drop -i myindex -n test `, HelpTxtSetupEnv), - PreRunE: func(_ *cobra.Command, _ []string) error { - if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { - return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) - } - - return nil - }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append(indexDropFlags.clientFlags.NewSLogAttr(), diff --git a/cmd/indexList.go b/cmd/indexList.go index ca0dc32..b4ba940 100644 --- a/cmd/indexList.go +++ b/cmd/indexList.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( @@ -14,7 +11,6 @@ import ( commonFlags "github.com/aerospike/tools-common-go/flags" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/spf13/viper" ) var indexListFlags = &struct { @@ -48,13 +44,6 @@ For example: %s asvec index ls `, flags.Verbose, HelpTxtSetupEnv), - PreRunE: func(_ *cobra.Command, _ []string) error { - if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { - return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) - } - - return nil - }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append(indexListFlags.clientFlags.NewSLogAttr(), diff --git a/cmd/rolesList.go b/cmd/rolesList.go index 326bf9d..a145652 100644 --- a/cmd/rolesList.go +++ b/cmd/rolesList.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( @@ -11,7 +8,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/spf13/viper" ) var rolesListFlags = &struct { @@ -43,13 +39,6 @@ For example: %s asvec role ls `, HelpTxtSetupEnv), - PreRunE: func(_ *cobra.Command, _ []string) error { - if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { - return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) - } - - return nil - }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", rolesListFlags.clientFlags.NewSLogAttr()..., @@ -70,8 +59,6 @@ asvec role ls return err } - cancel() - logger.Debug("server role list", slog.String("response", userList.String())) view.PrintRoles(userList) diff --git a/cmd/userCreate.go b/cmd/userCreate.go index df38793..18f87fb 100644 --- a/cmd/userCreate.go +++ b/cmd/userCreate.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( @@ -44,7 +41,7 @@ func newUserCreateCmd() *cobra.Command { Use: "create", Short: "A command for creating new users", Long: fmt.Sprintf(`A command for creating new users. Users are assigned -roles which have certain privileges. Users should be have the minimum number of +roles which have certain privileges. Users should have the minimum number of roles necessary to perform their tasks. For example: diff --git a/cmd/userDrop.go b/cmd/userDrop.go index aef0ec8..3ae0e7f 100644 --- a/cmd/userDrop.go +++ b/cmd/userDrop.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( diff --git a/cmd/userGrant.go b/cmd/userGrant.go index a7e663d..d99c9db 100644 --- a/cmd/userGrant.go +++ b/cmd/userGrant.go @@ -1,6 +1,4 @@ -/* -Copyright © 2024 NAME HERE -*/ +//nolint:dupl // Ignore code duplication package cmd import ( @@ -48,7 +46,6 @@ For example: %s asvec user grant --%s foo --%s admin `, HelpTxtSetupEnv, flags.Name, flags.Roles), - //nolint:dupl // Ignore code duplication RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( diff --git a/cmd/userList.go b/cmd/userList.go index cbb392b..d9dc175 100644 --- a/cmd/userList.go +++ b/cmd/userList.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( @@ -61,8 +58,6 @@ asvec user ls return err } - cancel() - logger.Debug("server user list", slog.String("response", userList.String())) view.PrintUsers(userList) diff --git a/cmd/userNewPassword.go b/cmd/userNewPassword.go index c61cd7e..c0bae2b 100644 --- a/cmd/userNewPassword.go +++ b/cmd/userNewPassword.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( @@ -14,11 +11,11 @@ import ( "github.com/spf13/pflag" ) +//nolint:govet // Padding not a concern for a CLI var userNewPassFlags = &struct { clientFlags flags.ClientFlags username string password string - roles []string }{ clientFlags: *flags.NewClientFlags(), } @@ -55,7 +52,6 @@ asvec user new-password --%s foo append( userNewPassFlags.clientFlags.NewSLogAttr(), slog.String(flags.Name, userNewPassFlags.username), - slog.Any(flags.Roles, userNewPassFlags.roles), )..., ) diff --git a/cmd/userRevoke.go b/cmd/userRevoke.go index aeae5c0..25931a7 100644 --- a/cmd/userRevoke.go +++ b/cmd/userRevoke.go @@ -1,6 +1,4 @@ -/* -Copyright © 2024 NAME HERE -*/ +//nolint:dupl // Ignore code duplication package cmd import ( @@ -13,7 +11,6 @@ import ( commonFlags "github.com/aerospike/tools-common-go/flags" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/spf13/viper" ) var userRevokeFlags = &struct { @@ -27,7 +24,7 @@ var userRevokeFlags = &struct { func newUserRevokeFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} flagSet.AddFlagSet(userRevokeFlags.clientFlags.NewClientFlagSet()) - flagSet.StringVar(&userRevokeFlags.revokeUser, flags.Name, "", commonFlags.DefaultWrapHelpString("The existing user to grant new roles.")) //nolint:lll // For readability + flagSet.StringVar(&userRevokeFlags.revokeUser, flags.Name, "", commonFlags.DefaultWrapHelpString("The existing user to revoke new roles.")) //nolint:lll // For readability flagSet.StringSliceVar(&userRevokeFlags.roles, flags.Roles, []string{}, commonFlags.DefaultWrapHelpString("The roles to revoke from the user. Roles are removed from a user's existing roles.")) //nolint:lll // For readability return flagSet @@ -49,14 +46,6 @@ For example: %s asvec user revoke --%s foo --%s admin `, HelpTxtSetupEnv, flags.Name, flags.Roles), - PreRunE: func(_ *cobra.Command, _ []string) error { - if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { - return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) - } - - return nil - }, - //nolint:dupl // Ignore code duplication RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( diff --git a/main.go b/main.go index 23be779..581cb68 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,3 @@ -/* -Copyright © 2024 NAME HERE -*/ package main import "asvec/cmd" From ee7b06106be128ec414fb90513cd61ee017db20f Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 16:09:21 -0700 Subject: [PATCH 24/26] add back check for seeds and host --- cmd/indexCreate.go | 3 +++ cmd/indexDrop.go | 3 +++ cmd/indexList.go | 3 +++ cmd/rolesList.go | 3 +++ cmd/userCreate.go | 3 +++ cmd/userDrop.go | 3 +++ cmd/userGrant.go | 3 +++ cmd/userList.go | 3 +++ cmd/userNewPassword.go | 4 +++- cmd/userRevoke.go | 3 +++ cmd/utils.go | 9 +++++++++ 11 files changed, 39 insertions(+), 1 deletion(-) diff --git a/cmd/indexCreate.go b/cmd/indexCreate.go index d87669f..f087df0 100644 --- a/cmd/indexCreate.go +++ b/cmd/indexCreate.go @@ -92,6 +92,9 @@ For example: asvec index create -i myindex -n test -s testset -d 256 -m COSINE --%s vector \ --%s test --%s false `, HelpTxtSetupEnv, flags.VectorField, flags.StorageNamespace, flags.BatchEnabled), + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkSeedsAndHost() + }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append(indexCreateFlags.clientFlags.NewSLogAttr(), diff --git a/cmd/indexDrop.go b/cmd/indexDrop.go index 57bb5cf..1575ef9 100644 --- a/cmd/indexDrop.go +++ b/cmd/indexDrop.go @@ -51,6 +51,9 @@ For example: %s asvec index drop -i myindex -n test `, HelpTxtSetupEnv), + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkSeedsAndHost() + }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append(indexDropFlags.clientFlags.NewSLogAttr(), diff --git a/cmd/indexList.go b/cmd/indexList.go index b4ba940..c62bfe6 100644 --- a/cmd/indexList.go +++ b/cmd/indexList.go @@ -44,6 +44,9 @@ For example: %s asvec index ls `, flags.Verbose, HelpTxtSetupEnv), + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkSeedsAndHost() + }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append(indexListFlags.clientFlags.NewSLogAttr(), diff --git a/cmd/rolesList.go b/cmd/rolesList.go index a145652..02bae76 100644 --- a/cmd/rolesList.go +++ b/cmd/rolesList.go @@ -39,6 +39,9 @@ For example: %s asvec role ls `, HelpTxtSetupEnv), + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkSeedsAndHost() + }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", rolesListFlags.clientFlags.NewSLogAttr()..., diff --git a/cmd/userCreate.go b/cmd/userCreate.go index 18f87fb..fd235bf 100644 --- a/cmd/userCreate.go +++ b/cmd/userCreate.go @@ -49,6 +49,9 @@ For example: %s asvec user create --%s foo --%s read-write `, HelpTxtSetupEnv, flags.Name, flags.Roles), + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkSeedsAndHost() + }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( diff --git a/cmd/userDrop.go b/cmd/userDrop.go index 3ae0e7f..da4e99d 100644 --- a/cmd/userDrop.go +++ b/cmd/userDrop.go @@ -42,6 +42,9 @@ For example: %s asvec user drop --%s foo `, HelpTxtSetupEnv, flags.Name), + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkSeedsAndHost() + }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( diff --git a/cmd/userGrant.go b/cmd/userGrant.go index d99c9db..643b532 100644 --- a/cmd/userGrant.go +++ b/cmd/userGrant.go @@ -46,6 +46,9 @@ For example: %s asvec user grant --%s foo --%s admin `, HelpTxtSetupEnv, flags.Name, flags.Roles), + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkSeedsAndHost() + }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( diff --git a/cmd/userList.go b/cmd/userList.go index d9dc175..aac118b 100644 --- a/cmd/userList.go +++ b/cmd/userList.go @@ -38,6 +38,9 @@ For example: %s asvec user ls `, HelpTxtSetupEnv), + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkSeedsAndHost() + }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", userListFlags.clientFlags.NewSLogAttr()..., diff --git a/cmd/userNewPassword.go b/cmd/userNewPassword.go index c0bae2b..d3ed667 100644 --- a/cmd/userNewPassword.go +++ b/cmd/userNewPassword.go @@ -46,7 +46,9 @@ For example: %s asvec user new-password --%s foo `, HelpTxtSetupEnv, flags.Name), - + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkSeedsAndHost() + }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( diff --git a/cmd/userRevoke.go b/cmd/userRevoke.go index 25931a7..91615d5 100644 --- a/cmd/userRevoke.go +++ b/cmd/userRevoke.go @@ -46,6 +46,9 @@ For example: %s asvec user revoke --%s foo --%s admin `, HelpTxtSetupEnv, flags.Name, flags.Roles), + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkSeedsAndHost() + }, RunE: func(_ *cobra.Command, _ []string) error { logger.Debug("parsed flags", append( diff --git a/cmd/utils.go b/cmd/utils.go index ea4776e..7771e93 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -11,6 +11,7 @@ import ( "golang.org/x/term" avs "github.com/aerospike/avs-client-go" + "github.com/spf13/viper" ) func passwordPrompt(prompt string) (string, error) { @@ -107,3 +108,11 @@ func confirm(prompt string) bool { return strings.EqualFold(confirm, "y") } + +func checkSeedsAndHost() error { + if viper.IsSet(flags.Seeds) && viper.IsSet(flags.Host) { + return fmt.Errorf("only --%s or --%s allowed", flags.Seeds, flags.Host) + } + + return nil +} From 79977454015f0c1c1f20dd8a910e41e4f2ace9ee Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 16:12:00 -0700 Subject: [PATCH 25/26] fix release --- .github/workflows/create-prerelease.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create-prerelease.yml b/.github/workflows/create-prerelease.yml index 1edb868..0decd84 100644 --- a/.github/workflows/create-prerelease.yml +++ b/.github/workflows/create-prerelease.yml @@ -113,11 +113,12 @@ jobs: cd ~/work/asvec/asvec/bin/packages COMMIT=$(git rev-parse --short HEAD) VER=$(cat ../../VERSION.md) + RPM_VER=$(shell echo $(VER) | sed 's/-/_/g') BRANCH=$(git rev-parse --abbrev-ref HEAD) TAG=${VER}-${COMMIT} [ "${ADDCOMMIT}" = "false" ] && TAG=${VER} FULLCOMMIT=$(git rev-parse HEAD) - gh release create -R github.com/aerospike/asvec --notes-file ../../RELEASE.md --prerelease --target ${FULLCOMMIT} --title "Asvec - v${TAG}" ${TAG} asvec-linux-amd64-${VER}.deb asvec-linux-amd64-${VER}.rpm asvec-linux-amd64-${VER}.zip asvec-linux-arm64-${VER}.deb asvec-linux-arm64-${VER}.rpm asvec-linux-arm64-${VER}.zip asvec-macos-${VER}.pkg asvec-macos-amd64-${VER}.zip asvec-macos-arm64-${VER}.zip asvec-windows-amd64-${VER}.zip asvec-windows-arm64-${VER}.zip + gh release create -R github.com/aerospike/asvec --notes-file ../../RELEASE.md --prerelease --target ${FULLCOMMIT} --title "Asvec - v${TAG}" ${TAG} asvec-linux-amd64-${VER}.deb asvec-linux-amd64-${RPM_VER}.rpm asvec-linux-amd64-${VER}.zip asvec-linux-arm64-${VER}.deb asvec-linux-arm64-${RPM_VER}.rpm asvec-linux-arm64-${VER}.zip asvec-macos-${VER}.pkg asvec-macos-amd64-${VER}.zip asvec-macos-arm64-${VER}.zip asvec-windows-amd64-${VER}.zip asvec-windows-arm64-${VER}.zip - name: "Delete previous pre-release" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From f89d3cf9c5fdf1f5458aff7e9885b2817f5c47a8 Mon Sep 17 00:00:00 2001 From: Jesse Schmidt Date: Wed, 3 Jul 2024 16:26:00 -0700 Subject: [PATCH 26/26] add help text around env vars --- .github/workflows/create-prerelease.yml | 2 +- cmd/flags/client.go | 12 ++++++------ cmd/root.go | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/create-prerelease.yml b/.github/workflows/create-prerelease.yml index 0decd84..a488b4a 100644 --- a/.github/workflows/create-prerelease.yml +++ b/.github/workflows/create-prerelease.yml @@ -113,7 +113,7 @@ jobs: cd ~/work/asvec/asvec/bin/packages COMMIT=$(git rev-parse --short HEAD) VER=$(cat ../../VERSION.md) - RPM_VER=$(shell echo $(VER) | sed 's/-/_/g') + RPM_VER=$(echo ${VER} | sed 's/-/_/g') BRANCH=$(git rev-parse --abbrev-ref HEAD) TAG=${VER}-${COMMIT} [ "${ADDCOMMIT}" = "false" ] && TAG=${VER} diff --git a/cmd/flags/client.go b/cmd/flags/client.go index e9280f7..a906e75 100644 --- a/cmd/flags/client.go +++ b/cmd/flags/client.go @@ -30,12 +30,12 @@ func NewClientFlags() *ClientFlags { func (cf *ClientFlags) NewClientFlagSet() *pflag.FlagSet { flagSet := &pflag.FlagSet{} - flagSet.VarP(cf.Host, Host, "h", commonFlags.DefaultWrapHelpString(fmt.Sprintf("The AVS host to connect to. If cluster discovery is needed use --%s", Seeds))) //nolint:lll // For readability - flagSet.Var(cf.Seeds, Seeds, commonFlags.DefaultWrapHelpString(fmt.Sprintf("The AVS seeds to use for cluster discovery. If no cluster discovery is needed (i.e. load-balancer) then use --%s", Host))) //nolint:lll // For readability - flagSet.VarP(&cf.ListenerName, ListenerName, "l", commonFlags.DefaultWrapHelpString("The listener to ask the AVS server for as configured in the AVS server. Likely required for cloud deployments.")) //nolint:lll // For readability - flagSet.VarP(&cf.User, AuthUser, "U", commonFlags.DefaultWrapHelpString("The AVS user to authenticate with.")) //nolint:lll // For readability - flagSet.VarP(&cf.Password, AuthPassword, "P", commonFlags.DefaultWrapHelpString("The AVS password for the specified user.")) //nolint:lll // For readability - flagSet.DurationVar(&cf.Timeout, Timeout, time.Second*5, commonFlags.DefaultWrapHelpString("The timeout to use for each request to AVS")) //nolint:lll // For readability + flagSet.VarP(cf.Host, Host, "h", commonFlags.DefaultWrapHelpString(fmt.Sprintf("The AVS host to connect to. If cluster discovery is needed use --%s. Additionally can be set using the environment variable ASVEC_HOST.", Seeds))) //nolint:lll // For readability + flagSet.Var(cf.Seeds, Seeds, commonFlags.DefaultWrapHelpString(fmt.Sprintf("The AVS seeds to use for cluster discovery. If no cluster discovery is needed (i.e. load-balancer) then use --%s. Additionally can be set using the environment variable ASVEC_SEEDS.", Host))) //nolint:lll // For readability + flagSet.VarP(&cf.ListenerName, ListenerName, "l", commonFlags.DefaultWrapHelpString("The listener to ask the AVS server for as configured in the AVS server. Likely required for cloud deployments.")) //nolint:lll // For readability + flagSet.VarP(&cf.User, AuthUser, "U", commonFlags.DefaultWrapHelpString("The AVS user to authenticate with. Additionally can be set using the environment variable ASVEC_USER")) //nolint:lll // For readability + flagSet.VarP(&cf.Password, AuthPassword, "P", commonFlags.DefaultWrapHelpString("The AVS password for the specified user. By default the environment variable ASVEC_PASSWORD will be checked. Other environment variables can also be used as well as different formats (i.e. base64)")) //nolint:lll // For readability + flagSet.DurationVar(&cf.Timeout, Timeout, time.Second*5, commonFlags.DefaultWrapHelpString("The timeout to use for each request to AVS")) //nolint:lll // For readability flagSet.AddFlagSet(cf.NewTLSFlagSet(commonFlags.DefaultWrapHelpString)) return flagSet diff --git a/cmd/root.go b/cmd/root.go index df0b9fd..add7105 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -27,15 +27,15 @@ var rootFlags = &struct { var rootCmd = &cobra.Command{ Use: "asvec", Short: "Aerospike Vector Search CLI", - Long: `Welcome to the AVS Deployment Manager CLI Tool! + Long: fmt.Sprintf(`Welcome to the AVS Deployment Manager CLI Tool! To start using this tool, please consult the detailed documentation available at https://aerospike.com/docs/vector. Should you encounter any issues or have questions, feel free to report them by creating a GitHub issue. Enterprise customers requiring support should contact Aerospike Support directly at https://aerospike.com/support. For example: - +%s asvec --help - `, + `, HelpTxtSetupEnv), PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { if rootFlags.logLevel.NotSet() { lvl.Set(slog.LevelError + 1) // disable all logging