diff --git a/internal/services/recoveryservices/registration.go b/internal/services/recoveryservices/registration.go index d8ec8352fe30..32c86fee4a75 100644 --- a/internal/services/recoveryservices/registration.go +++ b/internal/services/recoveryservices/registration.go @@ -17,12 +17,15 @@ func (r Registration) AssociatedGitHubLabel() string { } func (r Registration) DataSources() []sdk.DataSource { - return []sdk.DataSource{} + return []sdk.DataSource{ + SiteRecoveryReplicationRecoveryPlanDataSource{}, + } } func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ BackupProtectionPolicyVMWorkloadResource{}, + SiteRecoveryReplicationRecoveryPlanResource{}, } } diff --git a/internal/services/recoveryservices/site_recovery_replication_recovery_plan_data_source.go b/internal/services/recoveryservices/site_recovery_replication_recovery_plan_data_source.go new file mode 100644 index 000000000000..eccb9b202629 --- /dev/null +++ b/internal/services/recoveryservices/site_recovery_replication_recovery_plan_data_source.go @@ -0,0 +1,200 @@ +package recoveryservices + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicessiterecovery/2022-10-01/replicationrecoveryplans" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type SiteRecoveryReplicationRecoveryPlanDataSource struct{} + +var _ sdk.DataSource = SiteRecoveryReplicationRecoveryPlanDataSource{} + +func (r SiteRecoveryReplicationRecoveryPlanDataSource) ResourceType() string { + return "azurerm_site_recovery_replication_recovery_plan" +} + +func (r SiteRecoveryReplicationRecoveryPlanDataSource) ModelObject() interface{} { + return &SiteRecoveryReplicationRecoveryPlanModel{} +} + +func (r SiteRecoveryReplicationRecoveryPlanDataSource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return replicationrecoveryplans.ValidateReplicationRecoveryPlanID +} + +func (r SiteRecoveryReplicationRecoveryPlanDataSource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var metaModel SiteRecoveryReplicationRecoveryPlanModel + if err := metadata.Decode(&metaModel); err != nil { + return fmt.Errorf("decoding %+v", err) + } + + client := metadata.Client.RecoveryServices.ReplicationRecoveryPlansClient + subscriptionId := metadata.Client.Account.SubscriptionId + + vaultId, err := replicationrecoveryplans.ParseVaultID(metaModel.RecoveryVaultId) + if err != nil { + return err + } + + id := replicationrecoveryplans.NewReplicationRecoveryPlanID(subscriptionId, vaultId.ResourceGroupName, vaultId.ResourceName, metaModel.Name) + + resp, err := client.Get(ctx, id) + if err != nil { + if response.WasNotFound(resp.HttpResponse) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("making Read request on site recovery replication plan %s : %+v", id.String(), err) + } + + model := resp.Model + if model == nil { + return fmt.Errorf("making Read request on site recovery replication plan %s : model is nil", id.String()) + } + + state := SiteRecoveryReplicationRecoveryPlanModel{ + Name: id.RecoveryPlanName, + RecoveryVaultId: vaultId.ID(), + } + + if prop := model.Properties; prop != nil { + if prop.PrimaryFabricId != nil { + state.SourceRecoveryFabricId = handleAzureSdkForGoBug2824(*prop.PrimaryFabricId) + } + if prop.RecoveryFabricId != nil { + state.TargetRecoveryFabricId = handleAzureSdkForGoBug2824(*prop.RecoveryFabricId) + } + + if group := prop.Groups; group != nil { + state.RecoveryGroup = flattenRecoveryGroups(*group) + } + } + + metadata.SetID(id) + return metadata.Encode(&state) + }, + } + +} + +func (r SiteRecoveryReplicationRecoveryPlanDataSource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "recovery_vault_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: replicationrecoveryplans.ValidateVaultID, + }, + } +} + +func (r SiteRecoveryReplicationRecoveryPlanDataSource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "source_recovery_fabric_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "target_recovery_fabric_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "failover_deployment_model": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "recovery_group": { + Type: pluginsdk.TypeSet, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "replicated_protected_items": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + "pre_action": { + Type: pluginsdk.TypeSet, + Computed: true, + Elem: dataSourceSiteRecoveryReplicationPlanActions(), + }, + "post_action": { + Type: pluginsdk.TypeSet, + Computed: true, + Elem: dataSourceSiteRecoveryReplicationPlanActions(), + }, + }, + }, + }, + } +} + +func dataSourceSiteRecoveryReplicationPlanActions() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Elem: &pluginsdk.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "fail_over_directions": { + Type: pluginsdk.TypeSet, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + "fail_over_types": { + Type: pluginsdk.TypeSet, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + "runbook_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "fabric_location": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "manual_action_instruction": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "script_path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + } +} diff --git a/internal/services/recoveryservices/site_recovery_replication_recovery_plan_data_source_test.go b/internal/services/recoveryservices/site_recovery_replication_recovery_plan_data_source_test.go new file mode 100644 index 000000000000..64766870918a --- /dev/null +++ b/internal/services/recoveryservices/site_recovery_replication_recovery_plan_data_source_test.go @@ -0,0 +1,42 @@ +package recoveryservices_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" +) + +type SiteRecoveryReplicationRecoveryPlanDataSource struct{} + +func TestAccDataSourceSiteRecoveryReplicationRecoveryPlan_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_site_recovery_replication_recovery_plan", "test") + r := SiteRecoveryReplicationRecoveryPlanDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("name").Exists(), + check.That(data.ResourceName).Key("recovery_vault_id").Exists(), + check.That(data.ResourceName).Key("source_recovery_fabric_id").Exists(), + check.That(data.ResourceName).Key("target_recovery_fabric_id").Exists(), + check.That(data.ResourceName).Key("recovery_group.0.type").HasValue("Boot"), + check.That(data.ResourceName).Key("recovery_group.1.type").HasValue("Failover"), + check.That(data.ResourceName).Key("recovery_group.2.type").HasValue("Shutdown"), + ), + }, + }) +} + +func (SiteRecoveryReplicationRecoveryPlanDataSource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +data "azurerm_site_recovery_replication_recovery_plan" "test" { + name = azurerm_site_recovery_replication_recovery_plan.test.name + recovery_vault_id = azurerm_site_recovery_replication_recovery_plan.test.recovery_vault_id +} +`, SiteRecoveryReplicationRecoveryPlan{}.basic(data)) +} diff --git a/internal/services/recoveryservices/site_recovery_replication_recovery_plan_resource.go b/internal/services/recoveryservices/site_recovery_replication_recovery_plan_resource.go new file mode 100644 index 000000000000..5f1a9576981a --- /dev/null +++ b/internal/services/recoveryservices/site_recovery_replication_recovery_plan_resource.go @@ -0,0 +1,545 @@ +package recoveryservices + +import ( + "context" + "fmt" + "regexp" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/response" + "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" + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +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"` +} + +type RecoveryGroupModel struct { + GroupType replicationrecoveryplans.RecoveryPlanGroupType `tfschema:"type"` + PostAction []ActionModel `tfschema:"post_action"` + PreAction []ActionModel `tfschema:"pre_action"` + ReplicatedProtectedItems []string `tfschema:"replicated_protected_items"` +} + +type ActionModel struct { + ActionDetailType string `tfschema:"type"` + FabricLocation replicationrecoveryplans.RecoveryPlanActionLocation `tfschema:"fabric_location"` + FailOverDirections []string `tfschema:"fail_over_directions"` + FailOverTypes []string `tfschema:"fail_over_types"` + ManualActionInstruction string `tfschema:"manual_action_instruction"` + Name string `tfschema:"name"` + RunbookId string `tfschema:"runbook_id"` + ScriptPath string `tfschema:"script_path"` +} + +type SiteRecoveryReplicationRecoveryPlanResource struct{} + +var _ sdk.ResourceWithUpdate = SiteRecoveryReplicationRecoveryPlanResource{} + +func (r SiteRecoveryReplicationRecoveryPlanResource) ResourceType() string { + return "azurerm_site_recovery_replication_recovery_plan" +} + +func (r SiteRecoveryReplicationRecoveryPlanResource) ModelObject() interface{} { + return &SiteRecoveryReplicationRecoveryPlanModel{} +} + +func (r SiteRecoveryReplicationRecoveryPlanResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return replicationrecoveryplans.ValidateReplicationRecoveryPlanID +} + +func (r SiteRecoveryReplicationRecoveryPlanResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-zA-Z][a-zA-Z0-9-]{1,63}[a-zA-Z0-9]$`), "The name can contain only letters, numbers, and hyphens. It should start with a letter and end with a letter or a number."), + }, + + "recovery_vault_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: replicationfabrics.ValidateVaultID, + }, + + "source_recovery_fabric_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: replicationfabrics.ValidateReplicationFabricID, + }, + + "target_recovery_fabric_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: replicationfabrics.ValidateReplicationFabricID, + }, + + "recovery_group": { + Type: pluginsdk.TypeSet, + Optional: true, + MinItems: 3, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(replicationrecoveryplans.RecoveryPlanGroupTypeBoot), + string(replicationrecoveryplans.RecoveryPlanGroupTypeShutdown), + string(replicationrecoveryplans.RecoveryPlanGroupTypeFailover), + }, false), + }, + "replicated_protected_items": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: azure.ValidateResourceID, + }, + }, + "pre_action": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: schemaAction(), + }, + "post_action": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: schemaAction(), + }, + }, + }, + }, + } +} + +func schemaAction() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "AutomationRunbookActionDetails", + "ManualActionDetails", + "ScriptActionDetails", + }, false), + }, + + "fail_over_directions": { + Type: pluginsdk.TypeSet, + Required: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(replicationrecoveryplans.PossibleOperationsDirectionsPrimaryToRecovery), + string(replicationrecoveryplans.PossibleOperationsDirectionsRecoveryToPrimary), + }, false), + }, + }, + + "fail_over_types": { + Type: pluginsdk.TypeSet, + Required: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(replicationrecoveryplans.ReplicationProtectedItemOperationPlannedFailover), + string(replicationrecoveryplans.ReplicationProtectedItemOperationTestFailover), + string(replicationrecoveryplans.ReplicationProtectedItemOperationUnplannedFailover), + }, false), + }, + }, + "runbook_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: azure.ValidateResourceID, + }, + "fabric_location": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(replicationrecoveryplans.RecoveryPlanActionLocationPrimary), + string(replicationrecoveryplans.RecoveryPlanActionLocationRecovery), + }, false), + }, + "manual_action_instruction": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "script_path": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + } +} + +func (r SiteRecoveryReplicationRecoveryPlanResource) Attributes() map[string]*schema.Schema { + return map[string]*schema.Schema{} +} + +func (r SiteRecoveryReplicationRecoveryPlanResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var model SiteRecoveryReplicationRecoveryPlanModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding %+v", err) + } + + client := metadata.Client.RecoveryServices.ReplicationRecoveryPlansClient + subscriptionId := metadata.Client.Account.SubscriptionId + + vaultId, err := replicationrecoveryplans.ParseVaultID(model.RecoveryVaultId) + if err != nil { + return err + } + + id := replicationrecoveryplans.NewReplicationRecoveryPlanID(subscriptionId, vaultId.ResourceGroupName, vaultId.ResourceName, model.Name) + + existing, err := client.Get(ctx, id) + if err != nil { + // NOTE: Bad Request due to https://github.com/Azure/azure-rest-api-specs/issues/12759 + if !response.WasNotFound(existing.HttpResponse) && !response.WasBadRequest(existing.HttpResponse) { + return fmt.Errorf("checking for presence of existing site recovery plan %q: %+v", id, err) + } + } + + if existing.Model != nil && existing.Model.Id != nil && *existing.Model.Id != "" { + return tf.ImportAsExistsError("azurerm_site_recovery_replication_recovery_plan", *existing.Model.Id) + } + + // FailoverDeploymentModelClassic is used for other cloud service back up to Azure. + deploymentModel := replicationrecoveryplans.FailoverDeploymentModelResourceManager + groupValue, err := expandRecoverGroup(model.RecoveryGroup) + if err != nil { + return fmt.Errorf("when expanding recovery group: %s", err) + } + + parameters := replicationrecoveryplans.CreateRecoveryPlanInput{ + Properties: replicationrecoveryplans.CreateRecoveryPlanInputProperties{ + PrimaryFabricId: model.SourceRecoveryFabricId, + RecoveryFabricId: model.TargetRecoveryFabricId, + FailoverDeploymentModel: &deploymentModel, + Groups: groupValue, + }, + } + + err = client.CreateThenPoll(ctx, id, parameters) + if err != nil { + return fmt.Errorf("creating site recovery replication plan %q: %+v", id, err) + } + + metadata.SetID(id) + + return nil + }, + } +} + +func (r SiteRecoveryReplicationRecoveryPlanResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.RecoveryServices.ReplicationRecoveryPlansClient + + id, err := replicationrecoveryplans.ParseReplicationRecoveryPlanID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + vaultId := replicationrecoveryplans.NewVaultID(id.SubscriptionId, id.ResourceGroupName, id.ResourceName) + + resp, err := client.Get(ctx, *id) + if err != nil { + if response.WasNotFound(resp.HttpResponse) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("making Read request on site recovery replication plan %s : %+v", id.String(), err) + } + + model := resp.Model + if model == nil { + return fmt.Errorf("making Read request on site recovery replication plan %s : model is nil", id.String()) + } + + state := SiteRecoveryReplicationRecoveryPlanModel{ + Name: id.RecoveryPlanName, + RecoveryVaultId: vaultId.ID(), + } + + if prop := model.Properties; prop != nil { + if prop.PrimaryFabricId != nil { + state.SourceRecoveryFabricId = handleAzureSdkForGoBug2824(*prop.PrimaryFabricId) + } + if prop.RecoveryFabricId != nil { + state.TargetRecoveryFabricId = handleAzureSdkForGoBug2824(*prop.RecoveryFabricId) + } + + if group := prop.Groups; group != nil { + state.RecoveryGroup = flattenRecoveryGroups(*group) + } + } + + return metadata.Encode(&state) + }, + } +} + +func (r SiteRecoveryReplicationRecoveryPlanResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var model SiteRecoveryReplicationRecoveryPlanModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + client := metadata.Client.RecoveryServices.ReplicationRecoveryPlansClient + + id, err := replicationrecoveryplans.ParseReplicationRecoveryPlanID(metadata.ResourceData.Id()) + if err != nil { + return fmt.Errorf("parse Site reocvery replication plan id: %+v", err) + } + + recoveryPlanGroup, err := expandRecoverGroup(model.RecoveryGroup) + if err != nil { + return fmt.Errorf("when expanding recovery group: %s", err) + } + + parameters := replicationrecoveryplans.UpdateRecoveryPlanInput{ + Properties: &replicationrecoveryplans.UpdateRecoveryPlanInputProperties{ + Groups: &recoveryPlanGroup, + }, + } + + err = client.UpdateThenPoll(ctx, *id, parameters) + if err != nil { + return fmt.Errorf("updating site recovery replication plan %s (vault %s): %+v", id.RecoveryPlanName, id.ResourceName, err) + } + + return nil + }, + } +} + +func (r SiteRecoveryReplicationRecoveryPlanResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.RecoveryServices.ReplicationRecoveryPlansClient + + id, err := replicationrecoveryplans.ParseReplicationRecoveryPlanID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + err = client.DeleteThenPoll(ctx, *id) + if err != nil { + return fmt.Errorf("deleting site recovery protection replication plan %q : %+v", id, err) + } + + return nil + }, + } +} + +func expandRecoverGroup(input []RecoveryGroupModel) ([]replicationrecoveryplans.RecoveryPlanGroup, error) { + output := make([]replicationrecoveryplans.RecoveryPlanGroup, 0) + if pass, err := validateRecoverGroup(input); !pass { + return output, err + } + + for _, group := range input { + + protectedItems := make([]replicationrecoveryplans.RecoveryPlanProtectedItem, 0) + for _, protectedItem := range group.ReplicatedProtectedItems { + protectedItems = append(protectedItems, replicationrecoveryplans.RecoveryPlanProtectedItem{ + Id: utils.String(protectedItem), + }) + } + + preActions := make([]replicationrecoveryplans.RecoveryPlanAction, 0) + for _, preActionInput := range group.PreAction { + + failoverDirections := make([]replicationrecoveryplans.PossibleOperationsDirections, 0) + for _, direction := range preActionInput.FailOverDirections { + failoverDirections = append(failoverDirections, replicationrecoveryplans.PossibleOperationsDirections(direction)) + } + + failoverTypes := make([]replicationrecoveryplans.ReplicationProtectedItemOperation, 0) + for _, failoveType := range preActionInput.FailOverTypes { + failoverTypes = append(failoverTypes, replicationrecoveryplans.ReplicationProtectedItemOperation(failoveType)) + } + + preActions = append(preActions, replicationrecoveryplans.RecoveryPlanAction{ + ActionName: preActionInput.Name, + FailoverDirections: failoverDirections, + FailoverTypes: failoverTypes, + CustomDetails: expandActionDetail(preActionInput), + }) + } + + postActions := make([]replicationrecoveryplans.RecoveryPlanAction, 0) + for _, postActionInput := range group.PostAction { + + failoverDirections := make([]replicationrecoveryplans.PossibleOperationsDirections, 0) + for _, direction := range postActionInput.FailOverDirections { + failoverDirections = append(failoverDirections, replicationrecoveryplans.PossibleOperationsDirections(direction)) + } + + failoverTypes := make([]replicationrecoveryplans.ReplicationProtectedItemOperation, 0) + for _, failoveType := range postActionInput.FailOverTypes { + failoverTypes = append(failoverTypes, replicationrecoveryplans.ReplicationProtectedItemOperation(failoveType)) + } + + postActions = append(postActions, replicationrecoveryplans.RecoveryPlanAction{ + ActionName: postActionInput.Name, + FailoverDirections: failoverDirections, + FailoverTypes: failoverTypes, + CustomDetails: expandActionDetail(postActionInput), + }) + } + + output = append(output, replicationrecoveryplans.RecoveryPlanGroup{ + GroupType: group.GroupType, + ReplicationProtectedItems: &protectedItems, + StartGroupActions: &preActions, + EndGroupActions: &postActions, + }) + + } + return output, nil +} + +func validateRecoverGroup(input []RecoveryGroupModel) (bool, error) { + bootCount := 0 + shutdownCount := 0 + failoverCount := 0 + for _, group := range input { + if group.GroupType == replicationrecoveryplans.RecoveryPlanGroupTypeBoot { + bootCount += 1 + } + if group.GroupType == replicationrecoveryplans.RecoveryPlanGroupTypeFailover { + failoverCount += 1 + } + if group.GroupType == replicationrecoveryplans.RecoveryPlanGroupTypeShutdown { + shutdownCount += 1 + } + } + if bootCount == 0 || shutdownCount == 0 || failoverCount == 0 { + return false, fmt.Errorf("every group type needs at least one recovery group") + } + return true, nil +} + +func flattenRecoveryGroups(input []replicationrecoveryplans.RecoveryPlanGroup) []RecoveryGroupModel { + output := make([]RecoveryGroupModel, 0) + for _, groupItem := range input { + recoveryGroupOutput := RecoveryGroupModel{} + recoveryGroupOutput.GroupType = groupItem.GroupType + if groupItem.ReplicationProtectedItems != nil { + recoveryGroupOutput.ReplicatedProtectedItems = flattenRecoveryPlanProtectedItems(groupItem.ReplicationProtectedItems) + } + if groupItem.StartGroupActions != nil { + recoveryGroupOutput.PreAction = flattenRecoveryPlanActions(groupItem.StartGroupActions) + } + if groupItem.EndGroupActions != nil { + recoveryGroupOutput.PostAction = flattenRecoveryPlanActions(groupItem.EndGroupActions) + } + output = append(output, recoveryGroupOutput) + } + return output +} + +func expandActionDetail(input ActionModel) (output replicationrecoveryplans.RecoveryPlanActionDetails) { + switch input.ActionDetailType { + case "AutomationRunbookActionDetails": + output = replicationrecoveryplans.RecoveryPlanAutomationRunbookActionDetails{ + RunbookId: utils.String(input.RunbookId), + FabricLocation: input.FabricLocation, + } + case "ManualActionDetails": + output = replicationrecoveryplans.RecoveryPlanManualActionDetails{ + Description: utils.String(input.ManualActionInstruction), + } + case "ScriptActionDetails": + output = replicationrecoveryplans.RecoveryPlanScriptActionDetails{ + Path: input.ScriptPath, + FabricLocation: input.FabricLocation, + } + } + return +} + +func flattenRecoveryPlanProtectedItems(input *[]replicationrecoveryplans.RecoveryPlanProtectedItem) []string { + protectedItemOutputs := make([]string, 0) + for _, protectedItem := range *input { + protectedItemOutputs = append(protectedItemOutputs, handleAzureSdkForGoBug2824(*protectedItem.Id)) + } + return protectedItemOutputs +} + +func flattenRecoveryPlanActions(input *[]replicationrecoveryplans.RecoveryPlanAction) []ActionModel { + actionOutputs := make([]ActionModel, 0) + for _, action := range *input { + actionOutput := ActionModel{ + Name: action.ActionName, + } + switch detail := action.CustomDetails.(type) { + case replicationrecoveryplans.RecoveryPlanAutomationRunbookActionDetails: + actionOutput.ActionDetailType = "AutomationRunbookActionDetails" + actionOutput.FabricLocation = detail.FabricLocation + if detail.RunbookId != nil { + actionOutput.RunbookId = *detail.RunbookId + } + case replicationrecoveryplans.RecoveryPlanManualActionDetails: + actionOutput.ActionDetailType = "ManualActionDetails" + if detail.Description != nil { + actionOutput.ManualActionInstruction = *detail.Description + } + case replicationrecoveryplans.RecoveryPlanScriptActionDetails: + actionOutput.ActionDetailType = "ScriptActionDetails" + actionOutput.ScriptPath = detail.Path + actionOutput.FabricLocation = detail.FabricLocation + } + + failoverDirections := make([]string, 0) + for _, failoverDirection := range action.FailoverDirections { + failoverDirections = append(failoverDirections, string(failoverDirection)) + } + + failoverTypes := make([]string, 0) + for _, failoverType := range action.FailoverTypes { + failoverTypes = append(failoverTypes, string(failoverType)) + } + actionOutput.FailOverDirections = failoverDirections + actionOutput.FailOverTypes = failoverTypes + actionOutputs = append(actionOutputs, actionOutput) + } + return actionOutputs +} diff --git a/internal/services/recoveryservices/site_recovery_replication_recovery_plan_resource_test.go b/internal/services/recoveryservices/site_recovery_replication_recovery_plan_resource_test.go new file mode 100644 index 000000000000..0f8e34300a65 --- /dev/null +++ b/internal/services/recoveryservices/site_recovery_replication_recovery_plan_resource_test.go @@ -0,0 +1,398 @@ +package recoveryservices_test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicessiterecovery/2022-10-01/replicationrecoveryplans" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type SiteRecoveryReplicationRecoveryPlan struct{} + +func TestAccSiteRecoveryReplicationRecoveryPlan_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_site_recovery_replication_recovery_plan", "test") + r := SiteRecoveryReplicationRecoveryPlan{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSiteRecoveryReplicationRecoveryPlan_withPreActions(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_site_recovery_replication_recovery_plan", "test") + r := SiteRecoveryReplicationRecoveryPlan{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withPreActions(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSiteRecoveryReplicationRecoveryPlan_withPostActions(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_site_recovery_replication_recovery_plan", "test") + r := SiteRecoveryReplicationRecoveryPlan{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withPostActions(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") { + tags = ` + tags = { + "azsecpack" = "nonprod" + "platformsettings.host_environment.service.platform_optedin_for_rootcerts" = "true" + } +` + } + return fmt.Sprintf(` +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-recovery-%[1]d-1" + location = "%[3]s" +} + +resource "azurerm_resource_group" "test2" { + name = "acctestRG-recovery-%[1]d-2" + location = "%[4]s" +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-vault-%[1]d" + location = azurerm_resource_group.test2.location + resource_group_name = azurerm_resource_group.test2.name + sku = "Standard" + + soft_delete_enabled = false +} + +resource "azurerm_site_recovery_fabric" "test1" { + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + name = "acctest-fabric1-%[1]d" + location = azurerm_resource_group.test.location +} + +resource "azurerm_site_recovery_fabric" "test2" { + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + name = "acctest-fabric2-%[1]d" + location = azurerm_resource_group.test2.location + depends_on = [azurerm_site_recovery_fabric.test1] +} + +resource "azurerm_site_recovery_protection_container" "test1" { + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + recovery_fabric_name = azurerm_site_recovery_fabric.test1.name + name = "acctest-protection-cont1-%[1]d" +} + +resource "azurerm_site_recovery_protection_container" "test2" { + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + recovery_fabric_name = azurerm_site_recovery_fabric.test2.name + name = "acctest-protection-cont2-%[1]d" +} + +resource "azurerm_site_recovery_replication_policy" "test" { + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + name = "acctest-policy-%[1]d" + recovery_point_retention_in_minutes = 24 * 60 + application_consistent_snapshot_frequency_in_minutes = 4 * 60 +} + +resource "azurerm_site_recovery_protection_container_mapping" "test" { + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + recovery_fabric_name = azurerm_site_recovery_fabric.test1.name + recovery_source_protection_container_name = azurerm_site_recovery_protection_container.test1.name + recovery_target_protection_container_id = azurerm_site_recovery_protection_container.test2.id + recovery_replication_policy_id = azurerm_site_recovery_replication_policy.test.id + name = "mapping-%[1]d" +} + +resource "azurerm_virtual_network" "test1" { + name = "net-%[1]d" + resource_group_name = azurerm_resource_group.test.name + address_space = ["192.168.1.0/24"] + location = azurerm_site_recovery_fabric.test1.location +} + +resource "azurerm_subnet" "test1" { + name = "snet-%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test1.name + address_prefixes = ["192.168.1.0/24"] +} + +resource "azurerm_virtual_network" "test2" { + name = "net-%[1]d" + resource_group_name = azurerm_resource_group.test2.name + address_space = ["192.168.2.0/24"] + location = azurerm_site_recovery_fabric.test2.location +} + +resource "azurerm_subnet" "test2_1" { + name = "acctest-snet-%[1]d_1" + resource_group_name = "${azurerm_resource_group.test2.name}" + virtual_network_name = "${azurerm_virtual_network.test2.name}" + address_prefixes = ["192.168.2.0/27"] +} + +resource "azurerm_subnet" "test2_2" { + name = "snet-%[1]d_2" + resource_group_name = "${azurerm_resource_group.test2.name}" + virtual_network_name = "${azurerm_virtual_network.test2.name}" + address_prefixes = ["192.168.2.32/27"] +} + +resource "azurerm_site_recovery_network_mapping" "test" { + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + name = "mapping-%[1]d" + source_recovery_fabric_name = azurerm_site_recovery_fabric.test1.name + target_recovery_fabric_name = azurerm_site_recovery_fabric.test2.name + source_network_id = azurerm_virtual_network.test1.id + target_network_id = azurerm_virtual_network.test2.id +} + +resource "azurerm_network_interface" "test" { + name = "vm-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + ip_configuration { + name = "vm-%[1]d" + subnet_id = azurerm_subnet.test1.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_virtual_machine" "test" { + name = "vm-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + vm_size = "Standard_B1s" + + delete_os_disk_on_termination = true + + storage_image_reference { + publisher = "OpenLogic" + offer = "CentOS" + sku = "7.5" + version = "latest" + } + + storage_os_disk { + name = "disk-%[1]d" + os_type = "Linux" + caching = "ReadWrite" + create_option = "FromImage" + managed_disk_type = "Premium_LRS" + } + + os_profile { + admin_username = "testadmin" + admin_password = "Password1234!" + computer_name = "vm-%[1]d" + } + + os_profile_linux_config { + disable_password_authentication = false + } + network_interface_ids = [azurerm_network_interface.test.id] + + %[5]s +} + +resource "azurerm_storage_account" "test" { + name = "acct%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + account_tier = "Standard" + account_replication_type = "LRS" +} + + +resource "azurerm_site_recovery_replicated_vm" "test" { + name = "repl-%[2]d" + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + source_vm_id = azurerm_virtual_machine.test.id + source_recovery_fabric_name = azurerm_site_recovery_fabric.test1.name + recovery_replication_policy_id = azurerm_site_recovery_replication_policy.test.id + source_recovery_protection_container_name = azurerm_site_recovery_protection_container.test1.name + + target_resource_group_id = azurerm_resource_group.test2.id + target_recovery_fabric_id = azurerm_site_recovery_fabric.test2.id + target_recovery_protection_container_id = azurerm_site_recovery_protection_container.test2.id + + managed_disk { + disk_id = azurerm_virtual_machine.test.storage_os_disk[0].managed_disk_id + staging_storage_account_id = azurerm_storage_account.test.id + target_resource_group_id = azurerm_resource_group.test2.id + target_disk_type = "Premium_LRS" + target_replica_disk_type = "Premium_LRS" + } + + network_interface { + source_network_interface_id = azurerm_network_interface.test.id + target_subnet_name = "snet-%[2]d_2" + } + + depends_on = [ + azurerm_site_recovery_protection_container_mapping.test, + azurerm_site_recovery_network_mapping.test, + ] +} +`, data.RandomInteger, data.RandomInteger, data.Locations.Primary, data.Locations.Secondary, tags) +} + +func (r SiteRecoveryReplicationRecoveryPlan) basic(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" + } +} +`, r.template(data), data.RandomInteger) +} + +func (r SiteRecoveryReplicationRecoveryPlan) withPreActions(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] + pre_action { + name = "testPreAction" + type = "ManualActionDetails" + fail_over_directions = ["PrimaryToRecovery"] + fail_over_types = ["TestFailover"] + manual_action_instruction = "test instruction" + } + } + + recovery_group { + type = "Failover" + } + + recovery_group { + type = "Shutdown" + } +} +`, r.template(data), data.RandomInteger) +} + +func (r SiteRecoveryReplicationRecoveryPlan) withPostActions(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] + post_action { + name = "testPreAction" + type = "ManualActionDetails" + fail_over_directions = ["PrimaryToRecovery"] + fail_over_types = ["TestFailover"] + manual_action_instruction = "test instruction" + } + } + + recovery_group { + type = "Failover" + } + + recovery_group { + type = "Shutdown" + } + +} +`, 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 { + return nil, err + } + + resp, err := clients.RecoveryServices.ReplicationRecoveryPlansClient.Get(ctx, *id) + if err != nil { + return nil, fmt.Errorf("reading site recovery replication plan (%s): %+v", id.String(), err) + } + + model := resp.Model + if model == nil { + return nil, fmt.Errorf("reading site recovery replication plan (%s): model is nil. ", id.String()) + } + + return utils.Bool(model.Id != nil), nil +} diff --git a/website/docs/d/site_recovery_replication_recovery_plan.html.markdown b/website/docs/d/site_recovery_replication_recovery_plan.html.markdown new file mode 100644 index 000000000000..b2ca7d6e7c37 --- /dev/null +++ b/website/docs/d/site_recovery_replication_recovery_plan.html.markdown @@ -0,0 +1,84 @@ +--- +subcategory: "Recovery Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_site_recovery_replication_recovery_plan" +description: |- + Get information about an Azure Site Recovery Plan within a Recovery Services vault. +--- + +# azurerm_site_recovery_replication_recovery_plan + +Get information about an Azure Site Recovery Plan within a Recovery Services vault. A recovery plan gathers machines into recovery groups for the purpose of failover. + +## Example Usage + +```hcl +data "azurerm_recovery_services_vault" "vault" { + name = "tfex-recovery_vault" + resource_group_name = "tfex-resource_group" +} + +data "azurerm_site_recovery_replication_recovery_plan" "example" { + name = "example-recovery-plan" + recovery_vault_id = data.azurerm_recovery_services_vault.vault.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Replication Plan. + +* `recovery_vault_id` - (Required) The ID of the vault that should be updated. + + +## Attributes Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The ID of the Site Recovery Fabric. + +* `source_recovery_fabric_id` - The ID of source fabric to be recovered from. + +* `target_recovery_fabric_id` - The ID of target fabric to recover. + +* `recovery_group` - `recovery_group` block defined as below. +--- + +A `recovery_groups` block supports the following: + +* `type` - The Recovery Plan Group Type. Possible values are `Boot`, `Failover` and `Shutdown`. + +* `replicated_protected_items` - one or more id of protected VM. + +* `pre_action` - one or more `action` block. which will be executed before the group recovery. + +* `post_action` - one or more `action` block. which will be executed after the group recovery. + +--- + +An `action` block supports the following: + +* `name` - Name of the Action. + +* `type` - Type of the action detail. + +* `fail_over_directions` - Directions of fail over. + +* `fail_over_types` - Types of fail over. + +* `fabric_location` - The fabric location of runbook or script. + +* `runbook_id` - Id of runbook. + +* `manual_action_instruction` - Instructions of manual action. + +* `script_path` - Path of action script. + + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when retrieving the Site Recovery Replication Plan. diff --git a/website/docs/r/site_recovery_replication_recovery_plan.html.markdown b/website/docs/r/site_recovery_replication_recovery_plan.html.markdown new file mode 100644 index 000000000000..febfbd4b238f --- /dev/null +++ b/website/docs/r/site_recovery_replication_recovery_plan.html.markdown @@ -0,0 +1,145 @@ +--- +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. +--- + +# 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. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "source" { + name = "example-source-rg" + location = "west us" +} + + +resource "azurerm_resource_group" "target" { + name = "example-target-rg" + location = "east us" +} + +resource "azurerm_recovery_services_vault" "example" { + name = "example-kv" + location = azurerm_resource_group.target.location + resource_group_name = azurerm_resource_group.target.name + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "source" { + resource_group_name = azurerm_resource_group.example.name + recovery_vault_name = azurerm_recovery_services_vault.example.name + name = "example-fabric-source" + location = azurerm_resource_group.source.location +} + +resource "azurerm_site_recovery_fabric" "target" { + resource_group_name = azurerm_resource_group.target.name + recovery_vault_name = azurerm_recovery_services_vault.example.name + name = "example-fabric-target" + location = azurerm_resource_group.target.location + depends_on = [azurerm_site_recovery_fabric.source] +} + +resource "azurerm_site_recovery_replication_recovery_plan" "example" { + name = "example-recover-plan" + recovery_vault_id = azurerm_recovery_services_vault.target.id + source_recovery_fabric_id = azurerm_site_recovery_fabric.source.id + target_recovery_fabric_id = azurerm_site_recovery_fabric.target.id + + recovery_group { + type = "Boot" + replicated_protected_items = [azurerm_site_recovery_replicated_vm.test.id] + } + recovery_group { + type = "Failover" + } + recovery_group { + type = "Shutdown" + } + +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Replication Plan. The name can contain only letters, numbers, and hyphens. It should start with a letter and end with a letter or a number. Can be a maximum of 63 characters. + +* `recovery_vault_id` - (Required) The ID of the vault that should be updated. + +* `source_recovery_fabric_id` - (Required) ID of source fabric to be recovered from. Changing this forces a new Replication Plan to be created. + +* `target_recovery_fabric_id` - (Required) ID of target fabric to recover. Changing this forces a new Replication Plan to be created. + +* `recovery_group` - (Required) Three or more `recovery_group` block. + +--- + +A `recovery_groups` block supports the following: + +* `type` - (Required) The Recovery Plan Group Type. Possible values are `Boot`, `Failover` and `Shutdown`. + +* `replicated_protected_items` - (required) one or more id of protected VM. + +* `pre_action` - (Optional) one or more `action` block. which will be executed before the group recovery. + +* `post_action` - (Optional) one or more `action` block. which will be executed after the group recovery. + +--- + +An `action` block supports the following: + +* `name` - (Required) Name of the Action. + +* `type` - (Required) Type of the action detail. Possible values are `AutomationRunbookActionDetails`, `ManualActionDetails` and `ScriptActionDetails`. + +* `fail_over_directions` - (Required) Directions of fail over. Possible values are `PrimaryToRecovery` and `RecoveryToPrimary` + +* `fail_over_types` - (Required) Types of fail over. Possible values are `TestFailover`, `PlannedFailover` and `UnplannedFailover` + +* `fabric_location` - (Optional) The fabric location of runbook or script. Possible values are `Primary` and `Recovery`. + +-> **NOTE:** This is required when `type` is set to `AutomationRunbookActionDetails` or `ScriptActionDetails`. + +* `runbook_id` - (Optional) Id of runbook. + +-> **NOTE:** This property is required when `type` is set to `AutomationRunbookActionDetails`. + +* `manual_action_instruction` - (Optional) Instructions of manual action. + +-> **NOTE:** This property is required when `type` is set to `ManualActionDetails`. + +* `script_path` - (Optional) Path of action script. + +-> **NOTE:** This property is required when `type` is set to `ScriptActionDetails`. + + +## Attributes Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The ID of the Site Recovery Fabric. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Site Recovery Replication Plan. +* `update` - (Defaults to 30 minutes) Used when updating the Site Recovery Replication Plan. +* `read` - (Defaults to 5 minutes) Used when retrieving the Site Recovery Replication Plan. +* `delete` - (Defaults to 30 minutes) Used when deleting the Site Recovery Replication Plan. + +## Import + +Site Recovery Fabric can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_site_recovery_fabric.myfabric-id=/subscriptions/00000000-0000-0000-0000-00000000000/resourceGroups/groupName/providers/Microsoft.RecoveryServices/vaults/vaultName/replicationRecoveryPlans/planName +```