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 c5e3674..2135547 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -94,7 +94,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 ecdd4d7..bf89e63 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) @@ -294,10 +294,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") @@ -316,7 +316,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", @@ -325,7 +325,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()), }, } @@ -333,7 +333,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) @@ -342,7 +342,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) @@ -357,7 +357,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") @@ -394,7 +394,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 │ ├───┬──────┬───────────┬────────┬────────────┬─────────────────┬──────────┤ @@ -414,7 +414,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 │ ├───┬───────┬───────────┬────────┬────────┬────────────┬─────────────────┬──────────┤ @@ -436,7 +436,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 │ ├───┬───────┬───────────┬────────┬────────┬────────────┬─────────────────┬──────────┬───────────────────────┬────────────────────────────────┤ @@ -510,6 +510,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 @@ -518,62 +734,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\"", }, } diff --git a/go.mod b/go.mod index 9adea42..39ed16b 100644 --- a/go.mod +++ b/go.mod @@ -2,18 +2,17 @@ module asvec go 1.21.7 -// replace github.com/aerospike/avs-client-go => /Users/jesseschmidt/Developer/avs-client-go - -// replace github.com/aerospike/tools-common-go => /Users/jesseschmidt/Developer/tools-common-go +replace github.com/aerospike/avs-client-go => /Users/jesseschmidt/Developer/avs-client-go require ( github.com/aerospike/avs-client-go v0.0.0-20240625001515-2ad1adca5de0 - github.com/aerospike/tools-common-go v0.0.0-20240618165632-595098741f89 + github.com/aerospike/tools-common-go v0.0.0-20240701164814-36eec593d9c6 github.com/jedib0t/go-pretty/v6 v6.5.9 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 + golang.org/x/term v0.21.0 ) require ( diff --git a/go.sum b/go.sum index 8b40062..ff43bda 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/aerospike/aerospike-client-go/v7 v7.4.0 h1:g8/7v8RHhQhTArhW3C7Au7o+u8j8x5eySZL6MXfpHKU= github.com/aerospike/aerospike-client-go/v7 v7.4.0/go.mod h1:pPKnWiS8VDJcH4IeB1b8SA2TWnkjcVLHwAAJ+BHfGK8= -github.com/aerospike/avs-client-go v0.0.0-20240625001515-2ad1adca5de0 h1:ptyMV3i84ErLHyXIs3aTqIxTgJnJInfa6Nn1+L8GqO0= -github.com/aerospike/avs-client-go v0.0.0-20240625001515-2ad1adca5de0/go.mod h1:brxlQNig1y4tU7EP/jX+kXVgmwLERvtP/xhRoNw8Ark= -github.com/aerospike/tools-common-go v0.0.0-20240618165632-595098741f89 h1:5rYc5QsaQeAnSzUm30gOUANEIEsMS8knbnjouenRV7E= -github.com/aerospike/tools-common-go v0.0.0-20240618165632-595098741f89/go.mod h1:Ig1lRynXx0tXNOY3MdtanTsKz1ifG/2AyDFMXn3RMTc= +github.com/aerospike/tools-common-go v0.0.0-20240701164814-36eec593d9c6 h1:tOLqcGsc6A656WNEZhYZYxDiX7d6wCkEN6+jLDSDeUU= +github.com/aerospike/tools-common-go v0.0.0-20240701164814-36eec593d9c6/go.mod h1:Ig1lRynXx0tXNOY3MdtanTsKz1ifG/2AyDFMXn3RMTc= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -123,6 +121,8 @@ golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=