diff --git a/azurerm/internal/services/dataprotection/data_protection_backup_instance_blob_storage_resource.go b/azurerm/internal/services/dataprotection/data_protection_backup_instance_blob_storage_resource.go new file mode 100644 index 0000000000000..4fbfc917ae356 --- /dev/null +++ b/azurerm/internal/services/dataprotection/data_protection_backup_instance_blob_storage_resource.go @@ -0,0 +1,203 @@ +package dataprotection + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/go-azure-helpers/response" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/dataprotection/legacysdk/dataprotection" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/dataprotection/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/dataprotection/validate" + storageParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage/parse" + storageValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceDataProtectionBackupInstanceBlobStorage() *schema.Resource { + return &schema.Resource{ + Create: resourceDataProtectionBackupInstanceBlobStorageCreateUpdate, + Read: resourceDataProtectionBackupInstanceBlobStorageRead, + Update: resourceDataProtectionBackupInstanceBlobStorageCreateUpdate, + Delete: resourceDataProtectionBackupInstanceBlobStorageDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.BackupInstanceID(id) + return err + }), + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": location.Schema(), + + "vault_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.BackupVaultID, + }, + + "storage_account_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: storageValidate.StorageAccountID, + }, + + "backup_policy_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.BackupPolicyID, + }, + }, + } +} +func resourceDataProtectionBackupInstanceBlobStorageCreateUpdate(d *schema.ResourceData, meta interface{}) error { + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + client := meta.(*clients.Client).DataProtection.BackupInstanceClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + vaultId, _ := parse.BackupVaultID(d.Get("vault_id").(string)) + id := parse.NewBackupInstanceID(subscriptionId, vaultId.ResourceGroup, vaultId.Name, name) + + if d.IsNewResource() { + existing, err := client.Get(ctx, id.BackupVaultName, id.ResourceGroup, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for existing DataProtection BackupInstance (%q): %+v", id, err) + } + } + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_data_protection_backup_instance_blob_storage", id.ID()) + } + } + + storageAccountId, _ := storageParse.StorageAccountID(d.Get("storage_account_id").(string)) + location := location.Normalize(d.Get("location").(string)) + policyId, _ := parse.BackupPolicyID(d.Get("backup_policy_id").(string)) + + parameters := dataprotection.BackupInstanceResource{ + Properties: &dataprotection.BackupInstance{ + DataSourceInfo: &dataprotection.Datasource{ + DatasourceType: utils.String("Microsoft.Storage/storageAccounts/blobServices"), + ObjectType: utils.String("Datasource"), + ResourceID: utils.String(storageAccountId.ID()), + ResourceLocation: utils.String(location), + ResourceName: utils.String(storageAccountId.Name), + ResourceType: utils.String("Microsoft.Storage/storageAccounts"), + ResourceURI: utils.String(storageAccountId.ID()), + }, + FriendlyName: utils.String(id.Name), + PolicyInfo: &dataprotection.PolicyInfo{ + PolicyID: utils.String(policyId.ID()), + }, + }, + } + + future, err := client.CreateOrUpdate(ctx, id.BackupVaultName, id.ResourceGroup, id.Name, parameters) + if err != nil { + return fmt.Errorf("creating/updating DataProtection BackupInstance (%q): %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation/update of the DataProtection BackupInstance (%q): %+v", id, err) + } + + deadline, ok := ctx.Deadline() + if !ok { + return fmt.Errorf("context had no deadline") + } + stateConf := &pluginsdk.StateChangeConf{ + Pending: []string{string(dataprotection.ConfiguringProtection), string(dataprotection.UpdatingProtection)}, + Target: []string{string(dataprotection.ProtectionConfigured)}, + Refresh: policyProtectionStateRefreshFunc(ctx, client, id), + MinTimeout: 1 * time.Minute, + Timeout: time.Until(deadline), + } + + if _, err = stateConf.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for BackupInstance(%q) policy protection to be completed: %+v", id, err) + } + + d.SetId(id.ID()) + return resourceDataProtectionBackupInstanceBlobStorageRead(d, meta) +} + +func resourceDataProtectionBackupInstanceBlobStorageRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).DataProtection.BackupInstanceClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.BackupInstanceID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.BackupVaultName, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] dataprotection %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("retrieving DataProtection BackupInstance (%q): %+v", id, err) + } + vaultId := parse.NewBackupVaultID(id.SubscriptionId, id.ResourceGroup, id.BackupVaultName) + d.Set("name", id.Name) + d.Set("vault_id", vaultId.ID()) + if props := resp.Properties; props != nil { + if props.DataSourceInfo != nil { + d.Set("storage_account_id", props.DataSourceInfo.ResourceID) + d.Set("location", props.DataSourceInfo.ResourceLocation) + } + if props.PolicyInfo != nil { + d.Set("backup_policy_id", props.PolicyInfo.PolicyID) + } + } + return nil +} + +func resourceDataProtectionBackupInstanceBlobStorageDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).DataProtection.BackupInstanceClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.BackupInstanceID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.BackupVaultName, id.ResourceGroup, id.Name) + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return fmt.Errorf("deleting DataProtection BackupInstance (%q): %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for deletion of the DataProtection BackupInstance (%q): %+v", id.Name, err) + } + return nil +} diff --git a/azurerm/internal/services/dataprotection/data_protection_backup_instance_blob_storage_resource_test.go b/azurerm/internal/services/dataprotection/data_protection_backup_instance_blob_storage_resource_test.go new file mode 100644 index 0000000000000..a37bbb1fd7968 --- /dev/null +++ b/azurerm/internal/services/dataprotection/data_protection_backup_instance_blob_storage_resource_test.go @@ -0,0 +1,199 @@ +package dataprotection_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/dataprotection/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type DataProtectionBackupInstanceBlobStorageResource struct{} + +func TestAccDataProtectionBackupInstanceBlobStorage_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_protection_backup_instance_blob_storage", "test") + r := DataProtectionBackupInstanceBlobStorageResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccDataProtectionBackupInstanceBlobStorage_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_protection_backup_instance_blob_storage", "test") + r := DataProtectionBackupInstanceBlobStorageResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccDataProtectionBackupInstanceBlobStorage_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_protection_backup_instance_blob_storage", "test") + r := DataProtectionBackupInstanceBlobStorageResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccDataProtectionBackupInstanceBlobStorage_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_protection_backup_instance_blob_storage", "test") + r := DataProtectionBackupInstanceBlobStorageResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r DataProtectionBackupInstanceBlobStorageResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.BackupInstanceID(state.ID) + if err != nil { + return nil, err + } + resp, err := client.DataProtection.BackupInstanceClient.Get(ctx, id.BackupVaultName, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving DataProtection BackupInstance (%q): %+v", id, err) + } + return utils.Bool(true), nil +} + +func (r DataProtectionBackupInstanceBlobStorageResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctest-dataprotection-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_data_protection_backup_vault" "test" { + name = "acctest-dataprotection-vault-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + datastore_type = "VaultStore" + redundancy = "LocallyRedundant" + + identity { + type = "SystemAssigned" + } +} + +resource "azurerm_role_assignment" "test" { + scope = azurerm_storage_account.test.id + role_definition_name = "Storage Account Backup Contributor Role" + principal_id = azurerm_data_protection_backup_vault.test.identity[0].principal_id +} + +resource "azurerm_data_protection_backup_policy_blob_storage" "test" { + name = "acctest-dbp-%d" + vault_id = azurerm_data_protection_backup_vault.test.id + retention_duration = "P30D" +} + +resource "azurerm_data_protection_backup_policy_blob_storage" "another" { + name = "acctest-dbp-other-%d" + vault_id = azurerm_data_protection_backup_vault.test.id + retention_duration = "P30D" +} + +`, data.RandomInteger, data.Locations.Primary, data.RandomIntOfLength(8), data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + +func (r DataProtectionBackupInstanceBlobStorageResource) basic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_data_protection_backup_instance_blob_storage" "test" { + name = "acctest-dbi-%d" + location = azurerm_resource_group.test.location + vault_id = azurerm_data_protection_backup_vault.test.id + storage_account_id = azurerm_storage_account.test.id + backup_policy_id = azurerm_data_protection_backup_policy_blob_storage.test.id +} +`, template, data.RandomInteger) +} + +func (r DataProtectionBackupInstanceBlobStorageResource) requiresImport(data acceptance.TestData) string { + config := r.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_data_protection_backup_instance_blob_storage" "import" { + name = azurerm_data_protection_backup_instance_blob_storage.test.name + location = azurerm_data_protection_backup_instance_blob_storage.test.location + vault_id = azurerm_data_protection_backup_instance_blob_storage.test.vault_id + storage_account_id = azurerm_data_protection_backup_instance_blob_storage.test.storage_account_id + backup_policy_id = azurerm_data_protection_backup_instance_blob_storage.test.backup_policy_id +} +`, config) +} + +func (r DataProtectionBackupInstanceBlobStorageResource) complete(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_data_protection_backup_instance_blob_storage" "test" { + name = "acctest-dbi-%d" + location = azurerm_resource_group.test.location + vault_id = azurerm_data_protection_backup_vault.test.id + storage_account_id = azurerm_storage_account.test.id + backup_policy_id = azurerm_data_protection_backup_policy_blob_storage.another.id +} +`, template, data.RandomInteger) +} diff --git a/azurerm/internal/services/dataprotection/registration.go b/azurerm/internal/services/dataprotection/registration.go index 191286b227eed..b70de3bbf04e9 100644 --- a/azurerm/internal/services/dataprotection/registration.go +++ b/azurerm/internal/services/dataprotection/registration.go @@ -24,10 +24,11 @@ func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { return map[string]*pluginsdk.Resource{ - "azurerm_data_protection_backup_vault": resourceDataProtectionBackupVault(), - "azurerm_data_protection_backup_policy_blob_storage": resourceDataProtectionBackupPolicyBlobStorage(), - "azurerm_data_protection_backup_policy_disk": resourceDataProtectionBackupPolicyDisk(), - "azurerm_data_protection_backup_policy_postgresql": resourceDataProtectionBackupPolicyPostgreSQL(), - "azurerm_data_protection_backup_instance_postgresql": resourceDataProtectionBackupInstancePostgreSQL(), + "azurerm_data_protection_backup_vault": resourceDataProtectionBackupVault(), + "azurerm_data_protection_backup_policy_blob_storage": resourceDataProtectionBackupPolicyBlobStorage(), + "azurerm_data_protection_backup_policy_disk": resourceDataProtectionBackupPolicyDisk(), + "azurerm_data_protection_backup_policy_postgresql": resourceDataProtectionBackupPolicyPostgreSQL(), + "azurerm_data_protection_backup_instance_blob_storage": resourceDataProtectionBackupInstanceBlobStorage(), + "azurerm_data_protection_backup_instance_postgresql": resourceDataProtectionBackupInstancePostgreSQL(), } } diff --git a/website/docs/r/data_protection_backup_instance_blob_storage.html.markdown b/website/docs/r/data_protection_backup_instance_blob_storage.html.markdown new file mode 100644 index 0000000000000..ae4e99b392ea8 --- /dev/null +++ b/website/docs/r/data_protection_backup_instance_blob_storage.html.markdown @@ -0,0 +1,96 @@ +--- +subcategory: "DataProtection" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_data_protection_backup_instance_blob_storage" +description: |- + Manages a Backup Instance Blob Storage. +--- + +# azurerm_data_protection_backup_instance_blob_storage + +Manages a Backup Instance Blob Storage. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "rg" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_storage_account" "example" { + name = "example-storage-account" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_data_protection_backup_vault" "example" { + name = "example-backup-vault" + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location + datastore_type = "VaultStore" + redundancy = "LocallyRedundant" +} + +resource "azurerm_role_assignment" "example" { + scope = azurerm_storage_account.example.id + role_definition_name = "Storage Account Backup Contributor Role" + principal_id = azurerm_data_protection_backup_vault.example.identity[0].principal_id +} + +resource "azurerm_data_protection_backup_policy_blob_storage" "example" { + name = "example-backup-policy" + resource_group_name = azurerm_resource_group.rg.name + vault_name = azurerm_data_protection_backup_vault.example.name + retention_duration = "P30D" +} + +resource "azurerm_data_protection_backup_instance_blob_storage" "example" { + name = "example-backup-instance" + vault_id = azurerm_data_protection_backup_vault.example.id + storage_account_location = azurerm_resource_group.rg.location + storage_account_id = azurerm_storage_account.example.id + backup_policy_id = azurerm_data_protection_backup_policy_blob_storage.example.id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Backup Instance Blob Storage. Changing this forces a new Backup Instance Blob Storage to be created. + +* `resource_group_name` - (Required) The name of the Resource Group where the Backup Instance Blob Storage should exist. Changing this forces a new Backup Instance Blob Storage to be created. + +* `vault_id` - (Required) The ID of the Backup Vault within which the Backup Instance Blob Storage should exist. Changing this forces a new Backup Instance Blob Storage to be created. + +* `storage_account_id` - (Required) The ID of the source Storage Account. Changing this forces a new Backup Instance Blob Storage to be created. + +* `storage_account_location` - (Required) The location of the source Storage Account. Changing this forces a new Backup Instance Blob Storage to be created. + +* `backup_policy_id` - (Required) The ID of the Backup Policy. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Backup Instance Blob Storage. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Backup Instance Blob Storage. +* `read` - (Defaults to 5 minutes) Used when retrieving the Backup Instance Blob Storage. +* `update` - (Defaults to 30 minutes) Used when updating the Backup Instance Blob Storage. +* `delete` - (Defaults to 30 minutes) Used when deleting the Backup Instance Blob Storage. + +## Import + +Backup Instance Blob Storages can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_data_protection_backup_instance_blob_storage.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.DataProtection/backupVaults/vault1/backupInstances/backupInstance1 +```