diff --git a/Makefile b/Makefile index 8f55041..7e832b7 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ LCAF_ENV_FILE = .lcafenv # Source repository for repo manifests REPO_MANIFESTS_URL ?= https://github.com/launchbynttdata/launch-common-automation-framework.git # Branch of source repository for repo manifests. Other tags not currently supported. -REPO_BRANCH ?= refs/tags/1.0.0 +REPO_BRANCH ?= refs/tags/1.7.1 # Path to seed manifest in repository referenced in REPO_MANIFESTS_URL REPO_MANIFEST ?= manifests/terraform_modules/seed/manifest.xml diff --git a/README.md b/README.md index 180e162..81a5aa4 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,8 @@ No providers. | [signalr](#module\_signalr) | terraform.registry.launch.nttdata.com/module_primitive/signalr/azurerm | ~> 1.0 | | [log\_analytics\_workspace](#module\_log\_analytics\_workspace) | terraform.registry.launch.nttdata.com/module_primitive/log_analytics_workspace/azurerm | ~> 1.0 | | [diagnostic\_setting](#module\_diagnostic\_setting) | terraform.registry.launch.nttdata.com/module_primitive/monitor_diagnostic_setting/azurerm | ~> 1.0 | +| [action\_group](#module\_action\_group) | terraform.registry.launch.nttdata.com/module_primitive/monitor_action_group/azurerm | ~> 1.0.0 | +| [metric\_alert](#module\_metric\_alert) | terraform.registry.launch.nttdata.com/module_primitive/monitor_metric_alert/azurerm | ~> 2.0.0 | ## Resources @@ -163,6 +165,8 @@ No resources. | [enabled\_log](#input\_enabled\_log) | n/a |
list(object({
category_group = optional(string, "allLogs")
category = optional(string, null)
}))
| `null` | no | | [metric](#input\_metric) | n/a |
object({
category = optional(string)
enabled = optional(bool)
})
| `null` | no | | [tags](#input\_tags) | A mapping of tags to assign to the resource. | `map(string)` | `{}` | no | +| [metric\_alerts](#input\_metric\_alerts) | n/a |
map(object({
description = string
action_groups = optional(set(string), [])
enabled = optional(bool, true)
severity = optional(number, 3)
frequency = optional(string, "PT1M")
webhook_properties = optional(map(string))

criterias = optional(list(object({
threshold = number
metric_namespace = string
metric_name = string
aggregation = string
operator = string
dimensions = optional(list(object({
name = string
operator = string
values = list(string)
})))
})), [])

dynamic_criteria = optional(object({
alert_sensitivity = string
metric_name = string
metric_namespace = string
aggregation = string
operator = string
dimensions = optional(list(object({
name = string
operator = string
values = list(string)
})))
}))
}))
| `{}` | no | +| [action\_groups](#input\_action\_groups) | n/a |
map(object({
action_group_name = string
short_name = string
arm_role_receivers = optional(set(string))
email_receivers = optional(set(string))
}))
| `{}` | no | ## Outputs @@ -172,4 +176,6 @@ No resources. | [signalr\_name](#output\_signalr\_name) | n/a | | [location](#output\_location) | n/a | | [resource\_group\_name](#output\_resource\_group\_name) | n/a | +| [metric\_alerts](#output\_metric\_alerts) | n/a | +| [action\_groups](#output\_action\_groups) | n/a | diff --git a/examples/complete/README.md b/examples/complete/README.md index aae104c..d8b5e46 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -26,6 +26,12 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [logical\_product\_family](#input\_logical\_product\_family) | Name of the product family for which the resource is created | `string` | `"launch"` | no | +| [logical\_product\_service](#input\_logical\_product\_service) | Name of the product service for which the resource is created | `string` | `"signalr"` | no | +| [environment](#input\_environment) | Environment in which the resource should be provisioned like dev, qa, prod etc. | `string` | `"dev"` | no | +| [environment\_number](#input\_environment\_number) | The environment count for the respective environment. Defaults to 000. Increments in value of 1 | `string` | `"000"` | no | +| [resource\_number](#input\_resource\_number) | The resource count for the respective resource. Defaults to 000. Increments in value of 1 | `string` | `"000"` | no | +| [use\_azure\_region\_abbr](#input\_use\_azure\_region\_abbr) | Abbreviate the region in the resource names | `bool` | `true` | no | | [region](#input\_region) | Azure Region in which the infra needs to be provisioned | `string` | `"eastus"` | no | | [log\_analytics\_workspace\_sku](#input\_log\_analytics\_workspace\_sku) | Specifies the SKU of the Log Analytics Workspace. Possible values are Free, PerNode, Premium, Standard, Standalone, Unlimited, CapacityReservation, and PerGB2018 (new SKU as of 2018-04-03). Defaults to PerGB2018. | `string` | `"PerGB2018"` | no | | [log\_analytics\_workspace\_retention\_in\_days](#input\_log\_analytics\_workspace\_retention\_in\_days) | The workspace data retention in days. Possible values are either 7 (Free Tier only) or range between 30 and 730. | `number` | `"30"` | no | @@ -33,6 +39,7 @@ No resources. | [log\_analytics\_destination\_type](#input\_log\_analytics\_destination\_type) | (Optional) Specifies the type of destination for the logs. Possible values are 'Dedicated' or 'AzureDiagnostics'. | `string` | `"AzureDiagnostics"` | no | | [enabled\_log](#input\_enabled\_log) | n/a |
list(object({
category_group = optional(string, "allLogs")
category = optional(string, null)
}))
|
[
{
"category_group": "allLogs"
}
]
| no | | [metric](#input\_metric) | n/a |
object({
category = optional(string, "AllMetrics")
enabled = optional(bool, false)
})
|
{
"category": "AllMetrics"
}
| no | +| [metric\_alerts](#input\_metric\_alerts) | n/a |
map(object({
description = string
action_groups = optional(set(string), [])
enabled = optional(bool, true)
severity = optional(number, 3)
frequency = optional(string)
webhook_properties = optional(map(string))

criterias = optional(list(object({
threshold = number
metric_namespace = string
metric_name = string
aggregation = string
operator = string
dimensions = optional(list(object({
name = string
operator = string
values = list(string)
})))
})), [])

dynamic_criteria = optional(object({
alert_sensitivity = string
metric_name = string
metric_namespace = string
aggregation = string
operator = string
dimensions = optional(list(object({
name = string
operator = string
values = list(string)
})))
}))
}))
| `{}` | no | | [tags](#input\_tags) | A mapping of tags to assign to the resource. | `map(string)` | `{}` | no | ## Outputs @@ -43,4 +50,6 @@ No resources. | [signalr\_name](#output\_signalr\_name) | n/a | | [location](#output\_location) | n/a | | [resource\_group\_name](#output\_resource\_group\_name) | n/a | +| [metric\_alerts](#output\_metric\_alerts) | n/a | +| [action\_groups](#output\_action\_groups) | n/a | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index bd844bb..5cfa3ee 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -13,6 +13,13 @@ module "signalr" { source = "../.." + logical_product_family = var.logical_product_family + logical_product_service = var.logical_product_service + environment = var.environment + environment_number = var.environment_number + resource_number = var.resource_number + use_azure_region_abbr = var.use_azure_region_abbr + signalr_location = var.region cors_allowed_origins = ["*"] @@ -26,5 +33,7 @@ module "signalr" { enabled_log = var.enabled_log metric = var.metric + metric_alerts = var.metric_alerts + tags = local.tags } diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index 1090bc0..9bdbda4 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -25,3 +25,11 @@ output "location" { output "resource_group_name" { value = module.signalr.resource_group_name } + +output "metric_alerts" { + value = module.signalr.metric_alerts +} + +output "action_groups" { + value = module.signalr.action_groups +} diff --git a/examples/complete/test.tfvars b/examples/complete/test.tfvars index 2fcdac3..50b034b 100644 --- a/examples/complete/test.tfvars +++ b/examples/complete/test.tfvars @@ -1 +1,21 @@ // empty. + +logical_product_family = "launch" +logical_product_service = "signalr" +environment = "test" +environment_number = "001" +resource_number = "001" +use_azure_region_abbr = true + +metric_alerts = { + "SystemErrorsHigh" = { + description = "Perecentage of system errors are higher than usual" + dynamic_criteria = { + alert_sensitivity = "Low" + metric_name = "SystemErrors" + metric_namespace = "Microsoft.SignalRService/SignalR" + aggregation = "Maximum" + operator = "GreaterThan" + } + } +} diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 9a7f578..9d82a85 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -10,6 +10,46 @@ // See the License for the specific language governing permissions and // limitations under the License. +# naming variables + +variable "logical_product_family" { + description = "Name of the product family for which the resource is created" + type = string + default = "launch" +} + +variable "logical_product_service" { + description = "Name of the product service for which the resource is created" + type = string + default = "signalr" +} + +variable "environment" { + description = "Environment in which the resource should be provisioned like dev, qa, prod etc." + type = string + default = "dev" +} + +variable "environment_number" { + description = "The environment count for the respective environment. Defaults to 000. Increments in value of 1" + type = string + default = "000" +} + +variable "resource_number" { + description = "The resource count for the respective resource. Defaults to 000. Increments in value of 1" + type = string + default = "000" +} + +variable "use_azure_region_abbr" { + description = "Abbreviate the region in the resource names" + type = bool + default = true +} + +# signalr variables + variable "region" { description = "Azure Region in which the infra needs to be provisioned" type = string @@ -63,6 +103,50 @@ variable "metric" { } } +variable "metric_alerts" { + type = map(object({ + description = string + action_groups = optional(set(string), []) + enabled = optional(bool, true) + severity = optional(number, 3) + frequency = optional(string) + webhook_properties = optional(map(string)) + + criterias = optional(list(object({ + threshold = number + metric_namespace = string + metric_name = string + aggregation = string + operator = string + dimensions = optional(list(object({ + name = string + operator = string + values = list(string) + }))) + })), []) + + dynamic_criteria = optional(object({ + alert_sensitivity = string + metric_name = string + metric_namespace = string + aggregation = string + operator = string + dimensions = optional(list(object({ + name = string + operator = string + values = list(string) + }))) + })) + })) + default = {} + + validation { + condition = alltrue( + [for alert in var.metric_alerts : !(alert.criterias == null && alert.dynamic_criteria == null)], + ) + error_message = "At least one of 'criteria', 'dynamic_criteria' must be defined for all metric alerts" + } +} variable "tags" { description = "A mapping of tags to assign to the resource." type = map(string) diff --git a/go.mod b/go.mod index da4b567..b98fb8b 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.11.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/signalr/armsignalr v1.2.0 github.com/gruntwork-io/terratest v0.43.12 github.com/launchbynttdata/lcaf-component-terratest v1.0.3 diff --git a/go.sum b/go.sum index d69fe62..4e4f1ec 100644 --- a/go.sum +++ b/go.sum @@ -198,6 +198,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xP github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.11.0 h1:Ds0KRF8ggpEGg4Vo42oX1cIt/IfOhHWJBikksZbVxeg= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.11.0/go.mod h1:jj6P8ybImR+5topJ+eH6fgcemSFBmU6/6bFF8KkwuDI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/signalr/armsignalr v1.2.0 h1:Y8CF7FyuVVDyX5W6Azwjj3PpwUZVbXBOCyQytv/0QEA= diff --git a/main.tf b/main.tf index dc25225..7a79a00 100644 --- a/main.tf +++ b/main.tf @@ -92,3 +92,39 @@ module "diagnostic_setting" { enabled_log = var.enabled_log metric = var.metric } + +# This module is used to create an Azure Monitor Action Group. +module "action_group" { + for_each = var.action_groups + source = "terraform.registry.launch.nttdata.com/module_primitive/monitor_action_group/azurerm" + version = "~> 1.0.0" + + action_group_name = each.key + resource_group_name = module.resource_group.name + short_name = each.value.short_name + tags = var.tags + arm_role_receivers = each.value.arm_role_receivers + email_receivers = each.value.email_receivers + depends_on = [module.resource_group] +} + +module "metric_alert" { + for_each = var.metric_alerts + source = "terraform.registry.launch.nttdata.com/module_primitive/monitor_metric_alert/azurerm" + version = "~> 2.0.0" + + name = each.key + resource_group_name = module.resource_group.name + scopes = [module.signalr.signalr_id] + description = each.value.description + frequency = each.value.frequency + severity = each.value.severity + enabled = each.value.enabled + action_group_ids = [for key in each.value.action_groups : module.action_group[key].action_group_id] + webhook_properties = each.value.webhook_properties + + criteria = each.value.criterias + + dynamic_criteria = each.value.dynamic_criteria + depends_on = [module.resource_group, module.action_group] +} diff --git a/outputs.tf b/outputs.tf index 1090bc0..d10cfc2 100644 --- a/outputs.tf +++ b/outputs.tf @@ -25,3 +25,21 @@ output "location" { output "resource_group_name" { value = module.signalr.resource_group_name } + +output "metric_alerts" { + value = { + for key, value in module.metric_alert : key => { + id = value.metric_alert_id + name = value.name + } + } +} + +output "action_groups" { + value = { + for key, value in module.action_group : key => { + id = value.action_group_id + name = value.name + } + } +} diff --git a/tests/testimpl/test_impl.go b/tests/testimpl/test_impl.go index bb9a8b5..9d533e6 100644 --- a/tests/testimpl/test_impl.go +++ b/tests/testimpl/test_impl.go @@ -9,6 +9,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + armMetric "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/signalr/armsignalr" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/launchbynttdata/lcaf-component-terratest/types" @@ -33,16 +34,21 @@ func TestSignalRExists(t *testing.T, ctx types.TestContext) { }, } - clientFactory, err := armsignalr.NewClientFactory(subscriptionId, credential, &options) + signalrClient, err := armsignalr.NewClient(subscriptionId, credential, &options) if err != nil { t.Fatalf("failed to create SignalR client: %v", err) } + armMetricAlertsClient, err := armMetric.NewMetricAlertsClient(subscriptionId, credential, &options) + if err != nil { + t.Fatalf("Error getting Metric Alerts client: %v", err) + } + t.Run("doesSignalRExist", func(t *testing.T) { resourceGroupName := terraform.Output(t, ctx.TerratestTerraformOptions(), "resource_group_name") signalrName := terraform.Output(t, ctx.TerratestTerraformOptions(), "signalr_name") - res, err := clientFactory.NewClient().Get(context.Background(), resourceGroupName, signalrName, nil) + res, err := signalrClient.Get(context.Background(), resourceGroupName, signalrName, nil) if err != nil { t.Fatalf("failed to finish the request: %v", err) } @@ -50,4 +56,20 @@ func TestSignalRExists(t *testing.T, ctx types.TestContext) { assert.Equal(t, *res.Name, signalrName) }) + t.Run("doesMetricAlertsExist", func(t *testing.T) { + resourceGroupName := terraform.Output(t, ctx.TerratestTerraformOptions(), "resource_group_name") + metricAlertsMap := terraform.OutputMapOfObjects(t, ctx.TerratestTerraformOptions(), "metric_alerts") + var metricAlertsName string + for _, v := range metricAlertsMap { + metricAlertsName = v.(map[string]interface{})["name"].(string) + break // Access the first metric alert and break + } + + metricAlerts, err := armMetricAlertsClient.Get(context.Background(), resourceGroupName, metricAlertsName, nil) + if err != nil { + t.Fatalf("Error getting MetricAlerts: %v", err) + } + + assert.Equal(t, metricAlertsName, *metricAlerts.Name) + }) } diff --git a/variables.tf b/variables.tf index 8aced1a..014ecf0 100644 --- a/variables.tf +++ b/variables.tf @@ -246,3 +246,58 @@ variable "tags" { type = map(string) default = {} } + +variable "metric_alerts" { + type = map(object({ + description = string + action_groups = optional(set(string), []) + enabled = optional(bool, true) + severity = optional(number, 3) + frequency = optional(string, "PT1M") + webhook_properties = optional(map(string)) + + criterias = optional(list(object({ + threshold = number + metric_namespace = string + metric_name = string + aggregation = string + operator = string + dimensions = optional(list(object({ + name = string + operator = string + values = list(string) + }))) + })), []) + + dynamic_criteria = optional(object({ + alert_sensitivity = string + metric_name = string + metric_namespace = string + aggregation = string + operator = string + dimensions = optional(list(object({ + name = string + operator = string + values = list(string) + }))) + })) + })) + default = {} + + validation { + condition = alltrue( + [for alert in var.metric_alerts : !(alert.criterias == null && alert.dynamic_criteria == null)], + ) + error_message = "At least one of 'criteria', 'dynamic_criteria' must be defined for all metric alerts" + } +} + +variable "action_groups" { + type = map(object({ + action_group_name = string + short_name = string + arm_role_receivers = optional(set(string)) + email_receivers = optional(set(string)) + })) + default = {} +}