Skip to content

Commit

Permalink
azurerm_site_recovery_replication_recovery_plan: support `azure_to_…
Browse files Browse the repository at this point in the history
…azure_settings` (#21666)
  • Loading branch information
ziyeqf authored May 24, 2023
1 parent d897ac6 commit 2b78ebc
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"regexp"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/edgezones"
"github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicessiterecovery/2022-10-01/replicationfabrics"
"github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicessiterecovery/2022-10-01/replicationrecoveryplans"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand All @@ -19,11 +21,12 @@ import (
)

type SiteRecoveryReplicationRecoveryPlanModel struct {
Name string `tfschema:"name"`
RecoveryGroup []RecoveryGroupModel `tfschema:"recovery_group"`
RecoveryVaultId string `tfschema:"recovery_vault_id"`
SourceRecoveryFabricId string `tfschema:"source_recovery_fabric_id"`
TargetRecoveryFabricId string `tfschema:"target_recovery_fabric_id"`
Name string `tfschema:"name"`
RecoveryGroup []RecoveryGroupModel `tfschema:"recovery_group"`
RecoveryVaultId string `tfschema:"recovery_vault_id"`
SourceRecoveryFabricId string `tfschema:"source_recovery_fabric_id"`
TargetRecoveryFabricId string `tfschema:"target_recovery_fabric_id"`
A2ASettings []ReplicationRecoveryPlanA2ASpecificInputModel `tfschema:"azure_to_azure_settings"`
}

type RecoveryGroupModel struct {
Expand All @@ -44,6 +47,13 @@ type ActionModel struct {
ScriptPath string `tfschema:"script_path"`
}

type ReplicationRecoveryPlanA2ASpecificInputModel struct {
PrimaryZone string `tfschema:"primary_zone"`
RecoveryZone string `tfschema:"recovery_zone"`
PrimaryEdgeZone string `tfschema:"primary_edge_zone"`
RecoveryEdgeZone string `tfschema:"recovery_edge_zone"`
}

type SiteRecoveryReplicationRecoveryPlanResource struct{}

var _ sdk.ResourceWithUpdate = SiteRecoveryReplicationRecoveryPlanResource{}
Expand Down Expand Up @@ -116,20 +126,22 @@ func (r SiteRecoveryReplicationRecoveryPlanResource) Arguments() map[string]*plu
"pre_action": {
Type: pluginsdk.TypeSet,
Optional: true,
Elem: schemaAction(),
Elem: replicationRecoveryPlanActionSchema(),
},
"post_action": {
Type: pluginsdk.TypeSet,
Optional: true,
Elem: schemaAction(),
Elem: replicationRecoveryPlanActionSchema(),
},
},
},
},

"azure_to_azure_settings": replicationRecoveryPlanA2ASchema(),
}
}

func schemaAction() *pluginsdk.Resource {
func replicationRecoveryPlanActionSchema() *pluginsdk.Resource {
return &pluginsdk.Resource{
Schema: map[string]*schema.Schema{
"name": {
Expand Down Expand Up @@ -199,6 +211,53 @@ func schemaAction() *pluginsdk.Resource {
}
}

func replicationRecoveryPlanA2ASchema() *pluginsdk.Schema {
return &pluginsdk.Schema{
Type: pluginsdk.TypeList,
Optional: true,
MaxItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"primary_zone": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
RequiredWith: []string{"azure_to_azure_settings.0.recovery_zone"},
},

"recovery_zone": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
RequiredWith: []string{"azure_to_azure_settings.0.primary_zone"},
},

"primary_edge_zone": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
StateFunc: edgezones.StateFunc,
DiffSuppressFunc: edgezones.DiffSuppressFunc,
RequiredWith: []string{"azure_to_azure_settings.0.recovery_edge_zone"},
},

"recovery_edge_zone": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
StateFunc: edgezones.StateFunc,
DiffSuppressFunc: edgezones.DiffSuppressFunc,
RequiredWith: []string{"azure_to_azure_settings.0.primary_edge_zone"},
},
},
},
}
}

func (r SiteRecoveryReplicationRecoveryPlanResource) Attributes() map[string]*schema.Schema {
return map[string]*schema.Schema{}
}
Expand Down Expand Up @@ -250,6 +309,10 @@ func (r SiteRecoveryReplicationRecoveryPlanResource) Create() sdk.ResourceFunc {
},
}

