From 96188d68acc46d4c0a4fcc3936ffa5ca37612c76 Mon Sep 17 00:00:00 2001 From: Matthew Frahry Date: Sun, 14 Jul 2019 16:33:46 -0700 Subject: [PATCH] Add ACL support for Storage Table --- azurerm/resource_arm_storage_table.go | 139 ++++++++++++++++++++- azurerm/resource_arm_storage_table_test.go | 121 ++++++++++++++++++ website/docs/r/storage_table.html.markdown | 21 ++++ 3 files changed, 278 insertions(+), 3 deletions(-) diff --git a/azurerm/resource_arm_storage_table.go b/azurerm/resource_arm_storage_table.go index 9b79c6b40feb..1eb14b6b17c3 100644 --- a/azurerm/resource_arm_storage_table.go +++ b/azurerm/resource_arm_storage_table.go @@ -6,8 +6,10 @@ import ( "regexp" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" "github.com/tombuildsstuff/giovanni/storage/2018-11-09/table/tables" ) @@ -17,6 +19,7 @@ func resourceArmStorageTable() *schema.Resource { Create: resourceArmStorageTableCreate, Read: resourceArmStorageTableRead, Delete: resourceArmStorageTableDelete, + Update: resourceArmStorageTableUpdate, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -41,7 +44,42 @@ func resourceArmStorageTable() *schema.Resource { // TODO: deprecate this in the docs "resource_group_name": azure.SchemaResourceGroupNameDeprecated(), - // TODO: support for ACL's + "acl": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 64), + }, + "access_policy": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "expiry": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "permissions": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + }, + }, + }, + }, + }, + }, }, } } @@ -52,6 +90,8 @@ func resourceArmStorageTableCreate(d *schema.ResourceData, meta interface{}) err tableName := d.Get("name").(string) accountName := d.Get("storage_account_name").(string) + aclsRaw := d.Get("acl").(*schema.Set).List() + acls := expandStorageTableACLs(aclsRaw) resourceGroup, err := storageClient.FindResourceGroup(ctx, accountName) if err != nil { @@ -82,6 +122,10 @@ func resourceArmStorageTableCreate(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error creating Table %q within Storage Account %q: %s", tableName, accountName, err) } + if _, err := client.SetACL(ctx, accountName, tableName, acls); err != nil { + return fmt.Errorf("Error setting ACL's for Storage Table %q (Account %q / Resource Group %q): %+v", tableName, accountName, *resourceGroup, err) + } + d.SetId(id) return resourceArmStorageTableRead(d, meta) } @@ -122,15 +166,19 @@ func resourceArmStorageTableRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error retrieving Table %q in Storage Account %q: %s", id.TableName, id.AccountName, err) } - _, err = client.GetACL(ctx, id.AccountName, id.TableName) + acls, err := client.GetACL(ctx, id.AccountName, id.TableName) if err != nil { - return fmt.Errorf("Error retrieving Table %q in Storage Account %q: %s", id.TableName, id.AccountName, err) + return fmt.Errorf("Error retrieving ACL's %q in Storage Account %q: %s", id.TableName, id.AccountName, err) } d.Set("name", id.TableName) d.Set("storage_account_name", id.AccountName) d.Set("resource_group_name", resourceGroup) + if err := d.Set("acl", flattenStorageTableACLs(acls)); err != nil { + return fmt.Errorf("Error flattening `acl`: %+v", err) + } + return nil } @@ -166,6 +214,47 @@ func resourceArmStorageTableDelete(d *schema.ResourceData, meta interface{}) err return nil } +func resourceArmStorageTableUpdate(d *schema.ResourceData, meta interface{}) error { + storageClient := meta.(*ArmClient).storage + ctx := meta.(*ArmClient).StopContext + + id, err := tables.ParseResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup, err := storageClient.FindResourceGroup(ctx, id.AccountName) + if err != nil { + return fmt.Errorf("Error locating Resource Group: %s", err) + } + + if resourceGroup == nil { + log.Printf("Unable to determine Resource Group for Storage Account %q (assuming removed)", id.AccountName) + d.SetId("") + return nil + } + + client, err := storageClient.TablesClient(ctx, *resourceGroup, id.AccountName) + if err != nil { + return fmt.Errorf("Error building Table Client: %s", err) + } + + if d.HasChange("acl") { + log.Printf("[DEBUG] Updating the ACL's for Storage Table %q (Storage Account %q)", id.TableName, id.AccountName) + + aclsRaw := d.Get("acl").(*schema.Set).List() + acls := expandStorageTableACLs(aclsRaw) + + if _, err := client.SetACL(ctx, id.AccountName, id.TableName, acls); err != nil { + return fmt.Errorf("Error updating ACL's for Storage Table %q (Storage Account %q): %s", id.TableName, id.AccountName, err) + } + + log.Printf("[DEBUG] Updated the ACL's for Storage Table %q (Storage Account %q)", id.TableName, id.AccountName) + } + + return resourceArmStorageTableRead(d, meta) +} + func validateArmStorageTableName(v interface{}, k string) (warnings []string, errors []error) { value := v.(string) if value == "table" { @@ -181,3 +270,47 @@ func validateArmStorageTableName(v interface{}, k string) (warnings []string, er return warnings, errors } + +func expandStorageTableACLs(input []interface{}) []tables.SignedIdentifier { + results := make([]tables.SignedIdentifier, 0) + + for _, v := range input { + vals := v.(map[string]interface{}) + + policies := vals["access_policy"].([]interface{}) + policy := policies[0].(map[string]interface{}) + + identifier := tables.SignedIdentifier{ + Id: vals["id"].(string), + AccessPolicy: tables.AccessPolicy{ + Start: policy["start"].(string), + Expiry: policy["expiry"].(string), + Permission: policy["permissions"].(string), + }, + } + results = append(results, identifier) + } + + return results +} + +func flattenStorageTableACLs(input tables.GetACLResult) []interface{} { + result := make([]interface{}, 0) + + for _, v := range input.SignedIdentifiers { + output := map[string]interface{}{ + "id": v.Id, + "access_policy": []interface{}{ + map[string]interface{}{ + "start": v.AccessPolicy.Start, + "expiry": v.AccessPolicy.Expiry, + "permissions": v.AccessPolicy.Permission, + }, + }, + } + + result = append(result, output) + } + + return result +} diff --git a/azurerm/resource_arm_storage_table_test.go b/azurerm/resource_arm_storage_table_test.go index f759caf3c624..3c5421b5d99a 100644 --- a/azurerm/resource_arm_storage_table_test.go +++ b/azurerm/resource_arm_storage_table_test.go @@ -97,6 +97,45 @@ func TestAccAzureRMStorageTable_disappears(t *testing.T) { }) } +func TestAccAzureRMStorageTable_acl(t *testing.T) { + var table storage.Table + + ri := tf.AccRandTimeInt() + rs := strings.ToLower(acctest.RandString(11)) + location := testLocation() + resourceName := "azurerm_storage_table.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageTableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageTable_acl(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageTableExists(resourceName, &table), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAzureRMStorageTable_aclUpdated(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageTableExists(resourceName, &table), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testCheckAzureRMStorageTableExists(resourceName string, t *storage.Table) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -291,3 +330,85 @@ resource "azurerm_storage_table" "import" { } `, template) } + +func testAccAzureRMStorageTable_acl(rInt int, rString string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" + + tags = { + environment = "staging" + } +} + +resource "azurerm_storage_table" "test" { + name = "acctestst%d" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + acl { + id = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI" + + access_policy { + permissions = "raud" + start = "2020-11-26T08:49:37.0000000Z" + expiry = "2020-11-27T08:49:37.0000000Z" + } + } +} +`, rInt, location, rString, rInt) +} + +func testAccAzureRMStorageTable_aclUpdated(rInt int, rString string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" + + tags = { + environment = "staging" + } +} + +resource "azurerm_storage_table" "test" { + name = "acctestst%d" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + + acl { + id = "AAAANDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI" + + access_policy { + permissions = "raud" + start = "2020-11-26T08:49:37.0000000Z" + expiry = "2020-11-27T08:49:37.0000000Z" + } + } + acl { + id = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI" + + access_policy { + permissions = "raud" + start = "2019-07-02T09:38:21.0000000Z" + expiry = "2019-07-02T10:38:21.0000000Z" + } + } +} +`, rInt, location, rString, rInt) +} diff --git a/website/docs/r/storage_table.html.markdown b/website/docs/r/storage_table.html.markdown index 689ca6b59640..9be021abfc41 100644 --- a/website/docs/r/storage_table.html.markdown +++ b/website/docs/r/storage_table.html.markdown @@ -44,6 +44,27 @@ The following arguments are supported: * `resource_group_name` - (Optional / **Deprecated**) The name of the resource group in which to create the storage table. +* `acl` - (Optional) One or more `acl` blocks as defined below. + +--- + +A `acl` block supports the following: + +* `id` - (Required) The ID which should be used for this Shared Identifier. + +* `access_policy` - (Required) An `access_policy` block as defined below. + +--- + +A `access_policy` block supports the following: + +* `expiry` - (Required) The ISO8061 UTC time at which this Access Policy should be valid until. + +* `permissions` - (Required) The permissions which should associated with this Shared Identifier. + +* `start` - (Required) The ISO8061 UTC time at which this Access Policy should be valid from. + + ## Attributes Reference The following attributes are exported in addition to the arguments listed above: