diff --git a/azurerm/internal/services/dataprotection/client/client.go b/azurerm/internal/services/dataprotection/client/client.go index 67cfcb9e1eb2..f56b70752bba 100644 --- a/azurerm/internal/services/dataprotection/client/client.go +++ b/azurerm/internal/services/dataprotection/client/client.go @@ -6,8 +6,9 @@ import ( ) type Client struct { - BackupVaultClient *dataprotection.BackupVaultsClient - BackupPolicyClient *dataprotection.BackupPoliciesClient + BackupVaultClient *dataprotection.BackupVaultsClient + BackupPolicyClient *dataprotection.BackupPoliciesClient + BackupInstanceClient *dataprotection.BackupInstancesClient } func NewClient(o *common.ClientOptions) *Client { @@ -17,8 +18,12 @@ func NewClient(o *common.ClientOptions) *Client { backupPolicyClient := dataprotection.NewBackupPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&backupPolicyClient.Client, o.ResourceManagerAuthorizer) + backupInstanceClient := dataprotection.NewBackupInstancesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&backupInstanceClient.Client, o.ResourceManagerAuthorizer) + return &Client{ - BackupVaultClient: &backupVaultClient, - BackupPolicyClient: &backupPolicyClient, + BackupVaultClient: &backupVaultClient, + BackupPolicyClient: &backupPolicyClient, + BackupInstanceClient: &backupInstanceClient, } } diff --git a/azurerm/internal/services/dataprotection/data_protection_backup_instance_postgresql_resource.go b/azurerm/internal/services/dataprotection/data_protection_backup_instance_postgresql_resource.go new file mode 100644 index 000000000000..16876d30e20c --- /dev/null +++ b/azurerm/internal/services/dataprotection/data_protection_backup_instance_postgresql_resource.go @@ -0,0 +1,224 @@ +package dataprotection + +import ( + "context" + "fmt" + "log" + "time" + + "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" + postgresParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/postgres/parse" + postgresValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/postgres/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 resourceDataProtectionBackupInstancePostgreSQL() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceDataProtectionBackupInstancePostgreSQLCreateUpdate, + Read: resourceDataProtectionBackupInstancePostgreSQLRead, + Update: resourceDataProtectionBackupInstancePostgreSQLCreateUpdate, + Delete: resourceDataProtectionBackupInstancePostgreSQLDelete, + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Update: pluginsdk.DefaultTimeout(30 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.BackupInstanceID(id) + return err + }), + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "location": location.Schema(), + + "vault_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.BackupVaultID, + }, + + "database_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: postgresValidate.DatabaseID, + }, + + "backup_policy_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.BackupPolicyID, + }, + }, + } +} +func resourceDataProtectionBackupInstancePostgreSQLCreateUpdate(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_postgresql", id.ID()) + } + } + + databaseId, _ := postgresParse.DatabaseID(d.Get("database_id").(string)) + location := location.Normalize(d.Get("location").(string)) + serverId := postgresParse.NewServerID(databaseId.SubscriptionId, databaseId.ResourceGroup, databaseId.ServerName) + policyId, _ := parse.BackupPolicyID(d.Get("backup_policy_id").(string)) + + parameters := dataprotection.BackupInstanceResource{ + Properties: &dataprotection.BackupInstance{ + DataSourceInfo: &dataprotection.Datasource{ + DatasourceType: utils.String("Microsoft.DBforPostgreSQL/servers/databases"), + ObjectType: utils.String("Datasource"), + ResourceID: utils.String(databaseId.ID()), + ResourceLocation: utils.String(location), + ResourceName: utils.String(databaseId.Name), + ResourceType: utils.String("Microsoft.DBforPostgreSQL/servers/databases"), + ResourceURI: utils.String(""), + }, + DataSourceSetInfo: &dataprotection.DatasourceSet{ + DatasourceType: utils.String("Microsoft.DBForPostgreSQL/servers"), + ObjectType: utils.String("DatasourceSet"), + ResourceID: utils.String(serverId.ID()), + ResourceLocation: utils.String(location), + ResourceName: utils.String(serverId.Name), + ResourceType: utils.String("Microsoft.DBForPostgreSQL/servers"), + ResourceURI: utils.String(""), + }, + 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 resourceDataProtectionBackupInstancePostgreSQLRead(d, meta) +} + +func resourceDataProtectionBackupInstancePostgreSQLRead(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("database_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 resourceDataProtectionBackupInstancePostgreSQLDelete(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 { + 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 +} + +func policyProtectionStateRefreshFunc(ctx context.Context, client *dataprotection.BackupInstancesClient, id parse.BackupInstanceId) pluginsdk.StateRefreshFunc { + return func() (interface{}, string, error) { + res, err := client.Get(ctx, id.BackupVaultName, id.ResourceGroup, id.Name) + if err != nil { + return nil, "", fmt.Errorf("retrieving DataProtection BackupInstance (%q): %+v", id, err) + } + if res.Properties == nil || res.Properties.ProtectionStatus == nil { + return nil, "", fmt.Errorf("error reading DataProtection BackupInstance (%q) protection status: %+v", id, err) + } + + return res, string(res.Properties.ProtectionStatus.Status), nil + } +} diff --git a/azurerm/internal/services/dataprotection/data_protection_backup_instance_postgresql_resource_test.go b/azurerm/internal/services/dataprotection/data_protection_backup_instance_postgresql_resource_test.go new file mode 100644 index 000000000000..f0722d547b2d --- /dev/null +++ b/azurerm/internal/services/dataprotection/data_protection_backup_instance_postgresql_resource_test.go @@ -0,0 +1,214 @@ +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 DataProtectionBackupInstancePostgreSQLResource struct{} + +func TestAccDataProtectionBackupInstancePostgreSQL_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_protection_backup_instance_postgresql", "test") + r := DataProtectionBackupInstancePostgreSQLResource{} + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccDataProtectionBackupInstancePostgreSQL_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_protection_backup_instance_postgresql", "test") + r := DataProtectionBackupInstancePostgreSQLResource{} + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccDataProtectionBackupInstancePostgreSQL_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_protection_backup_instance_postgresql", "test") + r := DataProtectionBackupInstancePostgreSQLResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccDataProtectionBackupInstancePostgreSQL_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_protection_backup_instance_postgresql", "test") + r := DataProtectionBackupInstancePostgreSQLResource{} + 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 DataProtectionBackupInstancePostgreSQLResource) 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 DataProtectionBackupInstancePostgreSQLResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctest-dataprotection-%d" + location = "%s" +} + +resource "azurerm_postgresql_server" "test" { + name = "acctest-postgresql-server-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku_name = "B_Gen5_2" + + storage_mb = 5120 + backup_retention_days = 7 + geo_redundant_backup_enabled = false + auto_grow_enabled = true + + administrator_login = "psqladminun" + administrator_login_password = "H@Sh1CoR3!" + version = "9.5" + ssl_enforcement_enabled = true +} + +resource "azurerm_postgresql_database" "test" { + name = "acctest-postgresql-database-%d" + resource_group_name = azurerm_resource_group.test.name + server_name = azurerm_postgresql_server.test.name + charset = "UTF8" + collation = "English_United States.1252" +} + +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_data_protection_backup_policy_postgresql" "test" { + name = "acctest-dp-%d" + resource_group_name = azurerm_resource_group.test.name + vault_name = azurerm_data_protection_backup_vault.test.name + backup_repeating_time_intervals = ["R/2021-05-23T02:30:00+00:00/P1W"] + default_retention_duration = "P4M" +} + +resource "azurerm_data_protection_backup_policy_postgresql" "another" { + name = "acctest-dp-second-%d" + resource_group_name = azurerm_resource_group.test.name + vault_name = azurerm_data_protection_backup_vault.test.name + backup_repeating_time_intervals = ["R/2021-05-23T02:30:00+00:00/P1W"] + default_retention_duration = "P3M" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + +func (r DataProtectionBackupInstancePostgreSQLResource) basic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_data_protection_backup_instance_postgresql" "test" { + name = "acctest-dbi-%d" + location = azurerm_resource_group.test.location + vault_id = azurerm_data_protection_backup_vault.test.id + database_id = azurerm_postgresql_database.test.id + backup_policy_id = azurerm_data_protection_backup_policy_postgresql.test.id +} +`, template, data.RandomInteger) +} + +func (r DataProtectionBackupInstancePostgreSQLResource) requiresImport(data acceptance.TestData) string { + config := r.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_data_protection_backup_instance_postgresql" "import" { + name = azurerm_data_protection_backup_instance_postgresql.test.name + location = azurerm_resource_group.test.location + vault_id = azurerm_data_protection_backup_instance_postgresql.test.vault_id + database_id = azurerm_postgresql_database.test.id + backup_policy_id = azurerm_data_protection_backup_policy_postgresql.test.id +} +`, config) +} + +func (r DataProtectionBackupInstancePostgreSQLResource) complete(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_data_protection_backup_instance_postgresql" "test" { + name = "acctest-dbi-%d" + location = azurerm_resource_group.test.location + vault_id = azurerm_data_protection_backup_vault.test.id + database_id = azurerm_postgresql_database.test.id + backup_policy_id = azurerm_data_protection_backup_policy_postgresql.another.id +} +`, template, data.RandomInteger) +} diff --git a/azurerm/internal/services/dataprotection/parse/backup_instance.go b/azurerm/internal/services/dataprotection/parse/backup_instance.go new file mode 100644 index 000000000000..6e14ab21edb2 --- /dev/null +++ b/azurerm/internal/services/dataprotection/parse/backup_instance.go @@ -0,0 +1,75 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type BackupInstanceId struct { + SubscriptionId string + ResourceGroup string + BackupVaultName string + Name string +} + +func NewBackupInstanceID(subscriptionId, resourceGroup, backupVaultName, name string) BackupInstanceId { + return BackupInstanceId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + BackupVaultName: backupVaultName, + Name: name, + } +} + +func (id BackupInstanceId) String() string { + segments := []string{ + fmt.Sprintf("Name %q", id.Name), + fmt.Sprintf("Backup Vault Name %q", id.BackupVaultName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Backup Instance", segmentsStr) +} + +func (id BackupInstanceId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.DataProtection/backupVaults/%s/backupInstances/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.BackupVaultName, id.Name) +} + +// BackupInstanceID parses a BackupInstance ID into an BackupInstanceId struct +func BackupInstanceID(input string) (*BackupInstanceId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := BackupInstanceId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.BackupVaultName, err = id.PopSegment("backupVaults"); err != nil { + return nil, err + } + if resourceId.Name, err = id.PopSegment("backupInstances"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/dataprotection/parse/backup_instance_test.go b/azurerm/internal/services/dataprotection/parse/backup_instance_test.go new file mode 100644 index 000000000000..15cbc3c02bc1 --- /dev/null +++ b/azurerm/internal/services/dataprotection/parse/backup_instance_test.go @@ -0,0 +1,128 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = BackupInstanceId{} + +func TestBackupInstanceIDFormatter(t *testing.T) { + actual := NewBackupInstanceID("12345678-1234-9876-4563-123456789012", "resourceGroup1", "vault1", "backupInstance1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/vault1/backupInstances/backupInstance1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestBackupInstanceID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *BackupInstanceId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing BackupVaultName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/", + Error: true, + }, + + { + // missing value for BackupVaultName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/", + Error: true, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/vault1/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/vault1/backupInstances/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/vault1/backupInstances/backupInstance1", + Expected: &BackupInstanceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resourceGroup1", + BackupVaultName: "vault1", + Name: "backupInstance1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESOURCEGROUP1/PROVIDERS/MICROSOFT.DATAPROTECTION/BACKUPVAULTS/VAULT1/BACKUPINSTANCES/BACKUPINSTANCE1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := BackupInstanceID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.BackupVaultName != v.Expected.BackupVaultName { + t.Fatalf("Expected %q but got %q for BackupVaultName", v.Expected.BackupVaultName, actual.BackupVaultName) + } + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/dataprotection/registration.go b/azurerm/internal/services/dataprotection/registration.go index 91e7f12eb3b3..03e20c912617 100644 --- a/azurerm/internal/services/dataprotection/registration.go +++ b/azurerm/internal/services/dataprotection/registration.go @@ -24,7 +24,8 @@ 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_postgresql": resourceDataProtectionBackupPolicyPostgreSQL(), + "azurerm_data_protection_backup_vault": resourceDataProtectionBackupVault(), + "azurerm_data_protection_backup_policy_postgresql": resourceDataProtectionBackupPolicyPostgreSQL(), + "azurerm_data_protection_backup_instance_postgresql": resourceDataProtectionBackupInstancePostgreSQL(), } } diff --git a/azurerm/internal/services/dataprotection/resourceids.go b/azurerm/internal/services/dataprotection/resourceids.go index 369f71ce6120..08d621225861 100644 --- a/azurerm/internal/services/dataprotection/resourceids.go +++ b/azurerm/internal/services/dataprotection/resourceids.go @@ -2,3 +2,4 @@ package dataprotection //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=BackupVault -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/vault1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=BackupPolicy -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/vault1/backupPolicies/backupPolicy1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=BackupInstance -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/vault1/backupInstances/backupInstance1 diff --git a/azurerm/internal/services/dataprotection/validate/backup_instance_id.go b/azurerm/internal/services/dataprotection/validate/backup_instance_id.go new file mode 100644 index 000000000000..7b58dc7777a5 --- /dev/null +++ b/azurerm/internal/services/dataprotection/validate/backup_instance_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/dataprotection/parse" +) + +func BackupInstanceID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.BackupInstanceID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/dataprotection/validate/backup_instance_id_test.go b/azurerm/internal/services/dataprotection/validate/backup_instance_id_test.go new file mode 100644 index 000000000000..198efb5bd556 --- /dev/null +++ b/azurerm/internal/services/dataprotection/validate/backup_instance_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestBackupInstanceID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing BackupVaultName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/", + Valid: false, + }, + + { + // missing value for BackupVaultName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/", + Valid: false, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/vault1/", + Valid: false, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/vault1/backupInstances/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.DataProtection/backupVaults/vault1/backupInstances/backupInstance1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESOURCEGROUP1/PROVIDERS/MICROSOFT.DATAPROTECTION/BACKUPVAULTS/VAULT1/BACKUPINSTANCES/BACKUPINSTANCE1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := BackupInstanceID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/website/docs/r/data_protection_backup_instance_postgresql.html.markdown b/website/docs/r/data_protection_backup_instance_postgresql.html.markdown new file mode 100644 index 000000000000..ed53529db01d --- /dev/null +++ b/website/docs/r/data_protection_backup_instance_postgresql.html.markdown @@ -0,0 +1,110 @@ +--- +subcategory: "DataProtection" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_data_protection_backup_instance_postgresql" +description: |- + Manages a Backup Instance to back up PostgreSQL. +--- + +# azurerm_data_protection_backup_instance_postgresql + +Manages a Backup Instance to back up PostgreSQL. + +-> **Note**: Before using this resource, there are some prerequisite permissions for configure backup and restore. See more details from https://docs.microsoft.com/en-us/azure/backup/backup-azure-database-postgresql#prerequisite-permissions-for-configure-backup-and-restore. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "rg" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_postgresql_server" "example" { + name = "example-postgresql-server" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + + sku_name = "B_Gen5_2" + + storage_mb = 5120 + backup_retention_days = 7 + geo_redundant_backup_enabled = false + auto_grow_enabled = true + + administrator_login = "psqladminun" + administrator_login_password = "H@Sh1CoR3!" + version = "9.5" + ssl_enforcement_enabled = true +} + +resource "azurerm_postgresql_database" "example" { + name = "example-postgresql-database" + resource_group_name = azurerm_resource_group.example.name + server_name = azurerm_postgresql_server.example.name + charset = "UTF8" + collation = "English_United States.1252" +} + +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_data_protection_backup_policy_postgresql" "example" { + name = "example-backup-policy" + resource_group_name = azurerm_resource_group.rg.name + vault_name = azurerm_data_protection_backup_vault.example.name + backup_repeating_time_intervals = ["R/2021-05-23T02:30:00+00:00/P1W"] + default_retention_duration = "P4M" +} + +resource "azurerm_data_protection_backup_instance_postgresql" "example" { + name = "example-backup-instance" + location = azurerm_resource_group.rg.location + vault_id = azurerm_data_protection_backup_vault.example.id + + database_id = azurerm_postgresql_database.example.id + backup_policy_id = azurerm_data_protection_backup_policy_postgresql.example.id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Backup Instance PostgreSQL. Changing this forces a new Backup Instance PostgreSQL to be created. + +* `location` - (Required) The location of the source database. Changing this forces a new Backup Instance PostgreSQL to be created. + +* `vault_id` - (Required) The ID of the Backup Vault within which the PostgreSQL Backup Instance should exist. Changing this forces a new Backup Instance PostgreSQL to be created. + +* `database_id` - (Required) The ID of the source database. Changing this forces a new Backup Instance PostgreSQL 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 PostgreSQL. + +## 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 PostgreSQL. +* `read` - (Defaults to 5 minutes) Used when retrieving the Backup Instance PostgreSQL. +* `update` - (Defaults to 30 minutes) Used when updating the Backup Instance PostgreSQL. +* `delete` - (Defaults to 30 minutes) Used when deleting the Backup Instance PostgreSQL. + +## Import + +Backup Instance PostgreSQL can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_data_protection_backup_instance_postgresql.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.DataProtection/backupVaults/vault1/backupInstances/backupInstance1 +```