Skip to content

INTMDB-382: Improve the importing of default alert configurations for a project #993

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

Merged
merged 9 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/atlas-alert-configurations/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.sh
alert-configurations.tf
32 changes: 32 additions & 0 deletions examples/atlas-alert-configurations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## Using the data source
Example exists in `alert-configurations-data.tf`. To use this example exactly:
- Copy directory to local disk
- Add a `terraform.tfvars`
- Add your `project_id`
- Run `terraform apply`

### Create alert resources and import them into state file
```
terraform output -raw alert_imports > import-alerts.sh
terraform output -raw alert_resources > alert-configurations.tf
chmod +x ./import-alerts.sh
./import-alerts.sh
terraform apply
```

## Contingency Plans
If unhappy with the resource file or imports, here are some things that can be done:

### Remove targeted resources from the appropriate files and remove the alet_configuration from state
- Manually remove the resource (ex: `mongodbatlas_alert_configuration.CLUSTER_MONGOS_IS_MISSING_2`) from the `tf` file, and then remove it from state, ex:
```
terraform state rm mongodbatlas_alert_configuration.CLUSTER_MONGOS_IS_MISSING_2
```

### Remove all alert_configurations from state
- Delete the `tf` file that was used for import, and then:
```
terraform state list | grep ^mongodbatlas_alert_configuration. | awk '{print "terraform state rm " $1}' > state-rm-alerts.sh
chmod +x state-rm-alerts.sh
./state-rm-alerts.sh
```
29 changes: 29 additions & 0 deletions examples/atlas-alert-configurations/alert-configurations-data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
data "mongodbatlas_alert_configurations" "import" {
project_id = var.project_id

output_type = ["resource_hcl", "resource_import"]
}

locals {
alerts = data.mongodbatlas_alert_configurations.import.results

alert_resources = compact([
for i, alert in local.alerts :
alert.output == null ? null :
length(alert.output) < 1 == null ? null : alert.output[0].value
])

alert_imports = compact([
for i, alert in local.alerts :
alert.output == null ? null :
length(alert.output) < 2 == null ? null : alert.output[1].value
])
}

output "alert_resources" {
value = join("\n", local.alert_resources)
}

output "alert_imports" {
value = join("", local.alert_imports)
}
4 changes: 4 additions & 0 deletions examples/atlas-alert-configurations/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
provider "mongodbatlas" {
public_key = var.public_key
private_key = var.private_key
}
10 changes: 10 additions & 0 deletions examples/atlas-alert-configurations/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
variable "public_key" {
description = "Public API key to authenticate to Atlas"
}
variable "private_key" {
description = "Private API key to authenticate to Atlas"
}
variable "project_id" {
description = "Atlas project name"
default = ""
}
8 changes: 8 additions & 0 deletions examples/atlas-alert-configurations/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
terraform {
required_providers {
mongodbatlas = {
source = "mongodb/mongodbatlas"
}
}
required_version = ">= 0.13"
}
214 changes: 214 additions & 0 deletions mongodbatlas/data_source_mongodbatlas_alert_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import (
"context"
"fmt"

"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/zclconf/go-cty/cty"
matlas "go.mongodb.org/atlas/mongodbatlas"
)

func dataSourceMongoDBAtlasAlertConfiguration() *schema.Resource {
Expand Down Expand Up @@ -245,6 +249,27 @@ func dataSourceMongoDBAtlasAlertConfiguration() *schema.Resource {
},
},
},
"output": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"resource_hcl", "resource_import"}, false),
},
"label": {
Type: schema.TypeString,
Optional: true,
},
"value": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
},
}
}
Expand Down Expand Up @@ -296,10 +321,199 @@ func dataSourceMongoDBAtlasAlertConfigurationRead(ctx context.Context, d *schema
return diag.FromErr(fmt.Errorf(errorAlertConfSetting, "notification", projectID, err))
}

if dOutput := d.Get("output"); dOutput != nil {
if err := d.Set("output", computeAlertConfigurationOutput(alert, dOutput.([]interface{}), alert.EventTypeName)); err != nil {
return diag.FromErr(fmt.Errorf(errorAlertConfSetting, "output", projectID, err))
}
}

d.SetId(encodeStateID(map[string]string{
"id": alert.ID,
"project_id": projectID,
}))

return nil
}

func computeAlertConfigurationOutput(alert *matlas.AlertConfiguration, outputConfigurations []interface{}, defaultLabel string) []map[string]interface{} {
output := make([]map[string]interface{}, 0)

for i := 0; i < len(outputConfigurations); i++ {
config := outputConfigurations[i].(map[string]interface{})
var o = map[string]interface{}{
"type": config["type"],
}

if label, ok := o["label"]; ok {
o["label"] = label
} else {
o["label"] = defaultLabel
}

if outputValue := outputAlertConfiguration(alert, o["type"].(string), o["label"].(string)); outputValue != "" {
o["value"] = outputValue
}

output = append(output, o)
}

return output
}

