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

New Resource: azurerm_monitor_action_group #1714

Closed
wants to merge 12 commits into from
5 changes: 5 additions & 0 deletions azurerm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ type ArmClient struct {
logicWorkflowsClient logic.WorkflowsClient

// Monitor
actionGroupsClient insights.ActionGroupsClient
monitorAlertRulesClient insights.AlertRulesClient

// MSI
Expand Down Expand Up @@ -772,6 +773,10 @@ func (c *ArmClient) registerLogicClients(endpoint, subscriptionId string, auth a
}

func (c *ArmClient) registerMonitorClients(endpoint, subscriptionId string, auth autorest.Authorizer, sender autorest.Sender) {
actionGroupsClient := insights.NewActionGroupsClientWithBaseURI(endpoint, subscriptionId)
c.configureClient(&actionGroupsClient.Client, auth)
c.actionGroupsClient = actionGroupsClient

arc := insights.NewAlertRulesClientWithBaseURI(endpoint, subscriptionId)
setUserAgent(&arc.Client)
arc.Authorizer = auth
Expand Down
1 change: 1 addition & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"azurerm_action_group": resourceArmActionGroup(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given this a resource within Azure Monitor this should be azurerm_monitor_action_group (we should probably update the naming for the azurerm_metric_alertrule and azurerm_autoscale_setting resources too, but we should fix this for new resources going forward)

"azurerm_azuread_application": resourceArmActiveDirectoryApplication(),
"azurerm_azuread_service_principal": resourceArmActiveDirectoryServicePrincipal(),
"azurerm_azuread_service_principal_password": resourceArmActiveDirectoryServicePrincipalPassword(),
Expand Down
313 changes: 313 additions & 0 deletions azurerm/resource_arm_action_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
package azurerm

import (
"fmt"

"github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmActionGroup() *schema.Resource {
return &schema.Resource{
Create: resourceArmActionGroupCreateOrUpdate,
Read: resourceArmActionGroupRead,
Update: resourceArmActionGroupCreateOrUpdate,
Delete: resourceArmActionGroupDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.NoZeroValues,
},

"location": locationSchema(),

"resource_group_name": resourceGroupNameSchema(),

"short_name": {
Type: schema.TypeString,
Required: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this only required for the SMS Receiver? if so - should this be optional?

ValidateFunc: validation.NoZeroValues,
},

"enabled": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},

"email_receiver": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.NoZeroValues,
},
"email_address": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.NoZeroValues,
},
},
},
},

"sms_receiver": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.NoZeroValues,
},
"country_code": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.NoZeroValues,
},
"phone_number": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.NoZeroValues,
},
},
},
},

"webhook_receiver": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.NoZeroValues,
},
"service_uri": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.NoZeroValues,
},
},
},
},

"tags": tagsSchema(),
},
}
}

func resourceArmActionGroupCreateOrUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).actionGroupsClient
ctx := meta.(*ArmClient).StopContext

name := d.Get("name").(string)
location := azureRMNormalizeLocation(d.Get("location").(string))
resGroup := d.Get("resource_group_name").(string)

shortName := d.Get("short_name").(string)
enabled := d.Get("enabled").(bool)

tags := d.Get("tags").(map[string]interface{})
expandedTags := expandTags(tags)

parameters := insights.ActionGroupResource{
Location: &location,
ActionGroup: &insights.ActionGroup{
GroupShortName: &shortName,
Enabled: &enabled,
},
Tags: expandedTags,
}

if v, ok := d.GetOk("email_receiver"); ok {
parameters.ActionGroup.EmailReceivers = expandActionGroupEmailReceiver(v.([]interface{}))
}

if v, ok := d.GetOk("sms_receiver"); ok {
parameters.ActionGroup.SmsReceivers = expandActionGroupSmsReceiver(v.([]interface{}))
}

if v, ok := d.GetOk("webhook_receiver"); ok {
parameters.ActionGroup.WebhookReceivers = expandActionGroupWebHookReceiver(v.([]interface{}))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can assign all of these directly:

emailReceiversRaw := d.Get("email_receiver").([]interface{})
emailReceivers = expandActionGroupEmailReceiver(emailReceiversRaw)

smsReceiversRaw := d.Get("sms_receiver").([]interface{})
smsReceivers = expandActionGroupSmsReceiver(smsReceiversRaw)

webhookReceiversRaw := d.Get("webhook_receiver").([]interface{})
webhookReceivers = expandActionGroupWebHookReceiver(webhookReceiversRaw)

parameters := insights.ActionGroupResource{
  Location: utils.String(location),
  ActionGroup: &insights.ActionGroup{
    GroupShortName: utils.String(shortName),
    Enabled:        utils.Bool(enabled),
    EmailReceivers: emailReceivers,
    SmsReceivers: smsReceivers,
    WebhookReceivers: webhookReceivers,
  },
  Tags: expandedTags,
}


_, err := client.CreateOrUpdate(ctx, resGroup, name, parameters)
if err != nil {
return fmt.Errorf("Error creating or updating action group %s (resource group %s): %+v", name, resGroup, err)
}

read, err := client.Get(ctx, resGroup, name)
if err != nil {
return fmt.Errorf("Error getting action group %s (resource group %s) after creation: %+v", name, resGroup, err)
}
if read.ID == nil {
return fmt.Errorf("Action group %s (resource group %s) ID is empty", name, resGroup)
}

d.SetId(*read.ID)

return resourceArmActionGroupRead(d, meta)
}

func resourceArmActionGroupRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).actionGroupsClient
ctx := meta.(*ArmClient).StopContext

id, err := parseAzureResourceID(d.Id())
if err != nil {
return fmt.Errorf("Error parsing action group resource ID \"%s\" during get: %+v", d.Id(), err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is an unrecoverable state for users, this there's no point in wrapping it and we can return err directly here.

}
resGroup := id.ResourceGroup
name := id.Path["actionGroups"]

resp, err := client.Get(ctx, resGroup, name)
if err != nil {
if response.WasNotFound(resp.Response.Response) {
d.SetId("")
return nil
}
return fmt.Errorf("Error getting action group %s (resource group %s): %+v", name, resGroup, err)
}

d.Set("name", name)
d.Set("resource_group_name", resGroup)
if location := resp.Location; location != nil {
d.Set("location", azureRMNormalizeLocation(*location))
}

d.Set("short_name", *resp.GroupShortName)
d.Set("enabled", *resp.Enabled)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove the pointer deference here and we should be accessing these on the .Properties block to match the Azure API Response


if err = d.Set("email_receiver", flattenActionGroupEmailReceiver(resp.EmailReceivers)); err != nil {
return fmt.Errorf("Error setting `email_receiver` of action group %s (resource group %s): %+v", name, resGroup, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we update %s ->%q to match the other resources?

}

if err = d.Set("sms_receiver", flattenActionGroupSmsReceiver(resp.SmsReceivers)); err != nil {
return fmt.Errorf("Error setting `sms_receiver` of action group %s (resource group %s): %+v", name, resGroup, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we update %s ->%q to match the other resources?

}

if err = d.Set("webhook_receiver", flattenActionGroupWebHookReceiver(resp.WebhookReceivers)); err != nil {
return fmt.Errorf("Error setting `webhook_receiver` of action group %s (resource group %s): %+v", name, resGroup, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we update %s ->%q to match the other resources?

}

flattenAndSetTags(d, resp.Tags)

return nil
}

func resourceArmActionGroupDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).actionGroupsClient
ctx := meta.(*ArmClient).StopContext

id, err := parseAzureResourceID(d.Id())
if err != nil {
return fmt.Errorf("Error parsing action group resource ID \"%s\" during delete: %+v", d.Id(), err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(as above) we can just return err here directly

}
resGroup := id.ResourceGroup
name := id.Path["actionGroups"]

resp, err := client.Delete(ctx, resGroup, name)
if err != nil {
if response.WasNotFound(resp.Response) {
return nil
}
return fmt.Errorf("Error deleting action group %s (resource group %s): %+v", name, resGroup, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can invert this if:

if response.WasNotFound(resp.Response) {
  return fmt.Errorf("Error deleting action group %q (resource group %q): %+v", name, resGroup, err)
}

}

return nil
}

func expandActionGroupEmailReceiver(v []interface{}) *[]insights.EmailReceiver {
receivers := make([]insights.EmailReceiver, 0)
for _, receiverValue := range v {
val := receiverValue.(map[string]interface{})
receiver := insights.EmailReceiver{
Name: utils.String(val["name"].(string)),
EmailAddress: utils.String(val["email_address"].(string)),
}
receivers = append(receivers, receiver)
}
return &receivers
}

func expandActionGroupSmsReceiver(v []interface{}) *[]insights.SmsReceiver {
receivers := make([]insights.SmsReceiver, 0)
for _, receiverValue := range v {
val := receiverValue.(map[string]interface{})
receiver := insights.SmsReceiver{
Name: utils.String(val["name"].(string)),
CountryCode: utils.String(val["country_code"].(string)),
PhoneNumber: utils.String(val["phone_number"].(string)),
}
receivers = append(receivers, receiver)
}
return &receivers
}

func expandActionGroupWebHookReceiver(v []interface{}) *[]insights.WebhookReceiver {
receivers := make([]insights.WebhookReceiver, 0)
for _, receiverValue := range v {
val := receiverValue.(map[string]interface{})
receiver := insights.WebhookReceiver{
Name: utils.String(val["name"].(string)),
ServiceURI: utils.String(val["service_uri"].(string)),
}
receivers = append(receivers, receiver)
}
return &receivers
}

func flattenActionGroupEmailReceiver(receivers *[]insights.EmailReceiver) []interface{} {
result := make([]interface{}, 0)
if receivers != nil {
for _, receiver := range *receivers {
val := make(map[string]interface{}, 0)
val["name"] = *receiver.Name
val["email_address"] = *receiver.EmailAddress
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should nil-check around both of these properties as there's potential crashes here

result = append(result, val)
}
}
return result
}

func flattenActionGroupSmsReceiver(receivers *[]insights.SmsReceiver) []interface{} {
result := make([]interface{}, 0)
if receivers != nil {
for _, receiver := range *receivers {
val := make(map[string]interface{}, 0)
val["name"] = *receiver.Name
val["country_code"] = *receiver.CountryCode
val["phone_number"] = *receiver.PhoneNumber
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should nil-check around all three of these properties as there's potential crashes here

result = append(result, val)
}
}
return result
}

func flattenActionGroupWebHookReceiver(receivers *[]insights.WebhookReceiver) []interface{} {
result := make([]interface{}, 0)
if receivers != nil {
for _, receiver := range *receivers {
val := make(map[string]interface{}, 0)
val["name"] = *receiver.Name
val["service_uri"] = *receiver.ServiceURI
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a couple of potential crashes here - can we nil check both of these?

result = append(result, val)
}
}
return result
}
Loading