From 43df260d1b4553a98357183b6d37b6f041ae45b5 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Thu, 7 Nov 2024 12:55:48 +0000 Subject: [PATCH 1/4] CLOUDP-283287 Move local search index code to collection level --- .../cli/deployments/search/indexes/create.go | 6 +- .../deployments/search/indexes/create_test.go | 24 +- .../cli/deployments/search/indexes/list.go | 2 +- .../deployments/search/indexes/list_test.go | 11 +- internal/decryption/log_record.go | 2 +- internal/mocks/mock_mongodb_client.go | 112 +++------ internal/mongodbclient/client.go | 2 +- internal/mongodbclient/collection.go | 155 +++++++++++- internal/mongodbclient/database.go | 49 +++- internal/mongodbclient/search_index.go | 223 ------------------ 10 files changed, 268 insertions(+), 318 deletions(-) delete mode 100644 internal/mongodbclient/search_index.go diff --git a/internal/cli/deployments/search/indexes/create.go b/internal/cli/deployments/search/indexes/create.go index af92804198..2d2d1868e3 100644 --- a/internal/cli/deployments/search/indexes/create.go +++ b/internal/cli/deployments/search/indexes/create.go @@ -88,12 +88,12 @@ func (opts *CreateOpts) RunLocal(ctx context.Context) error { telemetry.AppendOption(telemetry.WithSearchIndexType(opts.index.GetType())) - db := opts.mongodbClient.Database(opts.index.Database) - if idx, _ := db.SearchIndexByName(ctx, opts.index.Name, opts.index.CollectionName); idx != nil { + coll := opts.mongodbClient.Database(opts.index.Database).Collection(opts.index.CollectionName) + if idx, _ := coll.SearchIndexByName(ctx, opts.index.Name); idx != nil { return ErrSearchIndexDuplicated } - opts.index, err = db.CreateSearchIndex(ctx, opts.index.CollectionName, opts.index) + opts.index, err = coll.CreateSearchIndex(ctx, opts.index) return err } diff --git a/internal/cli/deployments/search/indexes/create_test.go b/internal/cli/deployments/search/indexes/create_test.go index e184d0b4fd..9a5beac7ca 100644 --- a/internal/cli/deployments/search/indexes/create_test.go +++ b/internal/cli/deployments/search/indexes/create_test.go @@ -50,6 +50,7 @@ func TestCreate_RunLocal(t *testing.T) { ctrl := gomock.NewController(t) mockMongodbClient := mocks.NewMockMongoDBClient(ctrl) mockDB := mocks.NewMockDatabase(ctrl) + mockColl := mocks.NewMockCollection(ctrl) ctx := context.Background() testDeployments := fixture.NewMockLocalDeploymentOpts(ctrl, expectedLocalDeployment) @@ -110,6 +111,11 @@ func TestCreate_RunLocal(t *testing.T) { Database(expectedDB). Return(mockDB). Times(1) + mockDB. + EXPECT(). + Collection(expectedCollection). + Return(mockColl). + Times(1) index := &atlasv2.ClusterSearchIndex{ Analyzer: &opts.Analyzer, @@ -138,15 +144,15 @@ func TestCreate_RunLocal(t *testing.T) { Type: pointer.Get(search.DefaultType), } - mockDB. + mockColl. EXPECT(). - SearchIndexByName(ctx, index.Name, index.CollectionName). + SearchIndexByName(ctx, index.Name). Return(nil, mongodbclient.ErrSearchIndexNotFound). Times(1) - mockDB. + mockColl. EXPECT(). - CreateSearchIndex(ctx, expectedCollection, gomock.Any()). + CreateSearchIndex(ctx, gomock.Any()). Return(indexWithID, nil). Times(1) @@ -166,6 +172,7 @@ func TestCreate_Duplicated(t *testing.T) { ctrl := gomock.NewController(t) mockMongodbClient := mocks.NewMockMongoDBClient(ctrl) mockDB := mocks.NewMockDatabase(ctrl) + mockColl := mocks.NewMockCollection(ctrl) ctx := context.Background() testDeployments := fixture.NewMockLocalDeploymentOpts(ctrl, expectedLocalDeployment) @@ -226,6 +233,11 @@ func TestCreate_Duplicated(t *testing.T) { Database(expectedDB). Return(mockDB). Times(1) + mockDB. + EXPECT(). + Collection(expectedCollection). + Return(mockColl). + Times(1) index := &atlasv2.ClusterSearchIndex{ Analyzer: &opts.Analyzer, @@ -252,9 +264,9 @@ func TestCreate_Duplicated(t *testing.T) { IndexID: &indexID, } - mockDB. + mockColl. EXPECT(). - SearchIndexByName(ctx, index.Name, index.CollectionName). + SearchIndexByName(ctx, index.Name). Return(indexWithID, nil). Times(1) if err := opts.Run(ctx); err == nil || !errors.Is(err, ErrSearchIndexDuplicated) { diff --git a/internal/cli/deployments/search/indexes/list.go b/internal/cli/deployments/search/indexes/list.go index 07b0775133..febcb45510 100644 --- a/internal/cli/deployments/search/indexes/list.go +++ b/internal/cli/deployments/search/indexes/list.go @@ -77,7 +77,7 @@ func (opts *ListOpts) RunLocal(ctx context.Context) error { } defer opts.mongodbClient.Disconnect() - r, err := opts.mongodbClient.Database(opts.DBName).SearchIndexes(ctx, opts.Collection) + r, err := opts.mongodbClient.Database(opts.DBName).Collection(opts.Collection).SearchIndexes(ctx) if err != nil { return err } diff --git a/internal/cli/deployments/search/indexes/list_test.go b/internal/cli/deployments/search/indexes/list_test.go index b6480c52f2..aa61155e3e 100644 --- a/internal/cli/deployments/search/indexes/list_test.go +++ b/internal/cli/deployments/search/indexes/list_test.go @@ -39,6 +39,7 @@ func TestList_RunLocal(t *testing.T) { ctrl := gomock.NewController(t) mockMongodbClient := mocks.NewMockMongoDBClient(ctrl) mockDB := mocks.NewMockDatabase(ctrl) + mockColl := mocks.NewMockCollection(ctrl) ctx := context.Background() const ( @@ -110,6 +111,12 @@ func TestList_RunLocal(t *testing.T) { Return(mockDB). Times(1) + mockDB. + EXPECT(). + Collection(expectedCollection). + Return(mockColl). + Times(1) + expected := []*atlasv2.ClusterSearchIndex{ { Name: expectedName, @@ -121,9 +128,9 @@ func TestList_RunLocal(t *testing.T) { }, } - mockDB. + mockColl. EXPECT(). - SearchIndexes(ctx, expectedCollection). + SearchIndexes(ctx). Return(expected, nil). Times(1) diff --git a/internal/decryption/log_record.go b/internal/decryption/log_record.go index aa536ba8c9..f80ae94d8b 100644 --- a/internal/decryption/log_record.go +++ b/internal/decryption/log_record.go @@ -106,7 +106,7 @@ func (logLine *AuditLogLine) logAdditionalAuthData() []byte { const AADByteSize = 8 additionalAuthData := make([]byte, AADByteSize) - //nolint:gosec + binary.LittleEndian.PutUint64(additionalAuthData, uint64(logLine.TS.UnixMilli())) return additionalAuthData } diff --git a/internal/mocks/mock_mongodb_client.go b/internal/mocks/mock_mongodb_client.go index cc59c585cc..656aa663b9 100644 --- a/internal/mocks/mock_mongodb_client.go +++ b/internal/mocks/mock_mongodb_client.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongodbclient (interfaces: MongoDBClient,Database,SearchIndex) +// Source: github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongodbclient (interfaces: MongoDBClient,Database,Collection) // Package mocks is a generated GoMock package. package mocks @@ -11,6 +11,7 @@ import ( gomock "github.com/golang/mock/gomock" mongodbclient "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongodbclient" admin "go.mongodb.org/atlas-sdk/v20240805005/admin" + mongo "go.mongodb.org/mongo-driver/mongo" ) // MockMongoDBClient is a mock of MongoDBClient interface. @@ -142,21 +143,6 @@ func (mr *MockDatabaseMockRecorder) Collection(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Collection", reflect.TypeOf((*MockDatabase)(nil).Collection), arg0) } -// CreateSearchIndex mocks base method. -func (m *MockDatabase) CreateSearchIndex(arg0 context.Context, arg1 string, arg2 *admin.ClusterSearchIndex) (*admin.ClusterSearchIndex, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateSearchIndex", arg0, arg1, arg2) - ret0, _ := ret[0].(*admin.ClusterSearchIndex) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateSearchIndex indicates an expected call of CreateSearchIndex. -func (mr *MockDatabaseMockRecorder) CreateSearchIndex(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSearchIndex", reflect.TypeOf((*MockDatabase)(nil).CreateSearchIndex), arg0, arg1, arg2) -} - // RunCommand mocks base method. func (m *MockDatabase) RunCommand(arg0 context.Context, arg1 interface{}) (interface{}, error) { m.ctrl.T.Helper() @@ -187,115 +173,85 @@ func (mr *MockDatabaseMockRecorder) SearchIndex(arg0, arg1 interface{}) *gomock. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchIndex", reflect.TypeOf((*MockDatabase)(nil).SearchIndex), arg0, arg1) } -// SearchIndexByName mocks base method. -func (m *MockDatabase) SearchIndexByName(arg0 context.Context, arg1, arg2 string) (*admin.ClusterSearchIndex, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SearchIndexByName", arg0, arg1, arg2) - ret0, _ := ret[0].(*admin.ClusterSearchIndex) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SearchIndexByName indicates an expected call of SearchIndexByName. -func (mr *MockDatabaseMockRecorder) SearchIndexByName(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchIndexByName", reflect.TypeOf((*MockDatabase)(nil).SearchIndexByName), arg0, arg1, arg2) -} - -// SearchIndexes mocks base method. -func (m *MockDatabase) SearchIndexes(arg0 context.Context, arg1 string) ([]*admin.ClusterSearchIndex, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SearchIndexes", arg0, arg1) - ret0, _ := ret[0].([]*admin.ClusterSearchIndex) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SearchIndexes indicates an expected call of SearchIndexes. -func (mr *MockDatabaseMockRecorder) SearchIndexes(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchIndexes", reflect.TypeOf((*MockDatabase)(nil).SearchIndexes), arg0, arg1) -} - -// MockSearchIndex is a mock of SearchIndex interface. -type MockSearchIndex struct { +// MockCollection is a mock of Collection interface. +type MockCollection struct { ctrl *gomock.Controller - recorder *MockSearchIndexMockRecorder + recorder *MockCollectionMockRecorder } -// MockSearchIndexMockRecorder is the mock recorder for MockSearchIndex. -type MockSearchIndexMockRecorder struct { - mock *MockSearchIndex +// MockCollectionMockRecorder is the mock recorder for MockCollection. +type MockCollectionMockRecorder struct { + mock *MockCollection } -// NewMockSearchIndex creates a new mock instance. -func NewMockSearchIndex(ctrl *gomock.Controller) *MockSearchIndex { - mock := &MockSearchIndex{ctrl: ctrl} - mock.recorder = &MockSearchIndexMockRecorder{mock} +// NewMockCollection creates a new mock instance. +func NewMockCollection(ctrl *gomock.Controller) *MockCollection { + mock := &MockCollection{ctrl: ctrl} + mock.recorder = &MockCollectionMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockSearchIndex) EXPECT() *MockSearchIndexMockRecorder { +func (m *MockCollection) EXPECT() *MockCollectionMockRecorder { return m.recorder } -// CreateSearchIndex mocks base method. -func (m *MockSearchIndex) CreateSearchIndex(arg0 context.Context, arg1 string, arg2 *admin.ClusterSearchIndex) (*admin.ClusterSearchIndex, error) { +// Aggregate mocks base method. +func (m *MockCollection) Aggregate(arg0 context.Context, arg1 interface{}) (*mongo.Cursor, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateSearchIndex", arg0, arg1, arg2) - ret0, _ := ret[0].(*admin.ClusterSearchIndex) + ret := m.ctrl.Call(m, "Aggregate", arg0, arg1) + ret0, _ := ret[0].(*mongo.Cursor) ret1, _ := ret[1].(error) return ret0, ret1 } -// CreateSearchIndex indicates an expected call of CreateSearchIndex. -func (mr *MockSearchIndexMockRecorder) CreateSearchIndex(arg0, arg1, arg2 interface{}) *gomock.Call { +// Aggregate indicates an expected call of Aggregate. +func (mr *MockCollectionMockRecorder) Aggregate(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSearchIndex", reflect.TypeOf((*MockSearchIndex)(nil).CreateSearchIndex), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Aggregate", reflect.TypeOf((*MockCollection)(nil).Aggregate), arg0, arg1) } -// SearchIndex mocks base method. -func (m *MockSearchIndex) SearchIndex(arg0 context.Context, arg1 string) (*admin.ClusterSearchIndex, error) { +// CreateSearchIndex mocks base method. +func (m *MockCollection) CreateSearchIndex(arg0 context.Context, arg1 *admin.ClusterSearchIndex) (*admin.ClusterSearchIndex, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SearchIndex", arg0, arg1) + ret := m.ctrl.Call(m, "CreateSearchIndex", arg0, arg1) ret0, _ := ret[0].(*admin.ClusterSearchIndex) ret1, _ := ret[1].(error) return ret0, ret1 } -// SearchIndex indicates an expected call of SearchIndex. -func (mr *MockSearchIndexMockRecorder) SearchIndex(arg0, arg1 interface{}) *gomock.Call { +// CreateSearchIndex indicates an expected call of CreateSearchIndex. +func (mr *MockCollectionMockRecorder) CreateSearchIndex(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchIndex", reflect.TypeOf((*MockSearchIndex)(nil).SearchIndex), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSearchIndex", reflect.TypeOf((*MockCollection)(nil).CreateSearchIndex), arg0, arg1) } // SearchIndexByName mocks base method. -func (m *MockSearchIndex) SearchIndexByName(arg0 context.Context, arg1, arg2 string) (*admin.ClusterSearchIndex, error) { +func (m *MockCollection) SearchIndexByName(arg0 context.Context, arg1 string) (*admin.ClusterSearchIndex, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SearchIndexByName", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "SearchIndexByName", arg0, arg1) ret0, _ := ret[0].(*admin.ClusterSearchIndex) ret1, _ := ret[1].(error) return ret0, ret1 } // SearchIndexByName indicates an expected call of SearchIndexByName. -func (mr *MockSearchIndexMockRecorder) SearchIndexByName(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockCollectionMockRecorder) SearchIndexByName(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchIndexByName", reflect.TypeOf((*MockSearchIndex)(nil).SearchIndexByName), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchIndexByName", reflect.TypeOf((*MockCollection)(nil).SearchIndexByName), arg0, arg1) } // SearchIndexes mocks base method. -func (m *MockSearchIndex) SearchIndexes(arg0 context.Context, arg1 string) ([]*admin.ClusterSearchIndex, error) { +func (m *MockCollection) SearchIndexes(arg0 context.Context) ([]*admin.ClusterSearchIndex, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SearchIndexes", arg0, arg1) + ret := m.ctrl.Call(m, "SearchIndexes", arg0) ret0, _ := ret[0].([]*admin.ClusterSearchIndex) ret1, _ := ret[1].(error) return ret0, ret1 } // SearchIndexes indicates an expected call of SearchIndexes. -func (mr *MockSearchIndexMockRecorder) SearchIndexes(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockCollectionMockRecorder) SearchIndexes(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchIndexes", reflect.TypeOf((*MockSearchIndex)(nil).SearchIndexes), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchIndexes", reflect.TypeOf((*MockCollection)(nil).SearchIndexes), arg0) } diff --git a/internal/mongodbclient/client.go b/internal/mongodbclient/client.go index 594899ff14..a70d075e7f 100644 --- a/internal/mongodbclient/client.go +++ b/internal/mongodbclient/client.go @@ -28,7 +28,7 @@ import ( var errConnectFailed = errors.New("failed to connect to mongodb server") -//go:generate mockgen -destination=../mocks/mock_mongodb_client.go -package=mocks github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongodbclient MongoDBClient,Database,SearchIndex +//go:generate mockgen -destination=../mocks/mock_mongodb_client.go -package=mocks github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongodbclient MongoDBClient,Database,Collection type MongoDBClient interface { Connect(connectionString string, waitSeconds int64) error diff --git a/internal/mongodbclient/collection.go b/internal/mongodbclient/collection.go index c44c8651b7..2d05938800 100644 --- a/internal/mongodbclient/collection.go +++ b/internal/mongodbclient/collection.go @@ -16,18 +16,169 @@ package mongodbclient import ( "context" + "encoding/json" + "errors" + "slices" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/log" + "go.mongodb.org/atlas-sdk/v20240805005/admin" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" ) +const ( + listSearchIndexes = "$listSearchIndexes" + addFields = "$addFields" + idField = "id" + collectionField = "collection" + databaseField = "database" +) + +var ErrSearchIndexNotFound = errors.New("search Index not found") + type Collection interface { Aggregate(context.Context, any) (*mongo.Cursor, error) + CreateSearchIndex(ctx context.Context, idx *admin.ClusterSearchIndex) (*admin.ClusterSearchIndex, error) + SearchIndexes(ctx context.Context) ([]*admin.ClusterSearchIndex, error) + SearchIndexByName(ctx context.Context, name string) (*admin.ClusterSearchIndex, error) } type collection struct { collection *mongo.Collection } -func (o *collection) Aggregate(ctx context.Context, pipeline any) (*mongo.Cursor, error) { - return o.collection.Aggregate(ctx, pipeline) +func (c *collection) Aggregate(ctx context.Context, pipeline any) (*mongo.Cursor, error) { + return c.collection.Aggregate(ctx, pipeline) +} + +type SearchIndexDefinition struct { + ID string `bson:"id,omitempty"` + Name string `bson:"name,omitempty"` + Collection string `bson:"collection,omitempty"` + Database string `bson:"database,omitempty"` + Analyzer *string `bson:"analyzer,omitempty"` + Analyzers []admin.ApiAtlasFTSAnalyzers `bson:"analyzers,omitempty"` + Synonyms []admin.SearchSynonymMappingDefinition `bson:"synonyms,omitempty"` + Mappings *admin.ApiAtlasFTSMappings `bson:"mappings,omitempty"` + Status *string `bson:"status,omitempty"` +} + +func (c *collection) CreateSearchIndex(ctx context.Context, idx *admin.ClusterSearchIndex) (*admin.ClusterSearchIndex, error) { + // To maintain formatting of the SDK, marshal object into JSON and then unmarshal into BSON + jsonIndex, err := json.Marshal(idx) + if err != nil { + return nil, err + } + + var index bson.D + err = bson.UnmarshalExtJSON(jsonIndex, true, &index) + if err != nil { + return nil, err + } + + // Empty these fields so that they are not included into the index definition for the MongoDB command + index = removeFields(index, "id", "collectionName", "database", "type") + + o := options.SearchIndexes().SetName(idx.Name) + if idx.Type != nil { + o.SetType(*idx.Type) + } + + _, err = c.collection.SearchIndexes().CreateOne(ctx, mongo.SearchIndexModel{ + Definition: index, + Options: o, + }) + + _, _ = log.Debugln("Creating search index with definition: ", index) + if err != nil { + return nil, err + } + + return c.SearchIndexByName(ctx, idx.Name) +} + +func removeFields(doc bson.D, fields ...string) bson.D { + cleanedDoc := bson.D{} + + for _, elem := range doc { + if slices.Contains(fields, elem.Key) { + continue + } + + cleanedDoc = append(cleanedDoc, elem) + } + + return cleanedDoc +} + +func (c *collection) SearchIndexByName(ctx context.Context, name string) (*admin.ClusterSearchIndex, error) { + indexes, err := c.SearchIndexes(ctx) + if err != nil { + return nil, err + } + + for _, index := range indexes { + if index.Name == name && index.Database == c.collection.Database().Name() { + return index, nil + } + } + + return nil, ErrSearchIndexNotFound +} + +func (c *collection) SearchIndexes(ctx context.Context) ([]*admin.ClusterSearchIndex, error) { + cursor, err := c.Aggregate(ctx, newSearchIndexesPipeline(c.collection.Database().Name(), c.collection.Name())) + if err != nil || cursor == nil { + return nil, err + } + + var results []*SearchIndexDefinition + if err = cursor.All(ctx, &results); err != nil { + return nil, err + } + + return newClusterSearchIndexes(results), nil +} + +func newSearchIndexesPipeline(db, coll string) []*bson.D { + return []*bson.D{ + { + { + Key: listSearchIndexes, Value: bson.D{}, + }, + }, + { + { + Key: addFields, Value: bson.D{ + { + Key: collectionField, Value: coll, + }, + { + Key: databaseField, Value: db, + }, + }, + }, + }, + } +} + +func newClusterSearchIndex(index *SearchIndexDefinition) *admin.ClusterSearchIndex { + return &admin.ClusterSearchIndex{ + Name: index.Name, + IndexID: &index.ID, + CollectionName: index.Collection, + Database: index.Database, + Status: index.Status, + } +} + +func newClusterSearchIndexes(indexes []*SearchIndexDefinition) []*admin.ClusterSearchIndex { + out := make([]*admin.ClusterSearchIndex, len(indexes)) + + for i, v := range indexes { + out[i] = newClusterSearchIndex(v) + } + + return out } diff --git a/internal/mongodbclient/database.go b/internal/mongodbclient/database.go index 1fe70bb837..ddf3393ed6 100644 --- a/internal/mongodbclient/database.go +++ b/internal/mongodbclient/database.go @@ -16,14 +16,16 @@ package mongodbclient import ( "context" + "fmt" + "go.mongodb.org/atlas-sdk/v20240805005/admin" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) type Database interface { RunCommand(ctx context.Context, runCommand any) (any, error) - SearchIndex + SearchIndex(ctx context.Context, id string) (*admin.ClusterSearchIndex, error) Collection(string) Collection } @@ -49,3 +51,48 @@ func (d *database) RunCommand(ctx context.Context, runCmd any) (any, error) { } return cmdResult, nil } + +func newSearchIndexPipeline(id string) []*bson.D { + return []*bson.D{ + { + { + Key: listSearchIndexes, Value: []bson.E{ + { + Key: idField, Value: id, + }, + }, + }, + }, + } +} + +func (d *database) SearchIndex(ctx context.Context, id string) (*admin.ClusterSearchIndex, error) { + collectionNames, err := d.db.ListCollectionNames(ctx, bson.D{}, nil) + if err != nil { + return nil, err + } + + // We search the index in all the collections of the database + for _, coll := range collectionNames { + cursor, err := d.db.Collection(coll).Aggregate(ctx, newSearchIndexPipeline(id)) + if err != nil || cursor == nil { + return nil, err + } + var results []SearchIndexDefinition + if err = cursor.All(ctx, &results); err != nil { + return nil, err + } + if len(results) >= 1 { + searchIndexDef := &SearchIndexDefinition{ + ID: results[0].ID, + Name: results[0].Name, + Collection: coll, + Database: d.db.Name(), + Status: results[0].Status, + } + return newClusterSearchIndex(searchIndexDef), nil + } + } + + return nil, fmt.Errorf("index `%s` not found: %w", id, ErrSearchIndexNotFound) +} diff --git a/internal/mongodbclient/search_index.go b/internal/mongodbclient/search_index.go deleted file mode 100644 index e896f97156..0000000000 --- a/internal/mongodbclient/search_index.go +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2023 MongoDB Inc -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package mongodbclient - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "slices" - - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/log" - "go.mongodb.org/atlas-sdk/v20240805005/admin" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const ( - listSearchIndexes = "$listSearchIndexes" - addFields = "$addFields" - idField = "id" - collectionField = "collection" - databaseField = "database" -) - -var ErrSearchIndexNotFound = errors.New("search Index not found") - -type SearchIndex interface { - CreateSearchIndex(ctx context.Context, collection string, idx *admin.ClusterSearchIndex) (*admin.ClusterSearchIndex, error) - SearchIndex(ctx context.Context, id string) (*admin.ClusterSearchIndex, error) - SearchIndexes(ctx context.Context, coll string) ([]*admin.ClusterSearchIndex, error) - SearchIndexByName(ctx context.Context, name string, collection string) (*admin.ClusterSearchIndex, error) -} - -type SearchIndexDefinition struct { - ID string `bson:"id,omitempty"` - Name string `bson:"name,omitempty"` - Collection string `bson:"collection,omitempty"` - Database string `bson:"database,omitempty"` - Analyzer *string `bson:"analyzer,omitempty"` - Analyzers []admin.ApiAtlasFTSAnalyzers `bson:"analyzers,omitempty"` - Synonyms []admin.SearchSynonymMappingDefinition `bson:"synonyms,omitempty"` - Mappings *admin.ApiAtlasFTSMappings `bson:"mappings,omitempty"` - Status *string `bson:"status,omitempty"` -} - -// todo: CLOUDP-199915 Use go-driver search index management helpers instead of createSearchIndex command -func (d *database) CreateSearchIndex(ctx context.Context, collection string, idx *admin.ClusterSearchIndex) (*admin.ClusterSearchIndex, error) { - // To maintain formatting of the SDK, marshal object into JSON and then unmarshal into BSON - jsonIndex, err := json.Marshal(idx) - if err != nil { - return nil, err - } - - var index bson.D - err = bson.UnmarshalExtJSON(jsonIndex, true, &index) - if err != nil { - return nil, err - } - - // Empty these fields so that they are not included into the index definition for the MongoDB command - index = removeFields(index, "id", "collectionName", "database", "type") - - o := options.SearchIndexes().SetName(idx.Name) - if idx.Type != nil { - o.SetType(*idx.Type) - } - - _, err = d.db.Collection(collection).SearchIndexes().CreateOne(ctx, mongo.SearchIndexModel{ - Definition: index, - Options: o, - }) - - _, _ = log.Debugln("Creating search index with definition: ", index) - if err != nil { - return nil, err - } - - return d.SearchIndexByName(ctx, idx.Name, collection) -} - -func removeFields(doc bson.D, fields ...string) bson.D { - cleanedDoc := bson.D{} - - for _, elem := range doc { - if slices.Contains(fields, elem.Key) { - continue - } - - cleanedDoc = append(cleanedDoc, elem) - } - - return cleanedDoc -} - -func (d *database) SearchIndex(ctx context.Context, id string) (*admin.ClusterSearchIndex, error) { - collectionNames, err := d.db.ListCollectionNames(ctx, bson.D{}, nil) - if err != nil { - return nil, err - } - - // We search the index in all the collections of the database - for _, coll := range collectionNames { - cursor, err := d.db.Collection(coll).Aggregate(ctx, newSearchIndexPipeline(id)) - if err != nil || cursor == nil { - return nil, err - } - var results []SearchIndexDefinition - if err = cursor.All(ctx, &results); err != nil { - return nil, err - } - if len(results) >= 1 { - searchIndexDef := &SearchIndexDefinition{ - ID: results[0].ID, - Name: results[0].Name, - Collection: coll, - Database: d.db.Name(), - Status: results[0].Status, - } - return newClusterSearchIndex(searchIndexDef), nil - } - } - - return nil, fmt.Errorf("index `%s` not found: %w", id, ErrSearchIndexNotFound) -} - -func (d *database) SearchIndexByName(ctx context.Context, name string, collection string) (*admin.ClusterSearchIndex, error) { - indexes, err := d.SearchIndexes(ctx, collection) - if err != nil { - return nil, err - } - - for _, index := range indexes { - if index.Name == name && index.Database == d.db.Name() { - return index, nil - } - } - - return nil, ErrSearchIndexNotFound -} - -func (d *database) SearchIndexes(ctx context.Context, coll string) ([]*admin.ClusterSearchIndex, error) { - cursor, err := d.db.Collection(coll).Aggregate(ctx, newSearchIndexesPipeline(d.db.Name(), coll)) - if err != nil || cursor == nil { - return nil, err - } - - var results []*SearchIndexDefinition - if err = cursor.All(ctx, &results); err != nil { - return nil, err - } - - return newClusterSearchIndexes(results), nil -} - -func newSearchIndexPipeline(id string) []*bson.D { - return []*bson.D{ - { - { - Key: listSearchIndexes, Value: []bson.E{ - { - Key: idField, Value: id, - }, - }, - }, - }, - } -} - -func newSearchIndexesPipeline(db, coll string) []*bson.D { - return []*bson.D{ - { - { - Key: listSearchIndexes, Value: bson.D{}, - }, - }, - { - { - Key: addFields, Value: bson.D{ - { - Key: collectionField, Value: coll, - }, - { - Key: databaseField, Value: db, - }, - }, - }, - }, - } -} - -func newClusterSearchIndex(index *SearchIndexDefinition) *admin.ClusterSearchIndex { - return &admin.ClusterSearchIndex{ - Name: index.Name, - IndexID: &index.ID, - CollectionName: index.Collection, - Database: index.Database, - Status: index.Status, - } -} - -func newClusterSearchIndexes(indexes []*SearchIndexDefinition) []*admin.ClusterSearchIndex { - out := make([]*admin.ClusterSearchIndex, len(indexes)) - - for i, v := range indexes { - out[i] = newClusterSearchIndex(v) - } - - return out -} From a7d3cf55f2afe3760b3e0b0823be3ab4b0347861 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Thu, 7 Nov 2024 12:59:12 +0000 Subject: [PATCH 2/4] remove NewClient without context --- internal/cli/deployments/search/indexes/list.go | 10 ++++++---- internal/mongodbclient/client.go | 6 ------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/internal/cli/deployments/search/indexes/list.go b/internal/cli/deployments/search/indexes/list.go index febcb45510..1fcb1e1988 100644 --- a/internal/cli/deployments/search/indexes/list.go +++ b/internal/cli/deployments/search/indexes/list.go @@ -85,9 +85,11 @@ func (opts *ListOpts) RunLocal(ctx context.Context) error { return opts.Print(r) } -func (opts *ListOpts) initMongoDBClient() error { - opts.mongodbClient = mongodbclient.NewClient() - return nil +func (opts *ListOpts) initMongoDBClient(ctx context.Context) func() error { + return func() error { + opts.mongodbClient = mongodbclient.NewClientWithContext(ctx) + return nil + } } func (opts *ListOpts) initStore(ctx context.Context) func() error { @@ -133,7 +135,7 @@ func ListBuilder() *cobra.Command { opts.InitOutput(w, listTemplate), opts.InitStore(cmd.Context(), cmd.OutOrStdout()), opts.initStore(cmd.Context()), - opts.initMongoDBClient, + opts.initMongoDBClient(cmd.Context()), ) }, RunE: func(cmd *cobra.Command, _ []string) error { diff --git a/internal/mongodbclient/client.go b/internal/mongodbclient/client.go index a70d075e7f..bb72dc7e41 100644 --- a/internal/mongodbclient/client.go +++ b/internal/mongodbclient/client.go @@ -43,12 +43,6 @@ type mongodbClient struct { ctx context.Context } -func NewClient() MongoDBClient { - return &mongodbClient{ - ctx: context.Background(), - } -} - func NewClientWithContext(ctx context.Context) MongoDBClient { return &mongodbClient{ ctx: ctx, From 3302133798b18b3aa64960560f29b179ed7883ba Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Thu, 7 Nov 2024 13:04:04 +0000 Subject: [PATCH 3/4] rename mongodbclient.NewClientWithContext to mongodbclient.NewClient --- internal/cli/deployments/search/indexes/create.go | 2 +- internal/cli/deployments/search/indexes/delete.go | 2 +- internal/cli/deployments/search/indexes/describe.go | 2 +- internal/cli/deployments/search/indexes/list.go | 2 +- internal/cli/deployments/setup.go | 2 +- internal/mongodbclient/client.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/cli/deployments/search/indexes/create.go b/internal/cli/deployments/search/indexes/create.go index 2d2d1868e3..26474684b3 100644 --- a/internal/cli/deployments/search/indexes/create.go +++ b/internal/cli/deployments/search/indexes/create.go @@ -128,7 +128,7 @@ func (opts *CreateOpts) Run(ctx context.Context) error { func (opts *CreateOpts) initMongoDBClient(ctx context.Context) func() error { return func() error { - opts.mongodbClient = mongodbclient.NewClientWithContext(ctx) + opts.mongodbClient = mongodbclient.NewClient(ctx) return nil } } diff --git a/internal/cli/deployments/search/indexes/delete.go b/internal/cli/deployments/search/indexes/delete.go index d7e71ef549..c5322b0296 100644 --- a/internal/cli/deployments/search/indexes/delete.go +++ b/internal/cli/deployments/search/indexes/delete.go @@ -94,7 +94,7 @@ func (opts *DeleteOpts) initStore(ctx context.Context) func() error { func (opts *DeleteOpts) initMongoDBClient(ctx context.Context) func() error { return func() error { - opts.mongodbClient = mongodbclient.NewClientWithContext(ctx) + opts.mongodbClient = mongodbclient.NewClient(ctx) return nil } } diff --git a/internal/cli/deployments/search/indexes/describe.go b/internal/cli/deployments/search/indexes/describe.go index 8df0349fd9..a176b6d91e 100644 --- a/internal/cli/deployments/search/indexes/describe.go +++ b/internal/cli/deployments/search/indexes/describe.go @@ -95,7 +95,7 @@ func (opts *DescribeOpts) RunLocal(ctx context.Context) error { func (opts *DescribeOpts) initMongoDBClient(ctx context.Context) func() error { return func() error { - opts.mongodbClient = mongodbclient.NewClientWithContext(ctx) + opts.mongodbClient = mongodbclient.NewClient(ctx) return nil } } diff --git a/internal/cli/deployments/search/indexes/list.go b/internal/cli/deployments/search/indexes/list.go index 1fcb1e1988..5ca0eab9eb 100644 --- a/internal/cli/deployments/search/indexes/list.go +++ b/internal/cli/deployments/search/indexes/list.go @@ -87,7 +87,7 @@ func (opts *ListOpts) RunLocal(ctx context.Context) error { func (opts *ListOpts) initMongoDBClient(ctx context.Context) func() error { return func() error { - opts.mongodbClient = mongodbclient.NewClientWithContext(ctx) + opts.mongodbClient = mongodbclient.NewClient(ctx) return nil } } diff --git a/internal/cli/deployments/setup.go b/internal/cli/deployments/setup.go index ddc59b0ae4..f59f21b926 100644 --- a/internal/cli/deployments/setup.go +++ b/internal/cli/deployments/setup.go @@ -118,7 +118,7 @@ type SetupOpts struct { func (opts *SetupOpts) initMongoDBClient(ctx context.Context) func() error { return func() error { - opts.mongodbClient = mongodbclient.NewClientWithContext(ctx) + opts.mongodbClient = mongodbclient.NewClient(ctx) return nil } } diff --git a/internal/mongodbclient/client.go b/internal/mongodbclient/client.go index bb72dc7e41..495161c79c 100644 --- a/internal/mongodbclient/client.go +++ b/internal/mongodbclient/client.go @@ -43,7 +43,7 @@ type mongodbClient struct { ctx context.Context } -func NewClientWithContext(ctx context.Context) MongoDBClient { +func NewClient(ctx context.Context) MongoDBClient { return &mongodbClient{ ctx: ctx, } From 891715258bc3fd5fd942536baf374a288040ead4 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Thu, 7 Nov 2024 16:06:15 +0000 Subject: [PATCH 4/4] fix lint --- internal/decryption/log_record.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/decryption/log_record.go b/internal/decryption/log_record.go index f80ae94d8b..aa536ba8c9 100644 --- a/internal/decryption/log_record.go +++ b/internal/decryption/log_record.go @@ -106,7 +106,7 @@ func (logLine *AuditLogLine) logAdditionalAuthData() []byte { const AADByteSize = 8 additionalAuthData := make([]byte, AADByteSize) - + //nolint:gosec binary.LittleEndian.PutUint64(additionalAuthData, uint64(logLine.TS.UnixMilli())) return additionalAuthData }