Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_function_app - Added storage_account_id and storage_account_access_key #6304

Merged
merged 7 commits into from Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -356,11 +356,12 @@ resource "azurerm_app_service_plan" "test" {
}

resource "azurerm_function_app" "test" {
name = "acctestFA-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
app_service_plan_id = "${azurerm_app_service_plan.test.id}"
storage_connection_string = "${azurerm_storage_account.test.primary_connection_string}"
name = "acctestFA-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
app_service_plan_id = "${azurerm_app_service_plan.test.id}"
storage_account_name = azurerm_storage_account.test.name
storage_account_access_key = azurerm_storage_account.test.primary_access_key
}

data "azurerm_monitor_action_group" "test" {
Expand Down
Expand Up @@ -747,11 +747,12 @@ resource "azurerm_app_service_plan" "test" {
}

resource "azurerm_function_app" "test" {
name = "acctestFA-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
app_service_plan_id = azurerm_app_service_plan.test.id
storage_connection_string = azurerm_storage_account.test.primary_connection_string
name = "acctestFA-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
app_service_plan_id = azurerm_app_service_plan.test.id
storage_account_name = azurerm_storage_account.test.name
storage_account_access_key = azurerm_storage_account.test.primary_access_key
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger)
}
Expand Down Expand Up @@ -947,11 +948,12 @@ resource "azurerm_app_service_plan" "test" {
}

resource "azurerm_function_app" "test" {
name = "acctestFA-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
app_service_plan_id = "${azurerm_app_service_plan.test.id}"
storage_connection_string = "${azurerm_storage_account.test.primary_connection_string}"
name = "acctestFA-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
app_service_plan_id = "${azurerm_app_service_plan.test.id}"
storage_account_name = azurerm_storage_account.test.name
storage_account_access_key = azurerm_storage_account.test.primary_access_key
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger)
}
Expand Down
116 changes: 101 additions & 15 deletions azurerm/internal/services/web/resource_arm_function_app.go
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags"
azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
Expand Down Expand Up @@ -76,11 +77,35 @@ func resourceArmFunctionApp() *schema.Resource {
Default: "~1",
},

// TODO remove this in 3.0
"storage_connection_string": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Sensitive: true,
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
Sensitive: true,
Deprecated: "Deprecated in favor of `storage_account_name` and `storage_account_access_key`",
ConflictsWith: []string{"storage_account_name", "storage_account_access_key"},
},

"storage_account_name": {
Type: schema.TypeString,
// Required: true, // Uncomment this in 3.0
Optional: true,
Computed: true, // Remove this in 3.0
ForceNew: true,
ValidateFunc: storage.ValidateArmStorageAccountName,
ConflictsWith: []string{"storage_connection_string"},
},

"storage_account_access_key": {
Type: schema.TypeString,
Optional: true,
Computed: true, // Remove this in 3.0
// Required: true, // Uncomment this in 3.0
Sensitive: true,
ValidateFunc: validation.NoZeroValues,
ConflictsWith: []string{"storage_connection_string"},
},

