MSSQL: decouple plugin (#89597)

* decouple from core

* yarn decouple

* make health check work and azure config

* f

* driver error not needed

* merge
This commit is contained in:
Andrew Hackmann
2024-08-26 23:09:21 +03:00
committed by GitHub
parent 4755eb5176
commit eac194815e
13 changed files with 172 additions and 37 deletions
@@ -1084,7 +1084,7 @@
"keywords": null
},
"dependencies": {
"grafanaDependency": "",
"grafanaDependency": "\u003e=10.4.0",
"grafanaVersion": "*",
"plugins": []
},
+24 -23
View File
@@ -48,7 +48,7 @@ const (
func ProvideService(cfg *setting.Cfg) *Service {
logger := backend.NewLoggerWith("logger", "tsdb.mssql")
return &Service{
im: datasource.NewInstanceManager(newInstanceSettings(cfg, logger)),
im: datasource.NewInstanceManager(NewInstanceSettings(cfg, logger)),
logger: logger,
}
}
@@ -136,12 +136,17 @@ func newMSSQL(ctx context.Context, driverName string, userFacingDefaultError str
return db, handler, nil
}
func newInstanceSettings(cfg *setting.Cfg, logger log.Logger) datasource.InstanceFactoryFunc {
func NewInstanceSettings(cfg *setting.Cfg, logger log.Logger) datasource.InstanceFactoryFunc {
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
grafCfg := backend.GrafanaConfigFromContext(ctx)
sqlCfg, err := grafCfg.SQL()
if err != nil {
return nil, err
}
jsonData := sqleng.JsonData{
MaxOpenConns: cfg.SqlDatasourceMaxOpenConnsDefault,
MaxIdleConns: cfg.SqlDatasourceMaxIdleConnsDefault,
ConnMaxLifetime: cfg.SqlDatasourceMaxConnLifetimeDefault,
MaxOpenConns: sqlCfg.DefaultMaxOpenConns,
MaxIdleConns: sqlCfg.DefaultMaxIdleConns,
ConnMaxLifetime: sqlCfg.DefaultMaxConnLifetimeSeconds,
Encrypt: "false",
ConnectionTimeout: 0,
SecureDSProxy: false,
@@ -176,20 +181,22 @@ func newInstanceSettings(cfg *setting.Cfg, logger log.Logger) datasource.Instanc
UID: settings.UID,
DecryptedSecureJSONData: settings.DecryptedSecureJSONData,
}
cnnstr, err := generateConnectionString(dsInfo, cfg, azureCredentials, kerberosAuth, logger)
cnnstr, err := generateConnectionString(dsInfo, cfg.Azure.ManagedIdentityClientId, cfg.Azure.AzureEntraPasswordCredentialsEnabled, azureCredentials, kerberosAuth, logger)
if err != nil {
return nil, err
}
if cfg.Env == setting.Dev {
logger.Debug("GetEngine", "connection", cnnstr)
}
driverName := "mssql"
if jsonData.AuthenticationType == azureAuthentication {
driverName = "azuresql"
}
_, handler, err := newMSSQL(ctx, driverName, cfg.UserFacingDefaultError, cfg.DataProxyRowLimit, dsInfo, cnnstr, logger, settings)
userFacingDefaultError, err := grafCfg.UserFacingDefaultError()
if err != nil {
return nil, err
}
_, handler, err := newMSSQL(ctx, driverName, userFacingDefaultError, sqlCfg.RowLimit, dsInfo, cnnstr, logger, settings)
if err != nil {
logger.Error("Failed connecting to MSSQL", "err", err)
@@ -230,7 +237,7 @@ func ParseURL(u string, logger DebugOnlyLogger) (*url.URL, error) {
}, nil
}
func generateConnectionString(dsInfo sqleng.DataSourceInfo, cfg *setting.Cfg, azureCredentials azcredentials.AzureCredentials, kerberosAuth kerberos.KerberosAuth, logger log.Logger) (string, error) {
func generateConnectionString(dsInfo sqleng.DataSourceInfo, azureManagedIdentityClientId string, azureEntraPasswordCredentialsEnabled bool, azureCredentials azcredentials.AzureCredentials, kerberosAuth kerberos.KerberosAuth, logger log.Logger) (string, error) {
const dfltPort = "0"
var addr util.NetworkAddress
if dsInfo.URL != "" {
@@ -268,7 +275,7 @@ func generateConnectionString(dsInfo sqleng.DataSourceInfo, cfg *setting.Cfg, az
switch dsInfo.JsonData.AuthenticationType {
case azureAuthentication:
azureCredentialDSNFragment, err := getAzureCredentialDSNFragment(azureCredentials, cfg)
azureCredentialDSNFragment, err := getAzureCredentialDSNFragment(azureCredentials, azureManagedIdentityClientId, azureEntraPasswordCredentialsEnabled)
if err != nil {
return "", err
}
@@ -305,12 +312,12 @@ func generateConnectionString(dsInfo sqleng.DataSourceInfo, cfg *setting.Cfg, az
return connStr, nil
}
func getAzureCredentialDSNFragment(azureCredentials azcredentials.AzureCredentials, cfg *setting.Cfg) (string, error) {
func getAzureCredentialDSNFragment(azureCredentials azcredentials.AzureCredentials, azureManagedIdentityClientId string, azureEntraPasswordCredentialsEnabled bool) (string, error) {
connStr := ""
switch c := azureCredentials.(type) {
case *azcredentials.AzureManagedIdentityCredentials:
if cfg.Azure.ManagedIdentityClientId != "" {
connStr += fmt.Sprintf("user id=%s;", cfg.Azure.ManagedIdentityClientId)
if azureManagedIdentityClientId != "" {
connStr += fmt.Sprintf("user id=%s;", azureManagedIdentityClientId)
}
connStr += fmt.Sprintf("fedauth=%s;",
"ActiveDirectoryManagedIdentity")
@@ -322,7 +329,7 @@ func getAzureCredentialDSNFragment(azureCredentials azcredentials.AzureCredentia
"ActiveDirectoryApplication",
)
case *azcredentials.AzureEntraPasswordCredentials:
if cfg.Azure.AzureEntraPasswordCredentialsEnabled {
if azureEntraPasswordCredentialsEnabled {
connStr += fmt.Sprintf("user id=%s;password=%s;applicationclientid=%s;fedauth=%s;",
c.UserId,
c.Password,
@@ -360,13 +367,7 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
return nil, err
}
err = dsHandler.Ping()
if err != nil {
return &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: dsHandler.TransformQueryError(s.logger, err).Error()}, nil
}
return &backend.CheckHealthResult{Status: backend.HealthStatusOk, Message: "Database Connection OK"}, nil
return dsHandler.CheckHealth(ctx, req)
}
func (t *mssqlQueryResultTransformer) GetConverterList() []sqlutil.StringConverter {
+1 -1
View File
@@ -1550,7 +1550,7 @@ func TestGenerateConnectionString(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
connStr, err := generateConnectionString(tc.dataSource, nil, nil, tc.kerberosCfg, logger)
connStr, err := generateConnectionString(tc.dataSource, "", false, nil, tc.kerberosCfg, logger)
require.NoError(t, err)
assert.Equal(t, tc.expConnStr, connStr)
})
+7 -2
View File
@@ -152,8 +152,13 @@ func (e *DataSourceHandler) Dispose() {
e.log.Debug("DB disposed")
}
func (e *DataSourceHandler) Ping() error {
return e.db.Ping()
func (e *DataSourceHandler) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
err := e.db.Ping()
if err != nil {
return &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: e.TransformQueryError(e.log, err).Error()}, nil
}
return &backend.CheckHealthResult{Status: backend.HealthStatusOk, Message: "Database Connection OK"}, nil
}
func (e *DataSourceHandler) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
+28
View File
@@ -0,0 +1,28 @@
package main
import (
"os"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/mssql"
)
func main() {
// Start listening to requests sent from Grafana. This call is blocking so
// it won't finish until Grafana shuts down the process or the plugin choose
// to exit by itself using os.Exit. Manage automatically manages life cycle
// of datasource instances. It accepts datasource instance factory as first
// argument. This factory will be automatically called on incoming request
// from Grafana to create different instances of SampleDatasource (per datasource
// ID). When datasource configuration changed Dispose method will be called and
// new datasource instance created using NewSampleDatasource factory.
logger := backend.NewLoggerWith("logger", "tsdb.mssql")
cfg := setting.NewCfg()
if err := datasource.Manage("mssql", mssql.NewInstanceSettings(cfg, logger), datasource.ManageOpts{}); err != nil {
log.DefaultLogger.Error(err.Error())
os.Exit(1)
}
}