if model.A2ASettings != nil && len(model.A2ASettings) == 1 {
parameters.Properties.ProviderSpecificInput = expandA2ASettings(model.A2ASettings[0])
}

err = client.CreateThenPoll(ctx, id, parameters)
if err != nil {
return fmt.Errorf("creating site recovery replication plan %q: %+v", id, err)
Expand Down Expand Up @@ -300,10 +363,12 @@ func (r SiteRecoveryReplicationRecoveryPlanResource) Read() sdk.ResourceFunc {
if prop.RecoveryFabricId != nil {
state.TargetRecoveryFabricId = handleAzureSdkForGoBug2824(*prop.RecoveryFabricId)
}

if group := prop.Groups; group != nil {
state.RecoveryGroup = flattenRecoveryGroups(*group)
}
if details := prop.ProviderSpecificDetails; details != nil && len(*details) > 0 {
state.A2ASettings = flattenRecoveryPlanProviderSpecficInput(details)
}
}

return metadata.Encode(&state)
Expand Down Expand Up @@ -436,6 +501,17 @@ func expandRecoverGroup(input []RecoveryGroupModel) ([]replicationrecoveryplans.
return output, nil
}

func expandA2ASettings(input ReplicationRecoveryPlanA2ASpecificInputModel) *[]replicationrecoveryplans.RecoveryPlanProviderSpecificInput {
return &[]replicationrecoveryplans.RecoveryPlanProviderSpecificInput{
replicationrecoveryplans.RecoveryPlanA2AInput{
PrimaryZone: pointer.To(input.PrimaryZone),
RecoveryZone: pointer.To(input.RecoveryZone),
PrimaryExtendedLocation: expandEdgeZone(input.PrimaryEdgeZone),
RecoveryExtendedLocation: expandEdgeZone(input.RecoveryEdgeZone),
},
}
}

func validateRecoverGroup(input []RecoveryGroupModel) (bool, error) {
bootCount := 0
shutdownCount := 0
Expand Down Expand Up @@ -543,3 +619,19 @@ func flattenRecoveryPlanActions(input *[]replicationrecoveryplans.RecoveryPlanAc
}
return actionOutputs
}

func flattenRecoveryPlanProviderSpecficInput(input *[]replicationrecoveryplans.RecoveryPlanProviderSpecificDetails) []ReplicationRecoveryPlanA2ASpecificInputModel {
output := make([]ReplicationRecoveryPlanA2ASpecificInputModel, 0)
for _, providerSpecificInput := range *input {
if a2aInput, ok := providerSpecificInput.(replicationrecoveryplans.RecoveryPlanA2ADetails); ok {
o := ReplicationRecoveryPlanA2ASpecificInputModel{
PrimaryZone: pointer.From(a2aInput.PrimaryZone),
RecoveryZone: pointer.From(a2aInput.RecoveryZone),
PrimaryEdgeZone: flattenEdgeZone(a2aInput.PrimaryExtendedLocation),
RecoveryEdgeZone: flattenEdgeZone(a2aInput.RecoveryExtendedLocation),
}
output = append(output, o)
}
}
return output
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,36 @@ func TestAccSiteRecoveryReplicationRecoveryPlan_withPostActions(t *testing.T) {
})
}

