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

feat: add Azure action group module #589

Merged
merged 11 commits into from
Sep 23, 2020
51 changes: 51 additions & 0 deletions examples/azure/terraform-azure-actiongroup-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Terraform Azure Action Group Example

This folder contains a Terraform module that deploys an [Azure Action Group](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/action-groups) in [Azure](https://azure.microsoft.com/) to demonstrate how you can use Terratest to write automated tests for your Azure Terraform code.

Check out [test/azure/terraform_azure_actiongroup_example_test.go](/test/azure/terraform/azure_actiongroup_example_test.go) to see how you can write automated tests for this module and validate the configuration of the parameters and options.

**WARNING**: This module and the automated tests for it deploy real resources into your Azure account which can cost you money.

## Prerequisite: Setup Azure CLI access
1. Sign up for [Azure](https://azure.microsoft.com/).
1. Install [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest)
2. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`.
3. Login to Azure on the CLI with `az login` or `az login --use-device`, and then configure the CLI.

## Running this module manually
1. Create [Service Principal](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest) then set the value to the environment variables.
1. Run `terraform init`.
2. Run `terraform apply`.
3. Log into Azure to validate resource was created.
4. When you're done, run `terraform destroy`.

### Example

```bash
$ az login
$ export ARM_SUBSCRIPTION_ID={YOUR_SUBSCRIPTION_ID}
$ az ad sp create-for-rbac
$ export TF_VAR_client_id={YOUR_SERVICE_PRINCIPAL_APP_ID}
$ export TF_VAR_client_secret={YOUR_SERVICE_PRINCIPAL_PASSWORD}
$ terraform init
$ terraform apply
$ terraform destroy
```

## Running automated tests against this module
1. Create [Service Principal](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest) then set the value to the environment variables.
1. Install [Golang](https://golang.org/) version `1.13+` required.
1. `cd test/azure`
1. `go test -v -timeout 60m -tags azure -run TestTerraformAzureActionGroupExample`


### Example

```bash
$ az login
$ export ARM_SUBSCRIPTION_ID={YOUR_SUBSCRIPTION_ID}
$ export TF_VAR_client_id={YOUR_SERVICE_PRINCIPAL_APP_ID}
$ export TF_VAR_client_secret={YOUR_SERVICE_PRINCIPAL_PASSWORD}
$ cd test/azure
$ go test -v -timeout 60m -tags azure -run TestTerraformAzureActionGroupExample
```
60 changes: 60 additions & 0 deletions examples/azure/terraform-azure-actiongroup-example/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY AN ACTION GROUP
# This is an example of how to deploy an Azure Action Group to be used for Azure Alerts
# ---------------------------------------------------------------------------------------------------------------------

# ---------------------------------------------------------------------------------------------------------------------
# CONFIGURE OUR AZURE CONNECTION
# ---------------------------------------------------------------------------------------------------------------------

provider "azurerm" {
features {}
}

# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY A RESOURCE GROUP
# ---------------------------------------------------------------------------------------------------------------------

resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
location = var.location
}

# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY AN AZURE APP SERVICE PLAN
tsalright marked this conversation as resolved.
Show resolved Hide resolved
# ---------------------------------------------------------------------------------------------------------------------

resource "azurerm_monitor_action_group" "actionGroup" {
name = var.app_name
resource_group_name = azurerm_resource_group.rg.name
short_name = var.short_name
tags = azurerm_resource_group.rg.tags

dynamic "email_receiver" {
for_each = var.enable_email ? ["email_receiver"] : []
content {
name = var.email_name
email_address = var.email_address
use_common_alert_schema = true
}
}

dynamic "sms_receiver" {
for_each = var.enable_sms ? ["sms_receiver"] : []
content {
name = var.sms_name
country_code = var.sms_country_code
phone_number = var.sms_phone_number
}
}

dynamic "webhook_receiver" {
for_each = var.enable_webhook ? ["webhook_receiver"] : []
content {
name = var.webhook_name
service_uri = var.webhook_service_uri
use_common_alert_schema = true
}
}

}
3 changes: 3 additions & 0 deletions examples/azure/terraform-azure-actiongroup-example/output.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "action_group_id" {
value = azurerm_monitor_action_group.actionGroup.id
}
94 changes: 94 additions & 0 deletions examples/azure/terraform-azure-actiongroup-example/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# ---------------------------------------------------------------------------------------------------------------------
# ENVIRONMENT VARIABLES
# Define these secrets as environment variables
# ---------------------------------------------------------------------------------------------------------------------

# ---------------------------------------------------------------------------------------------------------------------
# REQUIRED PARAMETERS
# You must provide a value for each of these parameters.
# ---------------------------------------------------------------------------------------------------------------------

variable "resource_group_name" {
tsalright marked this conversation as resolved.
Show resolved Hide resolved
description = "Name of the resource group that exists in Azure"
type = string
}

variable "app_name" {
description = "The base name of the application used in the naming convention."
type = string
}

variable "location" {
description = "Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created."
type = string
}

variable "short_name" {
description = "Shorthand name for SMS texts."
type = string
}

# ---------------------------------------------------------------------------------------------------------------------
# OPTIONAL PARAMETERS
# These parameters have reasonable defaults.
# ---------------------------------------------------------------------------------------------------------------------

variable "enable_email" {
description = "Enable email alert capabilities"
type = bool
default = false
}

variable "email_name" {
description = "Friendly Name for email address"
type = string
default = ""
}

variable "email_address" {
description = "email address to send alerts to"
type = string
default = ""
}

variable "enable_sms" {
description = "Enable Texting Alerts"
type = bool
default = false
}

variable "sms_name" {
description = "Friendly Name for phone number"
type = string
default = ""
}

variable "sms_country_code" {
description = "Country Code for phone number"
type = number
default = 1
}

variable "sms_phone_number" {
description = "Phone number for text alerts"
type = number
default = 0
}

variable "enable_webhook" {
description = "Enable Web Hook Alerts"
type = bool
default = false
}

variable "webhook_name" {
description = "Friendly Name for web hook"
type = string
default = ""
}

variable "webhook_service_uri" {
description = "The full URI for the webhook"
type = string
default = ""
}
4 changes: 2 additions & 2 deletions examples/azure/terraform-azure-aks-example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# ------------------------------------------------------------------------------

provider "azurerm" {
version = "1.40.0"
features {}
}

# ---------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -42,7 +42,7 @@ resource "azurerm_kubernetes_cluster" "k8s" {
default_node_pool {
name = "agentpool"
node_count = var.agent_count
vm_size = "Standard_DS1_v2"
vm_size = "Standard_DS2_v2"
}

service_principal {
Expand Down
2 changes: 1 addition & 1 deletion examples/azure/terraform-azure-example/main.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
provider "azurerm" {
version = "=1.31.0"
features {}
}

# ---------------------------------------------------------------------------------------------------------------------
Expand Down
61 changes: 61 additions & 0 deletions modules/azure/actiongroup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package azure

import (
"context"
"testing"

"github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/insights"
"github.com/stretchr/testify/require"
)

// GetActionGroupResource gets the ActionGroupResource.
// ruleName - required to find the ActionGroupResource.
// resGroupName - use an empty string if you have the AZURE_RES_GROUP_NAME environment variable set
// subscriptionId - use an empty string if you have the ARM_SUBSCRIPTION_ID environment variable set
tsalright marked this conversation as resolved.
Show resolved Hide resolved
func GetActionGroupResource(t *testing.T, ruleName string, resGroupName string, subscriptionID string) *insights.ActionGroupResource {
actionGroupResource, err := GetActionGroupResourceE(ruleName, resGroupName, subscriptionID)
require.NoError(t, err)

return actionGroupResource
}

// GetActionGroupResourceE gets the ActionGroupResource with Error details on error.
// ruleName - required to find the ActionGroupResource.
// resGroupName - use an empty string if you have the AZURE_RES_GROUP_NAME environment variable set
// subscriptionId - use an empty string if you have the ARM_SUBSCRIPTION_ID environment variable set
func GetActionGroupResourceE(ruleName string, resGroupName string, subscriptionID string) (*insights.ActionGroupResource, error) {
rgName, err := getTargetAzureResourceGroupName(resGroupName)
if err != nil {
return nil, err
}

client, err := getActionGroupClient(subscriptionID)
if err != nil {
return nil, err
}

actionGroup, err := client.Get(context.Background(), rgName, ruleName)
if err != nil {
return nil, err
}

return &actionGroup, nil
}

func getActionGroupClient(subscriptionID string) (*insights.ActionGroupsClient, error) {
subID, err := getTargetAzureSubscription(subscriptionID)
if err != nil {
return nil, err
}

metricAlertsClient := insights.NewActionGroupsClient(subID)

authorizer, err := NewAuthorizer()
if err != nil {
return nil, err
}

metricAlertsClient.Authorizer = *authorizer

return &metricAlertsClient, nil
}
53 changes: 53 additions & 0 deletions modules/azure/actiongroup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// +build azure

// NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for
// CircleCI.

package azure

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

/*
The below tests are currently stubbed out, with the expectation that they will throw errors.
If/when methods to create and delete network resources are added, these tests can be extended.
*/

func TestGetActionGroupResourceEWithMissingResourceGroupName(t *testing.T) {
t.Parallel()

ruleName := "Hello"
resGroupName := ""
subscriptionID := ""

_, err := GetActionGroupResourceE(ruleName, resGroupName, subscriptionID)

require.Error(t, err)
}

func TestGetActionGroupResourceEWithInvalidResourceGroupName(t *testing.T) {
t.Parallel()

ruleName := ""
resGroupName := "Hello"
subscriptionID := ""

_, err := GetActionGroupResourceE(ruleName, resGroupName, subscriptionID)

require.Error(t, err)
}

func TestGetActionGroupClient(t *testing.T) {
t.Parallel()

subscriptionID := ""

client, err := getActionGroupClient(subscriptionID)

require.NoError(t, err)
assert.NotEmpty(t, *client)
}
Loading