Skip to content

Commit

Permalink
New resource - azurerm_storage_account_blob_settings (#3807)
Browse files Browse the repository at this point in the history
I was not sure if it's the right choice to create a new resource for these storage account settings instead of putting it to the storage account resources. I decided to create a separate resource since it's a separate api call and there are many settings that can be done. I'm happy for any feedback regarding this point.

I only took care of the soft delete settings of the storage account. Other settings like Cors rules can also be set with this client call. so this resource can easily be extended.

Rest API Reference: docs.microsoft.com/en-us/rest/api/storagerp/blobservices

(fixes #1070)
  • Loading branch information
benjamin37 authored and katbyte committed Dec 17, 2019
1 parent acc0b60 commit 6def60f
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 2 deletions.
8 changes: 6 additions & 2 deletions azurerm/internal/services/storage/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ type Client struct {
AccountsClient *storage.AccountsClient
FileSystemsClient *filesystems.Client
ManagementPoliciesClient storage.ManagementPoliciesClient

environment az.Environment
BlobServicesClient storage.BlobServicesClient
environment az.Environment
}

func NewClient(options *common.ClientOptions) *Client {
Expand All @@ -37,12 +37,16 @@ func NewClient(options *common.ClientOptions) *Client {
managementPoliciesClient := storage.NewManagementPoliciesClientWithBaseURI(options.ResourceManagerEndpoint, options.SubscriptionId)
options.ConfigureClient(&managementPoliciesClient.Client, options.ResourceManagerAuthorizer)

blobServicesClient := storage.NewBlobServicesClientWithBaseURI(options.ResourceManagerEndpoint, options.SubscriptionId)
options.ConfigureClient(&blobServicesClient.Client, options.ResourceManagerAuthorizer)

// TODO: switch Storage Containers to using the storage.BlobContainersClient
// (which should fix #2977) when the storage clients have been moved in here
return &Client{
AccountsClient: &accountsClient,
FileSystemsClient: &fileSystemsClient,
ManagementPoliciesClient: managementPoliciesClient,
BlobServicesClient: blobServicesClient,
environment: options.Environment,
}
}
Expand Down
124 changes: 124 additions & 0 deletions azurerm/resource_arm_storage_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,32 @@ func resourceArmStorageAccount() *schema.Resource {
},
},

"blob_properties": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"delete_retention_policy": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"days": {
Type: schema.TypeInt,
Optional: true,
Default: 7,
ValidateFunc: validation.IntBetween(1, 365),
},
},
},
},
},
},
},

"queue_properties": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -754,6 +780,21 @@ func resourceArmStorageAccountCreate(d *schema.ResourceData, meta interface{}) e
}
}

if val, ok := d.GetOk("blob_properties"); ok {
// FileStorage does not support blob settings
if accountKind != string(storage.FileStorage) {
blobClient := meta.(*ArmClient).Storage.BlobServicesClient

blobProperties := expandBlobProperties(val.([]interface{}))

if _, err = blobClient.SetServiceProperties(ctx, resourceGroupName, storageAccountName, blobProperties); err != nil {
return fmt.Errorf("Error updating Azure Storage Account `blob_properties` %q: %+v", storageAccountName, err)
}
} else {
return fmt.Errorf("`blob_properties` aren't supported for File Storage accounts.")
}
}

if val, ok := d.GetOk("queue_properties"); ok {
storageClient := meta.(*ArmClient).Storage
account, err := storageClient.FindAccount(ctx, storageAccountName)
Expand Down Expand Up @@ -960,6 +1001,22 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e
d.SetPartial("enable_advanced_threat_protection")
}