func outputAlertConfiguration(alert *matlas.AlertConfiguration, outputType, resourceLabel string) string {
if outputType == "resource_hcl" {
return outputAlertConfigurationResourceHcl(resourceLabel, alert)
}
if outputType == "resource_import" {
return outputAlertConfigurationResourceImport(resourceLabel, alert)
}

return ""
}

func outputAlertConfigurationResourceHcl(label string, alert *matlas.AlertConfiguration) string {
f := hclwrite.NewEmptyFile()
root := f.Body()
resource := root.AppendNewBlock("resource", []string{"mongodbatlas_alert_configuration", label}).Body()

resource.SetAttributeValue("project_id", cty.StringVal(alert.GroupID))
resource.SetAttributeValue("event_type", cty.StringVal(alert.EventTypeName))

if alert.Enabled != nil {
resource.SetAttributeValue("enabled", cty.BoolVal(*alert.Enabled))
}

for _, matcher := range alert.Matchers {
values := convertMatcherToCtyValues(matcher)

appendBlockWithCtyValues(resource, "matcher", []string{}, values)
}

if alert.MetricThreshold != nil {
values := convertMetricThresholdToCtyValues(*alert.MetricThreshold)

appendBlockWithCtyValues(resource, "metric_threshold_config", []string{}, values)
}

if alert.Threshold != nil {
values := convertThresholdToCtyValues(*alert.Threshold)

appendBlockWithCtyValues(resource, "threshold_config", []string{}, values)
}

for i := 0; i < len(alert.Notifications); i++ {
values := convertNotificationToCtyValues(&alert.Notifications[i])

appendBlockWithCtyValues(resource, "notification", []string{}, values)
}

return string(f.Bytes())
}

func outputAlertConfigurationResourceImport(label string, alert *matlas.AlertConfiguration) string {
return fmt.Sprintf("terraform import mongodbatlas_alert_configuration.%s %s-%s\n", label, alert.GroupID, alert.ID)
}

func convertMatcherToCtyValues(matcher matlas.Matcher) map[string]cty.Value {
return map[string]cty.Value{
"field_name": cty.StringVal(matcher.FieldName),
"operator": cty.StringVal(matcher.Operator),
"value": cty.StringVal(matcher.Value),
}
}

func convertMetricThresholdToCtyValues(metric matlas.MetricThreshold) map[string]cty.Value {
return map[string]cty.Value{
"metric_name": cty.StringVal(metric.MetricName),
"operator": cty.StringVal(metric.Operator),
"threshold": cty.NumberFloatVal(metric.Threshold),
"units": cty.StringVal(metric.Units),
"mode": cty.StringVal(metric.Mode),
}
}

func convertThresholdToCtyValues(threshold matlas.Threshold) map[string]cty.Value {
return map[string]cty.Value{
"operator": cty.StringVal(threshold.Operator),
"units": cty.StringVal(threshold.Units),
"threshold": cty.NumberFloatVal(threshold.Threshold),
}
}

func convertNotificationToCtyValues(notification *matlas.Notification) map[string]cty.Value {
values := map[string]cty.Value{}

if notification.ChannelName != "" {
values["channel_name"] = cty.StringVal(notification.ChannelName)
}

if notification.DatadogRegion != "" {
values["datadog_region"] = cty.StringVal(notification.DatadogRegion)
}

if notification.EmailAddress != "" {
values["email_address"] = cty.StringVal(notification.EmailAddress)
}

if notification.FlowName != "" {
values["flow_name"] = cty.StringVal(notification.FlowName)
}

if notification.IntervalMin > 0 {
values["interval_min"] = cty.NumberIntVal(int64(notification.IntervalMin))
}

if notification.MobileNumber != "" {
values["mobile_number"] = cty.StringVal(notification.MobileNumber)
}

if notification.OpsGenieRegion != "" {
values["ops_genie_region"] = cty.StringVal(notification.OpsGenieRegion)
}

if notification.OrgName != "" {
values["org_name"] = cty.StringVal(notification.OrgName)
}

if notification.TeamID != "" {
values["team_id"] = cty.StringVal(notification.TeamID)
}

if notification.TeamName != "" {
values["team_name"] = cty.StringVal(notification.TeamName)
}

if notification.TypeName != "" {
values["type_name"] = cty.StringVal(notification.TypeName)
}

if notification.Username != "" {
values["username"] = cty.StringVal(notification.Username)
}

if notification.DelayMin != nil && *notification.DelayMin > 0 {
values["delay_min"] = cty.NumberIntVal(int64(*notification.DelayMin))
}

if notification.EmailEnabled != nil && *notification.EmailEnabled {
values["email_enabled"] = cty.BoolVal(*notification.EmailEnabled)
}

if notification.SMSEnabled != nil && *notification.SMSEnabled {
values["sms_enabled"] = cty.BoolVal(*notification.SMSEnabled)
}

if len(notification.Roles) > 0 {
roles := make([]cty.Value, 0)

for _, r := range notification.Roles {
if r != "" {
roles = append(roles, cty.StringVal(r))
}
}

values["roles"] = cty.TupleVal(roles)
}

return values
}
Loading