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

Introduce Action Group resource of Azure Monitor #1419

Merged
merged 12 commits into from
Aug 1, 2018
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(),
"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,
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,
Copy link
Contributor

Choose a reason for hiding this comment

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

this is a URI - so we can validate that it's either an Azure Resource URI (using azure.ValidateResourceID) or a URI in itself

Copy link
Author

Choose a reason for hiding this comment

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

We won't because Azure won't. We can pass anything to Azure. And if in the future Azure does, API call will return the error message.

},
},
},
},

"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,
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 use utils.Bool() and utils.String() here rather than de-referencing these directly

Copy link
Author

Choose a reason for hiding this comment

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

According to @katbyte (@jeffreyCline could confirm that) , the new style is to use addressof instead of utilities.

},
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.

given these are lists we can just parse these out above and set these values all the time - since otherwise it's not possible to remove items from the lists e.g.

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.

we don't need to wrap this error message

Copy link
Author

Choose a reason for hiding this comment

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

That's my personal style. I don't think it really matters.

}
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 don't need to de-pointer these; also we should be accessing these in the .Properties block

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 a shortcut in go instead of accessing .Properties.


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)
}

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)
}

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)
}

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.

we don't need to wrap this error message - so can we just return it directly?

Copy link
Author

Choose a reason for hiding this comment

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

Align with all other error messages in this code base would be a good style.

Copy link
Contributor

Choose a reason for hiding this comment

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

we consistently across the codebase don't wrap these error messages since they're an internal error users aren't going to be able to work around (vs a temporary issue in the Azure API in CRUD/Polling where they may be able to self-diagnose) - as such ID parsing error don't need wrapping.

}
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)
}

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.

there's two potential crashes here - can we nil-check these?

Copy link
Author

Choose a reason for hiding this comment

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

Azure won't allow nil values for these fields.

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.

there's three potential crashes here - can we nil-check these?

Copy link
Author

Choose a reason for hiding this comment

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

Azure won't allow nil values for these fields.

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 two potential crashes here - these should be nil-checked

Copy link
Author

Choose a reason for hiding this comment

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

Azure won't allow nil values for these fields.

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