diff --git a/azurerm/internal/services/storage/storage_management_policy_data_source.go b/azurerm/internal/services/storage/storage_management_policy_data_source.go index 6cf0a96f216c..ee93f5096705 100644 --- a/azurerm/internal/services/storage/storage_management_policy_data_source.go +++ b/azurerm/internal/services/storage/storage_management_policy_data_source.go @@ -53,6 +53,29 @@ func dataSourceStorageManagementPolicy() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, + + "match_blob_index_tag": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + + "operation": { + Type: schema.TypeString, + Computed: true, + }, + + "value": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, }, }, }, @@ -86,6 +109,15 @@ func dataSourceStorageManagementPolicy() *schema.Resource { Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "change_tier_to_archive_after_days_since_creation": { + Type: schema.TypeInt, + Computed: true, + }, + "change_tier_to_cool_after_days_since_creation": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, "delete_after_days_since_creation_greater_than": { Type: schema.TypeInt, Computed: true, @@ -93,6 +125,26 @@ func dataSourceStorageManagementPolicy() *schema.Resource { }, }, }, + "version": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "change_tier_to_archive_after_days_since_creation": { + Type: schema.TypeInt, + Computed: true, + }, + "change_tier_to_cool_after_days_since_creation": { + Type: schema.TypeInt, + Computed: true, + }, + "delete_after_days_since_creation": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, }, }, }, @@ -123,11 +175,10 @@ func dataSourceStorageManagementPolicyRead(d *schema.ResourceData, meta interfac } d.SetId(*result.ID) - if result.Policy != nil { - policy := result.Policy - if policy.Rules != nil { + if props := result.ManagementPolicyProperties; props != nil { + if policy := props.Policy; policy != nil { if err := d.Set("rule", flattenStorageManagementPolicyRules(policy.Rules)); err != nil { - return fmt.Errorf("Error flattening `rule`: %+v", err) + return fmt.Errorf("flattening `rule`: %+v", err) } } } diff --git a/azurerm/internal/services/storage/storage_management_policy_resource.go b/azurerm/internal/services/storage/storage_management_policy_resource.go index 053b75c88ab4..da3957ecc570 100644 --- a/azurerm/internal/services/storage/storage_management_policy_resource.go +++ b/azurerm/internal/services/storage/storage_management_policy_resource.go @@ -5,15 +5,15 @@ import ( "regexp" "time" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" - "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-01-01/storage" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) func resourceStorageManagementPolicy() *schema.Resource { @@ -82,6 +82,35 @@ func resourceStorageManagementPolicy() *schema.Resource { }, Set: schema.HashString, }, + + "match_blob_index_tag": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.StorageBlobIndexTagName, + }, + + "operation": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "==", + }, false), + Default: "==", + }, + + "value": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.StorageBlobIndexTagValue, + }, + }, + }, + }, }, }, }, @@ -100,22 +129,28 @@ func resourceStorageManagementPolicy() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "tier_to_cool_after_days_since_modification_greater_than": { - Type: schema.TypeInt, - Optional: true, - Default: nil, - ValidateFunc: validation.IntAtLeast(0), + Type: schema.TypeInt, + Optional: true, + Default: nil, + // todo: default change to -1 to allow value 0 in 3.0 + // for issue https://github.com/terraform-providers/terraform-provider-azurerm/issues/6158 + ValidateFunc: validation.IntBetween(0, 99999), }, "tier_to_archive_after_days_since_modification_greater_than": { - Type: schema.TypeInt, - Optional: true, - Default: nil, - ValidateFunc: validation.IntAtLeast(0), + Type: schema.TypeInt, + Optional: true, + Default: nil, + // todo: default change to -1 to allow value 0 in 3.0 + // for issue https://github.com/terraform-providers/terraform-provider-azurerm/issues/6158 + ValidateFunc: validation.IntBetween(0, 99999), }, "delete_after_days_since_modification_greater_than": { - Type: schema.TypeInt, - Optional: true, - Default: nil, - ValidateFunc: validation.IntAtLeast(0), + Type: schema.TypeInt, + Optional: true, + Default: nil, + // todo: default change to -1 to allow value 0 in 3.0 + // for issue https://github.com/terraform-providers/terraform-provider-azurerm/issues/6158 + ValidateFunc: validation.IntBetween(0, 99999), }, }, }, @@ -127,10 +162,51 @@ func resourceStorageManagementPolicy() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "change_tier_to_archive_after_days_since_creation": { + Type: schema.TypeInt, + Optional: true, + Default: -1, + ValidateFunc: validation.IntBetween(0, 99999), + }, + "change_tier_to_cool_after_days_since_creation": { + Type: schema.TypeInt, + Optional: true, + Default: -1, + ValidateFunc: validation.IntBetween(0, 99999), + }, "delete_after_days_since_creation_greater_than": { + Type: schema.TypeInt, + Optional: true, + // todo: default change to -1 to allow value 0 in 3.0 + // for issue https://github.com/terraform-providers/terraform-provider-azurerm/issues/6158 + ValidateFunc: validation.IntBetween(0, 99999), + }, + }, + }, + }, + "version": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "change_tier_to_archive_after_days_since_creation": { + Type: schema.TypeInt, + Optional: true, + Default: -1, + ValidateFunc: validation.IntBetween(0, 99999), + }, + "change_tier_to_cool_after_days_since_creation": { + Type: schema.TypeInt, + Optional: true, + Default: -1, + ValidateFunc: validation.IntBetween(0, 99999), + }, + "delete_after_days_since_creation": { Type: schema.TypeInt, Optional: true, - ValidateFunc: validation.IntAtLeast(0), + Default: -1, + ValidateFunc: validation.IntBetween(0, 99999), }, }, }, @@ -178,12 +254,12 @@ func resourceStorageManagementPolicyCreateOrUpdate(d *schema.ResourceData, meta result, err := client.CreateOrUpdate(ctx, resourceGroupName, storageAccountName, parameters) if err != nil { - return fmt.Errorf("Error creating Azure Storage Management Policy %q: %+v", storageAccountId, err) + return fmt.Errorf("creating Azure Storage Management Policy %q: %+v", storageAccountId, err) } result, err = client.Get(ctx, resourceGroupName, storageAccountName) if err != nil { - return fmt.Errorf("Error getting created Azure Storage Management Policy %q: %+v", storageAccountId, err) + return fmt.Errorf("getting created Azure Storage Management Policy %q: %+v", storageAccountId, err) } d.SetId(*result.ID) @@ -218,7 +294,7 @@ func resourceStorageManagementPolicyRead(d *schema.ResourceData, meta interface{ policy := result.Policy if rules := policy.Rules; rules != nil { if err := d.Set("rule", flattenStorageManagementPolicyRules(policy.Rules)); err != nil { - return fmt.Errorf("Error flattening `rule`: %+v", err) + return fmt.Errorf("flattening `rule`: %+v", err) } } } @@ -254,7 +330,14 @@ func expandStorageManagementPolicyRules(d *schema.ResourceData) (*[]storage.Mana for k, v := range rules { if v != nil { - result = append(result, expandStorageManagementPolicyRule(d, k)) + rule := expandStorageManagementPolicyRule(d, k) + _, blobIndexExist := d.GetOk(fmt.Sprintf("rule.%d.filters.0.match_blob_index_tag", k)) + _, snapshotExist := d.GetOk(fmt.Sprintf("rule.%d.actions.0.snapshot", k)) + _, versionExist := d.GetOk(fmt.Sprintf("rule.%d.actions.0.version", k)) + if blobIndexExist && (snapshotExist || versionExist) { + return nil, fmt.Errorf("`match_blob_index_tag` is not supported as a filter for versions and snapshots") + } + result = append(result, rule) } } return &result, nil @@ -291,6 +374,8 @@ func expandStorageManagementPolicyRule(d *schema.ResourceData, ruleIndex int) st } } definition.Filters.BlobTypes = &blobTypes + + definition.Filters.BlobIndexMatch = expandAzureRmStorageBlobIndexMatch(filterRef["match_blob_index_tag"].(*schema.Set).List()) } } if _, ok := d.GetOk(fmt.Sprintf("rule.%d.actions", ruleIndex)); ok { @@ -326,8 +411,38 @@ func expandStorageManagementPolicyRule(d *schema.ResourceData, ruleIndex int) st v2 := float64(v.(int)) snapshot.Delete = &storage.DateAfterCreation{DaysAfterCreationGreaterThan: &v2} } + if v := d.Get(fmt.Sprintf("rule.%d.actions.0.snapshot.0.change_tier_to_archive_after_days_since_creation", ruleIndex)); v != -1 { + snapshot.TierToArchive = &storage.DateAfterCreation{ + DaysAfterCreationGreaterThan: utils.Float(float64(v.(int))), + } + } + if v := d.Get(fmt.Sprintf("rule.%d.actions.0.snapshot.0.change_tier_to_cool_after_days_since_creation", ruleIndex)); v != -1 { + snapshot.TierToCool = &storage.DateAfterCreation{ + DaysAfterCreationGreaterThan: utils.Float(float64(v.(int))), + } + } definition.Actions.Snapshot = snapshot } + + if _, ok := d.GetOk(fmt.Sprintf("rule.%d.actions.0.version", ruleIndex)); ok { + version := &storage.ManagementPolicyVersion{} + if v := d.Get(fmt.Sprintf("rule.%d.actions.0.version.0.delete_after_days_since_creation", ruleIndex)); v != -1 { + version.Delete = &storage.DateAfterCreation{ + DaysAfterCreationGreaterThan: utils.Float(float64(v.(int))), + } + } + if v := d.Get(fmt.Sprintf("rule.%d.actions.0.version.0.change_tier_to_archive_after_days_since_creation", ruleIndex)); v != -1 { + version.TierToArchive = &storage.DateAfterCreation{ + DaysAfterCreationGreaterThan: utils.Float(float64(v.(int))), + } + } + if v := d.Get(fmt.Sprintf("rule.%d.actions.0.version.0.change_tier_to_cool_after_days_since_creation", ruleIndex)); v != -1 { + version.TierToCool = &storage.DateAfterCreation{ + DaysAfterCreationGreaterThan: utils.Float(float64(v.(int))), + } + } + definition.Actions.Version = version + } } rule := storage.ManagementPolicyRule{ @@ -373,6 +488,9 @@ func flattenStorageManagementPolicyRules(armRules *[]storage.ManagementPolicyRul } filter["blob_types"] = blobTypes } + + filter["match_blob_index_tag"] = flattenAzureRmStorageBlobIndexMatch(armFilter.BlobIndexMatch) + rule["filters"] = [1]interface{}{filter} } @@ -399,12 +517,39 @@ func flattenStorageManagementPolicyRules(armRules *[]storage.ManagementPolicyRul armActionSnaphost := armAction.Snapshot if armActionSnaphost != nil { - snapshot := make(map[string]interface{}) + deleteAfterCreation, archiveAfterCreation, coolAfterCreation := 0, -1, -1 if armActionSnaphost.Delete != nil && armActionSnaphost.Delete.DaysAfterCreationGreaterThan != nil { - intTemp := int(*armActionSnaphost.Delete.DaysAfterCreationGreaterThan) - snapshot["delete_after_days_since_creation_greater_than"] = intTemp + deleteAfterCreation = int(*armActionSnaphost.Delete.DaysAfterCreationGreaterThan) + } + if armActionSnaphost.TierToArchive != nil && armActionSnaphost.TierToArchive.DaysAfterCreationGreaterThan != nil { + archiveAfterCreation = int(*armActionSnaphost.TierToArchive.DaysAfterCreationGreaterThan) + } + if armActionSnaphost.TierToCool != nil && armActionSnaphost.TierToCool.DaysAfterCreationGreaterThan != nil { + coolAfterCreation = int(*armActionSnaphost.TierToCool.DaysAfterCreationGreaterThan) + } + action["snapshot"] = [1]interface{}{map[string]interface{}{ + "delete_after_days_since_creation_greater_than": deleteAfterCreation, + "change_tier_to_archive_after_days_since_creation": archiveAfterCreation, + "change_tier_to_cool_after_days_since_creation": coolAfterCreation, + }} + } + + if armActionVersion := armAction.Version; armActionVersion != nil { + deleteAfterCreation, archiveAfterCreation, coolAfterCreation := -1, -1, -1 + if armActionVersion.Delete != nil && armActionVersion.Delete.DaysAfterCreationGreaterThan != nil { + deleteAfterCreation = int(*armActionVersion.Delete.DaysAfterCreationGreaterThan) + } + if armActionVersion.TierToArchive != nil && armActionVersion.TierToArchive.DaysAfterCreationGreaterThan != nil { + archiveAfterCreation = int(*armActionVersion.TierToArchive.DaysAfterCreationGreaterThan) + } + if armActionVersion.TierToCool != nil && armActionVersion.TierToCool.DaysAfterCreationGreaterThan != nil { + coolAfterCreation = int(*armActionVersion.TierToCool.DaysAfterCreationGreaterThan) } - action["snapshot"] = [1]interface{}{snapshot} + action["version"] = [1]interface{}{map[string]interface{}{ + "delete_after_days_since_creation": deleteAfterCreation, + "change_tier_to_archive_after_days_since_creation": archiveAfterCreation, + "change_tier_to_cool_after_days_since_creation": coolAfterCreation, + }} } rule["actions"] = [1]interface{}{action} @@ -416,3 +561,51 @@ func flattenStorageManagementPolicyRules(armRules *[]storage.ManagementPolicyRul return rules } + +func expandAzureRmStorageBlobIndexMatch(blobIndexMatches []interface{}) *[]storage.TagFilter { + if len(blobIndexMatches) == 0 { + return nil + } + + results := make([]storage.TagFilter, 0) + for _, v := range blobIndexMatches { + blobIndexMatch := v.(map[string]interface{}) + + filter := storage.TagFilter{ + Name: utils.String(blobIndexMatch["name"].(string)), + Op: utils.String(blobIndexMatch["operation"].(string)), + Value: utils.String(blobIndexMatch["value"].(string)), + } + + results = append(results, filter) + } + + return &results +} + +func flattenAzureRmStorageBlobIndexMatch(blobIndexMatches *[]storage.TagFilter) []map[string]interface{} { + result := make([]map[string]interface{}, 0) + + if blobIndexMatches == nil || len(*blobIndexMatches) == 0 { + return result + } + + for _, blobIndexMatch := range *blobIndexMatches { + var name, op, value string + if blobIndexMatch.Name != nil { + name = *blobIndexMatch.Name + } + if blobIndexMatch.Op != nil { + op = *blobIndexMatch.Op + } + if blobIndexMatch.Value != nil { + value = *blobIndexMatch.Value + } + result = append(result, map[string]interface{}{ + "name": name, + "operation": op, + "value": value, + }) + } + return result +} diff --git a/azurerm/internal/services/storage/storage_management_policy_resource_test.go b/azurerm/internal/services/storage/storage_management_policy_resource_test.go index 4b4218b0e49e..9216751878b3 100644 --- a/azurerm/internal/services/storage/storage_management_policy_resource_test.go +++ b/azurerm/internal/services/storage/storage_management_policy_resource_test.go @@ -264,6 +264,101 @@ func TestAccStorageManagementPolicy_blobTypes(t *testing.T) { }) } +func TestAccStorageManagementPolicy_blobIndexMatch(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_storage_management_policy", "test") + r := StorageManagementPolicyResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.blobIndexMatchDisabled(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.blobIndexMatch(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.blobIndexMatchDisabled(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccStorageManagementPolicy_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_storage_management_policy", "test") + r := StorageManagementPolicyResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccStorageManagementPolicy_zero(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_storage_management_policy", "test") + r := StorageManagementPolicyResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.zero(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccStorageManagementPolicy_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_storage_management_policy", "test") + r := StorageManagementPolicyResource{} + + 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.completeUpdate(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 StorageManagementPolicyResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) { storageAccountId := state.Attributes["storage_account_id"] id, err := parse.StorageAccountID(storageAccountId) @@ -585,3 +680,259 @@ resource "azurerm_storage_management_policy" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomString) } + +func (r StorageManagementPolicyResource) blobIndexMatchTemplate(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-storage-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "unlikely23exst2acct%s" + resource_group_name = azurerm_resource_group.test.name + + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_role_assignment" "test" { + scope = azurerm_storage_account.test.id + role_definition_name = "Storage Blob Data Owner" + principal_id = data.azurerm_client_config.current.object_id +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r StorageManagementPolicyResource) blobIndexMatch(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_storage_management_policy" "test" { + storage_account_id = azurerm_storage_account.test.id + + rule { + name = "rule1" + enabled = true + filters { + prefix_match = ["container1/prefix1"] + blob_types = ["blockBlob"] + + match_blob_index_tag { + name = "tag1" + value = "val1" + } + + match_blob_index_tag { + name = "tag2" + operation = "==" + value = "val2" + } + } + actions { + base_blob { + tier_to_cool_after_days_since_modification_greater_than = 10 + tier_to_archive_after_days_since_modification_greater_than = 50 + delete_after_days_since_modification_greater_than = 100 + } + } + } + + depends_on = [azurerm_role_assignment.test] +} +`, r.blobIndexMatchTemplate(data)) +} + +func (r StorageManagementPolicyResource) blobIndexMatchDisabled(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_storage_management_policy" "test" { + storage_account_id = azurerm_storage_account.test.id + + rule { + name = "rule1" + enabled = true + filters { + prefix_match = ["container1/prefix1"] + blob_types = ["blockBlob"] + } + actions { + base_blob { + tier_to_cool_after_days_since_modification_greater_than = 10 + tier_to_archive_after_days_since_modification_greater_than = 50 + delete_after_days_since_modification_greater_than = 100 + } + } + } + + depends_on = [azurerm_role_assignment.test] +} +`, r.blobIndexMatchTemplate(data)) +} + +func (r StorageManagementPolicyResource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-storage-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "unlikely23exst2acct%s" + resource_group_name = azurerm_resource_group.test.name + + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" + account_kind = "BlobStorage" +} + +resource "azurerm_storage_management_policy" "test" { + storage_account_id = azurerm_storage_account.test.id + + rule { + name = "rule1" + enabled = true + filters { + prefix_match = ["container1/prefix1"] + blob_types = ["blockBlob"] + } + actions { + base_blob { + tier_to_cool_after_days_since_modification_greater_than = 10 + tier_to_archive_after_days_since_modification_greater_than = 50 + delete_after_days_since_modification_greater_than = 100 + } + snapshot { + change_tier_to_archive_after_days_since_creation = 90 + change_tier_to_cool_after_days_since_creation = 23 + delete_after_days_since_creation_greater_than = 30 + } + version { + change_tier_to_archive_after_days_since_creation = 9 + change_tier_to_cool_after_days_since_creation = 90 + delete_after_days_since_creation = 3 + } + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r StorageManagementPolicyResource) completeUpdate(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-storage-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "unlikely23exst2acct%s" + resource_group_name = azurerm_resource_group.test.name + + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" + account_kind = "BlobStorage" +} + +resource "azurerm_storage_management_policy" "test" { + storage_account_id = azurerm_storage_account.test.id + + rule { + name = "rule2" + enabled = true + filters { + prefix_match = ["container2/prefix2"] + blob_types = ["blockBlob"] + } + actions { + base_blob { + tier_to_cool_after_days_since_modification_greater_than = 11 + tier_to_archive_after_days_since_modification_greater_than = 51 + delete_after_days_since_modification_greater_than = 101 + } + snapshot { + change_tier_to_archive_after_days_since_creation = 91 + change_tier_to_cool_after_days_since_creation = 24 + delete_after_days_since_creation_greater_than = 31 + } + version { + change_tier_to_archive_after_days_since_creation = 10 + change_tier_to_cool_after_days_since_creation = 91 + delete_after_days_since_creation = 4 + } + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r StorageManagementPolicyResource) zero(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-storage-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "unlikely23exst2acct%s" + resource_group_name = azurerm_resource_group.test.name + + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" + account_kind = "BlobStorage" +} + +resource "azurerm_storage_management_policy" "test" { + storage_account_id = azurerm_storage_account.test.id + + rule { + name = "rule1" + enabled = true + filters { + prefix_match = ["container1/prefix1"] + blob_types = ["blockBlob"] + } + actions { + base_blob { + tier_to_cool_after_days_since_modification_greater_than = 10 + tier_to_archive_after_days_since_modification_greater_than = 50 + delete_after_days_since_modification_greater_than = 100 + } + snapshot { + change_tier_to_archive_after_days_since_creation = 0 + change_tier_to_cool_after_days_since_creation = 0 + delete_after_days_since_creation_greater_than = 30 + } + version { + change_tier_to_archive_after_days_since_creation = 0 + change_tier_to_cool_after_days_since_creation = 0 + delete_after_days_since_creation = 0 + } + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} diff --git a/azurerm/internal/services/storage/validate/storage_blob_index_tag.go b/azurerm/internal/services/storage/validate/storage_blob_index_tag.go new file mode 100644 index 000000000000..67f93ca81239 --- /dev/null +++ b/azurerm/internal/services/storage/validate/storage_blob_index_tag.go @@ -0,0 +1,23 @@ +package validate + +import ( + "fmt" +) + +func StorageBlobIndexTagName(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + if len(value) == 0 || len(value) > 128 { + errors = append(errors, fmt.Errorf( + "%q must be between 1 and 128 characters: %q", k, value)) + } + return warnings, errors +} + +func StorageBlobIndexTagValue(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + if len(value) > 256 { + errors = append(errors, fmt.Errorf( + "%q must be between 0 and 256 characters: %q", k, value)) + } + return warnings, errors +} diff --git a/azurerm/internal/services/storage/validate/storage_blob_index_tag_test.go b/azurerm/internal/services/storage/validate/storage_blob_index_tag_test.go new file mode 100644 index 000000000000..cab46d26ab22 --- /dev/null +++ b/azurerm/internal/services/storage/validate/storage_blob_index_tag_test.go @@ -0,0 +1,53 @@ +package validate + +import ( + "strings" + "testing" +) + +func TestStorageBlobIndexTagName(t *testing.T) { + validNames := []string{ + "valid-name", + "valid02-name", + strings.Repeat("w", 128), + } + for _, v := range validNames { + _, errors := StorageBlobIndexTagName(v, "name") + if len(errors) != 0 { + t.Fatalf("%q should be a valid Blob Index Tag Name: %q", v, errors) + } + } + + invalidNames := []string{ + "", + strings.Repeat("w", 129), + } + for _, v := range invalidNames { + if _, errors := StorageBlobIndexTagName(v, "name"); len(errors) == 0 { + t.Fatalf("%q should be an invalid Blob Index Tag Name", v) + } + } +} + +func TestStorageBlobIndexTagValue(t *testing.T) { + validNames := []string{ + "valid-name", + "", + strings.Repeat("w", 256), + } + for _, v := range validNames { + _, errors := StorageBlobIndexTagValue(v, "name") + if len(errors) != 0 { + t.Fatalf("%q should be a valid Blob Index Tag Value: %q", v, errors) + } + } + + invalidNames := []string{ + strings.Repeat("w", 257), + } + for _, v := range invalidNames { + if _, errors := StorageBlobIndexTagValue(v, "name"); len(errors) == 0 { + t.Fatalf("%q should be an invalid Blob Index Tag Value", v) + } + } +} diff --git a/website/docs/d/storage_management_policy.html.markdown b/website/docs/d/storage_management_policy.html.markdown index 3b270accb395..b71f0719d479 100644 --- a/website/docs/d/storage_management_policy.html.markdown +++ b/website/docs/d/storage_management_policy.html.markdown @@ -49,13 +49,14 @@ The following arguments are supported: * `prefix_match` - An array of strings for prefixes to be matched. * `blob_types` - An array of predefined values. Valid options are `blockBlob` and `appendBlob`. - +* `match_blob_index_tag` - A `match_blob_index_tag` block as defined below. The block defines the blob index tag based filtering for blob objects. --- `actions` supports the following: * `base_blob` - A `base_blob` block as documented below. * `snapshot` - A `snapshot` block as documented below. +* `version` - A `version` block as documented below. --- @@ -69,7 +70,26 @@ The following arguments are supported: `snapshot` supports the following: -* `delete_after_days_since_creation_greater_than` - The age in days after create to delete the snapshot. +* `change_tier_to_archive_after_days_since_creation` - The age in days after creation to tier blob snapshot to archive storage. +* `change_tier_to_cool_after_days_since_creation` - The age in days after creation to tier blob snapshot to cool storage. +* `delete_after_days_since_creation_greater_than` - The age in days after creation to delete the blob snapshot. + +--- + +`version` supports the following: + +* `change_tier_to_archive_after_days_since_creation` - The age in days after creation to tier blob version to archive storage. +* `change_tier_to_cool_after_days_since_creation` - The age in days after creation to tier blob version to cool storage. +* `delete_after_days_since_creation` - The age in days after creation to delete the blob version. + +--- + +`match_blob_index_tag` supports the following: + +* `name` - The filter tag name used for tag based filtering for blob objects. +* `operation` - The comparison operator which is used for object comparison and filtering. Possible value is `==`. Defaults to `==`. +* `value` - The filter tag value used for tag based filtering for blob objects. + ## Timeouts diff --git a/website/docs/r/storage_management_policy.html.markdown b/website/docs/r/storage_management_policy.html.markdown index c538d4da272b..c242f1ae4518 100644 --- a/website/docs/r/storage_management_policy.html.markdown +++ b/website/docs/r/storage_management_policy.html.markdown @@ -37,6 +37,11 @@ resource "azurerm_storage_management_policy" "example" { filters { prefix_match = ["container1/prefix1"] blob_types = ["blockBlob"] + match_blob_index_tag { + name = "tag1" + operation = "==" + value = "val1" + } } actions { base_blob { @@ -63,7 +68,14 @@ resource "azurerm_storage_management_policy" "example" { delete_after_days_since_modification_greater_than = 101 } snapshot { - delete_after_days_since_creation_greater_than = 31 + change_tier_to_archive_after_days_since_creation = 90 + change_tier_to_cool_after_days_since_creation = 23 + delete_after_days_since_creation_greater_than = 31 + } + version { + change_tier_to_archive_after_days_since_creation = 9 + change_tier_to_cool_after_days_since_creation = 90 + delete_after_days_since_creation = 3 } } } @@ -93,27 +105,48 @@ The following arguments are supported: * `prefix_match` - An array of strings for prefixes to be matched. * `blob_types` - An array of predefined values. Valid options are `blockBlob` and `appendBlob`. +* `match_blob_index_tag` - A `match_blob_index_tag` block as defined below. The block defines the blob index tag based filtering for blob objects. +~> **NOTE:** The `match_blob_index_tag property requires enabling the `blobIndex` feature with [PSH or Cli commands](https://azure.microsoft.com/en-us/blog/manage-and-find-data-with-blob-index-for-azure-storage-now-in-preview/). --- `actions` supports the following: * `base_blob` - A `base_blob` block as documented below. * `snapshot` - A `snapshot` block as documented below. +* `version` - A `version` block as documented below. --- `base_blob` supports the following: -* `tier_to_cool_after_days_since_modification_greater_than` - The age in days after last modification to tier blobs to cool storage. Supports blob currently at Hot tier. Must be at least 0. -* `tier_to_archive_after_days_since_modification_greater_than` - The age in days after last modification to tier blobs to archive storage. Supports blob currently at Hot or Cool tier. Must be at least 0. -* `delete_after_days_since_modification_greater_than` - The age in days after last modification to delete the blob. Must be at least 0. +* `tier_to_cool_after_days_since_modification_greater_than` - The age in days after last modification to tier blobs to cool storage. Supports blob currently at Hot tier. Must be between 0 and 99999. +* `tier_to_archive_after_days_since_modification_greater_than` - The age in days after last modification to tier blobs to archive storage. Supports blob currently at Hot or Cool tier. Must be between 0 and 99999. +* `delete_after_days_since_modification_greater_than` - The age in days after last modification to delete the blob. Must be between 0 and 99999. --- `snapshot` supports the following: -* `delete_after_days_since_creation_greater_than` - The age in days after create to delete the snaphot. Must be at least 0. +* `change_tier_to_archive_after_days_since_creation` - The age in days after creation to tier blob snapshot to archive storage. Must be between 0 and 99999. +* `change_tier_to_cool_after_days_since_creation` - The age in days after creation to tier blob snapshot to cool storage. Must be between 0 and 99999. +* `delete_after_days_since_creation_greater_than` - The age in days after creation to delete the blob snapshot. Must be between 0 and 99999. + +--- + +`version` supports the following: + +* `change_tier_to_archive_after_days_since_creation` - The age in days after creation to tier blob version to archive storage. Must be between 0 and 99999. +* `change_tier_to_cool_after_days_since_creation` - The age in days creation create to tier blob version to cool storage. Must be between 0 and 99999. +* `delete_after_days_since_creation` - The age in days after creation to delete the blob version. Must be between 0 and 99999. + +--- + +`match_blob_index_tag` supports the following: + +* `name` - The filter tag name used for tag based filtering for blob objects. +* `operation` - The comparison operator which is used for object comparison and filtering. Possible value is `==`. Defaults to `==`. +* `value` - The filter tag value used for tag based filtering for blob objects. ## Attributes Reference