Skip to content

Commit fbe6f4c

Browse files
authored
Merge pull request #31 from clidey/hk/feature/elasticsearch
[Feature] Add support for Elastic Search
2 parents 2fd45f1 + 38d26a3 commit fbe6f4c

File tree

20 files changed

+530
-23
lines changed

20 files changed

+530
-23
lines changed

core/go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ go 1.22.1
44

55
require (
66
github.com/99designs/gqlgen v0.17.48
7+
github.com/elastic/go-elasticsearch v0.0.0
8+
github.com/elastic/go-elasticsearch/v8 v8.14.0
79
github.com/go-chi/chi/v5 v5.0.12
810
github.com/go-chi/cors v1.2.1
911
github.com/go-redis/redis/v8 v8.11.5
@@ -22,6 +24,9 @@ require (
2224
github.com/cespare/xxhash/v2 v2.1.2 // indirect
2325
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
2426
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
27+
github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect
28+
github.com/go-logr/logr v1.4.1 // indirect
29+
github.com/go-logr/stdr v1.2.2 // indirect
2530
github.com/go-sql-driver/mysql v1.7.0 // indirect
2631
github.com/golang/snappy v0.0.4 // indirect
2732
github.com/gorilla/websocket v1.5.0 // indirect
@@ -45,6 +50,9 @@ require (
4550
github.com/xdg-go/stringprep v1.0.4 // indirect
4651
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
4752
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
53+
go.opentelemetry.io/otel v1.24.0 // indirect
54+
go.opentelemetry.io/otel/metric v1.24.0 // indirect
55+
go.opentelemetry.io/otel/trace v1.24.0 // indirect
4856
golang.org/x/crypto v0.22.0 // indirect
4957
golang.org/x/mod v0.17.0 // indirect
5058
golang.org/x/sync v0.7.0 // indirect

core/go.sum

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,23 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
2222
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
2323
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
2424
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
25+
github.com/elastic/elastic-transport-go/v8 v8.6.0 h1:Y2S/FBjx1LlCv5m6pWAF2kDJAHoSjSRSJCApolgfthA=
26+
github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
27+
github.com/elastic/go-elasticsearch v0.0.0 h1:Pd5fqOuBxKxv83b0+xOAJDAkziWYwFinWnBO0y+TZaA=
28+
github.com/elastic/go-elasticsearch v0.0.0/go.mod h1:TkBSJBuTyFdBnrNqoPc54FN0vKf5c04IdM4zuStJ7xg=
29+
github.com/elastic/go-elasticsearch/v8 v8.14.0 h1:1ywU8WFReLLcxE1WJqii3hTtbPUE2hc38ZK/j4mMFow=
30+
github.com/elastic/go-elasticsearch/v8 v8.14.0/go.mod h1:WRvnlGkSuZyp83M2U8El/LGXpCjYLrvlkSgkAH4O5I4=
2531
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
2632
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
2733
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
2834
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
2935
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
3036
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
37+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
38+
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
39+
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
40+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
41+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
3142
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
3243
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
3344
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
@@ -104,6 +115,14 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul
104115
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
105116
go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4=
106117
go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
118+
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
119+
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
120+
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
121+
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
122+
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
123+
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
124+
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
125+
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
107126
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
108127
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
109128
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=

core/graph/model/models_gen.go

Lines changed: 8 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/graph/schema.graphqls

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ enum DatabaseType {
88
Sqlite3,
99
MongoDB,
1010
Redis,
11+
ElasticSearch,
1112
}
1213

1314
type Column {

core/src/engine/engine.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import "github.com/clidey/whodb/core/graph/model"
55
type DatabaseType string
66

77
const (
8-
DatabaseType_Postgres = "Postgres"
9-
DatabaseType_MySQL = "MySQL"
10-
DatabaseType_Sqlite3 = "Sqlite3"
11-
DatabaseType_MongoDB = "MongoDB"
12-
DatabaseType_Redis = "Redis"
8+
DatabaseType_Postgres = "Postgres"
9+
DatabaseType_MySQL = "MySQL"
10+
DatabaseType_Sqlite3 = "Sqlite3"
11+
DatabaseType_MongoDB = "MongoDB"
12+
DatabaseType_Redis = "Redis"
13+
DatabaseType_ElasticSearch = "ElasticSearch"
1314
)
1415

1516
type Engine struct {

core/src/plugins/elasticsearch/db.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package elasticsearch
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/clidey/whodb/core/src/common"
7+
"github.com/clidey/whodb/core/src/engine"
8+
"github.com/elastic/go-elasticsearch/v8"
9+
)
10+
11+
func DB(config *engine.PluginConfig) (*elasticsearch.Client, error) {
12+
var addresses []string
13+
port := common.GetRecordValueOrDefault(config.Credentials.Advanced, "Port", "9200")
14+
sslMode := common.GetRecordValueOrDefault(config.Credentials.Advanced, "SSL Mode", "disable")
15+
if sslMode == "enable" {
16+
addresses = []string{
17+
fmt.Sprintf("https://%s:%s", config.Credentials.Hostname, port),
18+
}
19+
} else {
20+
addresses = []string{
21+
fmt.Sprintf("http://%s:%s", config.Credentials.Hostname, port),
22+
}
23+
}
24+
25+
cfg := elasticsearch.Config{
26+
Addresses: addresses,
27+
Username: config.Credentials.Username,
28+
Password: config.Credentials.Password,
29+
}
30+
31+
client, err := elasticsearch.NewClient(cfg)
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
res, err := client.Info()
37+
if err != nil || res.IsError() {
38+
return nil, fmt.Errorf("error pinging Elasticsearch: %v", err)
39+
}
40+
41+
return client, nil
42+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package elasticsearch
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
10+
"github.com/clidey/whodb/core/src/engine"
11+
)
12+
13+
type ElasticSearchPlugin struct{}
14+
15+
func (p *ElasticSearchPlugin) IsAvailable(config *engine.PluginConfig) bool {
16+
client, err := DB(config)
17+
if err != nil {
18+
return false
19+
}
20+
res, err := client.Info()
21+
if err != nil || res.IsError() {
22+
return false
23+
}
24+
return true
25+
}
26+
27+
func (p *ElasticSearchPlugin) GetDatabases() ([]string, error) {
28+
return nil, errors.ErrUnsupported
29+
}
30+
31+
func (p *ElasticSearchPlugin) GetSchema(config *engine.PluginConfig) ([]string, error) {
32+
return nil, errors.ErrUnsupported
33+
}
34+
35+
func (p *ElasticSearchPlugin) GetStorageUnits(config *engine.PluginConfig, database string) ([]engine.StorageUnit, error) {
36+
client, err := DB(config)
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
res, err := client.Indices.Stats()
42+
if err != nil {
43+
return nil, err
44+
}
45+
defer res.Body.Close()
46+
47+
if res.IsError() {
48+
return nil, fmt.Errorf("error getting stats for indices: %s", res.String())
49+
}
50+
51+
var stats map[string]interface{}
52+
if err := json.NewDecoder(res.Body).Decode(&stats); err != nil {
53+
return nil, err
54+
}
55+
56+
indicesStats := stats["indices"].(map[string]interface{})
57+
storageUnits := make([]engine.StorageUnit, 0, len(indicesStats))
58+
59+
for indexName, indexStatsInterface := range indicesStats {
60+
indexStats := indexStatsInterface.(map[string]interface{})
61+
primaries := indexStats["primaries"].(map[string]interface{})
62+
docs := primaries["docs"].(map[string]interface{})
63+
store := primaries["store"].(map[string]interface{})
64+
65+
storageUnit := engine.StorageUnit{
66+
Name: indexName,
67+
Attributes: []engine.Record{
68+
{Key: "Storage Size", Value: fmt.Sprintf("%v", store["size_in_bytes"])},
69+
{Key: "Count", Value: fmt.Sprintf("%v", docs["count"])},
70+
},
71+
}
72+
storageUnits = append(storageUnits, storageUnit)
73+
}
74+
75+
return storageUnits, nil
76+
}
77+
78+
func (p *ElasticSearchPlugin) GetRows(config *engine.PluginConfig, database, collection, filter string, pageSize, pageOffset int) (*engine.GetRowsResult, error) {
79+
client, err := DB(config)
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
var elasticSearchConditions map[string]interface{}
85+
if len(filter) > 0 {
86+
if err := json.Unmarshal([]byte(filter), &elasticSearchConditions); err != nil {
87+
return nil, fmt.Errorf("invalid filter format: %v", err)
88+
}
89+
}
90+
91+
query := map[string]interface{}{
92+
"from": pageOffset,
93+
"size": pageSize,
94+
}
95+
96+
for key, value := range elasticSearchConditions {
97+
query[key] = value
98+
}
99+
100+
var buf bytes.Buffer
101+
if err := json.NewEncoder(&buf).Encode(query); err != nil {
102+
return nil, err
103+
}
104+
105+
res, err := client.Search(
106+
client.Search.WithContext(context.Background()),
107+
client.Search.WithIndex(collection),
108+
client.Search.WithBody(&buf),
109+
client.Search.WithTrackTotalHits(true),
110+
)
111+
if err != nil {
112+
return nil, err
113+
}
114+
defer res.Body.Close()
115+
116+
if res.IsError() {
117+
return nil, fmt.Errorf("error searching documents: %s", res.String())
118+
}
119+
120+
var searchResult map[string]interface{}
121+
if err := json.NewDecoder(res.Body).Decode(&searchResult); err != nil {
122+
return nil, err
123+
}
124+
125+
hits := searchResult["hits"].(map[string]interface{})["hits"].([]interface{})
126+
result := &engine.GetRowsResult{
127+
Columns: []engine.Column{
128+
{Name: "document", Type: "Document"},
129+
},
130+
Rows: [][]string{},
131+
}
132+
133+
for _, hit := range hits {
134+
hitMap := hit.(map[string]interface{})
135+
source := hitMap["_source"]
136+
id := hitMap["_id"]
137+
document := map[string]interface{}{}
138+
document["_id"] = id
139+
document["source"] = source
140+
jsonBytes, err := json.Marshal(document)
141+
if err != nil {
142+
return nil, err
143+
}
144+
result.Rows = append(result.Rows, []string{string(jsonBytes)})
145+
}
146+
147+
return result, nil
148+
}
149+
150+
func (p *ElasticSearchPlugin) RawExecute(config *engine.PluginConfig, query string) (*engine.GetRowsResult, error) {
151+
return nil, errors.New("unsupported operation")
152+
}
153+
154+
func NewElasticSearchPlugin() *engine.Plugin {
155+
return &engine.Plugin{
156+
Type: engine.DatabaseType_ElasticSearch,
157+
PluginFunctions: &ElasticSearchPlugin{},
158+
}
159+
}

0 commit comments

Comments
 (0)