mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Azure Monitor: support AzureCredentials in common format on backend (#77424)
* Use GetDefaultCloud from SDK * Use GetAzureCloud from SDK * Credentials parser moved to azmoncredentials * Refactor legacy credentials * Tests * Fix test description Co-authored-by: Andreas Christou <andreas.christou@grafana.com> --------- Co-authored-by: Andreas Christou <andreas.christou@grafana.com>
This commit is contained in:
parent
b8e8d84ef7
commit
40152922d3
119
pkg/tsdb/azuremonitor/azmoncredentials/builder.go
Normal file
119
pkg/tsdb/azuremonitor/azmoncredentials/builder.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package azmoncredentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
|
||||||
|
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
||||||
|
"github.com/grafana/grafana-azure-sdk-go/util/maputil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FromDatasourceData(data map[string]interface{}, secureData map[string]string) (azcredentials.AzureCredentials, error) {
|
||||||
|
var credentials azcredentials.AzureCredentials
|
||||||
|
var err error
|
||||||
|
|
||||||
|
credentials, err = azcredentials.FromDatasourceData(data, secureData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to legacy credentials format
|
||||||
|
if credentials == nil {
|
||||||
|
credentials, err = getFromLegacy(data, secureData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFromLegacy(data map[string]interface{}, secureData map[string]string) (azcredentials.AzureCredentials, error) {
|
||||||
|
authType, err := maputil.GetStringOptional(data, "azureAuthType")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tenantId, err := maputil.GetStringOptional(data, "tenantId")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientId, err := maputil.GetStringOptional(data, "clientId")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if authType == "" {
|
||||||
|
// Some very old legacy datasources may not have explicit auth type specified,
|
||||||
|
// but they imply App Registration authentication
|
||||||
|
if tenantId != "" && clientId != "" {
|
||||||
|
authType = azcredentials.AzureAuthClientSecret
|
||||||
|
} else {
|
||||||
|
// No configuration present
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch authType {
|
||||||
|
case azcredentials.AzureAuthManagedIdentity:
|
||||||
|
credentials := &azcredentials.AzureManagedIdentityCredentials{}
|
||||||
|
return credentials, nil
|
||||||
|
|
||||||
|
case azcredentials.AzureAuthWorkloadIdentity:
|
||||||
|
credentials := &azcredentials.AzureWorkloadIdentityCredentials{}
|
||||||
|
return credentials, nil
|
||||||
|
|
||||||
|
case azcredentials.AzureAuthClientSecret:
|
||||||
|
legacyCloud, err := maputil.GetStringOptional(data, "cloudName")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cloud, err := resolveLegacyCloudName(legacyCloud)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientSecret := secureData["clientSecret"]
|
||||||
|
|
||||||
|
if secureData["clientSecret"] == "" {
|
||||||
|
return nil, fmt.Errorf("unable to instantiate credentials, clientSecret must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials := &azcredentials.AzureClientSecretCredentials{
|
||||||
|
AzureCloud: cloud,
|
||||||
|
TenantId: tenantId,
|
||||||
|
ClientId: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("the authentication type '%s' not supported", authType)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy Azure cloud names used by the Azure Monitor datasource
|
||||||
|
const (
|
||||||
|
azureMonitorPublic = "azuremonitor"
|
||||||
|
azureMonitorChina = "chinaazuremonitor"
|
||||||
|
azureMonitorUSGovernment = "govazuremonitor"
|
||||||
|
azureMonitorCustomized = "customizedazuremonitor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolveLegacyCloudName(cloudName string) (string, error) {
|
||||||
|
switch cloudName {
|
||||||
|
case azureMonitorPublic:
|
||||||
|
return azsettings.AzurePublic, nil
|
||||||
|
case azureMonitorChina:
|
||||||
|
return azsettings.AzureChina, nil
|
||||||
|
case azureMonitorUSGovernment:
|
||||||
|
return azsettings.AzureUSGovernment, nil
|
||||||
|
case azureMonitorCustomized:
|
||||||
|
return azsettings.AzureCustomized, nil
|
||||||
|
case "":
|
||||||
|
return azsettings.AzurePublic, nil
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("the Azure cloud '%s' not supported by Azure Monitor datasource", cloudName)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
215
pkg/tsdb/azuremonitor/azmoncredentials/builder_test.go
Normal file
215
pkg/tsdb/azuremonitor/azmoncredentials/builder_test.go
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
package azmoncredentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
|
||||||
|
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFromDatasourceData(t *testing.T) {
|
||||||
|
t.Run("should return nil when no credentials configured", func(t *testing.T) {
|
||||||
|
var data = map[string]interface{}{}
|
||||||
|
var secureData = map[string]string{}
|
||||||
|
|
||||||
|
result, err := FromDatasourceData(data, secureData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Nil(t, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return managed identity credentials when auth type is managed identity", func(t *testing.T) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"azureAuthType": "msi",
|
||||||
|
"cloudName": "chinaazuremonitor",
|
||||||
|
"tenantId": "LEGACY-TENANT-ID",
|
||||||
|
"clientId": "LEGACY-CLIENT-ID",
|
||||||
|
}
|
||||||
|
var secureData = map[string]string{
|
||||||
|
"clientSecret": "FAKE-LEGACY-SECRET",
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials, err := FromDatasourceData(data, secureData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.IsType(t, &azcredentials.AzureManagedIdentityCredentials{}, credentials)
|
||||||
|
msiCredentials := credentials.(*azcredentials.AzureManagedIdentityCredentials)
|
||||||
|
|
||||||
|
// Azure Monitor datasource doesn't support user-assigned managed identities (ClientId is always empty)
|
||||||
|
assert.Equal(t, "", msiCredentials.ClientId)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return workload identity credentials when auth type is workload identity", func(t *testing.T) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"azureAuthType": azcredentials.AzureAuthWorkloadIdentity,
|
||||||
|
}
|
||||||
|
var secureData = map[string]string{}
|
||||||
|
|
||||||
|
credentials, err := FromDatasourceData(data, secureData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.IsType(t, &azcredentials.AzureWorkloadIdentityCredentials{}, credentials)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when legacy client secret configuration present", func(t *testing.T) {
|
||||||
|
t.Run("should return client secret credentials when auth type is client secret", func(t *testing.T) {
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"azureAuthType": "clientsecret",
|
||||||
|
"cloudName": "chinaazuremonitor",
|
||||||
|
"tenantId": "LEGACY-TENANT-ID",
|
||||||
|
"clientId": "LEGACY-CLIENT-ID",
|
||||||
|
}
|
||||||
|
var secureData = map[string]string{
|
||||||
|
"clientSecret": "FAKE-LEGACY-SECRET",
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := FromDatasourceData(data, secureData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NotNil(t, result)
|
||||||
|
assert.IsType(t, &azcredentials.AzureClientSecretCredentials{}, result)
|
||||||
|
credential := (result).(*azcredentials.AzureClientSecretCredentials)
|
||||||
|
|
||||||
|
assert.Equal(t, azsettings.AzureChina, credential.AzureCloud)
|
||||||
|
assert.Equal(t, "LEGACY-TENANT-ID", credential.TenantId)
|
||||||
|
assert.Equal(t, "LEGACY-CLIENT-ID", credential.ClientId)
|
||||||
|
assert.Equal(t, "FAKE-LEGACY-SECRET", credential.ClientSecret)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return client secret credentials when auth type is not specified but configuration present", func(t *testing.T) {
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"cloudName": "chinaazuremonitor",
|
||||||
|
"tenantId": "LEGACY-TENANT-ID",
|
||||||
|
"clientId": "LEGACY-CLIENT-ID",
|
||||||
|
}
|
||||||
|
var secureData = map[string]string{
|
||||||
|
"clientSecret": "FAKE-LEGACY-SECRET",
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := FromDatasourceData(data, secureData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NotNil(t, result)
|
||||||
|
assert.IsType(t, &azcredentials.AzureClientSecretCredentials{}, result)
|
||||||
|
credential := (result).(*azcredentials.AzureClientSecretCredentials)
|
||||||
|
|
||||||
|
assert.Equal(t, azsettings.AzureChina, credential.AzureCloud)
|
||||||
|
assert.Equal(t, "LEGACY-TENANT-ID", credential.TenantId)
|
||||||
|
assert.Equal(t, "LEGACY-CLIENT-ID", credential.ClientId)
|
||||||
|
assert.Equal(t, "FAKE-LEGACY-SECRET", credential.ClientSecret)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should error if no client secret is set", func(t *testing.T) {
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"azureAuthType": "clientsecret",
|
||||||
|
"cloudName": "chinaazuremonitor",
|
||||||
|
"tenantId": "LEGACY-TENANT-ID",
|
||||||
|
"clientId": "LEGACY-CLIENT-ID",
|
||||||
|
}
|
||||||
|
var secureData = map[string]string{}
|
||||||
|
|
||||||
|
_, err := FromDatasourceData(data, secureData)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
assert.ErrorContains(t, err, "clientSecret must be set")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return client secret credentials when client secret auth configured even if legacy configuration present", func(t *testing.T) {
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"azureCredentials": map[string]interface{}{
|
||||||
|
"authType": "clientsecret",
|
||||||
|
"azureCloud": "AzureChinaCloud",
|
||||||
|
"tenantId": "TENANT-ID",
|
||||||
|
"clientId": "CLIENT-TD",
|
||||||
|
},
|
||||||
|
"azureAuthType": "clientsecret",
|
||||||
|
"cloudName": "azuremonitor",
|
||||||
|
"tenantId": "LEGACY-TENANT-ID",
|
||||||
|
"clientId": "LEGACY-CLIENT-ID",
|
||||||
|
}
|
||||||
|
var secureData = map[string]string{
|
||||||
|
"azureClientSecret": "FAKE-SECRET",
|
||||||
|
"clientSecret": "FAKE-LEGACY-SECRET",
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := FromDatasourceData(data, secureData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NotNil(t, result)
|
||||||
|
assert.IsType(t, &azcredentials.AzureClientSecretCredentials{}, result)
|
||||||
|
credential := (result).(*azcredentials.AzureClientSecretCredentials)
|
||||||
|
|
||||||
|
assert.Equal(t, credential.AzureCloud, azsettings.AzureChina)
|
||||||
|
assert.Equal(t, credential.TenantId, "TENANT-ID")
|
||||||
|
assert.Equal(t, credential.ClientId, "CLIENT-TD")
|
||||||
|
assert.Equal(t, credential.ClientSecret, "FAKE-SECRET")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return error when credentials not supported even if legacy configuration present", func(t *testing.T) {
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"azureCredentials": map[string]interface{}{
|
||||||
|
"authType": "invalid",
|
||||||
|
"azureCloud": "AzureChinaCloud",
|
||||||
|
"tenantId": "TENANT-ID",
|
||||||
|
"clientId": "CLIENT-TD",
|
||||||
|
},
|
||||||
|
"cloudName": "azuremonitor",
|
||||||
|
"tenantId": "LEGACY-TENANT-ID",
|
||||||
|
"clientId": "LEGACY-CLIENT-ID",
|
||||||
|
"onBehalfOf": true,
|
||||||
|
"oauthPassThru": true,
|
||||||
|
}
|
||||||
|
var secureData = map[string]string{
|
||||||
|
"azureClientSecret": "FAKE-SECRET",
|
||||||
|
"clientSecret": "FAKE-LEGACY-SECRET",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := FromDatasourceData(data, secureData)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizedCloudName(t *testing.T) {
|
||||||
|
t.Run("should return normalized cloud name", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
legacyCloud string
|
||||||
|
normalizedCloud string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
legacyCloud: azureMonitorPublic,
|
||||||
|
normalizedCloud: azsettings.AzurePublic,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
legacyCloud: azureMonitorChina,
|
||||||
|
normalizedCloud: azsettings.AzureChina,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
legacyCloud: azureMonitorUSGovernment,
|
||||||
|
normalizedCloud: azsettings.AzureUSGovernment,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
legacyCloud: "",
|
||||||
|
normalizedCloud: azsettings.AzurePublic,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
actualCloud, err := resolveLegacyCloudName(tt.legacyCloud)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.normalizedCloud, actualCloud)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should fail when cloud is unknown", func(t *testing.T) {
|
||||||
|
legacyCloud := "unknown"
|
||||||
|
|
||||||
|
_, err := resolveLegacyCloudName(legacyCloud)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
16
pkg/tsdb/azuremonitor/azmoncredentials/default.go
Normal file
16
pkg/tsdb/azuremonitor/azmoncredentials/default.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package azmoncredentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
|
||||||
|
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDefaultCredentials(settings *azsettings.AzureSettings) azcredentials.AzureCredentials {
|
||||||
|
if settings.ManagedIdentityEnabled {
|
||||||
|
return &azcredentials.AzureManagedIdentityCredentials{}
|
||||||
|
} else if settings.WorkloadIdentityEnabled {
|
||||||
|
return &azcredentials.AzureWorkloadIdentityCredentials{}
|
||||||
|
} else {
|
||||||
|
return &azcredentials.AzureClientSecretCredentials{AzureCloud: settings.GetDefaultCloud()}
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
|
||||||
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"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/datasource"
|
||||||
@ -19,6 +20,7 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azmoncredentials"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics"
|
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/metrics"
|
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/metrics"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/resourcegraph"
|
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/resourcegraph"
|
||||||
@ -77,19 +79,26 @@ func getDatasourceService(ctx context.Context, settings *backend.DataSourceInsta
|
|||||||
|
|
||||||
func NewInstanceSettings(cfg *setting.Cfg, clientProvider *httpclient.Provider, executors map[string]azDatasourceExecutor) datasource.InstanceFactoryFunc {
|
func NewInstanceSettings(cfg *setting.Cfg, clientProvider *httpclient.Provider, executors map[string]azDatasourceExecutor) datasource.InstanceFactoryFunc {
|
||||||
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
||||||
jsonDataObj := map[string]any{}
|
jsonData := map[string]any{}
|
||||||
err := json.Unmarshal(settings.JSONData, &jsonDataObj)
|
err := json.Unmarshal(settings.JSONData, &jsonData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error reading settings: %w", err)
|
return nil, fmt.Errorf("error reading settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
azSettings := types.AzureSettings{}
|
azMonitorSettings := types.AzureMonitorSettings{}
|
||||||
err = json.Unmarshal(settings.JSONData, &azSettings)
|
err = json.Unmarshal(settings.JSONData, &azMonitorSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error reading settings: %w", err)
|
return nil, fmt.Errorf("error reading settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cloud, err := getAzureCloud(cfg, &azSettings.AzureClientSettings)
|
credentials, err := azmoncredentials.FromDatasourceData(jsonData, settings.DecryptedSecureJSONData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting credentials: %w", err)
|
||||||
|
} else if credentials == nil {
|
||||||
|
credentials = azmoncredentials.GetDefaultCredentials(cfg.Azure)
|
||||||
|
}
|
||||||
|
|
||||||
|
cloud, err := azcredentials.GetAzureCloud(cfg.Azure, credentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting credentials: %w", err)
|
return nil, fmt.Errorf("error getting credentials: %w", err)
|
||||||
}
|
}
|
||||||
@ -99,16 +108,11 @@ func NewInstanceSettings(cfg *setting.Cfg, clientProvider *httpclient.Provider,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
credentials, err := getAzureCredentials(cfg, &azSettings.AzureClientSettings, settings.DecryptedSecureJSONData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting credentials: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
model := types.DatasourceInfo{
|
model := types.DatasourceInfo{
|
||||||
Cloud: cloud,
|
Cloud: cloud,
|
||||||
Credentials: credentials,
|
Credentials: credentials,
|
||||||
Settings: azSettings.AzureMonitorSettings,
|
Settings: azMonitorSettings,
|
||||||
JSONData: jsonDataObj,
|
JSONData: jsonData,
|
||||||
DecryptedSecureJSONData: settings.DecryptedSecureJSONData,
|
DecryptedSecureJSONData: settings.DecryptedSecureJSONData,
|
||||||
DatasourceID: settings.ID,
|
DatasourceID: settings.ID,
|
||||||
Routes: routesForModel,
|
Routes: routesForModel,
|
||||||
|
@ -155,19 +155,19 @@ func Test_newMux(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "creates an Azure Monitor executor",
|
name: "creates an Azure Monitor executor",
|
||||||
queryType: azureMonitor,
|
queryType: azureMonitor,
|
||||||
expectedURL: routes[azureMonitorPublic][azureMonitor].URL,
|
expectedURL: routes[azsettings.AzurePublic][azureMonitor].URL,
|
||||||
Err: require.NoError,
|
Err: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "creates an Azure Log Analytics executor",
|
name: "creates an Azure Log Analytics executor",
|
||||||
queryType: azureLogAnalytics,
|
queryType: azureLogAnalytics,
|
||||||
expectedURL: routes[azureMonitorPublic][azureLogAnalytics].URL,
|
expectedURL: routes[azsettings.AzurePublic][azureLogAnalytics].URL,
|
||||||
Err: require.NoError,
|
Err: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "creates an Azure Traces executor",
|
name: "creates an Azure Traces executor",
|
||||||
queryType: azureTraces,
|
queryType: azureTraces,
|
||||||
expectedURL: routes[azureMonitorPublic][azureLogAnalytics].URL,
|
expectedURL: routes[azsettings.AzurePublic][azureLogAnalytics].URL,
|
||||||
Err: require.NoError,
|
Err: require.NoError,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -176,10 +176,10 @@ func Test_newMux(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
s := &Service{
|
s := &Service{
|
||||||
im: &fakeInstance{
|
im: &fakeInstance{
|
||||||
routes: routes[azureMonitorPublic],
|
routes: routes[azsettings.AzurePublic],
|
||||||
services: map[string]types.DatasourceService{
|
services: map[string]types.DatasourceService{
|
||||||
tt.queryType: {
|
tt.queryType: {
|
||||||
URL: routes[azureMonitorPublic][tt.queryType].URL,
|
URL: routes[azsettings.AzurePublic][tt.queryType].URL,
|
||||||
HTTPClient: &http.Client{},
|
HTTPClient: &http.Client{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,136 +0,0 @@
|
|||||||
package azuremonitor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
|
|
||||||
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Azure cloud names specific to Azure Monitor
|
|
||||||
const (
|
|
||||||
azureMonitorPublic = "azuremonitor"
|
|
||||||
azureMonitorChina = "chinaazuremonitor"
|
|
||||||
azureMonitorUSGovernment = "govazuremonitor"
|
|
||||||
azureMonitorCustomized = "customizedazuremonitor"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getAuthType(cfg *setting.Cfg, jsonData *types.AzureClientSettings) string {
|
|
||||||
if azureAuthType := jsonData.AzureAuthType; azureAuthType != "" {
|
|
||||||
return azureAuthType
|
|
||||||
} else {
|
|
||||||
tenantId := jsonData.TenantId
|
|
||||||
clientId := jsonData.ClientId
|
|
||||||
|
|
||||||
// If authentication type isn't explicitly specified and datasource has client credentials,
|
|
||||||
// then this is existing datasource which is configured for app registration (client secret)
|
|
||||||
if tenantId != "" && clientId != "" {
|
|
||||||
return azcredentials.AzureAuthClientSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
// For newly created datasource with no configuration the order is as follows:
|
|
||||||
// Managed identity is the default if enabled
|
|
||||||
// Workload identity is the next option if enabled
|
|
||||||
// Client secret is the final fallback
|
|
||||||
if cfg.Azure.ManagedIdentityEnabled {
|
|
||||||
return azcredentials.AzureAuthManagedIdentity
|
|
||||||
} else if cfg.Azure.WorkloadIdentityEnabled {
|
|
||||||
return azcredentials.AzureAuthWorkloadIdentity
|
|
||||||
} else {
|
|
||||||
return azcredentials.AzureAuthClientSecret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefaultAzureCloud(cfg *setting.Cfg) (string, error) {
|
|
||||||
// Allow only known cloud names
|
|
||||||
cloudName := ""
|
|
||||||
if cfg != nil && cfg.Azure != nil {
|
|
||||||
cloudName = cfg.Azure.Cloud
|
|
||||||
}
|
|
||||||
switch cloudName {
|
|
||||||
case azsettings.AzurePublic:
|
|
||||||
return azsettings.AzurePublic, nil
|
|
||||||
case azsettings.AzureChina:
|
|
||||||
return azsettings.AzureChina, nil
|
|
||||||
case azsettings.AzureUSGovernment:
|
|
||||||
return azsettings.AzureUSGovernment, nil
|
|
||||||
case azsettings.AzureCustomized:
|
|
||||||
return azsettings.AzureCustomized, nil
|
|
||||||
case "":
|
|
||||||
// Not set cloud defaults to public
|
|
||||||
return azsettings.AzurePublic, nil
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("the cloud '%s' not supported", cloudName)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeAzureCloud(cloudName string) (string, error) {
|
|
||||||
switch cloudName {
|
|
||||||
case azureMonitorPublic:
|
|
||||||
return azsettings.AzurePublic, nil
|
|
||||||
case azureMonitorChina:
|
|
||||||
return azsettings.AzureChina, nil
|
|
||||||
case azureMonitorUSGovernment:
|
|
||||||
return azsettings.AzureUSGovernment, nil
|
|
||||||
case azureMonitorCustomized:
|
|
||||||
return azsettings.AzureCustomized, nil
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("the cloud '%s' not supported", cloudName)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAzureCloud(cfg *setting.Cfg, jsonData *types.AzureClientSettings) (string, error) {
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
switch authType {
|
|
||||||
case azcredentials.AzureAuthManagedIdentity, azcredentials.AzureAuthWorkloadIdentity:
|
|
||||||
// In case of managed identity and workload identity, the cloud is always same as where Grafana is hosted
|
|
||||||
return getDefaultAzureCloud(cfg)
|
|
||||||
case azcredentials.AzureAuthClientSecret:
|
|
||||||
if cloud := jsonData.CloudName; cloud != "" {
|
|
||||||
return normalizeAzureCloud(cloud)
|
|
||||||
} else {
|
|
||||||
return getDefaultAzureCloud(cfg)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("the authentication type '%s' not supported", authType)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAzureCredentials(cfg *setting.Cfg, jsonData *types.AzureClientSettings, secureJsonData map[string]string) (azcredentials.AzureCredentials, error) {
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
switch authType {
|
|
||||||
case azcredentials.AzureAuthManagedIdentity:
|
|
||||||
credentials := &azcredentials.AzureManagedIdentityCredentials{}
|
|
||||||
return credentials, nil
|
|
||||||
case azcredentials.AzureAuthWorkloadIdentity:
|
|
||||||
credentials := &azcredentials.AzureWorkloadIdentityCredentials{}
|
|
||||||
return credentials, nil
|
|
||||||
case azcredentials.AzureAuthClientSecret:
|
|
||||||
cloud, err := getAzureCloud(cfg, jsonData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if secureJsonData["clientSecret"] == "" {
|
|
||||||
return nil, fmt.Errorf("unable to instantiate credentials, clientSecret must be set")
|
|
||||||
}
|
|
||||||
credentials := &azcredentials.AzureClientSecretCredentials{
|
|
||||||
AzureCloud: cloud,
|
|
||||||
TenantId: jsonData.TenantId,
|
|
||||||
ClientId: jsonData.ClientId,
|
|
||||||
ClientSecret: secureJsonData["clientSecret"],
|
|
||||||
}
|
|
||||||
return credentials, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("the authentication type '%s' not supported", authType)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,266 +0,0 @@
|
|||||||
package azuremonitor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
|
|
||||||
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCredentials_getAuthType(t *testing.T) {
|
|
||||||
cfg := &setting.Cfg{
|
|
||||||
Azure: &azsettings.AzureSettings{},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("when managed identities enabled", func(t *testing.T) {
|
|
||||||
cfg.Azure.ManagedIdentityEnabled = true
|
|
||||||
|
|
||||||
t.Run("should be client secret if auth type is set to client secret", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: azcredentials.AzureAuthClientSecret,
|
|
||||||
}
|
|
||||||
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should be managed identity if datasource not configured", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
assert.Equal(t, azcredentials.AzureAuthManagedIdentity, authType)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should be client secret if auth type not specified but credentials configured", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: "",
|
|
||||||
TenantId: "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c",
|
|
||||||
ClientId: "849ccbb0-92eb-4226-b228-ef391abd8fe6",
|
|
||||||
}
|
|
||||||
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("when managed identities disabled", func(t *testing.T) {
|
|
||||||
cfg.Azure.ManagedIdentityEnabled = false
|
|
||||||
|
|
||||||
t.Run("should be managed identity if auth type is set to managed identity", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: azcredentials.AzureAuthManagedIdentity,
|
|
||||||
}
|
|
||||||
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
assert.Equal(t, azcredentials.AzureAuthManagedIdentity, authType)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should be client secret if datasource not configured", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("when workload identities enabled", func(t *testing.T) {
|
|
||||||
cfg.Azure.WorkloadIdentityEnabled = true
|
|
||||||
|
|
||||||
t.Run("should be client secret if auth type is set to client secret", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: azcredentials.AzureAuthClientSecret,
|
|
||||||
}
|
|
||||||
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should be workload identity if datasource not configured and managed identity is disabled", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
assert.Equal(t, azcredentials.AzureAuthWorkloadIdentity, authType)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should be client secret if auth type not specified but credentials configured", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: "",
|
|
||||||
TenantId: "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c",
|
|
||||||
ClientId: "849ccbb0-92eb-4226-b228-ef391abd8fe6",
|
|
||||||
}
|
|
||||||
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("when workload identities disabled", func(t *testing.T) {
|
|
||||||
cfg.Azure.WorkloadIdentityEnabled = false
|
|
||||||
|
|
||||||
t.Run("should be workload identity if auth type is set to workload identity", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: azcredentials.AzureAuthWorkloadIdentity,
|
|
||||||
}
|
|
||||||
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
assert.Equal(t, azcredentials.AzureAuthWorkloadIdentity, authType)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should be client secret if datasource not configured", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
authType := getAuthType(cfg, jsonData)
|
|
||||||
|
|
||||||
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCredentials_getAzureCloud(t *testing.T) {
|
|
||||||
cfg := &setting.Cfg{
|
|
||||||
Azure: &azsettings.AzureSettings{
|
|
||||||
Cloud: azsettings.AzureChina,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("when auth type is managed identity", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: azcredentials.AzureAuthManagedIdentity,
|
|
||||||
CloudName: azureMonitorUSGovernment,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("should be from server configuration regardless of datasource value", func(t *testing.T) {
|
|
||||||
cloud, err := getAzureCloud(cfg, jsonData)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, azsettings.AzureChina, cloud)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should be public if not set in server configuration", func(t *testing.T) {
|
|
||||||
cfg := &setting.Cfg{
|
|
||||||
Azure: &azsettings.AzureSettings{
|
|
||||||
Cloud: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cloud, err := getAzureCloud(cfg, jsonData)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, azsettings.AzurePublic, cloud)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("when auth type is client secret", func(t *testing.T) {
|
|
||||||
t.Run("should be from datasource value normalized to known cloud name", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: azcredentials.AzureAuthClientSecret,
|
|
||||||
CloudName: azureMonitorUSGovernment,
|
|
||||||
}
|
|
||||||
|
|
||||||
cloud, err := getAzureCloud(cfg, jsonData)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, azsettings.AzureUSGovernment, cloud)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should be from server configuration if not set in datasource", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: azcredentials.AzureAuthClientSecret,
|
|
||||||
CloudName: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
cloud, err := getAzureCloud(cfg, jsonData)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, azsettings.AzureChina, cloud)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCredentials_getAzureCredentials(t *testing.T) {
|
|
||||||
cfg := &setting.Cfg{
|
|
||||||
Azure: &azsettings.AzureSettings{
|
|
||||||
Cloud: azsettings.AzureChina,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
secureJsonData := map[string]string{
|
|
||||||
"clientSecret": "59e3498f-eb12-4943-b8f0-a5aa42640058",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("when auth type is managed identity", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: azcredentials.AzureAuthManagedIdentity,
|
|
||||||
CloudName: azureMonitorUSGovernment,
|
|
||||||
TenantId: "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c",
|
|
||||||
ClientId: "849ccbb0-92eb-4226-b228-ef391abd8fe6",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("should return managed identity credentials", func(t *testing.T) {
|
|
||||||
credentials, err := getAzureCredentials(cfg, jsonData, secureJsonData)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.IsType(t, &azcredentials.AzureManagedIdentityCredentials{}, credentials)
|
|
||||||
msiCredentials := credentials.(*azcredentials.AzureManagedIdentityCredentials)
|
|
||||||
|
|
||||||
// Azure Monitor datasource doesn't support user-assigned managed identities (ClientId is always empty)
|
|
||||||
assert.Equal(t, "", msiCredentials.ClientId)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("when auth type is client secret", func(t *testing.T) {
|
|
||||||
jsonData := &types.AzureClientSettings{
|
|
||||||
AzureAuthType: azcredentials.AzureAuthClientSecret,
|
|
||||||
CloudName: azureMonitorUSGovernment,
|
|
||||||
TenantId: "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c",
|
|
||||||
ClientId: "849ccbb0-92eb-4226-b228-ef391abd8fe6",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("should return client secret credentials", func(t *testing.T) {
|
|
||||||
cfg := &setting.Cfg{}
|
|
||||||
|
|
||||||
credentials, err := getAzureCredentials(cfg, jsonData, secureJsonData)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.IsType(t, &azcredentials.AzureClientSecretCredentials{}, credentials)
|
|
||||||
clientSecretCredentials := credentials.(*azcredentials.AzureClientSecretCredentials)
|
|
||||||
|
|
||||||
assert.Equal(t, azsettings.AzureUSGovernment, clientSecretCredentials.AzureCloud)
|
|
||||||
assert.Equal(t, "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c", clientSecretCredentials.TenantId)
|
|
||||||
assert.Equal(t, "849ccbb0-92eb-4226-b228-ef391abd8fe6", clientSecretCredentials.ClientId)
|
|
||||||
assert.Equal(t, "59e3498f-eb12-4943-b8f0-a5aa42640058", clientSecretCredentials.ClientSecret)
|
|
||||||
|
|
||||||
// Azure Monitor datasource doesn't support custom IdP authorities (Authority is always empty)
|
|
||||||
assert.Equal(t, "", clientSecretCredentials.Authority)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should error if no client secret is set", func(t *testing.T) {
|
|
||||||
cfg := &setting.Cfg{}
|
|
||||||
_, err := getAzureCredentials(cfg, jsonData, map[string]string{
|
|
||||||
"clientSecret": "",
|
|
||||||
})
|
|
||||||
require.ErrorContains(t, err, "clientSecret must be set")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -30,24 +30,12 @@ type AzRoute struct {
|
|||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AzureSettings struct {
|
|
||||||
AzureMonitorSettings
|
|
||||||
AzureClientSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
type AzureMonitorSettings struct {
|
type AzureMonitorSettings struct {
|
||||||
SubscriptionId string `json:"subscriptionId"`
|
SubscriptionId string `json:"subscriptionId"`
|
||||||
LogAnalyticsDefaultWorkspace string `json:"logAnalyticsDefaultWorkspace"`
|
LogAnalyticsDefaultWorkspace string `json:"logAnalyticsDefaultWorkspace"`
|
||||||
AppInsightsAppId string `json:"appInsightsAppId"`
|
AppInsightsAppId string `json:"appInsightsAppId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AzureClientSettings struct {
|
|
||||||
AzureAuthType string
|
|
||||||
CloudName string
|
|
||||||
TenantId string
|
|
||||||
ClientId string
|
|
||||||
}
|
|
||||||
|
|
||||||
// AzureMonitorCustomizedCloudSettings is the extended Azure Monitor settings for customized cloud
|
// AzureMonitorCustomizedCloudSettings is the extended Azure Monitor settings for customized cloud
|
||||||
type AzureMonitorCustomizedCloudSettings struct {
|
type AzureMonitorCustomizedCloudSettings struct {
|
||||||
CustomizedRoutes map[string]AzRoute `json:"customizedRoutes"`
|
CustomizedRoutes map[string]AzRoute `json:"customizedRoutes"`
|
||||||
|
Loading…
Reference in New Issue
Block a user