func TestAccSiteRecoveryReplicationRecoveryPlan_withZones(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_site_recovery_replication_recovery_plan", "test")
r := SiteRecoveryReplicationRecoveryPlan{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.withZones(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccSiteRecoveryReplicationRecoveryPlan_withEdgeZones(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_site_recovery_replication_recovery_plan", "test")
r := SiteRecoveryReplicationRecoveryPlan{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.withEdgeZones(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func (SiteRecoveryReplicationRecoveryPlan) template(data acceptance.TestData) string {
tags := ""
if strings.HasPrefix(strings.ToLower(data.Client().SubscriptionID), "85b3dbca") {
Expand Down Expand Up @@ -378,6 +408,75 @@ resource "azurerm_site_recovery_replication_recovery_plan" "test" {

}

func (r SiteRecoveryReplicationRecoveryPlan) withZones(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
resource "azurerm_site_recovery_replication_recovery_plan" "test" {
name = "acctest-%[2]d"
recovery_vault_id = azurerm_recovery_services_vault.test.id
source_recovery_fabric_id = azurerm_site_recovery_fabric.test1.id
target_recovery_fabric_id = azurerm_site_recovery_fabric.test2.id
recovery_group {
type = "Boot"
replicated_protected_items = [azurerm_site_recovery_replicated_vm.test.id]
}
recovery_group {
type = "Failover"
}
recovery_group {
type = "Shutdown"
}
azure_to_azure_settings {
primary_zone = "1"
recovery_zone = "2"
}
}
`, r.template(data), data.RandomInteger)
}

func (r SiteRecoveryReplicationRecoveryPlan) withEdgeZones(data acceptance.TestData) string {
// WestUS has an edge zone available - so hard-code to that
data.Locations.Primary = "westus"

return fmt.Sprintf(`
%s
data "azurerm_extended_locations" "test" {
location = azurerm_resource_group.test.location
}
resource "azurerm_site_recovery_replication_recovery_plan" "test" {
name = "acctest-%[2]d"
recovery_vault_id = azurerm_recovery_services_vault.test.id
source_recovery_fabric_id = azurerm_site_recovery_fabric.test1.id
target_recovery_fabric_id = azurerm_site_recovery_fabric.test2.id
recovery_group {
type = "Boot"
replicated_protected_items = [azurerm_site_recovery_replicated_vm.test.id]
}
recovery_group {
type = "Failover"
}
recovery_group {
type = "Shutdown"
}
azure_to_azure_settings {
primary_edge_zone = data.azurerm_extended_locations.test.extended_locations[0]
recovery_edge_zone = data.azurerm_extended_locations.test.extended_locations[0]
}
}
`, r.template(data), data.RandomInteger)
}

func (r SiteRecoveryReplicationRecoveryPlan) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := replicationrecoveryplans.ParseReplicationRecoveryPlanID(state.ID)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ subcategory: "Recovery Services"
layout: "azurerm"
page_title: "Azure Resource Manager: azurerm_site_recovery_replication_recovery_plan"
description: |-
Manages an Azure Site Recovery Plan within a Recovery Services vault.
Manages a Site Recovery Replication Recovery Plan within a Recovery Services vault.
---

# azurerm_site_recovery_replication_recovery_plan

Manages an Azure Site Recovery Plan within a Recovery Services vault. A recovery plan gathers machines into recovery groups for the purpose of failover.
Manages a Site Recovery Replication Recovery Plan within a Recovery Services vault. A recovery plan gathers machines into recovery groups for the purpose of failover.

## Example Usage

Expand Down Expand Up @@ -248,13 +248,15 @@ The following arguments are supported:

* `target_recovery_fabric_id` - (Required) ID of target fabric to recover. Changing this forces a new Replication Plan to be created.

* `recovery_group` - (Optional) Three or more `recovery_group` block.
* `recovery_group` - (Optional) Three or more `recovery_group` block defined as below.

* `azure_to_azure_settings` - (Optional) An `azure_to_azure_settings` block defined as block.

---

A `recovery_group` block supports the following:

* `type` - (Required) The Recovery Plan Group Type. Possible values are `Boot`, `Failover` and `Shutdown`.
* `type` - (Required) The Recovery Plan Group Type. Possible values are `Boot`, `Failover` and `Shutdown`.

* `replicated_protected_items` - (Optional) (required) one or more id of protected VM.

Expand Down Expand Up @@ -290,6 +292,21 @@ An `action` block supports the following:

-> **NOTE:** This property is required when `type` is set to `ScriptActionDetails`.

---

An `azure_to_azure_settings` block supports the following:

* `primary_zone` - (Optional) The Availability Zone in which the VM is located. Changing this forces a new Site Recovery Replication Recovery Plan to be created.

* `recovery_zone` - (Optional) The Availability Zone in which the VM is recovered. Changing this forces a new Site Recovery Replication Recovery Plan to be created.

-> **Note:** `primary_zone` and `recovery_zone` must be specified together.

* `primary_edge_zone` - (Optional) The Edge Zone within the Azure Region where the VM exists. Changing this forces a new Site Recovery Replication Recovery Plan to be created.

* `recovery_edge_zone` - (Optional) The Edge Zone within the Azure Region where the VM is recovered. Changing this forces a new Site Recovery Replication Recovery Plan to be created.

-> **Note:** `primary_edge_zone` and `recovery_edge_zone` must be specified together.

## Attributes Reference

Expand Down

0 comments on commit 2b78ebc

Please sign in to comment.