Skip to content

Commit fb75a63

Browse files
authored
INTMDB-382: Improve the importing of default alert configurations for a project (#993)
* Added alert_configurations data_source * Added documentation on how to use data_source_alert_configuration outputs, as well as the new data source for all alert configurations * Removed duplicate function declaration * Resolving lint errors around looping * Added example for importing atlas-alert-configurations en masse * Got tests passing, added documentation for alert_configuration data_source * Fixed alert_configurations test * Updated readme to include instructions for creating the data source * Fixed spacing and added a link to the full example on the data source docs
1 parent da130dc commit fb75a63

13 files changed

+668
-3
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.sh
2+
alert-configurations.tf
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
## Using the data source
2+
Example exists in `alert-configurations-data.tf`. To use this example exactly:
3+
- Copy directory to local disk
4+
- Add a `terraform.tfvars`
5+
- Add your `project_id`
6+
- Run `terraform apply`
7+
8+
### Create alert resources and import them into state file
9+
```
10+
terraform output -raw alert_imports > import-alerts.sh
11+
terraform output -raw alert_resources > alert-configurations.tf
12+
chmod +x ./import-alerts.sh
13+
./import-alerts.sh
14+
terraform apply
15+
```
16+
17+
## Contingency Plans
18+
If unhappy with the resource file or imports, here are some things that can be done:
19+
20+
### Remove targeted resources from the appropriate files and remove the alet_configuration from state
21+
- Manually remove the resource (ex: `mongodbatlas_alert_configuration.CLUSTER_MONGOS_IS_MISSING_2`) from the `tf` file, and then remove it from state, ex:
22+
```
23+
terraform state rm mongodbatlas_alert_configuration.CLUSTER_MONGOS_IS_MISSING_2
24+
```
25+
26+
### Remove all alert_configurations from state
27+
- Delete the `tf` file that was used for import, and then:
28+
```
29+
terraform state list | grep ^mongodbatlas_alert_configuration. | awk '{print "terraform state rm " $1}' > state-rm-alerts.sh
30+
chmod +x state-rm-alerts.sh
31+
./state-rm-alerts.sh
32+
```
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
data "mongodbatlas_alert_configurations" "import" {
2+
project_id = var.project_id
3+
4+
output_type = ["resource_hcl", "resource_import"]
5+
}
6+
7+
locals {
8+
alerts = data.mongodbatlas_alert_configurations.import.results
9+
10+
alert_resources = compact([
11+
for i, alert in local.alerts :
12+
alert.output == null ? null :
13+
length(alert.output) < 1 == null ? null : alert.output[0].value
14+
])
15+
16+
alert_imports = compact([
17+
for i, alert in local.alerts :
18+
alert.output == null ? null :
19+
length(alert.output) < 2 == null ? null : alert.output[1].value
20+
])
21+
}
22+
23+
output "alert_resources" {
24+
value = join("\n", local.alert_resources)
25+
}
26+
27+
output "alert_imports" {
28+
value = join("", local.alert_imports)
29+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
provider "mongodbatlas" {
2+
public_key = var.public_key
3+
private_key = var.private_key
4+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
variable "public_key" {
2+
description = "Public API key to authenticate to Atlas"
3+
}
4+
variable "private_key" {
5+
description = "Private API key to authenticate to Atlas"
6+
}
7+
variable "project_id" {
8+
description = "Atlas project name"
9+
default = ""
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
terraform {
2+
required_providers {
3+
mongodbatlas = {
4+
source = "mongodb/mongodbatlas"
5+
}
6+
}
7+
required_version = ">= 0.13"
8+
}

mongodbatlas/data_source_mongodbatlas_alert_configuration.go

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ import (
44
"context"
55
"fmt"
66

7+
"github.com/hashicorp/hcl/v2/hclwrite"
78
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
89
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
11+
"github.com/zclconf/go-cty/cty"
12+
matlas "go.mongodb.org/atlas/mongodbatlas"
913
)
1014

1115
func dataSourceMongoDBAtlasAlertConfiguration() *schema.Resource {
@@ -245,6 +249,27 @@ func dataSourceMongoDBAtlasAlertConfiguration() *schema.Resource {
245249
},
246250
},
247251
},
252+
"output": {
253+
Type: schema.TypeList,
254+
Optional: true,
255+
Elem: &schema.Resource{
256+
Schema: map[string]*schema.Schema{
257+
"type": {
258+
Type: schema.TypeString,
259+
Required: true,
260+
ValidateFunc: validation.StringInSlice([]string{"resource_hcl", "resource_import"}, false),
261+
},
262+
"label": {
263+
Type: schema.TypeString,
264+
Optional: true,
265+
},
266+
"value": {
267+
Type: schema.TypeString,
268+
Computed: true,
269+
},
270+
},
271+
},
272+
},
248273
},
249274
}
250275
}
@@ -296,10 +321,199 @@ func dataSourceMongoDBAtlasAlertConfigurationRead(ctx context.Context, d *schema
296321
return diag.FromErr(fmt.Errorf(errorAlertConfSetting, "notification", projectID, err))
297322
}
298323

