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 #1725

Merged
merged 17 commits into from
Aug 10, 2018
Merged
Show file tree
Hide file tree
Changes from 12 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
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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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) #Resolved

"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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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? #WontFix

Copy link
Author

Choose a reason for hiding this comment

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

It is only used in Email and SMS, but is required for the whole resource. Otherwise Azure complains with "Code": "GroupShortNameIsNullOrEmpty".


In reply to: 207882329 [](ancestors = 207882329)

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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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,
}
``` #Resolved


_, 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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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. #Resolved

}
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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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 #Resolved

Copy link
Author

Choose a reason for hiding this comment

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

There is not .Properties field, only the embedded ActionGroup field is available.


In reply to: 207882533 [](ancestors = 207882533)


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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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? #Resolved

}

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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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? #Resolved

}

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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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? #Resolved

}

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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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 #Resolved

}
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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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)
}
``` #Resolved

}

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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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 #Resolved

Copy link
Author

@JunyiYi JunyiYi Aug 7, 2018

Choose a reason for hiding this comment

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

Do you mean check it like this? But these are required fields, if you just leave them empty in the schema, user will always get something like sms_receiver.0.phone_number: "" => "1231231234" when they do terraform plan.


In reply to: 207883120 [](ancestors = 207883120)

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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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 #Resolved

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

@tombuildsstuff tombuildsstuff Aug 6, 2018

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? #Resolved

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