diff --git a/internal/services/appservice/linux_function_app_resource.go b/internal/services/appservice/linux_function_app_resource.go index 84b0a290b071..2b7654b492fd 100644 --- a/internal/services/appservice/linux_function_app_resource.go +++ b/internal/services/appservice/linux_function_app_resource.go @@ -55,6 +55,7 @@ type LinuxFunctionAppModel struct { HttpsOnly bool `tfschema:"https_only"` KeyVaultReferenceIdentityID string `tfschema:"key_vault_reference_identity_id"` SiteConfig []helpers.SiteConfigLinuxFunctionApp `tfschema:"site_config"` + StorageAccounts []helpers.StorageAccount `tfschema:"storage_account"` Tags map[string]string `tfschema:"tags"` VirtualNetworkSubnetID string `tfschema:"virtual_network_subnet_id"` @@ -251,6 +252,8 @@ func (r LinuxFunctionAppResource) Arguments() map[string]*pluginsdk.Schema { "sticky_settings": helpers.StickySettingsSchema(), + "storage_account": helpers.StorageAccountSchema(), + "tags": tags.Schema(), "virtual_network_subnet_id": { @@ -493,7 +496,7 @@ func (r LinuxFunctionAppResource) Create() sdk.ResourceFunc { SlotConfigNames: stickySettings, } if _, err := client.UpdateSlotConfigurationNames(ctx, id.ResourceGroup, id.SiteName, stickySettingsUpdate); err != nil { - return fmt.Errorf("updating Sticky Settings for Windows %s: %+v", id, err) + return fmt.Errorf("updating Sticky Settings for Linux %s: %+v", id, err) } } @@ -511,6 +514,15 @@ func (r LinuxFunctionAppResource) Create() sdk.ResourceFunc { } } + storageConfig := helpers.ExpandStorageConfig(functionApp.StorageAccounts) + if storageConfig.Properties != nil { + if _, err := client.UpdateAzureStorageAccounts(ctx, id.ResourceGroup, id.SiteName, *storageConfig); err != nil { + if err != nil { + return fmt.Errorf("setting Storage Accounts for Linux %s: %+v", id, err) + } + } + } + connectionStrings := helpers.ExpandConnectionStrings(functionApp.ConnectionStrings) if connectionStrings.Properties != nil { if _, err := client.UpdateConnectionStrings(ctx, id.ResourceGroup, id.SiteName, *connectionStrings); err != nil { @@ -568,6 +580,11 @@ func (r LinuxFunctionAppResource) Read() sdk.ResourceFunc { return fmt.Errorf("reading Sticky Settings for Linux %s: %+v", id, err) } + storageAccounts, err := client.ListAzureStorageAccounts(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("reading Storage Account information for Linux %s: %+v", id, err) + } + siteCredentialsFuture, err := client.ListPublishingCredentials(ctx, id.ResourceGroup, id.SiteName) if err != nil { return fmt.Errorf("listing Site Publishing Credential information for Linux %s: %+v", id, err) @@ -648,6 +665,8 @@ func (r LinuxFunctionAppResource) Read() sdk.ResourceFunc { state.SiteConfig[0].AppServiceLogs = helpers.FlattenFunctionAppAppServiceLogs(logs) + state.StorageAccounts = helpers.FlattenStorageAccounts(storageAccounts) + state.HttpsOnly = utils.NormaliseNilableBool(functionApp.HTTPSOnly) state.ClientCertEnabled = utils.NormaliseNilableBool(functionApp.ClientCertEnabled) @@ -777,6 +796,13 @@ func (r LinuxFunctionAppResource) Update() sdk.ResourceFunc { existing.Tags = tags.FromTypedObject(state.Tags) } + if metadata.ResourceData.HasChange("storage_account") { + storageAccountUpdate := helpers.ExpandStorageConfig(state.StorageAccounts) + if _, err := client.UpdateAzureStorageAccounts(ctx, id.ResourceGroup, id.SiteName, *storageAccountUpdate); err != nil { + return fmt.Errorf("updating Storage Accounts for Linux %s: %+v", id, err) + } + } + storageString := state.StorageAccountName if !state.StorageUsesMSI { if state.StorageKeyVaultSecretID != "" { @@ -789,7 +815,7 @@ func (r LinuxFunctionAppResource) Update() sdk.ResourceFunc { if sendContentSettings { appSettingsResp, err := client.ListApplicationSettings(ctx, id.ResourceGroup, id.SiteName) if err != nil { - return fmt.Errorf("reading App Settings for Windows %s: %+v", id, err) + return fmt.Errorf("reading App Settings for Linux %s: %+v", id, err) } if state.AppSettings == nil { state.AppSettings = make(map[string]string) diff --git a/internal/services/appservice/linux_function_app_resource_test.go b/internal/services/appservice/linux_function_app_resource_test.go index 35f0ac14565d..587404fcc125 100644 --- a/internal/services/appservice/linux_function_app_resource_test.go +++ b/internal/services/appservice/linux_function_app_resource_test.go @@ -535,6 +535,79 @@ func TestAccLinuxFunctionApp_withAuthSettingsStandard(t *testing.T) { }) } +func TestAccLinuxFunctionApp_withStorageAccountBlock(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_function_app", "test") + r := LinuxFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLinuxFunctionApp_withStorageAccountBlocks(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_function_app", "test") + r := LinuxFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withStorageAccountMultiple(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLinuxFunctionApp_withStorageAccountBlockUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_function_app", "test") + r := LinuxFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountMultiple(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccLinuxFunctionApp_scmIpRestrictionSubnet(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_linux_function_app", "test") r := LinuxFunctionAppResource{} @@ -3514,7 +3587,6 @@ resource "azurerm_linux_function_app" "test" { storage_account_access_key = azurerm_storage_account.test.primary_access_key site_config {} - } `, r.template(data, planSku), data.RandomInteger, data.RandomInteger) } @@ -3577,7 +3649,6 @@ resource "azurerm_linux_function_app" "test" { storage_account_access_key = azurerm_storage_account.test.primary_access_key site_config {} - } `, r.template(data, planSku), data.RandomInteger, data.RandomInteger) } @@ -3585,7 +3656,6 @@ resource "azurerm_linux_function_app" "test" { func (r LinuxFunctionAppResource) withASEV3(data acceptance.TestData) string { return fmt.Sprintf(` %s - resource "azurerm_storage_account" "test" { name = "acctestsa%s" resource_group_name = azurerm_resource_group.test.name @@ -3607,6 +3677,140 @@ resource "azurerm_linux_function_app" "test" { vnet_route_all_enabled = true } } - `, ServicePlanResource{}.aseV3Linux(data), data.RandomString, data.RandomInteger) } + +func (r LinuxFunctionAppResource) withStorageAccountSingle(data acceptance.TestData, planSKU string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_linux_function_app" "test" { + name = "acctestWA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config {} + + storage_account { + name = "files" + type = "AzureFiles" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "/files" + } +} +`, r.templateWithStorageAccountExtras(data, planSKU), data.RandomInteger) +} + +func (r LinuxFunctionAppResource) withStorageAccountMultiple(data acceptance.TestData, planSKU string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_linux_function_app" "test" { + name = "acctestWA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config {} + + storage_account { + name = "files" + type = "AzureFiles" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "/files" + } + + storage_account { + name = "blobs" + type = "AzureBlob" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test2.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "/blob" + } +} +`, r.templateWithStorageAccountExtras(data, planSKU), data.RandomInteger) +} + +func (r LinuxFunctionAppResource) templateWithStorageAccountExtras(data acceptance.TestData, planSKU string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_storage_container" "test" { + name = "test" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "private" +} + +resource "azurerm_storage_share" "test" { + name = "test" + storage_account_name = azurerm_storage_account.test.name + quota = 1 +} + +resource "azurerm_storage_container" "test2" { + name = "test2" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "private" +} + +resource "azurerm_storage_share" "test2" { + name = "test2" + storage_account_name = azurerm_storage_account.test.name + quota = 1 +} + +data "azurerm_storage_account_sas" "test" { + connection_string = azurerm_storage_account.test.primary_connection_string + https_only = true + resource_types { + service = false + container = false + object = true + } + services { + blob = true + queue = false + table = false + file = false + } + start = "2021-04-01" + expiry = "2024-03-30" + permissions { + read = false + write = true + delete = false + list = false + add = false + create = false + update = false + process = false + tag = false + filter = false + } +} +`, r.template(data, planSKU), data.RandomInteger) +} diff --git a/internal/services/appservice/linux_function_app_slot_resource.go b/internal/services/appservice/linux_function_app_slot_resource.go index cfc8bd8e92db..31cf2a7d0255 100644 --- a/internal/services/appservice/linux_function_app_slot_resource.go +++ b/internal/services/appservice/linux_function_app_slot_resource.go @@ -58,6 +58,7 @@ type LinuxFunctionAppSlotModel struct { PossibleOutboundIPAddresses string `tfschema:"possible_outbound_ip_addresses"` PossibleOutboundIPAddressList []string `tfschema:"possible_outbound_ip_address_list"` SiteCredentials []helpers.SiteCredential `tfschema:"site_credential"` + StorageAccounts []helpers.StorageAccount `tfschema:"storage_account"` } var _ sdk.ResourceWithUpdate = LinuxFunctionAppSlotResource{} @@ -232,6 +233,8 @@ func (r LinuxFunctionAppSlotResource) Arguments() map[string]*pluginsdk.Schema { "site_config": helpers.SiteConfigSchemaLinuxFunctionAppSlot(), + "storage_account": helpers.StorageAccountSchema(), + "tags": tags.Schema(), "virtual_network_subnet_id": { @@ -502,6 +505,15 @@ func (r LinuxFunctionAppSlotResource) Create() sdk.ResourceFunc { } } + storageConfig := helpers.ExpandStorageConfig(functionAppSlot.StorageAccounts) + if storageConfig.Properties != nil { + if _, err := client.UpdateAzureStorageAccountsSlot(ctx, id.ResourceGroup, id.SiteName, *storageConfig, id.SlotName); err != nil { + if err != nil { + return fmt.Errorf("setting Storage Accounts for Linux %s: %+v", id, err) + } + } + } + if _, ok := metadata.ResourceData.GetOk("site_config.0.app_service_logs"); ok { appServiceLogs := helpers.ExpandFunctionAppAppServiceLogs(functionAppSlot.SiteConfig[0].AppServiceLogs) if _, err := client.UpdateDiagnosticLogsConfigSlot(ctx, id.ResourceGroup, id.SiteName, appServiceLogs, id.SlotName); err != nil { @@ -524,18 +536,18 @@ func (r LinuxFunctionAppSlotResource) Read() sdk.ResourceFunc { if err != nil { return err } - functionApp, err := client.GetSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName) + functionAppSlot, err := client.GetSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName) if err != nil { - if utils.ResponseWasNotFound(functionApp.Response) { + if utils.ResponseWasNotFound(functionAppSlot.Response) { return metadata.MarkAsGone(id) } return fmt.Errorf("reading Linux %s: %+v", id, err) } - if functionApp.SiteProperties == nil { + if functionAppSlot.SiteProperties == nil { return fmt.Errorf("reading properties of Linux %s", id) } - props := *functionApp.SiteProperties + props := *functionAppSlot.SiteProperties appSettingsResp, err := client.ListApplicationSettingsSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName) if err != nil { @@ -572,6 +584,11 @@ func (r LinuxFunctionAppSlotResource) Read() sdk.ResourceFunc { } } + storageAccounts, err := client.ListAzureStorageAccountsSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName) + if err != nil { + return fmt.Errorf("reading Storage Account information for Linux %s: %+v", id, err) + } + logs, err := client.GetDiagnosticLogsConfigurationSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName) if err != nil { return fmt.Errorf("reading logs configuration for Linux %s: %+v", id, err) @@ -580,12 +597,12 @@ func (r LinuxFunctionAppSlotResource) Read() sdk.ResourceFunc { state := LinuxFunctionAppSlotModel{ Name: id.SlotName, FunctionAppID: parse.NewFunctionAppID(id.SubscriptionId, id.ResourceGroup, id.SiteName).ID(), - Enabled: utils.NormaliseNilableBool(functionApp.Enabled), - ClientCertMode: string(functionApp.ClientCertMode), - ClientCertExclusionPaths: utils.NormalizeNilableString(functionApp.ClientCertExclusionPaths), + Enabled: utils.NormaliseNilableBool(functionAppSlot.Enabled), + ClientCertMode: string(functionAppSlot.ClientCertMode), + ClientCertExclusionPaths: utils.NormalizeNilableString(functionAppSlot.ClientCertExclusionPaths), DailyMemoryTimeQuota: int(utils.NormaliseNilableInt32(props.DailyMemoryTimeQuota)), - Tags: tags.ToTypedObject(functionApp.Tags), - Kind: utils.NormalizeNilableString(functionApp.Kind), + Tags: tags.ToTypedObject(functionAppSlot.Tags), + Kind: utils.NormalizeNilableString(functionAppSlot.Kind), KeyVaultReferenceIdentityID: utils.NormalizeNilableString(props.KeyVaultReferenceIdentity), CustomDomainVerificationId: utils.NormalizeNilableString(props.CustomDomainVerificationID), DefaultHostname: utils.NormalizeNilableString(props.DefaultHostName), @@ -614,8 +631,10 @@ func (r LinuxFunctionAppSlotResource) Read() sdk.ResourceFunc { state.SiteConfig[0].AppServiceLogs = helpers.FlattenFunctionAppAppServiceLogs(logs) - state.HttpsOnly = utils.NormaliseNilableBool(functionApp.HTTPSOnly) - state.ClientCertEnabled = utils.NormaliseNilableBool(functionApp.ClientCertEnabled) + state.StorageAccounts = helpers.FlattenStorageAccounts(storageAccounts) + + state.HttpsOnly = utils.NormaliseNilableBool(functionAppSlot.HTTPSOnly) + state.ClientCertEnabled = utils.NormaliseNilableBool(functionAppSlot.ClientCertEnabled) if subnetId := utils.NormalizeNilableString(props.VirtualNetworkSubnetID); subnetId != "" { state.VirtualNetworkSubnetID = subnetId @@ -625,7 +644,7 @@ func (r LinuxFunctionAppSlotResource) Read() sdk.ResourceFunc { return fmt.Errorf("encoding: %+v", err) } - flattenedIdentity, err := flattenIdentity(functionApp.Identity) + flattenedIdentity, err := flattenIdentity(functionAppSlot.Identity) if err != nil { return fmt.Errorf("flattening `identity`: %+v", err) } @@ -746,6 +765,13 @@ func (r LinuxFunctionAppSlotResource) Update() sdk.ResourceFunc { } } + if metadata.ResourceData.HasChange("storage_account") { + storageAccountUpdate := helpers.ExpandStorageConfig(state.StorageAccounts) + if _, err := client.UpdateAzureStorageAccountsSlot(ctx, id.ResourceGroup, id.SiteName, *storageAccountUpdate, id.SlotName); err != nil { + return fmt.Errorf("updating Storage Accounts for Linux %s: %+v", id, err) + } + } + if sendContentSettings { appSettingsResp, err := client.ListApplicationSettingsSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName) if err != nil { diff --git a/internal/services/appservice/linux_function_app_slot_resource_test.go b/internal/services/appservice/linux_function_app_slot_resource_test.go index f21f556fb046..3f5393b2d49b 100644 --- a/internal/services/appservice/linux_function_app_slot_resource_test.go +++ b/internal/services/appservice/linux_function_app_slot_resource_test.go @@ -1052,6 +1052,79 @@ func TestAccLinuxFunctionAppSlotASEv3_basic(t *testing.T) { }) } +func TestAccLinuxFunctionAppSlot_withStorageAccountBlock(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_function_app_slot", "test") + r := LinuxFunctionAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLinuxFunctionAppSlot_withStorageAccountBlocks(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_function_app_slot", "test") + r := LinuxFunctionAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withStorageAccountMultiple(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLinuxFunctionAppSlot_withStorageAccountBlockUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_function_app_slot", "test") + r := LinuxFunctionAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountMultiple(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + // Configs func (r LinuxFunctionAppSlotResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { @@ -2882,3 +2955,127 @@ resource "azurerm_linux_function_app_slot" "test" { `, ServicePlanResource{}.aseV3Linux(data), data.RandomString, data.RandomInteger) } + +func (r LinuxFunctionAppSlotResource) withStorageAccountSingle(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_linux_function_app_slot" "test" { + name = "acctest-LFAS-%d" + function_app_id = azurerm_linux_function_app.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + storage_account { + name = "files" + type = "AzureFiles" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "/storage/files" + } + + site_config {} +} +`, r.templateWithStorageAccountExtras(data, planSku), data.RandomInteger) +} + +func (r LinuxFunctionAppSlotResource) withStorageAccountMultiple(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_linux_function_app_slot" "test" { + name = "acctest-LFAS-%d" + function_app_id = azurerm_linux_function_app.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + storage_account { + name = "files" + type = "AzureFiles" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "/storage/files" + } + + storage_account { + name = "blobs" + type = "AzureBlob" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "/storage/blobs" + } + + site_config {} +} +`, r.templateWithStorageAccountExtras(data, planSku), data.RandomInteger) +} + +func (r LinuxFunctionAppSlotResource) templateWithStorageAccountExtras(data acceptance.TestData, planSKU string) string { + return fmt.Sprintf(` + +%s + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_storage_container" "test" { + name = "test" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "private" +} + +resource "azurerm_storage_share" "test" { + name = "test" + storage_account_name = azurerm_storage_account.test.name + quota = 1 +} + +data "azurerm_storage_account_sas" "test" { + connection_string = azurerm_storage_account.test.primary_connection_string + https_only = true + + resource_types { + service = false + container = false + object = true + } + + services { + blob = true + queue = false + table = false + file = false + } + + start = "2021-04-01" + expiry = "2024-03-30" + + permissions { + read = false + write = true + delete = false + list = false + add = false + create = false + update = false + process = false + tag = false + filter = false + } +} +`, r.template(data, planSKU), data.RandomInteger) +} diff --git a/internal/services/appservice/windows_function_app_resource.go b/internal/services/appservice/windows_function_app_resource.go index 6947a12238cb..967f672aa965 100644 --- a/internal/services/appservice/windows_function_app_resource.go +++ b/internal/services/appservice/windows_function_app_resource.go @@ -55,6 +55,7 @@ type WindowsFunctionAppModel struct { HttpsOnly bool `tfschema:"https_only"` KeyVaultReferenceIdentityID string `tfschema:"key_vault_reference_identity_id"` SiteConfig []helpers.SiteConfigWindowsFunctionApp `tfschema:"site_config"` + StorageAccounts []helpers.StorageAccount `tfschema:"storage_account"` Tags map[string]string `tfschema:"tags"` VirtualNetworkSubnetID string `tfschema:"virtual_network_subnet_id"` @@ -251,6 +252,8 @@ func (r WindowsFunctionAppResource) Arguments() map[string]*pluginsdk.Schema { "sticky_settings": helpers.StickySettingsSchema(), + "storage_account": helpers.StorageAccountSchemaWindows(), + "tags": tags.Schema(), "virtual_network_subnet_id": { @@ -397,6 +400,7 @@ func (r WindowsFunctionAppResource) Create() sdk.ResourceFunc { storageString = fmt.Sprintf(helpers.StorageStringFmt, functionApp.StorageAccountName, functionApp.StorageAccountKey, metadata.Client.Account.Environment.StorageEndpointSuffix) } } + siteConfig, err := helpers.ExpandSiteConfigWindowsFunctionApp(functionApp.SiteConfig, nil, metadata, functionApp.FunctionExtensionsVersion, storageString, functionApp.StorageUsesMSI) if err != nil { return fmt.Errorf("expanding site_config for Windows %s: %+v", id, err) @@ -510,6 +514,15 @@ func (r WindowsFunctionAppResource) Create() sdk.ResourceFunc { } } + storageConfig := helpers.ExpandStorageConfig(functionApp.StorageAccounts) + if storageConfig.Properties != nil { + if _, err := client.UpdateAzureStorageAccounts(ctx, id.ResourceGroup, id.SiteName, *storageConfig); err != nil { + if err != nil { + return fmt.Errorf("setting Storage Accounts for Windows %s: %+v", id, err) + } + } + } + connectionStrings := helpers.ExpandConnectionStrings(functionApp.ConnectionStrings) if connectionStrings.Properties != nil { if _, err := client.UpdateConnectionStrings(ctx, id.ResourceGroup, id.SiteName, *connectionStrings); err != nil { @@ -564,7 +577,12 @@ func (r WindowsFunctionAppResource) Read() sdk.ResourceFunc { stickySettings, err := client.ListSlotConfigurationNames(ctx, id.ResourceGroup, id.SiteName) if err != nil { - return fmt.Errorf("reading Sticky Settings for Linux %s: %+v", id, err) + return fmt.Errorf("reading Sticky Settings for Windows %s: %+v", id, err) + } + + storageAccounts, err := client.ListAzureStorageAccounts(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("reading Storage Account information for Windows %s: %+v", id, err) } siteCredentialsFuture, err := client.ListPublishingCredentials(ctx, id.ResourceGroup, id.SiteName) @@ -647,6 +665,8 @@ func (r WindowsFunctionAppResource) Read() sdk.ResourceFunc { state.SiteConfig[0].AppServiceLogs = helpers.FlattenFunctionAppAppServiceLogs(logs) + state.StorageAccounts = helpers.FlattenStorageAccounts(storageAccounts) + state.HttpsOnly = utils.NormaliseNilableBool(functionApp.HTTPSOnly) state.ClientCertEnabled = utils.NormaliseNilableBool(functionApp.ClientCertEnabled) @@ -776,6 +796,13 @@ func (r WindowsFunctionAppResource) Update() sdk.ResourceFunc { } } + if metadata.ResourceData.HasChange("storage_account") { + storageAccountUpdate := helpers.ExpandStorageConfig(state.StorageAccounts) + if _, err := client.UpdateAzureStorageAccounts(ctx, id.ResourceGroup, id.SiteName, *storageAccountUpdate); err != nil { + return fmt.Errorf("updating Storage Accounts for Windows %s: %+v", id, err) + } + } + storageString := state.StorageAccountName if !state.StorageUsesMSI { if state.StorageKeyVaultSecretID != "" { @@ -865,7 +892,7 @@ func (r WindowsFunctionAppResource) Update() sdk.ResourceFunc { } if _, err := client.UpdateSlotConfigurationNames(ctx, id.ResourceGroup, id.SiteName, stickySettingsUpdate); err != nil { - return fmt.Errorf("updating Sticky Settings for Linux %s: %+v", id, err) + return fmt.Errorf("updating Sticky Settings for Windows %s: %+v", id, err) } } diff --git a/internal/services/appservice/windows_function_app_resource_test.go b/internal/services/appservice/windows_function_app_resource_test.go index f195f04dc627..3481a90e3667 100644 --- a/internal/services/appservice/windows_function_app_resource_test.go +++ b/internal/services/appservice/windows_function_app_resource_test.go @@ -527,6 +527,79 @@ func TestAccWindowsFunctionApp_withAuthSettingsStandard(t *testing.T) { }) } +func TestAccWindowsFunctionApp_withStorageAccountBlock(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withStorageAccountBlocks(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withStorageAccountMultiple(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withStorageAccountBlockUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountMultiple(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccWindowsFunctionApp_builtInLogging(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") r := WindowsFunctionAppResource{} @@ -3156,8 +3229,10 @@ resource "azurerm_subnet" "test1" { resource_group_name = azurerm_resource_group.test.name virtual_network_name = azurerm_virtual_network.test.name address_prefixes = ["10.0.1.0/24"] + delegation { name = "delegation" + service_delegation { name = "Microsoft.Web/serverFarms" actions = ["Microsoft.Network/virtualNetworks/subnets/action"] @@ -3170,8 +3245,10 @@ resource "azurerm_subnet" "test2" { resource_group_name = azurerm_resource_group.test.name virtual_network_name = azurerm_virtual_network.test.name address_prefixes = ["10.0.2.0/24"] + delegation { name = "delegation" + service_delegation { name = "Microsoft.Web/serverFarms" actions = ["Microsoft.Network/virtualNetworks/subnets/action"] @@ -3187,9 +3264,9 @@ resource "azurerm_windows_function_app" "test" { virtual_network_subnet_id = azurerm_subnet.test1.id storage_account_name = azurerm_storage_account.test.name storage_account_access_key = azurerm_storage_account.test.primary_access_key + site_config {} } - `, r.template(data, planSku), data.RandomInteger, data.RandomInteger) } @@ -3211,8 +3288,10 @@ resource "azurerm_subnet" "test1" { resource_group_name = azurerm_resource_group.test.name virtual_network_name = azurerm_virtual_network.test.name address_prefixes = ["10.0.1.0/24"] + delegation { name = "delegation" + service_delegation { name = "Microsoft.Web/serverFarms" actions = ["Microsoft.Network/virtualNetworks/subnets/action"] @@ -3225,8 +3304,10 @@ resource "azurerm_subnet" "test2" { resource_group_name = azurerm_resource_group.test.name virtual_network_name = azurerm_virtual_network.test.name address_prefixes = ["10.0.2.0/24"] + delegation { name = "delegation" + service_delegation { name = "Microsoft.Web/serverFarms" actions = ["Microsoft.Network/virtualNetworks/subnets/action"] @@ -3244,7 +3325,6 @@ resource "azurerm_windows_function_app" "test" { storage_account_access_key = azurerm_storage_account.test.primary_access_key site_config {} } - `, r.template(data, planSku), data.RandomInteger, data.RandomInteger) } @@ -3273,6 +3353,147 @@ resource "azurerm_windows_function_app" "test" { vnet_route_all_enabled = true } } - `, ServicePlanResource{}.aseV3(data), data.RandomString, data.RandomInteger) } + +func (r WindowsFunctionAppResource) withStorageAccountSingle(data acceptance.TestData, planSKU string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctestWA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config {} + + storage_account { + name = "files" + type = "AzureFiles" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "\\mounts\\files" + } + +} +`, r.templateWithStorageAccountExtras(data, planSKU), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) withStorageAccountMultiple(data acceptance.TestData, planSKU string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctestWA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config {} + + storage_account { + name = "files" + type = "AzureFiles" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "\\mounts\\files" + } + + storage_account { + name = "morefiles" + type = "AzureFiles" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test2.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "\\mounts\\morefiles" + } + +} +`, r.templateWithStorageAccountExtras(data, planSKU), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) templateWithStorageAccountExtras(data acceptance.TestData, planSKU string) string { + return fmt.Sprintf(` + +%s + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_storage_container" "test" { + name = "test" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "private" +} + +resource "azurerm_storage_share" "test" { + name = "test" + storage_account_name = azurerm_storage_account.test.name + quota = 1 +} + +resource "azurerm_storage_container" "test2" { + name = "test2" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "private" +} + +resource "azurerm_storage_share" "test2" { + name = "test2" + storage_account_name = azurerm_storage_account.test.name + quota = 1 +} + +data "azurerm_storage_account_sas" "test" { + connection_string = azurerm_storage_account.test.primary_connection_string + https_only = true + + resource_types { + service = false + container = false + object = true + } + + services { + blob = true + queue = false + table = false + file = false + } + + start = "2021-04-01" + expiry = "2024-03-30" + + permissions { + read = false + write = true + delete = false + list = false + add = false + create = false + update = false + process = false + tag = false + filter = false + } +} +`, r.template(data, planSKU), data.RandomInteger) +} diff --git a/internal/services/appservice/windows_function_app_slot_resource.go b/internal/services/appservice/windows_function_app_slot_resource.go index aaace3101bc2..3c4134b8cadb 100644 --- a/internal/services/appservice/windows_function_app_slot_resource.go +++ b/internal/services/appservice/windows_function_app_slot_resource.go @@ -57,6 +57,7 @@ type WindowsFunctionAppSlotModel struct { PossibleOutboundIPAddresses string `tfschema:"possible_outbound_ip_addresses"` PossibleOutboundIPAddressList []string `tfschema:"possible_outbound_ip_address_list"` SiteCredentials []helpers.SiteCredential `tfschema:"site_credential"` + StorageAccounts []helpers.StorageAccount `tfschema:"storage_account"` VirtualNetworkSubnetID string `tfschema:"virtual_network_subnet_id"` } @@ -232,6 +233,8 @@ func (r WindowsFunctionAppSlotResource) Arguments() map[string]*pluginsdk.Schema "site_config": helpers.SiteConfigSchemaWindowsFunctionAppSlot(), + "storage_account": helpers.StorageAccountSchemaWindows(), + "tags": tags.Schema(), "virtual_network_subnet_id": { @@ -504,6 +507,15 @@ func (r WindowsFunctionAppSlotResource) Create() sdk.ResourceFunc { } } + storageConfig := helpers.ExpandStorageConfig(functionAppSlot.StorageAccounts) + if storageConfig.Properties != nil { + if _, err := client.UpdateAzureStorageAccountsSlot(ctx, id.ResourceGroup, id.SiteName, *storageConfig, id.SlotName); err != nil { + if err != nil { + return fmt.Errorf("setting Storage Accounts for Windows %s: %+v", id, err) + } + } + } + connectionStrings := helpers.ExpandConnectionStrings(functionAppSlot.ConnectionStrings) if connectionStrings.Properties != nil { if _, err := client.UpdateConnectionStringsSlot(ctx, id.ResourceGroup, id.SiteName, *connectionStrings, id.SlotName); err != nil { @@ -556,6 +568,11 @@ func (r WindowsFunctionAppSlotResource) Read() sdk.ResourceFunc { return fmt.Errorf("reading Connection String information for Windows %s: %+v", id, err) } + storageAccounts, err := client.ListAzureStorageAccountsSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName) + if err != nil { + return fmt.Errorf("reading Storage Account information for Windows %s: %+v", id, err) + } + siteCredentialsFuture, err := client.ListPublishingCredentialsSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName) if err != nil { return fmt.Errorf("listing Site Publishing Credential information for Windows %s: %+v", id, err) @@ -623,6 +640,8 @@ func (r WindowsFunctionAppSlotResource) Read() sdk.ResourceFunc { state.SiteConfig[0].AppServiceLogs = helpers.FlattenFunctionAppAppServiceLogs(logs) + state.StorageAccounts = helpers.FlattenStorageAccounts(storageAccounts) + state.HttpsOnly = utils.NormaliseNilableBool(functionApp.HTTPSOnly) state.ClientCertEnabled = utils.NormaliseNilableBool(functionApp.ClientCertEnabled) @@ -748,6 +767,13 @@ func (r WindowsFunctionAppSlotResource) Update() sdk.ResourceFunc { } } + if metadata.ResourceData.HasChange("storage_account") { + storageAccountUpdate := helpers.ExpandStorageConfig(state.StorageAccounts) + if _, err := client.UpdateAzureStorageAccountsSlot(ctx, id.ResourceGroup, id.SiteName, *storageAccountUpdate, id.SlotName); err != nil { + return fmt.Errorf("updating Storage Accounts for Windows %s: %+v", id, err) + } + } + storageString := state.StorageAccountName if !state.StorageUsesMSI { if state.StorageKeyVaultSecretID != "" { diff --git a/internal/services/appservice/windows_function_app_slot_resource_test.go b/internal/services/appservice/windows_function_app_slot_resource_test.go index d8c6de553727..4dbb8b9da507 100644 --- a/internal/services/appservice/windows_function_app_slot_resource_test.go +++ b/internal/services/appservice/windows_function_app_slot_resource_test.go @@ -98,8 +98,6 @@ func TestAccWindowsFunctionAppSlot_basicStandardPlan(t *testing.T) { }) } -// App Settings by Plan Type - func TestAccWindowsFunctionAppSlot_withAppSettingsConsumption(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_function_app_slot", "test") r := WindowsFunctionAppSlotResource{} @@ -117,6 +115,8 @@ func TestAccWindowsFunctionAppSlot_withAppSettingsConsumption(t *testing.T) { }) } +// App Settings by Plan Type + func TestAccWindowsFunctionAppSlot_withAppSettingsElasticPremiumPlan(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_function_app_slot", "test") r := WindowsFunctionAppSlotResource{} @@ -986,6 +986,79 @@ func TestAccWindowsFunctionAppSlotASEv3_basic(t *testing.T) { }) } +func TestAccWindowsFunctionAppSlot_withStorageAccountBlock(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app_slot", "test") + r := WindowsFunctionAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionAppSlot_withStorageAccountBlocks(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app_slot", "test") + r := WindowsFunctionAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withStorageAccountMultiple(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionAppSlot_withStorageAccountBlocksUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app_slot", "test") + r := WindowsFunctionAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountMultiple(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withStorageAccountSingle(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + // Exists func (r WindowsFunctionAppSlotResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { @@ -2663,3 +2736,139 @@ resource "azurerm_windows_function_app_slot" "test" { `, ServicePlanResource{}.aseV3(data), data.RandomString, data.RandomInteger) } + +func (r WindowsFunctionAppSlotResource) withStorageAccountSingle(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app_slot" "test" { + name = "acctest-WFAS-%d" + function_app_id = azurerm_windows_function_app.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + storage_account { + name = "files" + type = "AzureFiles" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "\\mounts\\files" + } + + site_config {} +} +`, r.templateWithStorageAccountExtras(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppSlotResource) withStorageAccountMultiple(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app_slot" "test" { + name = "acctest-WFAS-%d" + function_app_id = azurerm_windows_function_app.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + storage_account { + name = "files" + type = "AzureFiles" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "\\mounts\\files" + } + + storage_account { + name = "morefiles" + type = "AzureFiles" + account_name = azurerm_storage_account.test.name + share_name = azurerm_storage_share.test2.name + access_key = azurerm_storage_account.test.primary_access_key + mount_path = "\\mounts\\morefiles" + } + + site_config {} +} +`, r.templateWithStorageAccountExtras(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppSlotResource) templateWithStorageAccountExtras(data acceptance.TestData, planSKU string) string { + return fmt.Sprintf(` + +%s + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_storage_container" "test" { + name = "test" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "private" +} + +resource "azurerm_storage_share" "test" { + name = "test" + storage_account_name = azurerm_storage_account.test.name + quota = 1 +} + +resource "azurerm_storage_container" "test2" { + name = "test2" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "private" +} + +resource "azurerm_storage_share" "test2" { + name = "test2" + storage_account_name = azurerm_storage_account.test.name + quota = 1 +} + +data "azurerm_storage_account_sas" "test" { + connection_string = azurerm_storage_account.test.primary_connection_string + https_only = true + + resource_types { + service = false + container = false + object = true + } + + services { + blob = true + queue = false + table = false + file = false + } + + start = "2021-04-01" + expiry = "2024-03-30" + + permissions { + read = false + write = true + delete = false + list = false + add = false + create = false + update = false + process = false + tag = false + filter = false + } +} +`, r.template(data, planSKU), data.RandomInteger) +} diff --git a/website/docs/r/linux_function_app.html.markdown b/website/docs/r/linux_function_app.html.markdown index bfc3b6338c4a..e8284873af44 100644 --- a/website/docs/r/linux_function_app.html.markdown +++ b/website/docs/r/linux_function_app.html.markdown @@ -102,6 +102,8 @@ The following arguments are supported: * `key_vault_reference_identity_id` - (Optional) The User Assigned Identity ID used for accessing KeyVault secrets. The identity must be assigned to the application in the `identity` block. [For more information see - Access vaults with a user-assigned identity](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references#access-vaults-with-a-user-assigned-identity) +* `storage_account` - (Optional) One or more `storage_account` blocks as defined below. + * `sticky_settings` - A `sticky_settings` block as defined below. * `storage_account_access_key` - (Optional) The access key which will be used to access the backend storage account for the Function App. Conflicts with `storage_uses_managed_identity`. @@ -469,6 +471,22 @@ A `sticky_settings` block supports the following: --- +A `storage_account` block supports the following: + +* `access_key` - (Required) The Access key for the storage account. + +* `account_name` - (Required) The Name of the Storage Account. + +* `name` - (Required) The name which should be used for this Storage Account. + +* `share_name` - (Required) The Name of the File Share or Container Name for Blob storage. + +* `type` - (Required) The Azure Storage Type. Possible values include `AzureFiles` and `AzureBlob`. + +* `mount_path` - (Optional) The path at which to mount the storage share. + +--- + A `twitter` block supports the following: * `consumer_key` - (Required) The OAuth 1.0a consumer key of the Twitter application used for sign-in. diff --git a/website/docs/r/linux_function_app_slot.html.markdown b/website/docs/r/linux_function_app_slot.html.markdown index 8c83b188acd6..f111b8b402ae 100644 --- a/website/docs/r/linux_function_app_slot.html.markdown +++ b/website/docs/r/linux_function_app_slot.html.markdown @@ -104,6 +104,8 @@ The following arguments are supported: * `storage_account_name` - (Optional) The backend storage account name which will be used by this Function App Slot. +* `storage_account` - (Optional) One or more `storage_account` blocks as defined below. + * `storage_uses_managed_identity` - (Optional) Should the Function App Slot use its Managed Identity to access storage. ~> **NOTE:** One of `storage_account_access_key` or `storage_uses_managed_identity` must be specified when using `storage_account_name`. @@ -478,6 +480,23 @@ A `scm_ip_restriction` block supports the following: ~> **NOTE:** One and only one of `ip_address`, `service_tag` or `virtual_network_subnet_id` must be specified. +--- + +A `storage_account` block supports the following: + +* `access_key` - (Required) The Access key for the storage account. + +* `account_name` - (Required) The Name of the Storage Account. + +* `name` - (Required) The name which should be used for this Storage Account. + +* `share_name` - (Required) The Name of the File Share or Container Name for Blob storage. + +* `type` - (Required) The Azure Storage Type. Possible values include `AzureFiles` and `AzureBlob`. + +* `mount_path` - (Optional) The path at which to mount the storage share. + + ## Attributes Reference In addition to the Arguments listed above - the following Attributes are exported: diff --git a/website/docs/r/windows_function_app.html.markdown b/website/docs/r/windows_function_app.html.markdown index 1298bfd0624f..d5c4d4dd75bc 100644 --- a/website/docs/r/windows_function_app.html.markdown +++ b/website/docs/r/windows_function_app.html.markdown @@ -102,6 +102,8 @@ The following arguments are supported: * `key_vault_reference_identity_id` - (Optional) The User Assigned Identity ID used for accessing KeyVault secrets. The identity must be assigned to the application in the `identity` block. [For more information see - Access vaults with a user-assigned identity](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references#access-vaults-with-a-user-assigned-identity) +* `storage_account` - (Optional) One or more `storage_account` blocks as defined below. + * `sticky_settings` - A `sticky_settings` block as defined below. * `storage_account_access_key` - (Optional) The access key which will be used to access the backend storage account for the Function App. Conflicts with `storage_uses_managed_identity`. @@ -443,6 +445,22 @@ A `sticky_settings` block exports the following: --- +A `storage_account` block supports the following: + +* `access_key` - (Required) The Access key for the storage account. + +* `account_name` - (Required) The Name of the Storage Account. + +* `name` - (Required) The name which should be used for this Storage Account. + +* `share_name` - (Required) The Name of the File Share or Container Name for Blob storage. + +* `type` - (Required) The Azure Storage Type. Possible values include `AzureFiles`. + +* `mount_path` - (Optional) The path at which to mount the storage share. + +--- + A `twitter` block supports the following: * `consumer_key` - (Required) The OAuth 1.0a consumer key of the Twitter application used for sign-in. diff --git a/website/docs/r/windows_function_app_slot.html.markdown b/website/docs/r/windows_function_app_slot.html.markdown index de9374804913..d7bb3c2d7d05 100644 --- a/website/docs/r/windows_function_app_slot.html.markdown +++ b/website/docs/r/windows_function_app_slot.html.markdown @@ -103,6 +103,8 @@ The following arguments are supported: * `storage_account_name` - (Optional) The backend storage account name which will be used by this Function App Slot. +* `storage_account` - (Optional) One or more `storage_account` blocks as defined below. + * `storage_uses_managed_identity` - (Optional) Should the Function App Slot use its Managed Identity to access storage. ~> **NOTE:** One of `storage_account_access_key` or `storage_uses_managed_identity` must be specified when using `storage_account_name`. @@ -451,6 +453,22 @@ A `headers` block supports the following: * `x_forwarded_host` - (Optional) Specifies a list of Hosts for which matching should be applied. +--- + +A `storage_account` block supports the following: + +* `access_key` - (Required) The Access key for the storage account. + +* `account_name` - (Required) The Name of the Storage Account. + +* `name` - (Required) The name which should be used for this Storage Account. + +* `share_name` - (Required) The Name of the File Share or Container Name for Blob storage. + +* `type` - (Required) The Azure Storage Type. Possible values include `AzureFiles`. + +* `mount_path` - (Optional) The path at which to mount the storage share. + ## Attributes Reference