diff --git a/azurerm/config.go b/azurerm/config.go index 41e41e157458..ff33e3916b45 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -213,11 +213,13 @@ type ArmClient struct { managementGroupsSubscriptionClient managementgroups.SubscriptionsClient // Monitor - monitorActionGroupsClient insights.ActionGroupsClient - monitorActivityLogAlertsClient insights.ActivityLogAlertsClient - monitorAlertRulesClient insights.AlertRulesClient - monitorLogProfilesClient insights.LogProfilesClient - monitorMetricAlertsClient insights.MetricAlertsClient + monitorActionGroupsClient insights.ActionGroupsClient + monitorActivityLogAlertsClient insights.ActivityLogAlertsClient + monitorAlertRulesClient insights.AlertRulesClient + monitorDiagnosticSettingsClient insights.DiagnosticSettingsClient + monitorDiagnosticSettingsCategoryClient insights.DiagnosticSettingsCategoryClient + monitorLogProfilesClient insights.LogProfilesClient + monitorMetricAlertsClient insights.MetricAlertsClient // MSI userAssignedIdentitiesClient msi.UserAssignedIdentitiesClient @@ -825,6 +827,14 @@ func (c *ArmClient) registerMonitorClients(endpoint, subscriptionId string, auth autoscaleSettingsClient := insights.NewAutoscaleSettingsClientWithBaseURI(endpoint, subscriptionId) c.configureClient(&autoscaleSettingsClient.Client, auth) c.autoscaleSettingsClient = autoscaleSettingsClient + + monitoringInsightsClient := insights.NewDiagnosticSettingsClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&monitoringInsightsClient.Client, auth) + c.monitorDiagnosticSettingsClient = monitoringInsightsClient + + monitoringCategorySettingsClient := insights.NewDiagnosticSettingsCategoryClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&monitoringCategorySettingsClient.Client, auth) + c.monitorDiagnosticSettingsCategoryClient = monitoringCategorySettingsClient } func (c *ArmClient) registerNetworkingClients(endpoint, subscriptionId string, auth autorest.Authorizer) { diff --git a/azurerm/data_source_monitor_diagnostic_categories.go b/azurerm/data_source_monitor_diagnostic_categories.go new file mode 100644 index 000000000000..473458aff2d5 --- /dev/null +++ b/azurerm/data_source_monitor_diagnostic_categories.go @@ -0,0 +1,89 @@ +package azurerm + +import ( + "fmt" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +func dataSourceArmMonitorDiagnosticCategories() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmMonitorDiagnosticCategoriesRead, + Schema: map[string]*schema.Schema{ + "resource_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "logs": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Computed: true, + }, + + "metrics": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Computed: true, + }, + }, + } +} + +func dataSourceArmMonitorDiagnosticCategoriesRead(d *schema.ResourceData, meta interface{}) error { + categoriesClient := meta.(*ArmClient).monitorDiagnosticSettingsCategoryClient + ctx := meta.(*ArmClient).StopContext + + actualResourceId := d.Get("resource_id").(string) + // trim off the leading `/` since the CheckExistenceByID / List methods don't expect it + resourceId := strings.TrimPrefix(actualResourceId, "/") + + // then retrieve the possible Diagnostics Categories for this Resource + categories, err := categoriesClient.List(ctx, resourceId) + if err != nil { + return fmt.Errorf("Error retrieving Diagnostics Categories for Resource %q: %+v", actualResourceId, err) + } + + if categories.Value == nil { + return fmt.Errorf("Error retrieving Diagnostics Categories for Resource %q: `categories.Value` was nil", actualResourceId) + } + + d.SetId(actualResourceId) + val := *categories.Value + + metrics := make([]string, 0) + logs := make([]string, 0) + + for _, v := range val { + if v.Name == nil { + continue + } + + if category := v.DiagnosticSettingsCategory; category != nil { + switch category.CategoryType { + case insights.Logs: + logs = append(logs, *v.Name) + case insights.Metrics: + metrics = append(metrics, *v.Name) + default: + return fmt.Errorf("Unsupported category type %q", string(category.CategoryType)) + } + } + } + + if err := d.Set("logs", logs); err != nil { + return fmt.Errorf("Error setting `logs`: %+v", err) + } + + if err := d.Set("metrics", metrics); err != nil { + return fmt.Errorf("Error setting `metrics`: %+v", err) + } + + return nil +} diff --git a/azurerm/data_source_monitor_diagnostic_categories_test.go b/azurerm/data_source_monitor_diagnostic_categories_test.go new file mode 100644 index 000000000000..9c89a6c1c3bc --- /dev/null +++ b/azurerm/data_source_monitor_diagnostic_categories_test.go @@ -0,0 +1,105 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceArmMonitorDiagnosticCategories_appService(t *testing.T) { + dataSourceName := "data.azurerm_monitor_diagnostic_categories.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceArmMonitorDiagnosticCategories_appService(ri, location), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "metrics.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "logs.#", "1"), + ), + }, + }, + }) +} + +func TestAccDataSourceArmMonitorDiagnosticCategories_storageAccount(t *testing.T) { + dataSourceName := "data.azurerm_monitor_diagnostic_categories.test" + rs := acctest.RandString(8) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceArmMonitorDiagnosticCategories_storageAccount(rs, location), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "metrics.#", "2"), + resource.TestCheckResourceAttr(dataSourceName, "logs.#", "0"), + ), + }, + }, + }) +} + +func testAccDataSourceArmMonitorDiagnosticCategories_appService(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestrg-%d" + location = "%s" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + app_service_plan_id = "${azurerm_app_service_plan.test.id}" +} + +data "azurerm_monitor_diagnostic_categories" "test" { + resource_id = "${azurerm_app_service.test.id}" +} +`, rInt, location, rInt, rInt) +} + +func testAccDataSourceArmMonitorDiagnosticCategories_storageAccount(rString, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestrg-%s" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "GRS" + + tags { + environment = "staging" + } +} + +data "azurerm_monitor_diagnostic_categories" "test" { + resource_id = "${azurerm_storage_account.test.id}" +} +`, rString, location, rString) +} diff --git a/azurerm/provider.go b/azurerm/provider.go index 394122200252..167b81c98f3e 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -97,6 +97,7 @@ func Provider() terraform.ResourceProvider { "azurerm_logic_app_workflow": dataSourceArmLogicAppWorkflow(), "azurerm_managed_disk": dataSourceArmManagedDisk(), "azurerm_management_group": dataSourceArmManagementGroup(), + "azurerm_monitor_diagnostic_categories": dataSourceArmMonitorDiagnosticCategories(), "azurerm_monitor_log_profile": dataSourceArmMonitorLogProfile(), "azurerm_network_interface": dataSourceArmNetworkInterface(), "azurerm_network_security_group": dataSourceArmNetworkSecurityGroup(), @@ -218,6 +219,7 @@ func Provider() terraform.ResourceProvider { "azurerm_metric_alertrule": resourceArmMetricAlertRule(), "azurerm_monitor_action_group": resourceArmMonitorActionGroup(), "azurerm_monitor_activity_log_alert": resourceArmMonitorActivityLogAlert(), + "azurerm_monitor_diagnostic_setting": resourceArmMonitorDiagnosticSetting(), "azurerm_monitor_log_profile": resourceArmMonitorLogProfile(), "azurerm_monitor_metric_alert": resourceArmMonitorMetricAlert(), "azurerm_mysql_configuration": resourceArmMySQLConfiguration(), diff --git a/azurerm/resource_arm_monitor_diagnostic_setting.go b/azurerm/resource_arm_monitor_diagnostic_setting.go new file mode 100644 index 000000000000..b7ff45e836ed --- /dev/null +++ b/azurerm/resource_arm_monitor_diagnostic_setting.go @@ -0,0 +1,491 @@ +package azurerm + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmMonitorDiagnosticSetting() *schema.Resource { + return &schema.Resource{ + Create: resourceArmMonitorDiagnosticSettingCreateUpdate, + Read: resourceArmMonitorDiagnosticSettingRead, + Update: resourceArmMonitorDiagnosticSettingCreateUpdate, + Delete: resourceArmMonitorDiagnosticSettingDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + // NOTE: there's no validation requirements listed for this + // so we're intentionally doing the minimum we can here + ValidateFunc: validation.NoZeroValues, + }, + + "target_resource_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "eventhub_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: azure.ValidateEventHubName(), + }, + + "eventhub_authorization_rule_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "log_analytics_workspace_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "storage_account_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "log": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "category": { + Type: schema.TypeString, + Required: true, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "retention_policy": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Required: true, + }, + + "days": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(0), + }, + }, + }, + }, + }, + }, + }, + + "metric": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "category": { + Type: schema.TypeString, + Required: true, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "retention_policy": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Required: true, + }, + + "days": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(0), + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceArmMonitorDiagnosticSettingCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).monitorDiagnosticSettingsClient + ctx := meta.(*ArmClient).StopContext + log.Printf("[INFO] preparing arguments for Azure ARM Diagnostic Settings.") + + name := d.Get("name").(string) + actualResourceId := d.Get("target_resource_id").(string) + + logsRaw := d.Get("log").(*schema.Set).List() + logs := expandMonitorDiagnosticsSettingsLogs(logsRaw) + metricsRaw := d.Get("metric").(*schema.Set).List() + metrics := expandMonitorDiagnosticsSettingsMetrics(metricsRaw) + + // if no blocks are specified the API "creates" but 404's on Read + if len(logs) == 0 && len(metrics) == 0 { + return fmt.Errorf("At least one `log` or `metric` block must be specified") + } + + // also if there's none enabled + valid := false + for _, v := range logs { + if v.Enabled != nil && *v.Enabled { + valid = true + break + } + } + if !valid { + for _, v := range metrics { + if v.Enabled != nil && *v.Enabled { + valid = true + break + } + } + } + + if !valid { + return fmt.Errorf("At least one `log` or `metric` must be enabled") + } + + properties := insights.DiagnosticSettingsResource{ + DiagnosticSettings: &insights.DiagnosticSettings{ + Logs: &logs, + Metrics: &metrics, + }, + } + + valid = false + eventHubAuthorizationRuleId := d.Get("eventhub_authorization_rule_id").(string) + eventHubName := d.Get("eventhub_name").(string) + if eventHubAuthorizationRuleId != "" { + properties.DiagnosticSettings.EventHubAuthorizationRuleID = utils.String(eventHubAuthorizationRuleId) + properties.DiagnosticSettings.EventHubName = utils.String(eventHubName) + valid = true + } + + workspaceId := d.Get("log_analytics_workspace_id").(string) + if workspaceId != "" { + properties.DiagnosticSettings.WorkspaceID = utils.String(workspaceId) + valid = true + } + + storageAccountId := d.Get("storage_account_id").(string) + if storageAccountId != "" { + properties.DiagnosticSettings.StorageAccountID = utils.String(storageAccountId) + valid = true + } + + if !valid { + return fmt.Errorf("Either a `eventhub_authorization_rule_id`, `log_analytics_workspace_id` or `storage_account_id` must be set") + } + + // the Azure SDK prefixes the URI with a `/` such this makes a bad request if we don't trim the `/` + targetResourceId := strings.TrimPrefix(actualResourceId, "/") + if _, err := client.CreateOrUpdate(ctx, targetResourceId, properties, name); err != nil { + return fmt.Errorf("Error creating Monitor Diagnostics Setting %q for Resource %q: %+v", name, actualResourceId, err) + } + + read, err := client.Get(ctx, targetResourceId, name) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read ID for Monitor Diagnostics %q for Resource ID %q", name, actualResourceId) + } + + d.SetId(fmt.Sprintf("%s|%s", actualResourceId, name)) + + return resourceArmMonitorDiagnosticSettingRead(d, meta) +} + +func resourceArmMonitorDiagnosticSettingRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).monitorDiagnosticSettingsClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseMonitorDiagnosticId(d.Id()) + if err != nil { + return err + } + + actualResourceId := id.resourceID + targetResourceId := strings.TrimPrefix(actualResourceId, "/") + resp, err := client.Get(ctx, targetResourceId, id.name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[WARN] Monitor Diagnostics Setting %q was not found for Resource %q - removing from state!", id.name, actualResourceId) + d.SetId("") + return nil + } + + return fmt.Errorf("Error retrieving Monitor Diagnostics Setting %q for Resource %q: %+v", id.name, actualResourceId, err) + } + + d.Set("name", id.name) + d.Set("target_resource_id", id.resourceID) + + d.Set("eventhub_name", resp.EventHubName) + d.Set("eventhub_authorization_rule_id", resp.EventHubAuthorizationRuleID) + d.Set("log_analytics_workspace_id", resp.WorkspaceID) + d.Set("storage_account_id", resp.StorageAccountID) + + if err := d.Set("log", flattenMonitorDiagnosticLogs(resp.Logs)); err != nil { + return fmt.Errorf("Error setting `log`: %+v", err) + } + + if err := d.Set("metric", flattenMonitorDiagnosticMetrics(resp.Metrics)); err != nil { + return fmt.Errorf("Error setting `metric`: %+v", err) + } + + return nil +} + +func resourceArmMonitorDiagnosticSettingDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).monitorDiagnosticSettingsClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseMonitorDiagnosticId(d.Id()) + if err != nil { + return err + } + + targetResourceId := strings.TrimPrefix(id.resourceID, "/") + resp, err := client.Delete(ctx, targetResourceId, id.name) + if err != nil { + if !response.WasNotFound(resp.Response) { + return fmt.Errorf("Error deleting Monitor Diagnostics Setting %q for Resource %q: %+v", id.name, targetResourceId, err) + } + } + + // API appears to be eventually consistent (identified during tainting this resource) + log.Printf("[DEBUG] Waiting for Monitor Diagnostic Setting %q for Resource %q to disappear", id.name, id.resourceID) + stateConf := &resource.StateChangeConf{ + Pending: []string{"Exists"}, + Target: []string{"NotFound"}, + Refresh: monitorDiagnosticSettingDeletedRefreshFunc(ctx, client, targetResourceId, id.name), + Timeout: 60 * time.Minute, + MinTimeout: 15 * time.Second, + ContinuousTargetOccurence: 5, + } + if _, err = stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for Monitor Diagnostic Setting %q for Resource %q to become available: %s", id.name, id.resourceID, err) + } + + return nil +} + +func monitorDiagnosticSettingDeletedRefreshFunc(ctx context.Context, client insights.DiagnosticSettingsClient, targetResourceId string, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + res, err := client.Get(ctx, targetResourceId, name) + if err != nil { + if utils.ResponseWasNotFound(res.Response) { + return "NotFound", "NotFound", nil + } + return nil, "", fmt.Errorf("Error issuing read request in monitorDiagnosticSettingDeletedRefreshFunc: %s", err) + } + + return res, "Exists", nil + } +} + +func expandMonitorDiagnosticsSettingsLogs(input []interface{}) []insights.LogSettings { + results := make([]insights.LogSettings, 0) + + for _, raw := range input { + v := raw.(map[string]interface{}) + + category := v["category"].(string) + enabled := v["enabled"].(bool) + + policiesRaw := v["retention_policy"].([]interface{}) + policyRaw := policiesRaw[0].(map[string]interface{}) + retentionDays := policyRaw["days"].(int) + retentionEnabled := policyRaw["enabled"].(bool) + + output := insights.LogSettings{ + Category: utils.String(category), + Enabled: utils.Bool(enabled), + RetentionPolicy: &insights.RetentionPolicy{ + Days: utils.Int32(int32(retentionDays)), + Enabled: utils.Bool(retentionEnabled), + }, + } + + results = append(results, output) + } + + return results +} + +func flattenMonitorDiagnosticLogs(input *[]insights.LogSettings) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + for _, v := range *input { + output := make(map[string]interface{}) + + if v.Category != nil { + output["category"] = *v.Category + } + + if v.Enabled != nil { + output["enabled"] = *v.Enabled + } + + policies := make([]interface{}, 0) + + if inputPolicy := v.RetentionPolicy; inputPolicy != nil { + outputPolicy := make(map[string]interface{}) + + if inputPolicy.Days != nil { + outputPolicy["days"] = int(*inputPolicy.Days) + } + + if inputPolicy.Enabled != nil { + outputPolicy["enabled"] = *inputPolicy.Enabled + } + + policies = append(policies, outputPolicy) + } + + output["retention_policy"] = policies + + results = append(results, output) + } + + return results +} + +func expandMonitorDiagnosticsSettingsMetrics(input []interface{}) []insights.MetricSettings { + results := make([]insights.MetricSettings, 0) + + for _, raw := range input { + v := raw.(map[string]interface{}) + + category := v["category"].(string) + enabled := v["enabled"].(bool) + + policiesRaw := v["retention_policy"].([]interface{}) + policyRaw := policiesRaw[0].(map[string]interface{}) + + retentionDays := policyRaw["days"].(int) + retentionEnabled := policyRaw["enabled"].(bool) + + output := insights.MetricSettings{ + Category: utils.String(category), + Enabled: utils.Bool(enabled), + RetentionPolicy: &insights.RetentionPolicy{ + Days: utils.Int32(int32(retentionDays)), + Enabled: utils.Bool(retentionEnabled), + }, + } + + results = append(results, output) + } + + return results +} + +func flattenMonitorDiagnosticMetrics(input *[]insights.MetricSettings) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + for _, v := range *input { + output := make(map[string]interface{}) + + if v.Category != nil { + output["category"] = *v.Category + } + + if v.Enabled != nil { + output["enabled"] = *v.Enabled + } + + policies := make([]interface{}, 0) + + if inputPolicy := v.RetentionPolicy; inputPolicy != nil { + outputPolicy := make(map[string]interface{}) + + if inputPolicy.Days != nil { + outputPolicy["days"] = int(*inputPolicy.Days) + } + + if inputPolicy.Enabled != nil { + outputPolicy["enabled"] = *inputPolicy.Enabled + } + + policies = append(policies, outputPolicy) + } + + output["retention_policy"] = policies + + results = append(results, output) + } + + return results +} + +type monitorDiagnosticId struct { + resourceID string + name string +} + +func parseMonitorDiagnosticId(monitorId string) (*monitorDiagnosticId, error) { + v := strings.Split(monitorId, "|") + if len(v) != 2 { + return nil, fmt.Errorf("Expected the Monitor Diagnostics ID to be in the format `{resourceId}|{name}` but got %d segments", len(v)) + } + + identifier := monitorDiagnosticId{ + resourceID: v[0], + name: v[1], + } + return &identifier, nil +} diff --git a/azurerm/resource_arm_monitor_diagnostic_setting_test.go b/azurerm/resource_arm_monitor_diagnostic_setting_test.go new file mode 100644 index 000000000000..f7976a541a10 --- /dev/null +++ b/azurerm/resource_arm_monitor_diagnostic_setting_test.go @@ -0,0 +1,332 @@ +package azurerm + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMMonitorDiagnosticSetting_eventhub(t *testing.T) { + resourceName := "azurerm_monitor_diagnostic_setting.test" + ri := acctest.RandIntRange(10000, 99999) + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMMonitorDiagnosticSettingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMonitorDiagnosticSetting_eventhub(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMonitorDiagnosticSettingExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "eventhub_name"), + resource.TestCheckResourceAttrSet(resourceName, "eventhub_authorization_rule_id"), + resource.TestCheckResourceAttr(resourceName, "log.#", "1"), + resource.TestCheckResourceAttr(resourceName, "log.782743152.category", "AuditEvent"), + resource.TestCheckResourceAttr(resourceName, "metric.#", "1"), + resource.TestCheckResourceAttr(resourceName, "metric.1439188313.category", "AllMetrics"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMMonitorDiagnosticSetting_logAnalyticsWorkspace(t *testing.T) { + resourceName := "azurerm_monitor_diagnostic_setting.test" + ri := acctest.RandIntRange(10000, 99999) + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMMonitorDiagnosticSettingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMonitorDiagnosticSetting_logAnalyticsWorkspace(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMonitorDiagnosticSettingExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "log_analytics_workspace_id"), + resource.TestCheckResourceAttr(resourceName, "log.#", "1"), + resource.TestCheckResourceAttr(resourceName, "log.782743152.category", "AuditEvent"), + resource.TestCheckResourceAttr(resourceName, "metric.#", "1"), + resource.TestCheckResourceAttr(resourceName, "metric.1439188313.category", "AllMetrics"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMMonitorDiagnosticSetting_storageAccount(t *testing.T) { + resourceName := "azurerm_monitor_diagnostic_setting.test" + ri := acctest.RandIntRange(10000, 99999) + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMMonitorDiagnosticSettingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMonitorDiagnosticSetting_storageAccount(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMonitorDiagnosticSettingExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "storage_account_id"), + resource.TestCheckResourceAttr(resourceName, "log.#", "1"), + resource.TestCheckResourceAttr(resourceName, "log.782743152.category", "AuditEvent"), + resource.TestCheckResourceAttr(resourceName, "metric.#", "1"), + resource.TestCheckResourceAttr(resourceName, "metric.1439188313.category", "AllMetrics"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckAzureRMMonitorDiagnosticSettingExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %q", resourceName) + } + + client := testAccProvider.Meta().(*ArmClient).monitorDiagnosticSettingsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + name := rs.Primary.Attributes["name"] + actualResourceId := rs.Primary.Attributes["target_resource_id"] + targetResourceId := strings.TrimPrefix(actualResourceId, "/") + + resp, err := client.Get(ctx, targetResourceId, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Monitor Diagnostic Setting %q does not exist for Resource ID %s", name, targetResourceId) + } + + return fmt.Errorf("Bad: Get on monitorDiagnosticSettingsClient: %+v", err) + } + + return nil + } +} + +func testCheckAzureRMMonitorDiagnosticSettingDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).monitorDiagnosticSettingsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_monitor_diagnostic_setting" { + continue + } + + name := rs.Primary.Attributes["name"] + actualResourceId := rs.Primary.Attributes["target_resource_id"] + targetResourceId := strings.TrimPrefix(actualResourceId, "/") + + resp, err := client.Get(ctx, targetResourceId, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + + return err + } + } + + return nil +} + +func testAccAzureRMMonitorDiagnosticSetting_eventhub(rInt int, location string) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctest%d" + location = "%s" +} + +resource "azurerm_eventhub_namespace" "test" { + name = "acctesteventhubnamespace-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Basic" +} + +resource "azurerm_eventhub" "test" { + name = "acctesteventhub-%d" + namespace_name = "${azurerm_eventhub_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + partition_count = 2 + message_retention = 1 +} + +resource "azurerm_eventhub_namespace_authorization_rule" "test" { + name = "example" + namespace_name = "${azurerm_eventhub_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + listen = true + send = true + manage = true +} + +resource "azurerm_key_vault" "test" { + name = "acctestkv%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + + sku { + name = "standard" + } +} + +resource "azurerm_monitor_diagnostic_setting" "test" { + name = "acctestds%d" + target_resource_id = "${azurerm_key_vault.test.id}" + eventhub_authorization_rule_id = "${azurerm_eventhub_namespace_authorization_rule.test.id}" + eventhub_name = "${azurerm_eventhub.test.name}" + + log { + category = "AuditEvent" + enabled = false + + retention_policy { + enabled = false + } + } + + metric { + category = "AllMetrics" + + retention_policy { + enabled = false + } + } +} +`, rInt, location, rInt, rInt, rInt, rInt) +} + +func testAccAzureRMMonitorDiagnosticSetting_logAnalyticsWorkspace(rInt int, location string) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctest%d" + location = "%s" +} + +resource "azurerm_log_analytics_workspace" "test" { + name = "acctestlaw%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "PerGB2018" + retention_in_days = 30 +} + +resource "azurerm_key_vault" "test" { + name = "acctestkv%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + + sku { + name = "standard" + } +} + +resource "azurerm_monitor_diagnostic_setting" "test" { + name = "acctestds%d" + target_resource_id = "${azurerm_key_vault.test.id}" + log_analytics_workspace_id = "${azurerm_log_analytics_workspace.test.id}" + + log { + category = "AuditEvent" + enabled = false + + retention_policy { + enabled = false + } + } + + metric { + category = "AllMetrics" + + retention_policy { + enabled = false + } + } +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureRMMonitorDiagnosticSetting_storageAccount(rInt int, location string) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctest%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestlogs%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_replication_type = "LRS" + account_tier = "Standard" +} + +resource "azurerm_key_vault" "test" { + name = "acctestkv%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + + sku { + name = "standard" + } +} + +resource "azurerm_monitor_diagnostic_setting" "test" { + name = "acctestds%d" + target_resource_id = "${azurerm_key_vault.test.id}" + storage_account_id = "${azurerm_storage_account.test.id}" + + log { + category = "AuditEvent" + enabled = false + + retention_policy { + enabled = false + } + } + + metric { + category = "AllMetrics" + + retention_policy { + enabled = false + } + } +} +`, rInt, location, rInt, rInt, rInt) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 36da1ac9ef41..2831f53988be 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -136,6 +136,10 @@ azurerm_management_group + > + azurerm_monitor_diagnostic_categories + + > azurerm_monitor_log_profile @@ -860,6 +864,10 @@ azurerm_monitor_activity_log_alert + > + azurerm_monitor_diagnostic_setting + + > azurerm_monitor_log_profile diff --git a/website/docs/d/monitor_diagnostic_categories.html.markdown b/website/docs/d/monitor_diagnostic_categories.html.markdown new file mode 100644 index 000000000000..168b3d729314 --- /dev/null +++ b/website/docs/d/monitor_diagnostic_categories.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_monitor_diagnostic_categories" +sidebar_current: "docs-azurerm-datasource-monitor-diagnostic-categories" +description: |- + Gets information about an the Monitor Diagnostics Categories supported by an existing Resource. + +--- + +# Data Source: azurerm_monitor_diagnostic_categories + +Use this data source to access information about the Monitor Diagnostics Categories supported by an existing Resource. + +## Example Usage + +```hcl +data "azurerm_key_vault" "test" { + name = "${azurerm_key_vault.test.name}" + resource_group_name = "${azurerm_key_vault.test.resource_group_name}" +} + +data "azurerm_monitor_diagnostic_categories" "test" { + resource_id = "${azurerm_key_vault.test.id}" +} +``` + +## Argument Reference + +* `resource_id` - (Required) The ID of an existing Resource which Monitor Diagnostics Categories should be retrieved for. + +## Attributes Reference + +* `id` - The ID of the Resource. + +* `logs` - A list of the Log Categories supported for this Resource. + +* `metrics` - A list of the Metric Categories supported for this Resource. diff --git a/website/docs/r/monitor_diagnostic_setting.html.markdown b/website/docs/r/monitor_diagnostic_setting.html.markdown new file mode 100644 index 000000000000..bc23eef97224 --- /dev/null +++ b/website/docs/r/monitor_diagnostic_setting.html.markdown @@ -0,0 +1,101 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_monitor_diagnostic_setting" +sidebar_current: "docs-azurerm-resource-monitor-diagnostic-setting" +description: |- + Manages a Diagnostic Setting for an existing Resource. + +--- + +# azurerm_monitor_diagnostic_setting + +Manages a Diagnostic Setting for an existing Resource. + +## Example Usage + +``` + +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Diagnostic Setting. Changing this forces a new resource to be created. + +* `resource_id` - (Required) The ID of an existing Resource on which to configure Diagnostic Settings. Changing this forces a new resource to be created. + +* `event_hub_name` - (Optional) Specifies the name of the Event Hub where Diagnostics Data should be sent. Changing this forces a new resource to be created. + +-> **NOTE:** If this isn't specified then the default Event Hub will be used. + +* `event_hub_authorization_rule_id` - (Optional) Specifies the ID of an Event Hub Namespace Authorization Rule used to send Diagnostics Data. Changing this forces a new resource to be created. + +-> **NOTE:** One of `event_hub_authorization_rule_id`, `log_analytics_workspace_id` and `storage_account_id` must be specified. + +* `log` - (Optional) One or more `log` blocks as defined below. + +-> **NOTE:** At least one `log` or `metric` block must be specified. + +* `log_analytics_workspace_id` - (Optional) Specifies the ID of a Log Analytics Workspace where Diagnostics Data should be sent. Changing this forces a new resource to be created. + +-> **NOTE:** One of `event_hub_authorization_rule_id`, `log_analytics_workspace_id` and `storage_account_id` must be specified. + +* `metric` - (Optional) One or more `metric` blocks as defined below. + +-> **NOTE:** At least one `log` or `metric` block must be specified. + +* `storage_account_id` - (Optional) With this parameter you can specify a storage account which should be used to send the logs to. Parameter must be a valid Azure Resource ID. Changing this forces a new resource to be created. + +-> **NOTE:** One of `event_hub_authorization_rule_id`, `log_analytics_workspace_id` and `storage_account_id` must be specified. + +--- + +A `log` block supports the following: + +* `category` - (Required) The name of a Diagnostic Log Category for this Resource. + +-> **NOTE:** The Log Categories available vary depending on the Resource being used. You may wish to use [the `azurerm_monitor_diagnostic_categories` Data Source](../d/monitor_diagnostic_categories.html) to identify which categories are available for a given Resource. + +* `retention_policy` - (Required) A `retention_policy` block as defined below. + +* `enabled` - (Optional) Is this Diagnostic Log enabled? Defaults to `true`. + +--- + +A `metric` block supports the following: + +* `category` - (Required) The name of a Diagnostic Metric Category for this Resource. + +-> **NOTE:** The Metric Categories available vary depending on the Resource being used. You may wish to use [the `azurerm_monitor_diagnostic_categories` Data Source](../d/monitor_diagnostic_categories.html) to identify which categories are available for a given Resource. + +* `retention_policy` - (Required) A `retention_policy` block as defined below. + +* `enabled` - (Optional) Is this Diagnostic Metric enabled? Defaults to `true`. + +--- + +A `retention_policy` block supports the following: + +* `enabled` - (Required) Is this Retention Policy enabled? + +* `days` - (Optional) The number of days for which this Retention Policy should apply. + +-> **NOTE:** Setting this to `0` will retain the events indefinitely. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Diagnostic Setting. + +## Import + +Diagnostic Settings can be imported using the `resource id`, e.g. + +``` +terraform import azurerm_monitor_diagnostics.test /subscriptions/XXX/resourcegroups/resource_group/providers/microsoft.keyvault/vaults/vault|logMonitoring +``` + +-> **NOTE:** This is a Terraform specific Resource ID which uses the format `{resourceId}|{diagnosticSettingName}` \ No newline at end of file