if d.HasChange("blob_properties") {
// FileStorage does not support blob settings
if accountKind != string(storage.FileStorage) {
blobClient := meta.(*ArmClient).Storage.BlobServicesClient
blobProperties := expandBlobProperties(d.Get("blob_properties").([]interface{}))

if _, err = blobClient.SetServiceProperties(ctx, resourceGroupName, storageAccountName, blobProperties); err != nil {
return fmt.Errorf("Error updating Azure Storage Account `blob_properties` %q: %+v", storageAccountName, err)
}

d.SetPartial("blob_properties")
} else {
return fmt.Errorf("`blob_properties` aren't supported for File Storage accounts.")
}
}

if d.HasChange("queue_properties") {
storageClient := meta.(*ArmClient).Storage
account, err := storageClient.FindAccount(ctx, storageAccountName)
Expand Down Expand Up @@ -1162,6 +1219,22 @@ func resourceArmStorageAccountRead(d *schema.ResourceData, meta interface{}) err
return fmt.Errorf("Unable to locate Storage Account %q!", name)
}

blobClient := storageClient.BlobServicesClient

// FileStorage does not support blob settings
if resp.Kind != storage.FileStorage {
blobProps, err := blobClient.GetServiceProperties(ctx, resGroup, name)
if err != nil {
if !utils.ResponseWasNotFound(blobProps.Response) {
return fmt.Errorf("Error reading blob properties for AzureRM Storage Account %q: %+v", name, err)
}
}

if err := d.Set("blob_properties", flattenBlobProperties(blobProps)); err != nil {
return fmt.Errorf("Error setting `blob_properties `for AzureRM Storage Account %q: %+v", name, err)
}
}

queueClient, err := storageClient.QueuesClient(ctx, *account)
if err != nil {
return fmt.Errorf("Error building Queues Client: %s", err)
Expand Down Expand Up @@ -1341,6 +1414,30 @@ func expandStorageAccountBypass(networkRule map[string]interface{}) storage.Bypa
return storage.Bypass(strings.Join(bypassValues, ", "))
}

func expandBlobProperties(input []interface{}) storage.BlobServiceProperties {
properties := storage.BlobServiceProperties{
BlobServicePropertiesProperties: &storage.BlobServicePropertiesProperties{
DeleteRetentionPolicy: &storage.DeleteRetentionPolicy{
Enabled: utils.Bool(false),
},
},
}
if len(input) == 0 || input[0] == nil {
return properties
}

blobAttr := input[0].(map[string]interface{})
deletePolicy := blobAttr["delete_retention_policy"].([]interface{})
if len(deletePolicy) > 0 {
policy := deletePolicy[0].(map[string]interface{})
days := policy["days"].(int)
properties.BlobServicePropertiesProperties.DeleteRetentionPolicy.Enabled = utils.Bool(true)
properties.BlobServicePropertiesProperties.DeleteRetentionPolicy.Days = utils.Int32(int32(days))
}

return properties
}

