Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_application_insights - Disable Rule and Action Group that are automatically created when creating this resource #15892

Merged
merged 8 commits into from
Mar 23, 2022
3 changes: 3 additions & 0 deletions internal/features/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ func Default() UserFeatures {
ApiManagement: ApiManagementFeatures{
PurgeSoftDeleteOnDestroy: false,
},
ApplicationInsights: ApplicationInsightFeatures{
DisableGeneratedRule: false,
},
CognitiveAccount: CognitiveAccountFeatures{
PurgeSoftDeleteOnDestroy: true,
},
Expand Down
5 changes: 5 additions & 0 deletions internal/features/user_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package features

type UserFeatures struct {
ApiManagement ApiManagementFeatures
ApplicationInsights ApplicationInsightFeatures
CognitiveAccount CognitiveAccountFeatures
VirtualMachine VirtualMachineFeatures
VirtualMachineScaleSet VirtualMachineScaleSetFeatures
Expand Down Expand Up @@ -53,3 +54,7 @@ type ResourceGroupFeatures struct {
type ApiManagementFeatures struct {
PurgeSoftDeleteOnDestroy bool
}

type ApplicationInsightFeatures struct {
DisableGeneratedRule bool
}
24 changes: 24 additions & 0 deletions internal/provider/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ func schemaFeatures(supportLegacyTestSuite bool) *pluginsdk.Schema {
},
},

"application_insights": {
Type: pluginsdk.TypeList,
Optional: true,
MaxItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"disable_generated_rule": {
Type: pluginsdk.TypeBool,
Optional: true,
},
},
},
},
mbfrahry marked this conversation as resolved.
Show resolved Hide resolved

"cognitive_account": {
Type: pluginsdk.TypeList,
Optional: true,
Expand Down Expand Up @@ -254,6 +268,16 @@ func expandFeatures(input []interface{}) features.UserFeatures {
}
}

if raw, ok := val["application_insights"]; ok {
items := raw.([]interface{})
if len(items) > 0 && items[0] != nil {
applicationInsightsRaw := items[0].(map[string]interface{})
if v, ok := applicationInsightsRaw["disable_generated_rule"]; ok {
featuresMap.ApplicationInsights.DisableGeneratedRule = v.(bool)
}
}
}

