-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
378 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
version: '3' | ||
services: | ||
mongo: | ||
image: mongo:4.4.6 | ||
environment: | ||
- MONGO_INITDB_ROOT_USERNAME=user | ||
- MONGO_INITDB_ROOT_PASSWORD=abcd | ||
restart: on-failure | ||
ports: | ||
- 27017:27017 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package mongodb | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"sort" | ||
"time" | ||
|
||
"github.com/mitchellh/mapstructure" | ||
"go.mongodb.org/mongo-driver/bson" | ||
"go.mongodb.org/mongo-driver/mongo" | ||
"go.mongodb.org/mongo-driver/mongo/options" | ||
) | ||
|
||
type Extractor struct{} | ||
|
||
type Config struct { | ||
UserID string `mapstructure:"user_id"` | ||
Password string `mapstructure:"password"` | ||
Host string `mapstructure:"host"` | ||
} | ||
|
||
func (e *Extractor) Extract(configMap map[string]interface{}) (result []map[string]interface{}, err error) { | ||
config, err := e.getConfig(configMap) | ||
if err != nil { | ||
return | ||
} | ||
err = e.validateConfig(config) | ||
if err != nil { | ||
return | ||
} | ||
uri := "mongodb://" + config.UserID + ":" + config.Password + "@" + config.Host | ||
clientOptions := options.Client().ApplyURI(uri) | ||
client, err := mongo.NewClient(clientOptions) | ||
if err != nil { | ||
return | ||
} | ||
ctx, _ := context.WithTimeout(context.Background(), 4*time.Second) | ||
err = client.Connect(ctx) | ||
if err != nil { | ||
return | ||
} | ||
result, err = e.listCollections(client, ctx) | ||
if err != nil { | ||
return | ||
} | ||
return result, err | ||
} | ||
|
||
func (e *Extractor) listCollections(client *mongo.Client, ctx context.Context) (result []map[string]interface{}, err error) { | ||
databases, err := client.ListDatabaseNames(ctx, bson.M{}) | ||
if err != nil { | ||
return | ||
} | ||
sort.Strings(databases) | ||
var collections []string | ||
for _, db_name := range databases { | ||
db := client.Database(db_name) | ||
collections, err = db.ListCollectionNames(ctx, bson.D{}) | ||
if err != nil { | ||
return | ||
} | ||
sort.Strings(collections) | ||
for _, collection := range collections { | ||
row := make(map[string]interface{}) | ||
row["collection_name"] = collection | ||
row["database_name"] = db_name | ||
count, _ := db.Collection(collection).EstimatedDocumentCount(ctx) | ||
row["document_count"] = int(count) | ||
result = append(result, row) | ||
} | ||
} | ||
return result, err | ||
} | ||
|
||
func (e *Extractor) getConfig(configMap map[string]interface{}) (config Config, err error) { | ||
err = mapstructure.Decode(configMap, &config) | ||
return | ||
} | ||
|
||
func (e *Extractor) validateConfig(config Config) (err error) { | ||
if config.UserID == "" { | ||
return errors.New("user_id is required") | ||
} | ||
if config.Password == "" { | ||
return errors.New("password is required") | ||
} | ||
if config.Host == "" { | ||
return errors.New("host address is required") | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package mongodb_test | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"testing" | ||
"time" | ||
|
||
"github.com/odpf/meteor/extractors/mongodb" | ||
"github.com/stretchr/testify/assert" | ||
"go.mongodb.org/mongo-driver/bson" | ||
"go.mongodb.org/mongo-driver/mongo" | ||
"go.mongodb.org/mongo-driver/mongo/options" | ||
) | ||
|
||
var testDB string = "MeteorMongoExtractorTest" | ||
|
||
var posts = []interface{}{ | ||
bson.D{{"title", "World"}, {"body", "Hello World"}}, | ||
bson.D{{"title", "Mars"}, {"body", "Hello Mars"}}, | ||
bson.D{{"title", "Pluto"}, {"body", "Hello Pluto"}}, | ||
} | ||
|
||
var connections = []interface{}{ | ||
bson.D{{"name", "Albert"}, {"relation", "mutual"}}, | ||
bson.D{{"name", "Josh"}, {"relation", "following"}}, | ||
bson.D{{"name", "Abish"}, {"relation", "follower"}}, | ||
} | ||
|
||
var reach = []interface{}{ | ||
bson.D{{"views", "500"}, {"likes", "200"}, {"comments", "50"}}, | ||
bson.D{{"views", "400"}, {"likes", "100"}, {"comments", "5"}}, | ||
bson.D{{"views", "800"}, {"likes", "300"}, {"comments", "80"}}, | ||
} | ||
|
||
func TestExtract(t *testing.T) { | ||
t.Run("should return error if no user_id in config", func(t *testing.T) { | ||
extractor := new(mongodb.Extractor) | ||
_, err := extractor.Extract(map[string]interface{}{ | ||
"password": "abcd", | ||
"host": "localhost:27017", | ||
}) | ||
|
||
assert.NotNil(t, err) | ||
}) | ||
|
||
t.Run("should return error if no password in config", func(t *testing.T) { | ||
extractor := new(mongodb.Extractor) | ||
_, err := extractor.Extract(map[string]interface{}{ | ||
"user_id": "Gaurav_Ubuntu", | ||
"host": "localhost:27017", | ||
}) | ||
|
||
assert.NotNil(t, err) | ||
}) | ||
|
||
t.Run("should return error if no host in config", func(t *testing.T) { | ||
extractor := new(mongodb.Extractor) | ||
_, err := extractor.Extract(map[string]interface{}{ | ||
"user_id": "user", | ||
"password": "abcd", | ||
}) | ||
|
||
assert.NotNil(t, err) | ||
}) | ||
|
||
t.Run("should return mockdata we generated with mongo running on localhost", func(t *testing.T) { | ||
extractor := new(mongodb.Extractor) | ||
uri := "mongodb://user:abcd@localhost:27017" | ||
clientOptions := options.Client().ApplyURI(uri) | ||
|
||
err := mockDataGenerator(clientOptions) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
result, err := extractor.Extract(map[string]interface{}{ | ||
"user_id": "user", | ||
"password": "abcd", | ||
"host": "localhost:27017", | ||
}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
expected := getExpectedVal() | ||
assert.Equal(t, result, expected) | ||
}) | ||
} | ||
|
||
func getExpectedVal() (expected []map[string]interface{}) { | ||
expected = []map[string]interface{}{ | ||
{ | ||
"collection_name": "connection", | ||
"database_name": testDB, | ||
"document_count": 3, | ||
}, | ||
{ | ||
"collection_name": "posts", | ||
"database_name": testDB, | ||
"document_count": 3, | ||
}, | ||
{ | ||
"collection_name": "reach", | ||
"database_name": testDB, | ||
"document_count": 3, | ||
}, | ||
{ | ||
"collection_name": "system.users", | ||
"database_name": "admin", | ||
"document_count": 1, | ||
}, | ||
{ | ||
"collection_name": "system.version", | ||
"database_name": "admin", | ||
"document_count": 2, | ||
}, | ||
{ | ||
"collection_name": "system.sessions", | ||
"database_name": "config", | ||
"document_count": 0, | ||
}, | ||
} | ||
return | ||
} | ||
|
||
func mockDataGenerator(clientOptions *options.ClientOptions) (err error) { | ||
client, err := mongo.NewClient(clientOptions) | ||
if err != nil { | ||
log.Fatal(err) | ||
return | ||
} | ||
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) | ||
err = client.Connect(ctx) | ||
if err != nil { | ||
return | ||
} | ||
db := client.Database("local") | ||
_ = db.Collection("startup_log").Drop(ctx) | ||
db = client.Database(testDB) | ||
_ = db.Drop(ctx) | ||
err = insertPosts(ctx, client) | ||
if err != nil { | ||
return | ||
} | ||
err = insertConnections(ctx, client) | ||
if err != nil { | ||
return | ||
} | ||
err = insertReach(ctx, client) | ||
if err != nil { | ||
return | ||
} | ||
client.Disconnect(ctx) | ||
return | ||
} | ||
|
||
func insertPosts(ctx context.Context, client *mongo.Client) (err error) { | ||
collection := client.Database(testDB).Collection("posts") | ||
_, insertErr := collection.InsertMany(ctx, posts) | ||
if insertErr != nil { | ||
return insertErr | ||
} | ||
return | ||
} | ||
|
||
func insertConnections(ctx context.Context, client *mongo.Client) (err error) { | ||
collection := client.Database(testDB).Collection("connection") | ||
_, insertErr := collection.InsertMany(ctx, connections) | ||
if insertErr != nil { | ||
return insertErr | ||
} | ||
return | ||
} | ||
|
||
func insertReach(ctx context.Context, client *mongo.Client) (err error) { | ||
collection := client.Database(testDB).Collection("reach") | ||
_, insertErr := collection.InsertMany(ctx, reach) | ||
if insertErr != nil { | ||
return insertErr | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.