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

azurerm_site_recovery_replication_recovery_plan: support azure_to_azure_settings #21666

Merged
merged 4 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
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,17 @@ func (r SiteRecoveryReplicationRecoveryPlanResource) Create() sdk.ResourceFunc {
},
}

if model.A2ASettings != nil && len(model.A2ASettings) == 1 {
parameters.Properties.ProviderSpecificInput = pointer.To([]replicationrecoveryplans.RecoveryPlanProviderSpecificInput{
replicationrecoveryplans.RecoveryPlanA2AInput{
PrimaryZone: &model.A2ASettings[0].PrimaryZone,
RecoveryZone: &model.A2ASettings[0].RecoveryZone,
PrimaryExtendedLocation: expandEdgeZone(model.A2ASettings[0].PrimaryEdgeZone),
RecoveryExtendedLocation: expandEdgeZone(model.A2ASettings[0].RecoveryEdgeZone),
},
})
}

ziyeqf marked this conversation as resolved.
Show resolved Hide resolved
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 +370,22 @@ 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 {
detail := pointer.From(details)[0]
if a2a, ok := detail.(replicationrecoveryplans.RecoveryPlanA2ADetails); ok {
state.A2ASettings = []ReplicationRecoveryPlanA2ASpecificInputModel{
{
PrimaryZone: pointer.From(a2a.PrimaryZone),
RecoveryZone: pointer.From(a2a.RecoveryZone),
PrimaryEdgeZone: flattenEdgeZone(a2a.PrimaryExtendedLocation),
RecoveryEdgeZone: flattenEdgeZone(a2a.RecoveryExtendedLocation),
},
}
}
}
ziyeqf marked this conversation as resolved.
Show resolved Hide resolved
}

return metadata.Encode(&state)
Expand Down
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 an Site Recovery Replication Recovery Plan within a Recovery Services vault.
ziyeqf marked this conversation as resolved.
Show resolved Hide resolved
---

# 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 an Site Recovery Replication Recovery Plan within a Recovery Services vault. A recovery plan gathers machines into recovery groups for the purpose of failover.
ziyeqf marked this conversation as resolved.
Show resolved Hide resolved

## Example Usage

Expand Down Expand Up @@ -78,13 +78,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 @@ -120,6 +122,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