Skip to content

Commit 1abb88a

Browse files
authored
Add mongo atlas support (#13)
* Add mongo+srv scheme * bug fix * fix lint
1 parent 85a4f4a commit 1abb88a

File tree

7 files changed

+106
-68
lines changed

7 files changed

+106
-68
lines changed

pkg/models/settings.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import (
88
)
99

1010
type PluginSettings struct {
11-
Host string `json:"host"`
12-
Port int `json:"port"`
13-
Database string `json:"database"`
14-
AuthMethod string `json:"authType"`
15-
Username string `json:"username"`
16-
Secrets *SecretPluginSettings `json:"-"`
11+
Host string `json:"host"`
12+
Port int `json:"port"`
13+
Database string `json:"database"`
14+
AuthMethod string `json:"authType"`
15+
Username string `json:"username"`
16+
ConnectionStringScheme string `json:"connectionStringScheme"`
17+
ConnectionParameters string `json:"connectionParameters"`
18+
Secrets *SecretPluginSettings `json:"-"`
1719
}
1820

1921
type SecretPluginSettings struct {

pkg/plugin/datasource.go

Lines changed: 9 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package plugin
33
import (
44
"context"
55
"encoding/json"
6-
"errors"
76
"fmt"
87
"strings"
98
"time"
@@ -30,27 +29,15 @@ var (
3029
// NewDatasource creates a new MongoDB datasource instance.
3130
func NewDatasource(ctx context.Context, source backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
3231

33-
var uri string
34-
3532
config, err := models.LoadPluginSettings(source)
3633
if err != nil {
3734
backend.Logger.Error(fmt.Sprintf("Failed to load plugin settings: %s", err.Error()))
3835
return nil, err
3936
}
4037

41-
if config.Database == "" {
42-
return nil, errors.New("missing MongoDB database")
43-
}
44-
45-
if config.AuthMethod == "auth-none" {
46-
uri = fmt.Sprintf("mongodb://%s:%d", config.Host, config.Port)
47-
} else if config.AuthMethod == "auth-username-password" {
48-
if config.Username == "" || config.Secrets.Password == "" {
49-
return nil, errors.New("missing MongoDB username or password")
50-
}
51-
uri = fmt.Sprintf("mongodb://%s:%s@%s:%d", config.Username, config.Secrets.Password, config.Host, config.Port)
52-
} else {
53-
return nil, errors.New("authentication method not supported")
38+
uri, err := MongoUri(config)
39+
if err != nil {
40+
return nil, err
5441
}
5542

5643
opts := options.Client().ApplyURI(uri)
@@ -64,8 +51,7 @@ func NewDatasource(ctx context.Context, source backend.DataSourceInstanceSetting
6451
return &Datasource{
6552
client: client,
6653
database: config.Database,
67-
host: config.Host,
68-
port: config.Port}, nil
54+
}, nil
6955
}
7056

7157
// Dispose here tells plugin SDK that plugin wants to clean up resources when a new instance
@@ -83,7 +69,7 @@ func (d *Datasource) QueryData(ctx context.Context, req *backend.QueryDataReques
8369
// create response struct
8470
response := backend.NewQueryDataResponse()
8571
// loop over queries and execute them individually.
86-
backend.Logger.Debug("New queries", d.host, d.port, d.database)
72+
8773
for _, q := range req.Queries {
8874

8975
res := d.query(ctx, req.PluginContext, q)
@@ -157,49 +143,18 @@ func (d *Datasource) query(ctx context.Context, _ backend.PluginContext, query b
157143
func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
158144
res := &backend.CheckHealthResult{}
159145
backend.Logger.Debug("Checking health")
160-
config, err := models.LoadPluginSettings(*req.PluginContext.DataSourceInstanceSettings)
161146

147+
config, err := models.LoadPluginSettings(*req.PluginContext.DataSourceInstanceSettings)
162148
if err != nil {
163149
res.Status = backend.HealthStatusError
164150
res.Message = "Unable to load settings"
165151
return res, nil
166152
}
167153

168-
backend.Logger.Debug(fmt.Sprintf("Config: %v", config))
169-
170-
if config.AuthMethod == "" {
171-
res.Status = backend.HealthStatusError
172-
res.Message = "Please specify the authentication type"
173-
return res, nil
174-
}
175-
176-
if config.Host == "" {
177-
res.Status = backend.HealthStatusError
178-
res.Message = "Please specify the host address"
179-
return res, nil
180-
}
181-
182-
if config.Database == "" {
183-
res.Status = backend.HealthStatusError
184-
res.Message = "Please specify the database"
185-
return res, nil
186-
}
187-
188-
var uri string
189-
190-
if config.AuthMethod == "auth-none" {
191-
uri = fmt.Sprintf("mongodb://%s:%d", config.Host, config.Port)
192-
} else if config.AuthMethod == "auth-username-password" {
193-
if config.Username == "" || config.Secrets.Password == "" {
194-
res.Status = backend.HealthStatusError
195-
res.Message = "Please specify the username and password"
196-
return res, nil
197-
}
198-
uri = fmt.Sprintf("mongodb://%s:%s@%s:%d", config.Username, config.Secrets.Password, config.Host, config.Port)
199-
} else {
154+
uri, err := MongoUri(config)
155+
if err != nil {
200156
res.Status = backend.HealthStatusError
201-
res.Message = "Please specify the authentication type"
202-
return res, nil
157+
res.Message = err.Error()
203158
}
204159

205160
opts := options.Client().ApplyURI(uri).SetTimeout(5 * time.Second)

pkg/plugin/datasource_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ func TestQueryTableData(t *testing.T) {
4949

5050
ds := Datasource{
5151
client: client,
52-
host: "localhost",
53-
port: 27018,
5452
database: "test",
5553
}
5654

pkg/plugin/types.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99
// Datasource is a mongo datasource which can respond to data queries, reports
1010
// its health and has streaming skills.
1111
type Datasource struct {
12-
host string
13-
port int
1412
database string
1513
client *mongo.Client
1614
}

pkg/plugin/utils.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package plugin
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67

78
"github.com/grafana/grafana-plugin-sdk-go/data"
9+
"github.com/haohanyang/mongodb-datasource/pkg/models"
810
)
911

1012
func PrintDataFrame(dataFrame *data.Frame) {
@@ -61,3 +63,35 @@ func null[K any]() *K {
6163
var nullValue *K
6264
return nullValue
6365
}
66+
67+
// Validate mongo connection configuration and return connection string
68+
func MongoUri(config *models.PluginSettings) (string, error) {
69+
var uri string
70+
var creds string
71+
var params string
72+
73+
if config.Database == "" {
74+
return uri, errors.New("missing MongoDB database")
75+
}
76+
77+
if config.AuthMethod == "auth-username-password" {
78+
if config.Username == "" || config.Secrets.Password == "" {
79+
return uri, errors.New("missing MongoDB username or password")
80+
}
81+
creds = fmt.Sprintf("%s:%s@", config.Username, config.Secrets.Password)
82+
} else if config.AuthMethod != "auth-none" {
83+
return uri, errors.New("unsupported auth method")
84+
}
85+
86+
if config.ConnectionParameters != "" {
87+
params = "?" + config.ConnectionParameters
88+
}
89+
90+
if config.ConnectionStringScheme == "dns_seed_list" {
91+
uri = fmt.Sprintf("mongodb+srv://%s%s/%s", creds, config.Host, params)
92+
} else {
93+
uri = fmt.Sprintf("mongodb://%s%s:%d/%s", creds, config.Host, config.Port, params)
94+
}
95+
96+
return uri, nil
97+
}

src/components/ConfigEditor.tsx

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React, { ChangeEvent } from "react";
22
import { Divider, Field, FieldSet, InlineField, InlineFieldRow, Input, RadioButtonGroup, SecretInput } from "@grafana/ui";
33
import { DataSourcePluginOptionsEditorProps, SelectableValue } from "@grafana/data";
4-
import { MongoDataSourceOptions, MySecureJsonData, MongoDBAuthMethod } from "../types";
4+
import { MongoDataSourceOptions, MySecureJsonData, MongoDBAuthMethod, ConnectionStringScheme } from "../types";
5+
;
56

67
interface Props extends DataSourcePluginOptionsEditorProps<MongoDataSourceOptions, MySecureJsonData> { }
78

@@ -10,6 +11,11 @@ const mongoDBAuthMethods: SelectableValue[] = [
1011
{ label: "Username/Password", value: MongoDBAuthMethod.USERNAME_PASSWORD }
1112
];
1213

14+
const mongoConnectionStringSchemes: SelectableValue[] = [
15+
{ label: "mongodb", value: ConnectionStringScheme.STANDARD, description: "Standard Connection String Format" },
16+
{ label: "mongodb+srv", value: ConnectionStringScheme.DNS_SEED_LIST, description: "DNS Seed List Connection Format" }
17+
];
18+
1319
export function ConfigEditor(props: Props) {
1420
const { onOptionsChange, options } = props;
1521
const { jsonData, secureJsonFields, secureJsonData } = options;
@@ -22,6 +28,10 @@ export function ConfigEditor(props: Props) {
2228
jsonData.port = 27017;
2329
}
2430

31+
if (!jsonData.connectionStringScheme) {
32+
jsonData.connectionStringScheme = ConnectionStringScheme.STANDARD;
33+
}
34+
2535

2636
const onHostChange = (event: ChangeEvent<HTMLInputElement>) => {
2737
onOptionsChange({
@@ -43,6 +53,15 @@ export function ConfigEditor(props: Props) {
4353
});
4454
};
4555

56+
const onConnectionParametersChange = (event: ChangeEvent<HTMLInputElement>) => {
57+
onOptionsChange({
58+
...options,
59+
jsonData: {
60+
...jsonData,
61+
connectionParameters: event.target.value,
62+
},
63+
});
64+
};
4665

4766
const onPortChange = (event: ChangeEvent<HTMLInputElement>) => {
4867
onOptionsChange({
@@ -75,6 +94,16 @@ export function ConfigEditor(props: Props) {
7594
});
7695
};
7796

97+
const onConnectionStringSchemeChange = (scheme: string) => {
98+
onOptionsChange({
99+
...options,
100+
jsonData: {
101+
...jsonData,
102+
connectionStringScheme: scheme,
103+
},
104+
});
105+
};
106+
78107
const onPasswordChange = (event: ChangeEvent<HTMLInputElement>) => {
79108
onOptionsChange({
80109
...options,
@@ -100,6 +129,13 @@ export function ConfigEditor(props: Props) {
100129

101130
return (
102131
<>
132+
<Field label="Connection string scheme">
133+
<RadioButtonGroup
134+
options={mongoConnectionStringSchemes}
135+
value={jsonData.connectionStringScheme || ConnectionStringScheme.STANDARD}
136+
onChange={onConnectionStringSchemeChange}
137+
/>
138+
</Field>
103139
<InlineFieldRow label="Connection">
104140
<InlineField label="Host" tooltip="MongoDB host address">
105141
<Input
@@ -110,17 +146,16 @@ export function ConfigEditor(props: Props) {
110146
width={30}
111147
></Input>
112148
</InlineField>
113-
<InlineField label="Port" tooltip="MongoDB port">
149+
{jsonData.connectionStringScheme === ConnectionStringScheme.STANDARD && <InlineField label="Port" tooltip="MongoDB port">
114150
<Input
115-
required
116151
id="config-editor-port"
117152
value={jsonData.port}
118153
type="number"
119154
onChange={onPortChange}
120155
width={15}
121156
defaultValue={27017}
122157
></Input>
123-
</InlineField>
158+
</InlineField>}
124159
</InlineFieldRow>
125160
<InlineFieldRow>
126161
<InlineField label="Database" tooltip="MongoDB database">
@@ -133,6 +168,15 @@ export function ConfigEditor(props: Props) {
133168
></Input>
134169
</InlineField>
135170
</InlineFieldRow>
171+
<InlineField label="Connection parameters" tooltip="Connection parameters appended to the connection string. For example retryWrites=true&w=majority&appName=default-cluster">
172+
<Input
173+
required
174+
id="config-editor-connection-parameters"
175+
value={jsonData.connectionParameters}
176+
onChange={onConnectionParametersChange}
177+
width={35}
178+
></Input>
179+
</InlineField>
136180
<Divider />
137181
<FieldSet label="Authentication">
138182
<Field label="Authentication method">

src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,22 @@ export const MongoDBAuthMethod = {
4646
USERNAME_PASSWORD: "auth-username-password"
4747
};
4848

49+
export const ConnectionStringScheme = {
50+
STANDARD: "standard",
51+
DNS_SEED_LIST: "dns_seed_list"
52+
};
53+
4954

5055
/**
5156
* These are options configured for each DataSource instance
5257
*/
5358
export interface MongoDataSourceOptions extends DataSourceJsonData {
59+
connectionStringScheme?: string;
5460
host?: string;
5561
port?: number;
5662
database?: string;
5763
username?: string;
64+
connectionParameters?: string;
5865
}
5966

6067
/**

0 commit comments

Comments
 (0)