"app_settings": {
Expand Down Expand Up @@ -281,6 +306,7 @@ func resourceArmFunctionApp() *schema.Resource {

func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Web.AppServicesClient
endpointSuffix := meta.(*clients.Client).Account.Environment.StorageEndpointSuffix
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()

Expand Down Expand Up @@ -335,7 +361,10 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro
return err
}

basicAppSettings := getBasicFunctionAppAppSettings(d, appServiceTier)
basicAppSettings, err := getBasicFunctionAppAppSettings(d, appServiceTier, endpointSuffix)
if err != nil {
return err
}

siteConfig, err := expandFunctionAppSiteConfig(d)
if err != nil {
Expand Down Expand Up @@ -400,6 +429,7 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro

func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Web.AppServicesClient
endpointSuffix := meta.(*clients.Client).Account.Environment.StorageEndpointSuffix
ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()

Expand All @@ -424,11 +454,15 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro
t := d.Get("tags").(map[string]interface{})

appServiceTier, err := getFunctionAppServiceTier(ctx, appServicePlanID, meta)
if err != nil {
return err
}

basicAppSettings, err := getBasicFunctionAppAppSettings(d, appServiceTier, endpointSuffix)
if err != nil {
return err
}
basicAppSettings := getBasicFunctionAppAppSettings(d, appServiceTier)

siteConfig, err := expandFunctionAppSiteConfig(d)
if err != nil {
return fmt.Errorf("Error expanding `site_config` for Function App %q (Resource Group %q): %s", id.Name, id.ResourceGroup, err)
Expand Down Expand Up @@ -465,7 +499,10 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro
return fmt.Errorf("Error waiting for update of Function App %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}

appSettings := expandFunctionAppAppSettings(d, appServiceTier)
appSettings, err := expandFunctionAppAppSettings(d, appServiceTier, endpointSuffix)
if err != nil {
return err
}
settings := web.StringDictionary{
Properties: appSettings,
}
Expand Down Expand Up @@ -593,7 +630,26 @@ func resourceArmFunctionAppRead(d *schema.ResourceData, meta interface{}) error

appSettings := flattenAppServiceAppSettings(appSettingsResp.Properties)

d.Set("storage_connection_string", appSettings["AzureWebJobsStorage"])
connectionString := appSettings["AzureWebJobsStorage"]
d.Set("storage_connection_string", connectionString)

// This teases out the necessary attributes from the storage connection string
connectionStringParts := strings.Split(connectionString, ";")
for _, part := range connectionStringParts {
if strings.HasPrefix(part, "AccountName") {
accountNameParts := strings.Split(part, "AccountName=")
if len(accountNameParts) > 1 {
d.Set("storage_account_name", accountNameParts[1])
}
}
if strings.HasPrefix(part, "AccountKey") {
accountKeyParts := strings.Split(part, "AccountKey=")
if len(accountKeyParts) > 1 {
d.Set("storage_account_access_key", accountKeyParts[1])
}
}
}

d.Set("version", appSettings["FUNCTIONS_EXTENSION_VERSION"])

dashboard, ok := appSettings["AzureWebJobsDashboard"]
Expand Down Expand Up @@ -664,7 +720,7 @@ func resourceArmFunctionAppDelete(d *schema.ResourceData, meta interface{}) erro
return nil
}

func getBasicFunctionAppAppSettings(d *schema.ResourceData, appServiceTier string) []web.NameValuePair {
func getBasicFunctionAppAppSettings(d *schema.ResourceData, appServiceTier, endpointSuffix string) ([]web.NameValuePair, error) {
// TODO: This is a workaround since there are no public Functions API
// You may track the API request here: https://github.com/Azure/azure-rest-api-specs/issues/3750
dashboardPropName := "AzureWebJobsDashboard"
Expand All @@ -673,7 +729,34 @@ func getBasicFunctionAppAppSettings(d *schema.ResourceData, appServiceTier strin
contentSharePropName := "WEBSITE_CONTENTSHARE"
contentFileConnStringPropName := "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING"

storageConnection := d.Get("storage_connection_string").(string)
// TODO 3.0 - remove this logic for determining which storage account connection string to use
storageConnection := ""
if v, ok := d.GetOk("storage_connection_string"); ok {
storageConnection = v.(string)
}

storageAccount := ""
if v, ok := d.GetOk("storage_account_name"); ok {
storageAccount = v.(string)
}

connectionString := ""
if v, ok := d.GetOk("storage_account_access_key"); ok {
connectionString = v.(string)
}

if storageConnection == "" && storageAccount == "" && connectionString == "" {
return nil, fmt.Errorf("one of `storage_connection_string` or `storage_account_name` and `storage_account_access_key` must be specified")
}

if (storageAccount == "" && connectionString != "") || (storageAccount != "" && connectionString == "") {
return nil, fmt.Errorf("both `storage_account_name` and `storage_account_access_key` must be specified")
}

if connectionString != "" && storageAccount != "" {
storageConnection = fmt.Sprintf("DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;EndpointSuffix=%s", storageAccount, connectionString, endpointSuffix)
}

functionVersion := d.Get("version").(string)
contentShare := strings.ToLower(d.Get("name").(string)) + "-content"

Expand All @@ -696,10 +779,10 @@ func getBasicFunctionAppAppSettings(d *schema.ResourceData, appServiceTier strin

// On consumption and premium plans include WEBSITE_CONTENT components
if strings.EqualFold(appServiceTier, "dynamic") || strings.EqualFold(appServiceTier, "elasticpremium") {
return append(basicSettings, consumptionSettings...)
return append(basicSettings, consumptionSettings...), nil
}

return basicSettings
return basicSettings, nil
}

func getFunctionAppServiceTier(ctx context.Context, appServicePlanId string, meta interface{}) (string, error) {
Expand All @@ -724,15 +807,18 @@ func getFunctionAppServiceTier(ctx context.Context, appServicePlanId string, met
return "", fmt.Errorf("No `sku` block was returned for App Service Plan ID %q", appServicePlanId)
}

func expandFunctionAppAppSettings(d *schema.ResourceData, appServiceTier string) map[string]*string {
func expandFunctionAppAppSettings(d *schema.ResourceData, appServiceTier, endpointSuffix string) (map[string]*string, error) {
output := expandAppServiceAppSettings(d)

basicAppSettings := getBasicFunctionAppAppSettings(d, appServiceTier)
basicAppSettings, err := getBasicFunctionAppAppSettings(d, appServiceTier, endpointSuffix)
if err != nil {
return nil, err
}
for _, p := range basicAppSettings {
output[*p.Name] = p.Value
}

return output
return output, nil
}

func expandFunctionAppSiteConfig(d *schema.ResourceData) (web.SiteConfig, error) {
Expand Down