Skip to content

Commit

Permalink
Azure blob variable interpolation (#797)
Browse files Browse the repository at this point in the history
  • Loading branch information
yesoreyeram authored Mar 29, 2024
1 parent a37851f commit 379d1ea
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/hungry-owls-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'grafana-infinity-datasource': minor
---

Improved health check for azure blob storage connections
5 changes: 5 additions & 0 deletions .changeset/shy-tools-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'grafana-infinity-datasource': patch
---

Fixed a bug where AWS authentication doesn't work since 2.5.0-beta.1
5 changes: 5 additions & 0 deletions .changeset/sixty-hornets-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'grafana-infinity-datasource': minor
---

Support for variables in azure blob storage container and blob name
18 changes: 13 additions & 5 deletions pkg/infinity/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ func NewClient(ctx context.Context, settings models.InfinitySettings) (client *C
httpClient := getBaseHTTPClient(ctx, settings)
if httpClient == nil {
span.RecordError(errors.New("invalid http client"))
return nil, errors.New("invalid http client")
backend.Logger.Error("invalid http client", "datasource uid", settings.UID, "datasource name", settings.Name)
return client, errors.New("invalid http client")
}
httpClient = ApplyDigestAuth(ctx, httpClient, settings)
httpClient = ApplyOAuthClientCredentials(ctx, httpClient, settings)
Expand All @@ -108,7 +109,8 @@ func NewClient(ctx context.Context, settings models.InfinitySettings) (client *C

httpClient, err = ApplySecureSocksProxyConfiguration(httpClient, settings)
if err != nil {
return nil, err
backend.Logger.Error("error applying secure socks proxy", "datasource uid", settings.UID, "datasource name", settings.Name)
return client, err
}

client = &Client{
Expand All @@ -121,7 +123,8 @@ func NewClient(ctx context.Context, settings models.InfinitySettings) (client *C
if err != nil {
span.RecordError(err)
span.SetStatus(500, err.Error())
return nil, fmt.Errorf("invalid azure blob credentials. %s", err)
backend.Logger.Error("invalid azure blob credentials", "datasource uid", settings.UID, "datasource name", settings.Name)
return client, errors.New("invalid azure blob credentials")
}
clientUrl := "https://%s.blob.core.windows.net/"
if settings.AzureBlobAccountUrl != "" {
Expand All @@ -134,12 +137,14 @@ func NewClient(ctx context.Context, settings models.InfinitySettings) (client *C
if err != nil {
span.RecordError(err)
span.SetStatus(500, err.Error())
return nil, fmt.Errorf("invalid azure blob client. %s", err)
backend.Logger.Error("error creating azure blob client", "datasource uid", settings.UID, "datasource name", settings.Name)
return client, fmt.Errorf("error creating azure blob client. %s", err)
}
if azClient == nil {
span.RecordError(errors.New("invalid/empty azure blob client"))
span.SetStatus(500, "invalid/empty azure blob client")
return nil, errors.New("invalid/empty azure blob client")
backend.Logger.Error("invalid/empty azure blob client", "datasource uid", settings.UID, "datasource name", settings.Name)
return client, errors.New("invalid/empty azure blob client")
}
client.AzureBlobClient = azClient
}
Expand All @@ -150,6 +155,9 @@ func NewClient(ctx context.Context, settings models.InfinitySettings) (client *C
}

func ApplySecureSocksProxyConfiguration(httpClient *http.Client, settings models.InfinitySettings) (*http.Client, error) {
if IsAwsAuthConfigured(settings) {
return httpClient, nil
}
t := httpClient.Transport
if IsDigestAuthConfigured(settings) {
// if we are using Digest, the Transport is 'digest.Transport' that wraps 'http.Transport'
Expand Down
19 changes: 19 additions & 0 deletions pkg/models/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ const (
)

type InfinitySettings struct {
UID string
Name string
IsMock bool
AuthenticationMethod string
OAuth2Settings OAuth2Settings
Expand Down Expand Up @@ -120,6 +122,21 @@ func (s *InfinitySettings) Validate() error {
return errors.New("invalid or empty bearer token detected")
}
if s.AuthenticationMethod == AuthenticationMethodAzureBlob {
if strings.TrimSpace(s.AzureBlobAccountName) == "" {
return errors.New("invalid/empty azure blob account name")
}
if strings.TrimSpace(s.AzureBlobAccountKey) == "" {
return errors.New("invalid/empty azure blob key")
}
return nil
}
if s.AuthenticationMethod == AuthenticationMethodAWS && s.AWSSettings.AuthType == AWSAuthTypeKeys {
if strings.TrimSpace(s.AWSAccessKey) == "" {
return errors.New("invalid/empty AWS access key")
}
if strings.TrimSpace(s.AWSSecretKey) == "" {
return errors.New("invalid/empty AWS secret key")
}
return nil
}
if s.AuthenticationMethod != AuthenticationMethodNone && len(s.AllowedHosts) < 1 {
Expand Down Expand Up @@ -176,6 +193,8 @@ type InfinitySettingsJson struct {
}

func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings) (settings InfinitySettings, err error) {
settings.UID = config.UID
settings.Name = config.Name
settings.URL = config.URL
if config.URL == "__IGNORE_URL__" {
settings.URL = ""
Expand Down
15 changes: 14 additions & 1 deletion pkg/pluginhost/handler_checkhealth.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ func (ds *PluginHost) CheckHealth(ctx context.Context, req *backend.CheckHealthR
func CheckHealth(ctx context.Context, ds *PluginHost, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
logger := backend.Logger.FromContext(ctx)
client, err := getInstance(ctx, ds.im, req.PluginContext)
if err != nil || client == nil || client.client == nil {
if err != nil {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: fmt.Sprintf("error loading datasource settings. %s", err.Error()),
}, nil
}
if client == nil || client.client == nil {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "failed to get plugin instance",
Expand All @@ -47,6 +53,9 @@ func CheckHealth(ctx context.Context, ds *PluginHost, req *backend.CheckHealthRe
Message: fmt.Sprintf("invalid settings. %s", err.Error()),
}, nil
}
if client.client.Settings.AuthenticationMethod == models.AuthenticationMethodAzureBlob {
return checkHealthAzureBlobStorage(ctx, client)
}
if client.client.Settings.CustomHealthCheckEnabled && client.client.Settings.CustomHealthCheckUrl != "" {
_, statusCode, _, err := client.client.GetResults(ctx, models.Query{
Type: models.QueryTypeUQL,
Expand Down Expand Up @@ -80,3 +89,7 @@ func CheckHealth(ctx context.Context, ds *PluginHost, req *backend.CheckHealthRe
Message: "OK",
}, nil
}

func healthCheckError(msg string) (*backend.CheckHealthResult, error) {
return &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: msg}, nil
}
38 changes: 38 additions & 0 deletions pkg/pluginhost/handler_checkhealth_azblog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package pluginhost

import (
"context"
"strings"

"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service"
"github.com/grafana/grafana-plugin-sdk-go/backend"
)

func checkHealthAzureBlobStorage(ctx context.Context, client *instanceSettings) (*backend.CheckHealthResult, error) {
if client == nil {
return healthCheckError("invalid client")
}
if client.client == nil {
return healthCheckError("invalid infinity client")
}
if client.client.AzureBlobClient == nil {
return healthCheckError("invalid azure blob client")
}
blobServiceClient := client.client.AzureBlobClient.ServiceClient()
if blobServiceClient == nil {
return healthCheckError("invalid azure blob service client. check storage account name and key")
}
if _, err := blobServiceClient.GetAccountInfo(ctx, &service.GetAccountInfoOptions{}); err != nil {
if strings.Contains(err.Error(), "no such host") {
return healthCheckError("error connecting to blob storage. invalid blog storage name")
}
if strings.Contains(err.Error(), "RESPONSE 403") {
return healthCheckError("error connecting to blob storage. http 403. check blob storage key")
}
if strings.Contains(err.Error(), "RESPONSE 500") {
return healthCheckError("error connecting to blob storage. http 500")
}
return healthCheckError("error connecting to blob storage. check grafana logs for more details")
}
return &backend.CheckHealthResult{Status: backend.HealthStatusOk, Message: "OK"}, nil
}
3 changes: 3 additions & 0 deletions src/datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ export class Datasource extends DataSourceWithBackend<InfinityQuery, InfinityOpt
reportHealthCheck(o, this.instanceSettings, this.meta);
switch (o?.message) {
case 'OK':
if (this.instanceSettings?.jsonData?.auth_method === 'azureBlob' || this.instanceSettings?.jsonData?.auth_method === 'aws') {
return Promise.resolve({ status: 'success', message: 'OK. Settings saved' });
}
if (!(this.instanceSettings?.jsonData?.customHealthCheckEnabled && this.instanceSettings?.jsonData?.customHealthCheckUrl) && this.instanceSettings?.jsonData?.auth_method) {
const healthCheckMessage = [
'Success',
Expand Down
4 changes: 4 additions & 0 deletions src/interpolate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export const interpolateQuery = (query: InfinityQuery, scopedVars: ScopedVars):
if (newQuery.source === 'inline') {
newQuery.data = replaceVariable(newQuery.data, scopedVars);
}
if (newQuery.source === 'azure-blob') {
newQuery.azBlobName = replaceVariable(newQuery.azBlobName, scopedVars);
newQuery.azContainerName = replaceVariable(newQuery.azContainerName, scopedVars);
}
if (isDataQuery(newQuery)) {
newQuery.filters = (newQuery.filters || []).map((filter) => {
const value = (filter.value || []).map((val) => {
Expand Down

0 comments on commit 379d1ea

Please sign in to comment.