func expandQueueProperties(input []interface{}) (queues.StorageServiceProperties, error) {
var err error
properties := queues.StorageServiceProperties{}
Expand Down Expand Up @@ -1484,6 +1581,33 @@ func flattenStorageAccountVirtualNetworks(input *[]storage.VirtualNetworkRule) [
return virtualNetworks
}

func flattenBlobProperties(input storage.BlobServiceProperties) []interface{} {
if input.BlobServicePropertiesProperties == nil {
return []interface{}{}
}

deleteRetentionPolicies := make([]interface{}, 0)

if deletePolicy := input.BlobServicePropertiesProperties.DeleteRetentionPolicy; deletePolicy != nil {
if enabled := deletePolicy.Enabled; enabled != nil && *enabled {
days := 0
if deletePolicy.Days != nil {
days = int(*deletePolicy.Days)
}

deleteRetentionPolicies = append(deleteRetentionPolicies, map[string]interface{}{
"days": days,
})
}
}

return []interface{}{
map[string]interface{}{
"delete_retention_policy": deleteRetentionPolicies,
},
}
}

func flattenQueueProperties(input queues.StorageServicePropertiesResponse) []interface{} {
if input.Response.Response == nil {
return []interface{}{}
Expand Down
88 changes: 88 additions & 0 deletions azurerm/resource_arm_storage_account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,47 @@ func TestAccAzureRMStorageAccount_enableAdvancedThreatProtection(t *testing.T) {
})
}

func TestAccAzureRMStorageAccount_blobProperties(t *testing.T) {
resourceName := "azurerm_storage_account.testsa"
ri := tf.AccRandTimeInt()
rs := acctest.RandString(4)
location := testLocation()
preConfig := testAccAzureRMStorageAccount_blobProperties(ri, rs, location)
postConfig := testAccAzureRMStorageAccount_blobPropertiesUpdated(ri, rs, location)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMStorageAccountDestroy,
Steps: []resource.TestStep{
{
Config: preConfig,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMStorageAccountExists(resourceName),
resource.TestCheckResourceAttr("azurerm_storage_account.testsa", "blob_properties.0.delete_retention_policy.0.days", "300"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: postConfig,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMStorageAccountExists(resourceName),
resource.TestCheckResourceAttr("azurerm_storage_account.testsa", "blob_properties.0.delete_retention_policy.0.days", "7"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAzureRMStorageAccount_queueProperties(t *testing.T) {
resourceName := "azurerm_storage_account.testsa"
ri := tf.AccRandTimeInt()
Expand Down Expand Up @@ -1509,6 +1550,53 @@ resource "azurerm_storage_account" "testsa" {
`, rInt, location, rString)
}

func testAccAzureRMStorageAccount_blobProperties(rInt int, rString string, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "testrg" {
name = "acctestAzureRMSA-%d"
location = "%s"
}
resource "azurerm_storage_account" "testsa" {
name = "unlikely23exst2acct%s"
resource_group_name = "${azurerm_resource_group.testrg.name}"
location = "${azurerm_resource_group.testrg.location}"
account_tier = "Standard"
account_replication_type = "LRS"
blob_properties {
delete_retention_policy {
days = 300
}
}
}
`, rInt, location, rString)
}

func testAccAzureRMStorageAccount_blobPropertiesUpdated(rInt int, rString string, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "testrg" {
name = "acctestAzureRMSA-%d"
location = "%s"
}
resource "azurerm_storage_account" "testsa" {
name = "unlikely23exst2acct%s"
resource_group_name = "${azurerm_resource_group.testrg.name}"
location = "${azurerm_resource_group.testrg.location}"
account_tier = "Standard"
account_replication_type = "LRS"
blob_properties {
delete_retention_policy {
}
}
}
`, rInt, location, rString)
}

func testAccAzureRMStorageAccount_queueProperties(rInt int, rString string, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "testrg" {
Expand Down
14 changes: 14 additions & 0 deletions website/docs/r/storage_account.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ The following arguments are supported:

* `identity` - (Optional) A `identity` block as defined below.

* `blob_properties` - (Optional) A `blob_properties` block as defined below.

* `queue_properties` - (Optional) A `queue_properties` block as defined below.

~> **NOTE:** `queue_properties` cannot be set when the `access_tier` is set to `BlobStorage`
Expand All @@ -122,6 +124,12 @@ The following arguments are supported:

---

A `blob_properties` block supports the following:

* `delete_retention_policy` - (Optional) A `delete_retention_policy` block as defined below.

---

A `cors_rule` block supports the following:

* `allowed_headers` - (Required) A list of headers that are allowed to be a part of the cross-origin request.
Expand All @@ -142,6 +150,12 @@ A `custom_domain` block supports the following:
* `name` - (Optional) The Custom Domain Name to use for the Storage Account, which will be validated by Azure.
* `use_subdomain` - (Optional) Should the Custom Domain Name be validated by using indirect CNAME validation?

---

A `delete_retention_policy` block supports the following:

* `days` - (Optional) Specifies the number of days that the blob should be retained, between `1` and `365` days. Defaults to `7`.

---

A `hour_metrics` block supports the following:
Expand Down

0 comments on commit 6def60f

Please sign in to comment.