diff --git a/internal/services/recoveryservices/recovery_services_vault_hyperv_site_resource.go b/internal/services/recoveryservices/recovery_services_vault_hyperv_site_resource.go new file mode 100644 index 000000000000..9315f603cc63 --- /dev/null +++ b/internal/services/recoveryservices/recovery_services_vault_hyperv_site_resource.go @@ -0,0 +1,155 @@ +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/replicationfabrics" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type HyperVSiteModel struct { + Name string `tfschema:"name"` + RecoveryVaultId string `tfschema:"recovery_vault_id"` +} + +type HyperVSiteResource struct{} + +var _ sdk.Resource = HyperVSiteResource{} + +func (r HyperVSiteResource) Arguments() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "recovery_vault_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: replicationfabrics.ValidateVaultID, + }, + } +} + +func (r HyperVSiteResource) Attributes() map[string]*schema.Schema { + return map[string]*schema.Schema{} +} + +func (r HyperVSiteResource) ModelObject() interface{} { + return &HyperVSiteModel{} +} + +func (r HyperVSiteResource) ResourceType() string { + return "azurerm_site_recovery_services_vault_hyperv_site" +} + +func (r HyperVSiteResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var metaModel HyperVSiteModel + if err := metadata.Decode(&metaModel); err != nil { + return fmt.Errorf("decoding %s", err) + } + + client := metadata.Client.RecoveryServices.FabricClient + subscriptionId := metadata.Client.Account.SubscriptionId + + vaultId, err := replicationfabrics.ParseVaultID(metaModel.RecoveryVaultId) + if err != nil { + return err + } + + id := replicationfabrics.NewReplicationFabricID(subscriptionId, vaultId.ResourceGroupName, vaultId.VaultName, metaModel.Name) + + type HyperVSiteInstanceType struct { + InstanceType string `json:"instanceType"` + } + + // the instance type `HyperVSite` is not exposed in Swagger, tracked on https://github.com/Azure/azure-rest-api-specs/issues/22016 + parameters := replicationfabrics.FabricCreationInput{ + Properties: &replicationfabrics.FabricCreationInputProperties{ + CustomDetails: HyperVSiteInstanceType{ + InstanceType: "HyperVSite", + }, + }, + } + + err = client.CreateThenPoll(ctx, id, parameters) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r HyperVSiteResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.RecoveryServices.FabricClient + id, err := replicationfabrics.ParseReplicationFabricID(metadata.ResourceData.Id()) + if err != nil { + return fmt.Errorf("parsing %s: %+v", metadata.ResourceData.Id(), err) + } + + resp, err := client.Get(ctx, *id, replicationfabrics.DefaultGetOperationOptions()) + if err != nil { + if response.WasNotFound(resp.HttpResponse) { + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + if resp.Model == nil { + return fmt.Errorf("retrieving %s: `model` was nil", id) + } + + state := HyperVSiteModel{ + Name: id.ReplicationFabricName, + } + + vaultId := replicationfabrics.NewVaultID(id.SubscriptionId, id.ResourceGroupName, id.VaultName) + state.RecoveryVaultId = vaultId.ID() + + return metadata.Encode(&state) + }, + } +} + +func (r HyperVSiteResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 180 * time.Minute, // when a host connected to site, it will cost up to 180 minutes to delete. + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.RecoveryServices.FabricClient + id, err := replicationfabrics.ParseReplicationFabricID(metadata.ResourceData.Id()) + if err != nil { + return fmt.Errorf("parsing %s: %+v", metadata.ResourceData.Id(), err) + } + + err = client.DeleteThenPoll(ctx, *id) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + return nil + }, + } +} + +func (r HyperVSiteResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return replicationfabrics.ValidateReplicationFabricID +} diff --git a/internal/services/recoveryservices/recovery_services_vault_hyperv_site_resource_test.go b/internal/services/recoveryservices/recovery_services_vault_hyperv_site_resource_test.go new file mode 100644 index 000000000000..349ec87d41af --- /dev/null +++ b/internal/services/recoveryservices/recovery_services_vault_hyperv_site_resource_test.go @@ -0,0 +1,72 @@ +package recoveryservices_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicessiterecovery/2022-10-01/replicationfabrics" + "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 HyperVSiteResource struct{} + +func TestAccSiteRecoveryHyperVSite_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_site_recovery_services_vault_hyperv_site", "test") + r := HyperVSiteResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (HyperVSiteResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-recovery-%[1]d" + location = "%s" +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-vault-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "Standard" + + soft_delete_enabled = false +} + +resource "azurerm_site_recovery_services_vault_hyperv_site" "test" { + recovery_vault_id = azurerm_recovery_services_vault.test.id + name = "acctest-site-%[1]d" +} +`, data.RandomInteger, data.Locations.Primary) +} + +func (t HyperVSiteResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := replicationfabrics.ParseReplicationFabricID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.RecoveryServices.FabricClient.Get(ctx, *id, replicationfabrics.DefaultGetOperationOptions()) + if err != nil { + return nil, fmt.Errorf("reading Recovery Service Vault (%s): %+v", id.String(), err) + } + + return utils.Bool(resp.Model != nil), nil +} diff --git a/internal/services/recoveryservices/registration.go b/internal/services/recoveryservices/registration.go index 32c86fee4a75..757cdfa88314 100644 --- a/internal/services/recoveryservices/registration.go +++ b/internal/services/recoveryservices/registration.go @@ -26,6 +26,7 @@ func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ BackupProtectionPolicyVMWorkloadResource{}, SiteRecoveryReplicationRecoveryPlanResource{}, + HyperVSiteResource{}, } } diff --git a/website/docs/r/site_recovery_services_vault_hyperv_site.html.markdown b/website/docs/r/site_recovery_services_vault_hyperv_site.html.markdown new file mode 100644 index 000000000000..b89abc202d1c --- /dev/null +++ b/website/docs/r/site_recovery_services_vault_hyperv_site.html.markdown @@ -0,0 +1,65 @@ +--- +subcategory: "Recovery Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_site_recovery_services_vault_hyperv_site" +description: |- + Manages a HyperV Site in Recovery Service Vault. +--- + +# azurerm_site_recovery_services_vault_hyperv_site + +Manages a HyperV Site in Recovery Service Vault. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-rg" + location = "eastus" +} + +resource "azurerm_recovery_services_vault" "example" { + name = "example-vault" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku = "Standard" + + soft_delete_enabled = false +} + + +resource "azurerm_site_recovery_services_vault_hyperv_site" "example" { + name = "example-site" + recovery_vault_id = azurerm_recovery_services_vault.example.id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Recovery Service. Changing this forces a new Site to be created. + +* `recovery_vault_id` - (Required) The ID of the Recovery Services Vault where the Site created. Changing this forces a new Site to be created. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Recovery Service. + +## 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 Recovery Service. +* `read` - (Defaults to 5 minutes) Used when retrieving the Recovery Service. +* `delete` - (Defaults to 180 minutes) Used when deleting the Recovery Service. + +## Import + +Recovery Services can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_site_recovery_services_vault_hyperv_site.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.RecoveryServices/vaults/vault1/replicationFabrics/fabric1 +```