diff --git a/azsettings/cloud_settings.go b/azsettings/cloud_settings.go index d1a76d6..1ae04ee 100644 --- a/azsettings/cloud_settings.go +++ b/azsettings/cloud_settings.go @@ -1,6 +1,7 @@ package azsettings import ( + "encoding/json" "fmt" ) @@ -10,10 +11,10 @@ type AzureCloudInfo struct { } type AzureCloudSettings struct { - Name string - DisplayName string - AadAuthority string - Properties map[string]string + Name string `json:"name"` + DisplayName string `json:"displayName"` + AadAuthority string `json:"aadAuthority"` + Properties map[string]string `json:"properties"` } var predefinedClouds = []*AzureCloudSettings{ @@ -55,8 +56,8 @@ var predefinedClouds = []*AzureCloudSettings{ }, } -func (*AzureSettings) GetCloud(cloudName string) (*AzureCloudSettings, error) { - clouds := getClouds() +func (settings *AzureSettings) GetCloud(cloudName string) (*AzureCloudSettings, error) { + clouds := settings.getClouds() for _, cloud := range clouds { if cloud.Name == cloudName { @@ -68,15 +69,31 @@ func (*AzureSettings) GetCloud(cloudName string) (*AzureCloudSettings, error) { } // Returns all clouds configured on the instance, including custom clouds if any -func (*AzureSettings) Clouds() []AzureCloudInfo { - clouds := getClouds() +func (settings *AzureSettings) Clouds() []AzureCloudInfo { + clouds := settings.getClouds() return mapCloudInfo(clouds) } // Returns only the custom clouds configured on the instance -func (*AzureSettings) CustomClouds() []AzureCloudInfo { - clouds := getCustomClouds() - return mapCloudInfo(clouds) +func (settings *AzureSettings) CustomClouds() []AzureCloudInfo { + return mapCloudInfo(settings.CustomCloudList) +} + +// Parses the JSON list of custom clouds passed in, then stores the list on the instance +func (settings *AzureSettings) SetCustomClouds(customCloudsJSON string) error { + //only Unmarshal if the JSON has changed + if settings.CustomCloudListJSON != customCloudsJSON { + var customClouds []*AzureCloudSettings + if err := json.Unmarshal([]byte(customCloudsJSON), &customClouds); err != nil { + return err + } + + settings.CustomCloudList = customClouds + + // store it so we don't have to re-serialize back to JSON when adding to the plugin context + settings.CustomCloudListJSON = customCloudsJSON + } + return nil } func mapCloudInfo(clouds []*AzureCloudSettings) []AzureCloudInfo { @@ -91,16 +108,12 @@ func mapCloudInfo(clouds []*AzureCloudSettings) []AzureCloudInfo { return results } -func getClouds() []*AzureCloudSettings { - if clouds := getCustomClouds(); len(clouds) > 0 { - allClouds := append(clouds, predefinedClouds...) +func (settings *AzureSettings) getClouds() []*AzureCloudSettings { + clouds := settings.CustomCloudList + if len(settings.CustomCloudList) > 0 { + allClouds := append(predefinedClouds, clouds...) return allClouds } return predefinedClouds } - -func getCustomClouds() []*AzureCloudSettings { - // Configuration of Azure clouds not yet supported - return nil -} diff --git a/azsettings/cloud_settings_test.go b/azsettings/cloud_settings_test.go index 48c6cd8..50e6c35 100644 --- a/azsettings/cloud_settings_test.go +++ b/azsettings/cloud_settings_test.go @@ -7,7 +7,34 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetClouds(t *testing.T) { +var testCustomClouds = []*AzureCloudSettings{ + { + Name: "CustomCloud1", + DisplayName: "Custom Cloud 1", + AadAuthority: "https://login.contoso.com/", + Properties: map[string]string{ + "azureDataExplorerSuffix": ".kusto.cloud1.contoso.com", + "logAnalytics": "https://api.loganalytics.cloud1.contoso.com", + "portal": "https://portal.azure.cloud1.contoso.com", + "prometheusResourceId": "https://prometheus.monitor.azure.cloud1.contoso.com", + "resourceManager": "https://management.azure.cloud1.contoso.com", + }, + }, + { + Name: "CustomCloud2", + DisplayName: "Custom Cloud 2", + AadAuthority: "https://login.cloud2.contoso.com/", + Properties: map[string]string{ + "azureDataExplorerSuffix": ".kusto.cloud2.contoso.com", + "logAnalytics": "https://api.loganalytics.cloud2.contoso.com", + "portal": "https://portal.azure.cloud2.contoso.com", + "prometheusResourceId": "https://prometheus.monitor.cloud2.azure.contoso.com", + "resourceManager": "https://management.azure.cloud2.contoso.com", + }, + }, +} + +func TestGetCloudsNoCustomClouds(t *testing.T) { settings := &AzureSettings{} clouds := settings.Clouds() @@ -18,12 +45,39 @@ func TestGetClouds(t *testing.T) { assert.Equal(t, clouds[2].Name, "AzureUSGovernment") } +func TestGetCloudsWithCustomClouds(t *testing.T) { + settings := &AzureSettings{} + settings.CustomCloudList = testCustomClouds + + // should merge predefined and custom clouds into one list + clouds := settings.Clouds() + + assert.Len(t, clouds, 5) + assert.Equal(t, clouds[0].Name, "AzureCloud") + assert.Equal(t, clouds[1].Name, "AzureChinaCloud") + assert.Equal(t, clouds[2].Name, "AzureUSGovernment") + assert.Equal(t, clouds[3].Name, "CustomCloud1") + assert.Equal(t, clouds[4].Name, "CustomCloud2") +} + func TestGetCustomClouds(t *testing.T) { settings := &AzureSettings{} + settings.CustomCloudList = testCustomClouds + // should return ONLY the custom clouds clouds := settings.CustomClouds() - // Configuration of Azure clouds not yet supported + assert.Len(t, clouds, len(testCustomClouds)) +} + +func TestGetCustomCloudsWithNilList(t *testing.T) { + settings := &AzureSettings{} + settings.CustomCloudList = nil + + // should return ONLY the custom clouds + clouds := settings.CustomClouds() + + assert.NotNil(t, clouds) assert.Len(t, clouds, 0) } @@ -43,3 +97,38 @@ func TestGetCloud(t *testing.T) { assert.Error(t, err) }) } + +func TestSetCustomClouds(t *testing.T) { + settings := &AzureSettings{} + + json := `[ + { + "name":"CustomCloud1", + "displayName":"Custom Cloud 1", + "aadAuthority":"https://login.contoso.com/", + "properties":{ + "azureDataExplorerSuffix":".kusto.cloud1.contoso.com", + "logAnalytics":"https://api.loganalytics.cloud1.contoso.com", + "portal":"https://portal.azure.cloud1.contoso.com", + "prometheusResourceId":"https://prometheus.monitor.azure.cloud1.contoso.com", + "resourceManager":"https://management.azure.cloud1.contoso.com" + } + } + ]` + + err := settings.SetCustomClouds(json) + assert.Nil(t, err) + + clouds := settings.CustomCloudList + + assert.Len(t, clouds, 1) + cloud := clouds[0] + assert.Equal(t, cloud.Name, "CustomCloud1") + assert.Equal(t, cloud.DisplayName, "Custom Cloud 1") + assert.Equal(t, cloud.AadAuthority, "https://login.contoso.com/") + assert.Len(t, cloud.Properties, 5) + + cloud2, err := settings.GetCloud("CustomCloud1") + assert.Nil(t, err) + assert.Equal(t, cloud2.Name, "CustomCloud1") +} diff --git a/azsettings/env.go b/azsettings/env.go index 95a44e9..1e55508 100644 --- a/azsettings/env.go +++ b/azsettings/env.go @@ -7,7 +7,8 @@ import ( ) const ( - AzureCloud = "GFAZPL_AZURE_CLOUD" + AzureCloud = "GFAZPL_AZURE_CLOUD" + AzureCustomCloudsConfig = "GFAZPL_AZURE_CLOUDS_CONFIG" AzureAuthEnabled = "GFAZPL_AZURE_AUTH_ENABLED" @@ -45,6 +46,13 @@ func ReadFromEnv() (*AzureSettings, error) { azureSettings.AzureAuthEnabled = true } + if customCloudsJSON := envutil.GetOrDefault(AzureCustomCloudsConfig, ""); customCloudsJSON != "" { + // this method will parse the JSON and set the custom cloud list in one go + if err := azureSettings.SetCustomClouds(customCloudsJSON); err != nil { + return nil, err + } + } + // Managed Identity authentication if msiEnabled, err := envutil.GetBoolOrFallback(ManagedIdentityEnabled, fallbackManagedIdentityEnabled, false); err != nil { err = fmt.Errorf("invalid Azure configuration: %w", err) diff --git a/azsettings/settings.go b/azsettings/settings.go index 39a6baf..72d6182 100644 --- a/azsettings/settings.go +++ b/azsettings/settings.go @@ -22,6 +22,9 @@ type AzureSettings struct { // This field determines which plugins will receive the settings via plugin context ForwardSettingsPlugins []string + + CustomCloudList []*AzureCloudSettings + CustomCloudListJSON string } type WorkloadIdentitySettings struct { @@ -60,6 +63,13 @@ func ReadFromContext(ctx context.Context) (*AzureSettings, bool) { settings.AzureAuthEnabled = true } + if customCloudsJSON := cfg.Get(AzureCustomCloudsConfig); customCloudsJSON != "" { + // this method will parse the JSON and set the custom cloud list in one go + if err := settings.SetCustomClouds(customCloudsJSON); err != nil { + backend.Logger.Error("Error setting custom clouds: %w", err) + } + } + hasSettings := false if v := cfg.Get(AzureCloud); v != "" { settings.Cloud = v