if raw, ok := val["cognitive_account"]; ok {
items := raw.([]interface{})
if len(items) > 0 && items[0] != nil {
Expand Down
84 changes: 84 additions & 0 deletions internal/provider/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ func TestExpandFeatures(t *testing.T) {
ApiManagement: features.ApiManagementFeatures{
PurgeSoftDeleteOnDestroy: false,
},
ApplicationInsights: features.ApplicationInsightFeatures{
DisableGeneratedRule: false,
},
CognitiveAccount: features.CognitiveAccountFeatures{
PurgeSoftDeleteOnDestroy: true,
},
Expand Down Expand Up @@ -64,6 +67,11 @@ func TestExpandFeatures(t *testing.T) {
"purge_soft_delete_on_destroy": true,
},
},
"application_insights": []interface{}{
map[string]interface{}{
"disable_generated_rule": true,
},
},
"cognitive_account": []interface{}{
map[string]interface{}{
"purge_soft_delete_on_destroy": true,
Expand Down Expand Up @@ -121,6 +129,9 @@ func TestExpandFeatures(t *testing.T) {
ApiManagement: features.ApiManagementFeatures{
PurgeSoftDeleteOnDestroy: true,
},
ApplicationInsights: features.ApplicationInsightFeatures{
DisableGeneratedRule: true,
},
CognitiveAccount: features.CognitiveAccountFeatures{
PurgeSoftDeleteOnDestroy: true,
},
Expand Down Expand Up @@ -164,6 +175,11 @@ func TestExpandFeatures(t *testing.T) {
"purge_soft_delete_on_destroy": false,
},
},
"application_insights": []interface{}{
map[string]interface{}{
"disable_generated_rule": false,
},
},
"cognitive_account": []interface{}{
map[string]interface{}{
"purge_soft_delete_on_destroy": false,
Expand Down Expand Up @@ -221,6 +237,9 @@ func TestExpandFeatures(t *testing.T) {
ApiManagement: features.ApiManagementFeatures{
PurgeSoftDeleteOnDestroy: false,
},
ApplicationInsights: features.ApplicationInsightFeatures{
DisableGeneratedRule: false,
},
CognitiveAccount: features.CognitiveAccountFeatures{
PurgeSoftDeleteOnDestroy: false,
},
Expand Down Expand Up @@ -331,6 +350,71 @@ func TestExpandFeaturesApiManagement(t *testing.T) {
}
}

func TestExpandFeaturesApplicationInsights(t *testing.T) {
testData := []struct {
Name string
Input []interface{}
EnvVars map[string]interface{}
Expected features.UserFeatures
}{
{
Name: "Empty Block",
Input: []interface{}{
map[string]interface{}{
"application_insights": []interface{}{},
},
},
Expected: features.UserFeatures{
ApplicationInsights: features.ApplicationInsightFeatures{
DisableGeneratedRule: false,
},
},
},
{
Name: "Disable Generated Rule",
Input: []interface{}{
map[string]interface{}{
"application_insights": []interface{}{
map[string]interface{}{
"disable_generated_rule": true,
},
},
},
},
Expected: features.UserFeatures{
ApplicationInsights: features.ApplicationInsightFeatures{
DisableGeneratedRule: true,
},
},
},
{
Name: "Enable Generated Rule",
Input: []interface{}{
map[string]interface{}{
"application_insights": []interface{}{
map[string]interface{}{
"disable_generated_rule": false,
},
},
},
},
Expected: features.UserFeatures{
ApplicationInsights: features.ApplicationInsightFeatures{
DisableGeneratedRule: false,
},
},
},
}

for _, testCase := range testData {
t.Logf("[DEBUG] Test Case: %q", testCase.Name)
result := expandFeatures(testCase.Input)
if !reflect.DeepEqual(result.ApplicationInsights, testCase.Expected.ApplicationInsights) {
t.Fatalf("Expected %+v but got %+v", result.ApplicationInsights, testCase.Expected.ApplicationInsights)
}
}
}

func TestExpandFeaturesCognitiveServices(t *testing.T) {
testData := []struct {
Name string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import (
"strings"
"time"

"github.com/hashicorp/go-azure-helpers/resourcemanager/location"

"github.com/Azure/azure-sdk-for-go/services/appinsights/mgmt/2020-02-02/insights"
"github.com/Azure/azure-sdk-for-go/services/preview/alertsmanagement/mgmt/2019-06-01-preview/alertsmanagement"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/terraform-provider-azurerm/helpers/azure"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/applicationinsights/migration"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/applicationinsights/parse"
monitorParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/monitor/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/tags"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
Expand All @@ -40,7 +41,7 @@ func resourceApplicationInsights() *pluginsdk.Resource {
}),

Timeouts: &pluginsdk.ResourceTimeout{
Create: pluginsdk.DefaultTimeout(30 * time.Minute),
Create: pluginsdk.DefaultTimeout(60 * time.Minute),
Read: pluginsdk.DefaultTimeout(5 * time.Minute),
Update: pluginsdk.DefaultTimeout(30 * time.Minute),
Delete: pluginsdk.DefaultTimeout(30 * time.Minute),
Expand Down Expand Up @@ -169,6 +170,8 @@ func resourceApplicationInsights() *pluginsdk.Resource {

func resourceApplicationInsightsCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).AppInsights.ComponentsClient
ruleClient := meta.(*clients.Client).Monitor.SmartDetectorAlertRulesClient
actionGroupClient := meta.(*clients.Client).Monitor.ActionGroupsClient
billingClient := meta.(*clients.Client).AppInsights.BillingClient
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d)
Expand All @@ -179,7 +182,7 @@ func resourceApplicationInsightsCreateUpdate(d *pluginsdk.ResourceData, meta int
name := d.Get("name").(string)
resGroup := d.Get("resource_group_name").(string)

resourceId := parse.NewComponentID(subscriptionId, resGroup, name).ID()
resourceId := parse.NewComponentID(subscriptionId, resGroup, name)
if d.IsNewResource() {
existing, err := client.Get(ctx, resGroup, name)
if err != nil {
Expand All @@ -189,7 +192,7 @@ func resourceApplicationInsightsCreateUpdate(d *pluginsdk.ResourceData, meta int
}

if !utils.ResponseWasNotFound(existing.Response) {
return tf.ImportAsExistsError("azurerm_application_insights", resourceId)
return tf.ImportAsExistsError("azurerm_application_insights", resourceId.ID())
}
}

Expand Down Expand Up @@ -274,7 +277,67 @@ func resourceApplicationInsightsCreateUpdate(d *pluginsdk.ResourceData, meta int
return fmt.Errorf("update Application Insights Billing Feature %q (Resource Group %q): %+v", name, resGroup, err)
}

d.SetId(resourceId)
// https://github.com/hashicorp/terraform-provider-azurerm/issues/10563
// Azure creates a rule and action group when creating this resource that are very noisy
// We would like to delete them but deleting them just causes them to be recreated after a few minutes.
// Instead, we'll opt to disable them here
if d.IsNewResource() && meta.(*clients.Client).Features.ApplicationInsights.DisableGeneratedRule {
err := pluginsdk.Retry(d.Timeout(pluginsdk.TimeoutCreate), func() *pluginsdk.RetryError {
time.Sleep(30 * time.Second)
mbfrahry marked this conversation as resolved.
Show resolved Hide resolved
actionGroupId := monitorParse.NewActionGroupID(resourceId.SubscriptionId, resourceId.ResourceGroup, "Application Insights Smart Detection")

groupResult, err := actionGroupClient.Get(ctx, actionGroupId.ResourceGroup, actionGroupId.Name)
if err != nil {
if utils.ResponseWasNotFound(groupResult.Response) {
return pluginsdk.RetryableError(fmt.Errorf("expected %s to be created but was not found, retrying", actionGroupId))
}
return pluginsdk.NonRetryableError(fmt.Errorf("making Read request for %s: %+v", actionGroupId, err))
}

if groupResult.ActionGroup != nil {
groupResult.ActionGroup.Enabled = utils.Bool(false)
updateActionGroupResult, err := actionGroupClient.CreateOrUpdate(ctx, actionGroupId.ResourceGroup, actionGroupId.Name, groupResult)
if err != nil {
if !utils.ResponseWasNotFound(updateActionGroupResult.Response) {
return pluginsdk.NonRetryableError(fmt.Errorf("issuing disable request for %s: %+v", actionGroupId, err))
}
}
}
return nil
})
if err != nil {
return err
}

err = pluginsdk.Retry(d.Timeout(pluginsdk.TimeoutCreate), func() *pluginsdk.RetryError {
time.Sleep(30 * time.Second)
mbfrahry marked this conversation as resolved.
Show resolved Hide resolved
ruleName := fmt.Sprintf("Failure Anomalies - %s", resourceId.Name)
ruleId := monitorParse.NewSmartDetectorAlertRuleID(resourceId.SubscriptionId, resourceId.ResourceGroup, ruleName)
result, err := ruleClient.Get(ctx, ruleId.ResourceGroup, ruleId.Name, utils.Bool(true))
if err != nil {
if utils.ResponseWasNotFound(result.Response) {
return pluginsdk.RetryableError(fmt.Errorf("expected %s to be created but was not found, retrying", ruleId))
}
return pluginsdk.NonRetryableError(fmt.Errorf("making Read request for %s: %+v", ruleId, err))
}

if result.AlertRuleProperties != nil {
result.AlertRuleProperties.State = alertsmanagement.AlertRuleStateDisabled
updateRuleResult, err := ruleClient.CreateOrUpdate(ctx, ruleId.ResourceGroup, ruleId.Name, result)
if err != nil {
if !utils.ResponseWasNotFound(updateRuleResult.Response) {
return pluginsdk.NonRetryableError(fmt.Errorf("issuing disable request for %s: %+v", ruleId, err))
}
}
}
return nil
})
if err != nil {
return err
}
}

d.SetId(resourceId.ID())

return resourceApplicationInsightsRead(d, meta)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,22 @@ func TestAccApplicationInsights_withInternetIngestionEnabled(t *testing.T) {
})
}

func TestAccApplicationInsights_disableGeneratedRule(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_application_insights", "test")
r := AppInsightsResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.disableGeneratedRule(data, "web"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("application_type").HasValue("web"),
),
},
data.ImportStep(),
})
}

func (AppInsightsResource) basic(data acceptance.TestData, applicationType string) string {
return fmt.Sprintf(`
provider "azurerm" {
Expand Down Expand Up @@ -420,3 +436,27 @@ resource "azurerm_application_insights" "test" {
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger)
}

func (AppInsightsResource) disableGeneratedRule(data acceptance.TestData, applicationType string) string {
return fmt.Sprintf(`
provider "azurerm" {
features {
application_insights {
disable_generated_rule = true
}
}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-appinsights-%d"
location = "%s"
}

resource "azurerm_application_insights" "test" {
name = "acctestappinsights-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
application_type = "%s"
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, applicationType)
}