324+
if dOutput := d.Get("output"); dOutput != nil {
325+
if err := d.Set("output", computeAlertConfigurationOutput(alert, dOutput.([]interface{}), alert.EventTypeName)); err != nil {
326+
return diag.FromErr(fmt.Errorf(errorAlertConfSetting, "output", projectID, err))
327+
}
328+
}
329+
299330
d.SetId(encodeStateID(map[string]string{
300331
"id": alert.ID,
301332
"project_id": projectID,
302333
}))
303334

304335
return nil
305336
}
337+
338+
func computeAlertConfigurationOutput(alert *matlas.AlertConfiguration, outputConfigurations []interface{}, defaultLabel string) []map[string]interface{} {
339+
output := make([]map[string]interface{}, 0)
340+
341+
for i := 0; i < len(outputConfigurations); i++ {
342+
config := outputConfigurations[i].(map[string]interface{})
343+
var o = map[string]interface{}{
344+
"type": config["type"],
345+
}
346+
347+
if label, ok := o["label"]; ok {
348+
o["label"] = label
349+
} else {
350+
o["label"] = defaultLabel
351+
}
352+
353+
if outputValue := outputAlertConfiguration(alert, o["type"].(string), o["label"].(string)); outputValue != "" {
354+
o["value"] = outputValue
355+
}
356+
357+
output = append(output, o)
358+
}
359+
360+
return output
361+
}
362+
363+
func outputAlertConfiguration(alert *matlas.AlertConfiguration, outputType, resourceLabel string) string {
364+
if outputType == "resource_hcl" {
365+
return outputAlertConfigurationResourceHcl(resourceLabel, alert)
366+
}
367+
if outputType == "resource_import" {
368+
return outputAlertConfigurationResourceImport(resourceLabel, alert)
369+
}
370+
371+
return ""
372+
}
373+
374+
func outputAlertConfigurationResourceHcl(label string, alert *matlas.AlertConfiguration) string {
375+
f := hclwrite.NewEmptyFile()
376+
root := f.Body()
377+
resource := root.AppendNewBlock("resource", []string{"mongodbatlas_alert_configuration", label}).Body()
378+
379+
resource.SetAttributeValue("project_id", cty.StringVal(alert.GroupID))
380+
resource.SetAttributeValue("event_type", cty.StringVal(alert.EventTypeName))
381+
382+
if alert.Enabled != nil {
383+
resource.SetAttributeValue("enabled", cty.BoolVal(*alert.Enabled))
384+
}
385+
386+
for _, matcher := range alert.Matchers {
387+
values := convertMatcherToCtyValues(matcher)
388+
389+
appendBlockWithCtyValues(resource, "matcher", []string{}, values)
390+
}
391+
392+
if alert.MetricThreshold != nil {
393+
values := convertMetricThresholdToCtyValues(*alert.MetricThreshold)
394+
395+
appendBlockWithCtyValues(resource, "metric_threshold_config", []string{}, values)
396+
}
397+
398+
if alert.Threshold != nil {
399+
values := convertThresholdToCtyValues(*alert.Threshold)
400+
401+
appendBlockWithCtyValues(resource, "threshold_config", []string{}, values)
402+
}
403+
404+
for i := 0; i < len(alert.Notifications); i++ {
405+
values := convertNotificationToCtyValues(&alert.Notifications[i])
406+
407+
appendBlockWithCtyValues(resource, "notification", []string{}, values)
408+
}
409+
410+
return string(f.Bytes())
411+
}
412+
413+
func outputAlertConfigurationResourceImport(label string, alert *matlas.AlertConfiguration) string {
414+
return fmt.Sprintf("terraform import mongodbatlas_alert_configuration.%s %s-%s\n", label, alert.GroupID, alert.ID)
415+
}
416+
417+
func convertMatcherToCtyValues(matcher matlas.Matcher) map[string]cty.Value {
418+
return map[string]cty.Value{
419+
"field_name": cty.StringVal(matcher.FieldName),
420+
"operator": cty.StringVal(matcher.Operator),
421+
"value": cty.StringVal(matcher.Value),
422+
}
423+
}
424+
425+
func convertMetricThresholdToCtyValues(metric matlas.MetricThreshold) map[string]cty.Value {
426+
return map[string]cty.Value{
427+
"metric_name": cty.StringVal(metric.MetricName),
428+
"operator": cty.StringVal(metric.Operator),
429+
"threshold": cty.NumberFloatVal(metric.Threshold),
430+
"units": cty.StringVal(metric.Units),
431+
"mode": cty.StringVal(metric.Mode),
432+
}
433+
}
434+
435+
func convertThresholdToCtyValues(threshold matlas.Threshold) map[string]cty.Value {
436+
return map[string]cty.Value{
437+
"operator": cty.StringVal(threshold.Operator),
438+
"units": cty.StringVal(threshold.Units),
439+
"threshold": cty.NumberFloatVal(threshold.Threshold),
440+
}
441+
}
442+
443+
func convertNotificationToCtyValues(notification *matlas.Notification) map[string]cty.Value {
444+
values := map[string]cty.Value{}
445+
446+
if notification.ChannelName != "" {
447+
values["channel_name"] = cty.StringVal(notification.ChannelName)
448+
}
449+
450+
if notification.DatadogRegion != "" {
451+
values["datadog_region"] = cty.StringVal(notification.DatadogRegion)
452+
}
453+
454+
if notification.EmailAddress != "" {
455+
values["email_address"] = cty.StringVal(notification.EmailAddress)
456+
}
457+
458+
if notification.FlowName != "" {
459+
values["flow_name"] = cty.StringVal(notification.FlowName)
460+
}
461+
462+
if notification.IntervalMin > 0 {
463+
values["interval_min"] = cty.NumberIntVal(int64(notification.IntervalMin))
464+
}
465+
466+
if notification.MobileNumber != "" {
467+
values["mobile_number"] = cty.StringVal(notification.MobileNumber)
468+
}
469+
470+
if notification.OpsGenieRegion != "" {
471+
values["ops_genie_region"] = cty.StringVal(notification.OpsGenieRegion)
472+
}
473+
474+
if notification.OrgName != "" {
475+
values["org_name"] = cty.StringVal(notification.OrgName)
476+
}
477+
478+
if notification.TeamID != "" {
479+
values["team_id"] = cty.StringVal(notification.TeamID)
480+
}
481+
482+
if notification.TeamName != "" {
483+
values["team_name"] = cty.StringVal(notification.TeamName)
484+
}
485+
486+
if notification.TypeName != "" {
487+
values["type_name"] = cty.StringVal(notification.TypeName)
488+
}
489+
490+
if notification.Username != "" {
491+
values["username"] = cty.StringVal(notification.Username)
492+
}
493+
494+
if notification.DelayMin != nil && *notification.DelayMin > 0 {
495+
values["delay_min"] = cty.NumberIntVal(int64(*notification.DelayMin))
496+
}
497+
498+
if notification.EmailEnabled != nil && *notification.EmailEnabled {
499+
values["email_enabled"] = cty.BoolVal(*notification.EmailEnabled)
500+
}
501+
502+
if notification.SMSEnabled != nil && *notification.SMSEnabled {
503+
values["sms_enabled"] = cty.BoolVal(*notification.SMSEnabled)
504+
}
505+
506+
if len(notification.Roles) > 0 {
507+
roles := make([]cty.Value, 0)
508+
509+
for _, r := range notification.Roles {
510+
if r != "" {
511+
roles = append(roles, cty.StringVal(r))
512+
}
513+
}
514+
515+
values["roles"] = cty.TupleVal(roles)
516+
}
517+
518+
return values
519+
}

0 commit comments

Comments
 (0)