Skip to content

Commit

Permalink
feat: adding Azure Recovery Services module (#640)
Browse files Browse the repository at this point in the history
* recovery services module

* update doc and comments

* chg terraform prefix for resources

* move unit test

* rm azure ref

* rm var for policy

* refactor test vars

* refactor test vars

* update mod ex

* comments

* revert

* rm space

* add comment

* update nits

* per pr

* return empty map

* map unit test to fault

* fix 4 linter

* move comments

* refactor exists

* minor edits

* switch return from empty map to nil in error condition.

Co-authored-by: wmattlong <malong@microsoft.com>
Co-authored-by: richard guthrie <richardguthrie@MININT-A10U6Q6.fareast.corp.microsoft.com>
Co-authored-by: richard guthrie <richardguthrie@MacBook-Pro.local>
  • Loading branch information
4 people committed Dec 16, 2020
1 parent f178b8d commit b13ada2
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 0 deletions.
40 changes: 40 additions & 0 deletions examples/azure/terraform-azure-recoveryservices-example/README.md
@@ -0,0 +1,40 @@
# Terraform Azure Recovery Services Example

This folder contains a simple Terraform module that deploys resources in [Azure](https://azure.microsoft.com/) to demonstrate
how you can use Terratest to write automated tests for your Azure Terraform code. This module deploys a Recovery Services Vault with one backup virtual machine policy.

- A [Recovery Services](https://azure.microsoft.com/services/backup/) that gives the module the following:
- [Backup Vault](https://docs.microsoft.com/azure/backup/backup-azure-recovery-services-vault-overview) with the value specified in the `recovery_service_vault_name` output variable.
- [Backup VM Policy](https://azure.microsoft.com/en-in/updates/azure-vm-backup-policy-management/) with the value specified in the `backup_policy_vm_name` output variable.

Check out [test/azure/terraform_azure_recoveryservices_example_test.go](/test/azure/terraform_azure_recoveryservices_example_test.go) to see how you can write
automated tests for this module.

Note that the Recovery Services Vault and backup virtual machine policy in this module don't actually do anything; it just runs the resources for
demonstration purposes.

**WARNING**: This module and the automated tests for it deploy real resources into your Azure account which can cost you
money. The resources are all part of the [Azure Free Account](https://azure.microsoft.com/free/), so if you haven't used that up,
it should be free, but you are completely responsible for all Azure charges.

## Running this module manually

1. Sign up for [Azure](https://azure.microsoft.com/)
1. Configure your Azure credentials using one of the [supported methods for Azure CLI
tools](https://docs.microsoft.com/cli/azure/azure-cli-configuration?view=azure-cli-latest)
1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`
1. Ensure [environment variables](../README.md#review-environment-variables) are available
1. Run `terraform init`
1. Run `terraform apply`
1. When you're done, run `terraform destroy`

## Running automated tests against this module

1. Sign up for [Azure](https://azure.microsoft.com/)
1. Configure your Azure credentials using one of the [supported methods for Azure CLI
tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest)
1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`
1. Configure your Terratest [Go test environment](../README.md)
1. `cd test/azure`
1. `go build terraform_azure_recoveryservices_example_test.go`
1. `go test -v -run TestTerraformAzureRecoveryServicesExample`
78 changes: 78 additions & 0 deletions examples/azure/terraform-azure-recoveryservices-example/main.tf
@@ -0,0 +1,78 @@
# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY AN AZURE AVAILABILITY SET
# This is an example of how to deploy an Azure Availability Set with a Virtual Machine in the availability set
# and the minimum network resources for the VM.
# ---------------------------------------------------------------------------------------------------------------------
# See test/azure/terraform_azure_availabilityset_example_test.go for how to write automated tests for this code.
# ---------------------------------------------------------------------------------------------------------------------

provider "azurerm" {
version = "~> 2.20"
features {}
}

terraform {
# This module is now only being tested with Terraform 0.13.x. However, to make upgrading easier, we are setting
# 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it
# forwards compatible with 0.13.x code.
required_version = ">= 0.12.26"
}

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

resource "azurerm_resource_group" "resource_group" {
name = "terratest-ars-rg-${var.postfix}"
location = var.location
}

# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY A RECOVERY SERVICES VAULT
# ---------------------------------------------------------------------------------------------------------------------

resource "azurerm_recovery_services_vault" "vault" {
name = "rsvault${var.postfix}"
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
sku = "Standard"
}

# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY A BACKUP POLICY
# ---------------------------------------------------------------------------------------------------------------------

resource "azurerm_backup_policy_vm" "vm_policy" {
name = "vmpolicy-${var.postfix}"
resource_group_name = azurerm_resource_group.resource_group.name
recovery_vault_name = azurerm_recovery_services_vault.vault.name

timezone = "UTC"

backup {
frequency = "Daily"
time = "23:00"
}

retention_daily {
count = 10
}

retention_weekly {
count = 42
weekdays = ["Sunday", "Wednesday", "Friday", "Saturday"]
}

retention_monthly {
count = 7
weekdays = ["Sunday", "Wednesday"]
weeks = ["First", "Last"]
}

retention_yearly {
count = 77
weekdays = ["Sunday"]
weeks = ["Last"]
months = ["January"]
}
}
11 changes: 11 additions & 0 deletions examples/azure/terraform-azure-recoveryservices-example/outputs.tf
@@ -0,0 +1,11 @@
output "resource_group_name" {
value = azurerm_resource_group.resource_group.name
}

output "recovery_service_vault_name" {
value = azurerm_recovery_services_vault.vault.name
}

output "backup_policy_vm_name" {
value = azurerm_backup_policy_vm.vm_policy.name
}
@@ -0,0 +1,33 @@
# ---------------------------------------------------------------------------------------------------------------------
# ENVIRONMENT VARIABLES
# Define these secrets as environment variables
# ---------------------------------------------------------------------------------------------------------------------

# ARM_CLIENT_ID
# ARM_CLIENT_SECRET
# ARM_SUBSCRIPTION_ID
# ARM_TENANT_ID

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

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

variable "location" {
description = "The location to set for the storage account."
type = string
default = "East US"
}

variable "postfix" {
description = "A postfix string to centrally mitigate resource name collisions"
type = string
default = "resource"
}


151 changes: 151 additions & 0 deletions modules/azure/recoveryservices.go
@@ -0,0 +1,151 @@
package azure

import (
"context"
"fmt"
"testing"

"github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2016-06-01/recoveryservices"
"github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2020-02-02/backup"
"github.com/stretchr/testify/require"
)

// RecoveryServicesVaultExists indicates whether a recovery services vault exists; otherwise false.
// This function would fail the test if there is an error.
func RecoveryServicesVaultExists(t *testing.T, vaultName, resourceGroupName, subscriptionID string) bool {
exists, err := RecoveryServicesVaultExistsE(vaultName, resourceGroupName, subscriptionID)
require.NoError(t, err)
return exists
}

// GetRecoveryServicesVaultBackupPolicyList returns a list of backup policies for the given vault.
// This function would fail the test if there is an error.
func GetRecoveryServicesVaultBackupPolicyList(t *testing.T, vaultName, resourceGroupName, subscriptionID string) map[string]backup.ProtectionPolicyResource {
list, err := GetRecoveryServicesVaultBackupPolicyListE(vaultName, resourceGroupName, subscriptionID)
require.NoError(t, err)
return list
}

// GetRecoveryServicesVaultBackupProtectedVMList returns a list of protected VM's on the given vault/policy.
// This function would fail the test if there is an error.
func GetRecoveryServicesVaultBackupProtectedVMList(t *testing.T, policyName, vaultName, resourceGroupName, subscriptionID string) map[string]backup.AzureIaaSComputeVMProtectedItem {
list, err := GetRecoveryServicesVaultBackupProtectedVMListE(policyName, vaultName, resourceGroupName, subscriptionID)
require.NoError(t, err)
return list
}

// RecoveryServicesVaultExists indicates whether a recovery services vault exists; otherwise false or error.
func RecoveryServicesVaultExistsE(vaultName, resourceGroupName, subscriptionID string) (bool, error) {
_, err := GetRecoveryServicesVaultE(vaultName, resourceGroupName, subscriptionID)
if err != nil {
if ResourceNotFoundErrorExists(err) {
return false, nil
}
return false, err
}
return true, nil
}

// GetRecoveryServicesVaultE returns a vault instance.
func GetRecoveryServicesVaultE(vaultName, resourceGroupName, subscriptionID string) (*recoveryservices.Vault, error) {
subscriptionID, err := getTargetAzureSubscription(subscriptionID)
if err != nil {
return nil, err
}

resourceGroupName, err2 := getTargetAzureResourceGroupName((resourceGroupName))
if err2 != nil {
return nil, err2
}

client := recoveryservices.NewVaultsClient(subscriptionID)
// setup auth and create request params
authorizer, err := NewAuthorizer()
if err != nil {
return nil, err
}

client.Authorizer = *authorizer
vault, err := client.Get(context.Background(), resourceGroupName, vaultName)
if err != nil {
return nil, err
}
return &vault, nil
}

// GetRecoveryServicesVaultBackupPolicyListE returns a list of backup policies for the given vault.
func GetRecoveryServicesVaultBackupPolicyListE(vaultName, resourceGroupName, subscriptionID string) (map[string]backup.ProtectionPolicyResource, error) {
subscriptionID, err := getTargetAzureSubscription(subscriptionID)
if err != nil {
return nil, err
}

resourceGroupName, err2 := getTargetAzureResourceGroupName(resourceGroupName)
if err2 != nil {
return nil, err2
}

client := backup.NewPoliciesClient(subscriptionID)
// setup authorizer
authorizer, err := NewAuthorizer()
if err != nil {
return nil, err
}

client.Authorizer = *authorizer
listIter, err := client.ListComplete(context.Background(), vaultName, resourceGroupName, "")
if err != nil {
return nil, err
}

policyMap := make(map[string]backup.ProtectionPolicyResource)
for listIter.NotDone() {
v := listIter.Value()
policyMap[*v.Name] = v
err := listIter.NextWithContext(context.Background())
if err != nil {
return nil, err
}

}
return policyMap, nil
}

// GetRecoveryServicesVaultBackupProtectedVMListE returns a list of protected VM's on the given vault/policy.
func GetRecoveryServicesVaultBackupProtectedVMListE(policyName, vaultName, resourceGroupName, subscriptionID string) (map[string]backup.AzureIaaSComputeVMProtectedItem, error) {
subscriptionID, err := getTargetAzureSubscription(subscriptionID)
if err != nil {
return nil, err
}

resourceGroupName, err2 := getTargetAzureResourceGroupName(resourceGroupName)
if err != nil {
return nil, err2
}

client := backup.NewProtectedItemsGroupClient(subscriptionID)
// setup authorizer
authorizer, err := NewAuthorizer()
if err != nil {
return nil, err
}
client.Authorizer = *authorizer
// Build a filter string to narrow down results to just VM's
filter := fmt.Sprintf("backupManagementType eq 'AzureIaasVM' and itemType eq 'VM' and policyName eq '%s'", policyName)
listIter, err := client.ListComplete(context.Background(), vaultName, resourceGroupName, filter, "")
if err != nil {
return nil, err
}
// Prep the return container
vmList := make(map[string]backup.AzureIaaSComputeVMProtectedItem)
// First iterator check
for listIter.NotDone() {
currentVM, _ := listIter.Value().Properties.AsAzureIaaSComputeVMProtectedItem()
vmList[*currentVM.FriendlyName] = *currentVM
err := listIter.NextWithContext(context.Background())
if err != nil {
return nil, err
}
}
return vmList, nil
}
32 changes: 32 additions & 0 deletions modules/azure/recoveryservices_test.go
@@ -0,0 +1,32 @@
package azure

import (
"testing"

"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 recovery services resources are added, these tests can be extended.
*/

func TestRecoveryServicesVaultName(t *testing.T) {
_, err := GetRecoveryServicesVaultE("", "", "")
require.Error(t, err, "vault")
}

func TestRecoveryServicesVaultExists(t *testing.T) {
_, err := RecoveryServicesVaultExistsE("", "", "")
require.Error(t, err, "vault exists")
}

func TestRecoveryServicesVaultBackupPolicyList(t *testing.T) {
_, err := GetRecoveryServicesVaultBackupPolicyListE("", "", "")
require.Error(t, err, "Backup policy list not faulted")
}

func TestRecoveryServicesVaultBackupProtectedVMList(t *testing.T) {
_, err := GetRecoveryServicesVaultBackupProtectedVMListE("", "", "", "")
require.Error(t, err, "Backup policy protected vm list not faulted")
}

0 comments on commit b13ada2

Please sign in to comment.