From df2880b224ab7c0da6ce3c90595601253a9c9308 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Fri, 27 Aug 2021 13:21:06 +0100 Subject: [PATCH 01/16] Initial logic app resource creation --- .../logic/logic_app_standard_resource.go | 1452 +++++++++++ .../logic/logic_app_standard_resource_test.go | 2309 +++++++++++++++++ internal/services/logic/registration.go | 1 + 3 files changed, 3762 insertions(+) create mode 100644 internal/services/logic/logic_app_standard_resource.go create mode 100644 internal/services/logic/logic_app_standard_resource_test.go diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go new file mode 100644 index 000000000000..1cb0e33192b1 --- /dev/null +++ b/internal/services/logic/logic_app_standard_resource.go @@ -0,0 +1,1452 @@ +package logic + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + msiParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/validate" + storageValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/storage/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/web/parse" + webValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/web/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +func resourceLogicAppStandard() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceLogicAppStandardCreate, + Read: resourceLogicAppStandardRead, + Update: resourceLogicAppStandardUpdate, + Delete: resourceLogicAppStandardDelete, + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { + _, err := parse.FunctionAppID(id) + return err + }), + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Update: pluginsdk.DefaultTimeout(30 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: webValidate.AppServiceName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "app_service_plan_id": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "app_settings": { + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "client_affinity_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + + "client_cert_mode": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "Required", + "Optional", + }, false), + }, + + "daily_memory_time_quota": { + Type: pluginsdk.TypeInt, + Optional: true, + }, + + "enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + + "https_only": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "identity": schemaLogicAppStandardIdentity(), + + "site_config": schemaLogicAppStandardSiteConfig(), + + "connection_string": { + Type: pluginsdk.TypeSet, + Optional: true, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.ConnectionStringTypeAPIHub), + string(web.ConnectionStringTypeCustom), + string(web.ConnectionStringTypeDocDb), + string(web.ConnectionStringTypeEventHub), + string(web.ConnectionStringTypeMySQL), + string(web.ConnectionStringTypeNotificationHub), + string(web.ConnectionStringTypePostgreSQL), + string(web.ConnectionStringTypeRedisCache), + string(web.ConnectionStringTypeServiceBus), + string(web.ConnectionStringTypeSQLAzure), + string(web.ConnectionStringTypeSQLServer), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + + "value": { + Type: pluginsdk.TypeString, + Required: true, + Sensitive: true, + }, + }, + }, + }, + + "storage_account_name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: storageValidate.StorageAccountName, + }, + + "storage_account_access_key": { + Type: pluginsdk.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validation.NoZeroValues, + }, + + "version": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "~3", + }, + + "tags": tags.Schema(), + + // Computed Only + + "custom_domain_verification_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "default_hostname": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "kind": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "outbound_ip_addresses": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "possible_outbound_ip_addresses": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "site_credential": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "username": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "password": { + Type: pluginsdk.TypeString, + Computed: true, + Sensitive: true, + }, + }, + }, + }, + }, + } +} + +func resourceLogicAppStandardCreate(d *pluginsdk.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() + + log.Printf("[INFO] preparing arguments for AzureRM Logic App Standard creation.") + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing Logic App Standard %q (Resource Group %q): %s", name, resourceGroup, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_function_app", *existing.ID) + } + + availabilityRequest := web.ResourceNameAvailabilityRequest{ + Name: utils.String(name), + Type: web.CheckNameResourceTypesMicrosoftWebsites, + } + available, err := client.CheckNameAvailability(ctx, availabilityRequest) + if err != nil { + return fmt.Errorf("checking if the name %q was available: %+v", name, err) + } + + if !*available.NameAvailable { + return fmt.Errorf("name %q used for the Logic App Standard needs to be globally unique and isn't available: %s", name, *available.Message) + } + + location := azure.NormalizeLocation(d.Get("location").(string)) + kind := "functionapp,workflowapp" + appServicePlanID := d.Get("app_service_plan_id").(string) + enabled := d.Get("enabled").(bool) + clientAffinityEnabled := d.Get("client_affinity_enabled").(bool) + clientCertMode := d.Get("client_cert_mode").(string) + clientCertEnabled := clientCertMode != "" + httpsOnly := d.Get("https_only").(bool) + dailyMemoryTimeQuota := d.Get("daily_memory_time_quota").(int) + t := d.Get("tags").(map[string]interface{}) + appServiceTier, err := getLogicAppStandardServiceTier(ctx, appServicePlanID, meta) + if err != nil { + return err + } + + basicAppSettings, err := getBasicLogicAppSettings(d, appServiceTier, endpointSuffix) + if err != nil { + return err + } + + siteConfig, err := expandLogicAppStandardSiteConfig(d) + if err != nil { + return fmt.Errorf("expanding `site_config` for Logic App Standard %q (Resource Group %q): %s", name, resourceGroup, err) + } + + siteConfig.AppSettings = &basicAppSettings + + siteEnvelope := web.Site{ + Kind: &kind, + Location: &location, + Tags: tags.Expand(t), + SiteProperties: &web.SiteProperties{ + ServerFarmID: utils.String(appServicePlanID), + Enabled: utils.Bool(enabled), + ClientAffinityEnabled: utils.Bool(clientAffinityEnabled), + ClientCertEnabled: utils.Bool(clientCertEnabled), + HTTPSOnly: utils.Bool(httpsOnly), + DailyMemoryTimeQuota: utils.Int32(int32(dailyMemoryTimeQuota)), + SiteConfig: &siteConfig, + }, + } + + if clientCertMode != "" { + siteEnvelope.SiteProperties.ClientCertMode = web.ClientCertMode(clientCertMode) + } + + if _, ok := d.GetOk("identity"); ok { + appServiceIdentityRaw := d.Get("identity").([]interface{}) + appServiceIdentity := expandLogicAppStandardIdentity(appServiceIdentityRaw) + siteEnvelope.Identity = appServiceIdentity + } + + createFuture, err := client.CreateOrUpdate(ctx, resourceGroup, name, siteEnvelope) + if err != nil { + return err + } + + err = createFuture.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return err + } + + read, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return err + } + + if read.ID == nil || *read.ID == "" { + return fmt.Errorf("Cannot read Logic App Standard %s (resource group %s) ID", name, resourceGroup) + } + + d.SetId(*read.ID) + + return resourceLogicAppStandardUpdate(d, meta) +} + +func resourceLogicAppStandardUpdate(d *pluginsdk.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() + + id, err := parse.FunctionAppID(d.Id()) + if err != nil { + return err + } + + location := azure.NormalizeLocation(d.Get("location").(string)) + kind := "functionapp,workflowapp" + appServicePlanID := d.Get("app_service_plan_id").(string) + enabled := d.Get("enabled").(bool) + clientAffinityEnabled := d.Get("client_affinity_enabled").(bool) + clientCertMode := d.Get("client_cert_mode").(string) + clientCertEnabled := clientCertMode != "" + httpsOnly := d.Get("https_only").(bool) + dailyMemoryTimeQuota := d.Get("daily_memory_time_quota").(int) + t := d.Get("tags").(map[string]interface{}) + + appServiceTier, err := getLogicAppStandardServiceTier(ctx, appServicePlanID, meta) + if err != nil { + return err + } + + basicAppSettings, err := getBasicLogicAppSettings(d, appServiceTier, endpointSuffix) + if err != nil { + return err + } + + siteConfig, err := expandLogicAppStandardSiteConfig(d) + if err != nil { + return fmt.Errorf("expanding `site_config` for Logic App Standard %q (Resource Group %q): %s", id.SiteName, id.ResourceGroup, err) + } + + siteConfig.AppSettings = &basicAppSettings + + siteEnvelope := web.Site{ + Kind: &kind, + Location: &location, + Tags: tags.Expand(t), + SiteProperties: &web.SiteProperties{ + ServerFarmID: utils.String(appServicePlanID), + Enabled: utils.Bool(enabled), + ClientAffinityEnabled: utils.Bool(clientAffinityEnabled), + ClientCertEnabled: utils.Bool(clientCertEnabled), + HTTPSOnly: utils.Bool(httpsOnly), + DailyMemoryTimeQuota: utils.Int32(int32(dailyMemoryTimeQuota)), + SiteConfig: &siteConfig, + }, + } + + if clientCertMode != "" { + siteEnvelope.SiteProperties.ClientCertMode = web.ClientCertMode(clientCertMode) + } + + if _, ok := d.GetOk("identity"); ok { + appServiceIdentityRaw := d.Get("identity").([]interface{}) + appServiceIdentity := expandLogicAppStandardIdentity(appServiceIdentityRaw) + siteEnvelope.Identity = appServiceIdentity + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.SiteName, siteEnvelope) + if err != nil { + return fmt.Errorf("updating Logic App Standard %q (Resource Group %q): %+v", id.SiteName, id.ResourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for update of Logic App Standard %q (Resource Group %q): %+v", id.SiteName, id.ResourceGroup, err) + } + + appSettings, err := expandLogicAppStandardSettings(d, appServiceTier, endpointSuffix) + if err != nil { + return err + } + settings := web.StringDictionary{ + Properties: appSettings, + } + + if _, err = client.UpdateApplicationSettings(ctx, id.ResourceGroup, id.SiteName, settings); err != nil { + return fmt.Errorf("updating Application Settings for Logic App Standard %q: %+v", id.SiteName, err) + } + + if d.HasChange("site_config") { + siteConfig, err := expandLogicAppStandardSiteConfig(d) + if err != nil { + return fmt.Errorf("expanding `site_config` for Logic App Standard %q (Resource Group %q): %s", id.SiteName, id.ResourceGroup, err) + } + siteConfigResource := web.SiteConfigResource{ + SiteConfig: &siteConfig, + } + + if _, err := client.CreateOrUpdateConfiguration(ctx, id.ResourceGroup, id.SiteName, siteConfigResource); err != nil { + return fmt.Errorf("updating Configuration for Logic App Standard %q: %+v", id.SiteName, err) + } + } + + if d.HasChange("connection_string") { + connectionStrings := expandLogicAppStandardConnectionStrings(d) + properties := web.ConnectionStringDictionary{ + Properties: connectionStrings, + } + + if _, err := client.UpdateConnectionStrings(ctx, id.ResourceGroup, id.SiteName, properties); err != nil { + return fmt.Errorf("updating Connection Strings for App Service %q: %+v", id.SiteName, err) + } + } + + return resourceLogicAppStandardRead(d, meta) +} + +func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.AppServicesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.FunctionAppID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] Logic App Standard %q (resource group %q) was not found - removing from state", id.SiteName, id.ResourceGroup) + d.SetId("") + return nil + } + return fmt.Errorf("making Read request on AzureRM Logic App Standard %q: %+v", id.SiteName, err) + } + + appSettingsResp, err := client.ListApplicationSettings(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + if utils.ResponseWasNotFound(appSettingsResp.Response) { + log.Printf("[DEBUG] Application Settings of Logic App Standard %q (resource group %q) were not found", id.SiteName, id.ResourceGroup) + d.SetId("") + return nil + } + return fmt.Errorf("making Read request on AzureRM Logic App Standard AppSettings %q: %+v", id.SiteName, err) + } + + connectionStringsResp, err := client.ListConnectionStrings(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("making Read request on AzureRM Logic App Standard ConnectionStrings %q: %+v", id.SiteName, err) + } + + siteCredFuture, err := client.ListPublishingCredentials(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return err + } + err = siteCredFuture.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return err + } + siteCredResp, err := siteCredFuture.Result(*client) + if err != nil { + return fmt.Errorf("making Read request on AzureRM App Service Site Credential %q: %+v", id.SiteName, err) + } + + d.Set("name", id.SiteName) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("kind", resp.Kind) + if location := resp.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + + if props := resp.SiteProperties; props != nil { + d.Set("app_service_plan_id", props.ServerFarmID) + d.Set("enabled", props.Enabled) + d.Set("default_hostname", props.DefaultHostName) + d.Set("https_only", props.HTTPSOnly) + d.Set("daily_memory_time_quota", props.DailyMemoryTimeQuota) + d.Set("outbound_ip_addresses", props.OutboundIPAddresses) + d.Set("possible_outbound_ip_addresses", props.PossibleOutboundIPAddresses) + d.Set("client_affinity_enabled", props.ClientAffinityEnabled) + d.Set("custom_domain_verification_id", props.CustomDomainVerificationID) + + clientCertMode := "" + if props.ClientCertEnabled != nil && *props.ClientCertEnabled { + clientCertMode = string(props.ClientCertMode) + } + d.Set("client_cert_mode", clientCertMode) + } + + appSettings := flattenLogicAppStandardAppSettings(appSettingsResp.Properties) + + if err = d.Set("connection_string", flattenLogicAppStandardConnectionStrings(connectionStringsResp.Properties)); err != nil { + return err + } + + connectionString := appSettings["AzureWebJobsStorage"] + + // 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"]) + + if err = d.Set("app_settings", appSettings); err != nil { + return err + } + + identity, err := flattenLogicAppStandardIdentity(resp.Identity) + if err != nil { + return err + } + if err := d.Set("identity", identity); err != nil { + return fmt.Errorf("setting `identity`: %s", err) + } + + configResp, err := client.GetConfiguration(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("making Read request on AzureRM Logic App Standard Configuration %q: %+v", id.SiteName, err) + } + + siteConfig := flattenLogicAppStandardSiteConfig(configResp.SiteConfig) + if err = d.Set("site_config", siteConfig); err != nil { + return err + } + + siteCred := flattenLogicAppStandardSiteCredential(siteCredResp.UserProperties) + if err = d.Set("site_credential", siteCred); err != nil { + return err + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceLogicAppStandardDelete(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.AppServicesClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.FunctionAppID(d.Id()) + if err != nil { + return err + } + + log.Printf("[DEBUG] Deleting Logic App Standard %q (resource group %q)", id.SiteName, id.ResourceGroup) + + deleteMetrics := true + deleteEmptyServerFarm := false + resp, err := client.Delete(ctx, id.ResourceGroup, id.SiteName, &deleteMetrics, &deleteEmptyServerFarm) + if err != nil { + if !utils.ResponseWasNotFound(resp) { + return err + } + } + + return nil +} + +func getLogicAppStandardServiceTier(ctx context.Context, appServicePlanId string, meta interface{}) (string, error) { + id, err := parse.AppServicePlanID(appServicePlanId) + if err != nil { + return "", fmt.Errorf("[ERROR] Unable to parse App Service Plan ID %q: %+v", appServicePlanId, err) + } + + log.Printf("[DEBUG] Retrieving Service Plan %q (Resource Group %q)", id.ServerfarmName, id.ResourceGroup) + + appServicePlansClient := meta.(*clients.Client).Web.AppServicePlansClient + appServicePlan, err := appServicePlansClient.Get(ctx, id.ResourceGroup, id.ServerfarmName) + if err != nil { + return "", fmt.Errorf("[ERROR] Could not retrieve App Service Plan ID %q: %+v", appServicePlanId, err) + } + + if sku := appServicePlan.Sku; sku != nil { + if tier := sku.Tier; tier != nil { + return *tier, nil + } + } + return "", fmt.Errorf("No `sku` block was returned for App Service Plan ID %q", appServicePlanId) +} + +func getBasicLogicAppSettings(d *pluginsdk.ResourceData, appServiceTier, endpointSuffix string) ([]web.NameValuePair, error) { + storagePropName := "AzureWebJobsStorage" + functionVersionPropName := "FUNCTIONS_EXTENSION_VERSION" + contentSharePropName := "WEBSITE_CONTENTSHARE" + contentFileConnStringPropName := "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" + appKindPropName := "APP_KIND" + appKindPropValue := "workflowApp" + + storageAccount := d.Get("storage_account_name").(string) + accountKey := d.Get("storage_account_access_key").(string) + storageConnection := fmt.Sprintf("DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;EndpointSuffix=%s", storageAccount, accountKey, endpointSuffix) + functionVersion := d.Get("version").(string) + contentShare := strings.ToLower(d.Get("name").(string)) + "-content" + + basicSettings := []web.NameValuePair{ + {Name: &storagePropName, Value: &storageConnection}, + {Name: &functionVersionPropName, Value: &functionVersion}, + {Name: &appKindPropName, Value: &appKindPropValue}, + } + + consumptionSettings := []web.NameValuePair{ + {Name: &contentSharePropName, Value: &contentShare}, + {Name: &contentFileConnStringPropName, Value: &storageConnection}, + } + + return append(basicSettings, consumptionSettings...), nil +} + +func schemaLogicAppStandardSiteConfig() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "always_on": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "cors": schemaLogicAppCorsSettings(), + + "ftps_state": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.FtpsStateAllAllowed), + string(web.FtpsStateDisabled), + string(web.FtpsStateFtpsOnly), + }, false), + }, + + "http2_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "ip_restriction": schemaLogicAppStandardIpRestriction(), + + "min_tls_version": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.SupportedTLSVersionsOneFullStopZero), + string(web.SupportedTLSVersionsOneFullStopOne), + string(web.SupportedTLSVersionsOneFullStopTwo), + }, false), + }, + + "pre_warmed_instance_count": { + Type: pluginsdk.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntBetween(0, 20), + }, + + "use_32_bit_worker_process": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + + "websockets_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "health_check_path": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "java_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"1.8", "11"}, false), + }, + + "elastic_instance_minimum": { + Type: pluginsdk.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntBetween(0, 20), + }, + + "app_scale_limit": { + Type: pluginsdk.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntAtLeast(0), + }, + + "runtime_scale_monitoring_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "dotnet_framework_version": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "v4.0", + ValidateFunc: validation.StringInSlice([]string{ + "v4.0", + "v5.0", + "v6.0", + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + }, + }, + } +} + +func schemaLogicAppStandardIdentity() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "identity_ids": { + Type: pluginsdk.TypeList, + Optional: true, + MinItems: 1, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validate.UserAssignedIdentityID, + }, + }, + + "type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.ManagedServiceIdentityTypeNone), + string(web.ManagedServiceIdentityTypeSystemAssigned), + string(web.ManagedServiceIdentityTypeSystemAssignedUserAssigned), + string(web.ManagedServiceIdentityTypeUserAssigned), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + + "principal_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "tenant_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + } +} + +func schemaLogicAppCorsSettings() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "allowed_origins": { + Type: pluginsdk.TypeSet, + Required: true, + Elem: &pluginsdk.Schema{Type: pluginsdk.TypeString}, + }, + "support_credentials": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + } +} + +func schemaLogicAppStandardIpRestriction() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + ConfigMode: pluginsdk.SchemaConfigModeAttr, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "ip_address": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "service_tag": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "virtual_network_subnet_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "name": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "priority": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 65000, + ValidateFunc: validation.IntBetween(1, 2147483647), + }, + + "action": { + Type: pluginsdk.TypeString, + Default: "Allow", + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "Allow", + "Deny", + }, false), + }, + + //lintignore:XS003 + "headers": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + ConfigMode: pluginsdk.SchemaConfigModeAttr, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + + // lintignore:S018 + "x_forwarded_host": { + Type: pluginsdk.TypeSet, + Optional: true, + MaxItems: 8, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + // lintignore:S018 + "x_forwarded_for": { + Type: pluginsdk.TypeSet, + Optional: true, + MaxItems: 8, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsCIDR, + }, + }, + + // lintignore:S018 + "x_azure_fdid": { + Type: pluginsdk.TypeSet, + Optional: true, + MaxItems: 8, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, + }, + }, + + // lintignore:S018 + "x_fd_health_probe": { + Type: pluginsdk.TypeSet, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "1", + }, false), + }, + }, + }, + }, + }, + }, + }, + } +} + +func flattenLogicAppStandardAppSettings(input map[string]*string) map[string]string { + output := make(map[string]string) + for k, v := range input { + output[k] = *v + } + + return output +} + +func flattenLogicAppStandardConnectionStrings(input map[string]*web.ConnStringValueTypePair) interface{} { + results := make([]interface{}, 0) + + for k, v := range input { + result := make(map[string]interface{}) + result["name"] = k + result["type"] = string(v.Type) + result["value"] = *v.Value + results = append(results, result) + } + + return results +} + +func flattenLogicAppStandardIdentity(identity *web.ManagedServiceIdentity) ([]interface{}, error) { + if identity == nil { + return make([]interface{}, 0), nil + } + + principalId := "" + if identity.PrincipalID != nil { + principalId = *identity.PrincipalID + } + + tenantId := "" + if identity.TenantID != nil { + tenantId = *identity.TenantID + } + + identityIds := make([]string, 0) + if identity.UserAssignedIdentities != nil { + for key := range identity.UserAssignedIdentities { + parsedId, err := msiParse.UserAssignedIdentityID(key) + if err != nil { + return nil, err + } + identityIds = append(identityIds, parsedId.ID()) + } + } + + return []interface{}{ + map[string]interface{}{ + "identity_ids": identityIds, + "principal_id": principalId, + "tenant_id": tenantId, + "type": string(identity.Type), + }, + }, nil +} + +func flattenLogicAppStandardSiteConfig(input *web.SiteConfig) []interface{} { + results := make([]interface{}, 0) + result := make(map[string]interface{}) + + if input == nil { + log.Printf("[DEBUG] SiteConfig is nil") + return results + } + + if input.AlwaysOn != nil { + result["always_on"] = *input.AlwaysOn + } + + if input.Use32BitWorkerProcess != nil { + result["use_32_bit_worker_process"] = *input.Use32BitWorkerProcess + } + + if input.WebSocketsEnabled != nil { + result["websockets_enabled"] = *input.WebSocketsEnabled + } + + if input.HTTP20Enabled != nil { + result["http2_enabled"] = *input.HTTP20Enabled + } + + if input.PreWarmedInstanceCount != nil { + result["pre_warmed_instance_count"] = *input.PreWarmedInstanceCount + } + + result["ip_restriction"] = flattenLogicAppStandardIpRestriction(input.IPSecurityRestrictions) + + result["min_tls_version"] = string(input.MinTLSVersion) + result["ftps_state"] = string(input.FtpsState) + + result["cors"] = flattenLogicAppStandardCorsSettings(input.Cors) + + if input.AutoSwapSlotName != nil { + result["auto_swap_slot_name"] = *input.AutoSwapSlotName + } + + if input.HealthCheckPath != nil { + result["health_check_path"] = *input.HealthCheckPath + } + + if input.JavaVersion != nil { + result["java_version"] = *input.JavaVersion + } + + if input.MinimumElasticInstanceCount != nil { + result["elastic_instance_minimum"] = *input.MinimumElasticInstanceCount + } + + if input.FunctionAppScaleLimit != nil { + result["app_scale_limit"] = *input.FunctionAppScaleLimit + } + + if input.FunctionsRuntimeScaleMonitoringEnabled != nil { + result["runtime_scale_monitoring_enabled"] = *input.FunctionsRuntimeScaleMonitoringEnabled + } + + if input.NetFrameworkVersion != nil { + result["dotnet_framework_version"] = *input.NetFrameworkVersion + } + + results = append(results, result) + return results +} + +func flattenLogicAppStandardSiteCredential(input *web.UserProperties) []interface{} { + results := make([]interface{}, 0) + result := make(map[string]interface{}) + + if input == nil { + log.Printf("[DEBUG] UserProperties is nil") + return results + } + + if input.PublishingUserName != nil { + result["username"] = *input.PublishingUserName + } + + if input.PublishingPassword != nil { + result["password"] = *input.PublishingPassword + } + + return append(results, result) +} + +func flattenLogicAppStandardIpRestriction(input *[]web.IPSecurityRestriction) []interface{} { + restrictions := make([]interface{}, 0) + + if input == nil { + return restrictions + } + + for _, v := range *input { + restriction := make(map[string]interface{}) + if ip := v.IPAddress; ip != nil { + if *ip == "Any" { + continue + } else { + switch v.Tag { + case web.IPFilterTagServiceTag: + restriction["service_tag"] = *ip + default: + restriction["ip_address"] = *ip + } + } + } + + subnetId := "" + if subnetIdRaw := v.VnetSubnetResourceID; subnetIdRaw != nil { + subnetId = *subnetIdRaw + } + restriction["virtual_network_subnet_id"] = subnetId + + name := "" + if nameRaw := v.Name; nameRaw != nil { + name = *nameRaw + } + restriction["name"] = name + + priority := 0 + if priorityRaw := v.Priority; priorityRaw != nil { + priority = int(*priorityRaw) + } + restriction["priority"] = priority + + action := "" + if actionRaw := v.Action; actionRaw != nil { + action = *actionRaw + } + restriction["action"] = action + + if headers := v.Headers; headers != nil { + restriction["headers"] = flattenHeaders(headers) + } + + restrictions = append(restrictions, restriction) + } + + return restrictions +} + +func flattenLogicAppStandardCorsSettings(input *web.CorsSettings) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + result := make(map[string]interface{}) + + allowedOrigins := make([]interface{}, 0) + if s := input.AllowedOrigins; s != nil { + for _, v := range *s { + allowedOrigins = append(allowedOrigins, v) + } + } + result["allowed_origins"] = pluginsdk.NewSet(pluginsdk.HashString, allowedOrigins) + + if input.SupportCredentials != nil { + result["support_credentials"] = *input.SupportCredentials + } + + return append(results, result) +} + +func flattenHeaders(input map[string][]string) []interface{} { + output := make([]interface{}, 0) + headers := make(map[string]interface{}) + if input == nil { + return output + } + + if forwardedHost, ok := input["x-forwarded-host"]; ok && len(forwardedHost) > 0 { + headers["x_forwarded_host"] = forwardedHost + } + if forwardedFor, ok := input["x-forwarded-for"]; ok && len(forwardedFor) > 0 { + headers["x_forwarded_for"] = forwardedFor + } + if fdids, ok := input["x-azure-fdid"]; ok && len(fdids) > 0 { + headers["x_azure_fdid"] = fdids + } + if healthProbe, ok := input["x-fd-healthprobe"]; ok && len(healthProbe) > 0 { + headers["x_fd_health_probe"] = healthProbe + } + + return append(output, headers) +} + +func expandLogicAppStandardSiteConfig(d *pluginsdk.ResourceData) (web.SiteConfig, error) { + configs := d.Get("site_config").([]interface{}) + siteConfig := web.SiteConfig{} + + if len(configs) == 0 { + return siteConfig, nil + } + + config := configs[0].(map[string]interface{}) + + if v, ok := config["always_on"]; ok { + siteConfig.AlwaysOn = utils.Bool(v.(bool)) + } + + if v, ok := config["use_32_bit_worker_process"]; ok { + siteConfig.Use32BitWorkerProcess = utils.Bool(v.(bool)) + } + + if v, ok := config["websockets_enabled"]; ok { + siteConfig.WebSocketsEnabled = utils.Bool(v.(bool)) + } + + if v, ok := config["cors"]; ok { + expand := expandLogicAppStandardCorsSettings(v) + siteConfig.Cors = &expand + } + + if v, ok := config["http2_enabled"]; ok { + siteConfig.HTTP20Enabled = utils.Bool(v.(bool)) + } + + if v, ok := config["ip_restriction"]; ok { + restrictions, err := expandLogicAppStandardIpRestriction(v) + if err != nil { + return siteConfig, err + } + siteConfig.IPSecurityRestrictions = &restrictions + } + + if v, ok := config["min_tls_version"]; ok { + siteConfig.MinTLSVersion = web.SupportedTLSVersions(v.(string)) + } + + if v, ok := config["ftps_state"]; ok { + siteConfig.FtpsState = web.FtpsState(v.(string)) + } + + if v, ok := config["pre_warmed_instance_count"]; ok { + siteConfig.PreWarmedInstanceCount = utils.Int32(int32(v.(int))) + } + + if v, ok := config["health_check_path"]; ok { + siteConfig.HealthCheckPath = utils.String(v.(string)) + } + + if v, ok := config["java_version"]; ok { + siteConfig.JavaVersion = utils.String(v.(string)) + } + + if v, ok := config["elastic_instance_minimum"]; ok { + siteConfig.MinimumElasticInstanceCount = utils.Int32(int32(v.(int))) + } + + if v, ok := config["app_scale_limit"]; ok { + siteConfig.FunctionAppScaleLimit = utils.Int32(int32(v.(int))) + } + + if v, ok := config["runtime_scale_monitoring_enabled"]; ok { + siteConfig.FunctionsRuntimeScaleMonitoringEnabled = utils.Bool(v.(bool)) + } + + if v, ok := config["dotnet_framework_version"]; ok { + siteConfig.NetFrameworkVersion = utils.String(v.(string)) + } + + return siteConfig, nil +} + +func expandLogicAppStandardIdentity(input []interface{}) *web.ManagedServiceIdentity { + if len(input) == 0 { + return nil + } + identity := input[0].(map[string]interface{}) + identityType := web.ManagedServiceIdentityType(identity["type"].(string)) + + identityIds := make(map[string]*web.UserAssignedIdentity) + for _, id := range identity["identity_ids"].([]interface{}) { + identityIds[id.(string)] = &web.UserAssignedIdentity{} + } + + managedServiceIdentity := web.ManagedServiceIdentity{ + Type: identityType, + } + + if managedServiceIdentity.Type == web.ManagedServiceIdentityTypeUserAssigned || managedServiceIdentity.Type == web.ManagedServiceIdentityTypeSystemAssignedUserAssigned { + managedServiceIdentity.UserAssignedIdentities = identityIds + } + + return &managedServiceIdentity +} + +func expandLogicAppStandardSettings(d *pluginsdk.ResourceData, appServiceTier, endpointSuffix string) (map[string]*string, error) { + output := expandAppSettings(d) + + basicAppSettings, err := getBasicLogicAppSettings(d, appServiceTier, endpointSuffix) + if err != nil { + return nil, err + } + for _, p := range basicAppSettings { + output[*p.Name] = p.Value + } + + return output, nil +} + +func expandAppSettings(d *pluginsdk.ResourceData) map[string]*string { + input := d.Get("app_settings").(map[string]interface{}) + output := make(map[string]*string, len(input)) + + for k, v := range input { + output[k] = utils.String(v.(string)) + } + + return output +} + +func expandLogicAppStandardConnectionStrings(d *pluginsdk.ResourceData) map[string]*web.ConnStringValueTypePair { + input := d.Get("connection_string").(*pluginsdk.Set).List() + output := make(map[string]*web.ConnStringValueTypePair, len(input)) + + for _, v := range input { + vals := v.(map[string]interface{}) + + csName := vals["name"].(string) + csType := vals["type"].(string) + csValue := vals["value"].(string) + + output[csName] = &web.ConnStringValueTypePair{ + Value: utils.String(csValue), + Type: web.ConnectionStringType(csType), + } + } + + return output +} + +func expandLogicAppStandardCorsSettings(input interface{}) web.CorsSettings { + settings := input.([]interface{}) + corsSettings := web.CorsSettings{} + + if len(settings) == 0 { + return corsSettings + } + + setting := settings[0].(map[string]interface{}) + + if v, ok := setting["allowed_origins"]; ok { + input := v.(*pluginsdk.Set).List() + + allowedOrigins := make([]string, 0) + for _, param := range input { + allowedOrigins = append(allowedOrigins, param.(string)) + } + + corsSettings.AllowedOrigins = &allowedOrigins + } + + if v, ok := setting["support_credentials"]; ok { + corsSettings.SupportCredentials = utils.Bool(v.(bool)) + } + + return corsSettings +} + +func expandLogicAppStandardIpRestriction(input interface{}) ([]web.IPSecurityRestriction, error) { + restrictions := make([]web.IPSecurityRestriction, 0) + + for _, r := range input.([]interface{}) { + if r == nil { + continue + } + + restriction := r.(map[string]interface{}) + + ipAddress := restriction["ip_address"].(string) + vNetSubnetID := "" + + if subnetID, ok := restriction["virtual_network_subnet_id"]; ok && subnetID != "" { + vNetSubnetID = subnetID.(string) + } + + serviceTag := restriction["service_tag"].(string) + + name := restriction["name"].(string) + priority := restriction["priority"].(int) + action := restriction["action"].(string) + + if vNetSubnetID != "" && ipAddress != "" && serviceTag != "" { + return nil, fmt.Errorf("only one of `ip_address`, `service_tag` or `virtual_network_subnet_id` can be set for an IP restriction") + } + + if vNetSubnetID == "" && ipAddress == "" && serviceTag == "" { + return nil, fmt.Errorf("one of `ip_address`, `service_tag` or `virtual_network_subnet_id` must be set for an IP restriction") + } + + ipSecurityRestriction := web.IPSecurityRestriction{} + if ipAddress == "Any" { + continue + } + + if ipAddress != "" { + ipSecurityRestriction.IPAddress = &ipAddress + } + + if serviceTag != "" { + ipSecurityRestriction.IPAddress = &serviceTag + ipSecurityRestriction.Tag = web.IPFilterTagServiceTag + } + + if vNetSubnetID != "" { + ipSecurityRestriction.VnetSubnetResourceID = &vNetSubnetID + } + + if name != "" { + ipSecurityRestriction.Name = &name + } + + if priority != 0 { + ipSecurityRestriction.Priority = utils.Int32(int32(priority)) + } + + if action != "" { + ipSecurityRestriction.Action = &action + } + if headers, ok := restriction["headers"]; ok { + ipSecurityRestriction.Headers = expandHeaders(headers.([]interface{})) + } + + restrictions = append(restrictions, ipSecurityRestriction) + } + + return restrictions, nil +} + +func expandHeaders(input interface{}) map[string][]string { + output := make(map[string][]string) + + for _, r := range input.([]interface{}) { + if r == nil { + continue + } + + val := r.(map[string]interface{}) + if raw := val["x_forwarded_host"].(*pluginsdk.Set).List(); len(raw) > 0 { + output["x-forwarded-host"] = *utils.ExpandStringSlice(raw) + } + if raw := val["x_forwarded_for"].(*pluginsdk.Set).List(); len(raw) > 0 { + output["x-forwarded-for"] = *utils.ExpandStringSlice(raw) + } + if raw := val["x_azure_fdid"].(*pluginsdk.Set).List(); len(raw) > 0 { + output["x-azure-fdid"] = *utils.ExpandStringSlice(raw) + } + if raw := val["x_fd_health_probe"].(*pluginsdk.Set).List(); len(raw) > 0 { + output["x-fd-healthprobe"] = *utils.ExpandStringSlice(raw) + } + } + + return output +} diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go new file mode 100644 index 000000000000..a9dc0a89c56a --- /dev/null +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -0,0 +1,2309 @@ +package logic_test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/helpers/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/web/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type LogicAppStandardResource struct{} + +func TestAccLogicAppStandard_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + data.CheckWithClient(r.hasContentShareAppSetting(false)), + check.That(data.ResourceName).Key("version").HasValue("~1"), + check.That(data.ResourceName).Key("outbound_ip_addresses").Exists(), + check.That(data.ResourceName).Key("possible_outbound_ip_addresses").Exists(), + check.That(data.ResourceName).Key("custom_domain_verification_id").Exists(), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), + check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + data.CheckWithClient(r.hasContentShareAppSetting(false)), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccLogicAppStandard_tags(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.tags(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("tags.%").HasValue("1"), + check.That(data.ResourceName).Key("tags.environment").HasValue("production"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_tagsUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.tags(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("tags.%").HasValue("1"), + check.That(data.ResourceName).Key("tags.environment").HasValue("production"), + ), + }, + { + Config: r.tagsUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("tags.%").HasValue("2"), + check.That(data.ResourceName).Key("tags.environment").HasValue("production"), + check.That(data.ResourceName).Key("tags.hello").HasValue("Berlin"), + ), + }, + }) +} + +func TestAccLogicAppStandard_appSettings(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.appSettings(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.appSettingsUpdate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("app_settings.%", "app_settings.AzureWebJobsDashboard", "app_settings.AzureWebJobsStorage"), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_siteConfig(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.alwaysOn(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.always_on").HasValue("true"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_healthCheck(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.healthCheck(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.health_check_path").HasValue("/"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_connectionStrings(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.connectionStrings(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("connection_string.#").HasValue("1"), + check.That(data.ResourceName).Key("connection_string.163594034.name").HasValue("Example"), + check.That(data.ResourceName).Key("connection_string.163594034.type").HasValue("PostgreSQL"), + check.That(data.ResourceName).Key("connection_string.163594034.value").HasValue("some-postgresql-connection-string"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_updateVersion(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.version(data, "~1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("version").HasValue("~1"), + ), + }, + { + Config: r.version(data, "~2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("version").HasValue("~2"), + ), + }, + }) +} + +func TestAccLogicAppStandard_3264bit(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.use_32_bit_worker_process").HasValue("true"), + ), + }, + { + Config: r.app64bit(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.use_32_bit_worker_process").HasValue("false"), + ), + }, + }) +} + +func TestAccLogicAppStandard_httpsOnly(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.httpsOnly(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("https_only").HasValue("true"), + ), + }, + }) +} + +func TestAccLogicAppStandard_createIdentity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basicIdentity(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + acceptance.TestMatchResourceAttr(data.ResourceName, "identity.0.principal_id", validate.UUIDRegExp), + acceptance.TestMatchResourceAttr(data.ResourceName, "identity.0.tenant_id", validate.UUIDRegExp), + ), + }, + }) +} + +func TestAccLogicAppStandard_updateIdentity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("0"), + ), + }, + { + Config: r.basicIdentity(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + acceptance.TestMatchResourceAttr(data.ResourceName, "identity.0.principal_id", validate.UUIDRegExp), + acceptance.TestMatchResourceAttr(data.ResourceName, "identity.0.tenant_id", validate.UUIDRegExp), + ), + }, + }) +} + +func TestAccLogicAppStandard_userAssignedIdentity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.userAssignedIdentity(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.0.type").HasValue("UserAssigned"), + check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.principal_id").IsEmpty(), + check.That(data.ResourceName).Key("identity.0.tenant_id").IsEmpty(), + ), + }, + data.ImportStep(), + { + Config: r.userAssignedIdentityUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.0.type").HasValue("UserAssigned"), + check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("2"), + check.That(data.ResourceName).Key("identity.0.principal_id").IsEmpty(), + check.That(data.ResourceName).Key("identity.0.tenant_id").IsEmpty(), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_corsSettings(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.corsSettings(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.cors.#").HasValue("1"), + check.That(data.ResourceName).Key("site_config.0.cors.0.support_credentials").HasValue("true"), + check.That(data.ResourceName).Key("site_config.0.cors.0.allowed_origins.#").HasValue("4"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_enableHttp2(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.enableHttp2(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.http2_enabled").HasValue("true"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_minTlsVersion(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.minTlsVersion(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.min_tls_version").HasValue("1.2"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_ftpsState(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.ftpsState(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.ftps_state").HasValue("AllAllowed"), + ), + }, + data.ImportStep(), + }) +} +func TestAccLogicAppStandard_preWarmedInstanceCount(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.preWarmedInstanceCount(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.pre_warmed_instance_count").HasValue("1"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_computedPreWarmedInstanceCount(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.computedPreWarmedInstanceCount(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.pre_warmed_instance_count").HasValue("1"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_oneIpRestriction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.oneIpRestriction(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.ip_address").HasValue("10.10.10.10/32"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_oneServiceTagIpRestriction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.oneServiceTagIpRestriction(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.service_tag").HasValue("AzureEventGrid"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_changeIpToServiceTagIpRestriction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.oneIpRestriction(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.ip_address").HasValue("10.10.10.10/32"), + ), + }, + { + Config: r.oneServiceTagIpRestriction(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.service_tag").HasValue("AzureEventGrid"), + ), + }, + { + Config: r.oneIpRestriction(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.ip_address").HasValue("10.10.10.10/32"), + ), + }, + }) +} + +func TestAccLogicAppStandard_oneVNetSubnetIpRestriction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.oneVNetSubnetIpRestriction(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_ipRestrictionRemoved(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + // This configuration includes a single explicit ip_restriction + Config: r.oneIpRestriction(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.ip_restriction.#").HasValue("1"), + ), + }, + { + // This configuration has no site_config blocks at all. + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.ip_restriction.#").HasValue("1"), + ), + }, + { + // This configuration explicitly sets ip_restriction to [] using attribute syntax. + Config: r.ipRestrictionRemoved(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.ip_restriction.#").HasValue("0"), + ), + }, + }) +} + +func TestAccLogicAppStandard_manyIpRestrictions(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.manyIpRestrictions(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_updateStorageAccountKey(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.updateStorageAccountKey(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_clientCertMode(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("client_cert_mode").HasValue(""), + ), + }, + { + Config: r.clientCertMode(data, "Required"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("client_cert_mode").HasValue("Required"), + ), + }, + { + Config: r.clientCertMode(data, "Optional"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("client_cert_mode").HasValue("Optional"), + ), + }, + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("client_cert_mode").HasValue(""), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_elasticInstanceMinimum(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.elasticInstanceMinimum(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.elastic_instance_minimum").HasValue("1"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_appScaleLimit(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appScaleLimit(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.app_scale_limit").HasValue("1"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_runtimeScaleMonitoringEnabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.runtimeScaleMonitoringEnabled(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.runtime_scale_monitoring_enabled").HasValue("true"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_dotnetVersion4(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.dotnetVersion(data, "~1", "v4.0"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.dotnet_framework_version").HasValue("v4.0"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_dotnetVersion5(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.dotnetVersion(data, "~3", "v5.0"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.dotnet_framework_version").HasValue("v5.0"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_dotnetVersion6(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.dotnetVersion(data, "~4", "v6.0"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.dotnet_framework_version").HasValue("v6.0"), + ), + }, + data.ImportStep(), + }) +} + +func (r LogicAppStandardResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.FunctionAppID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.Web.AppServicesClient.Get(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving Function App %q (Resource Group %q): %+v", id.SiteName, id.ResourceGroup, err) + } + + // The SDK defines 404 as an "ok" status code.. + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + + return utils.Bool(true), nil +} + +func (r LogicAppStandardResource) hasContentShareAppSetting(shouldExist bool) func(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) error { + return func(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) error { + id, err := parse.FunctionAppID(state.ID) + if err != nil { + return err + } + + appSettingsResp, err := clients.Web.AppServicesClient.ListApplicationSettings(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("listing AppSettings: %+v", err) + } + + exists := false + for k := range appSettingsResp.Properties { + if strings.EqualFold("WEBSITE_CONTENTSHARE", k) { + exists = true + break + } + } + if exists != shouldExist { + return fmt.Errorf("expected %t but got %t", shouldExist, exists) + } + + return nil + } +} + +func (r LogicAppStandardResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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.RandomString) +} + +func (r LogicAppStandardResource) requiresImport(data acceptance.TestData) string { + template := r.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_logic_app_standard" "import" { + name = azurerm_function_app.test.name + location = azurerm_function_app.test.location + resource_group_name = azurerm_function_app.test.resource_group_name + app_service_plan_id = azurerm_function_app.test.app_service_plan_id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key +} +`, template) +} + +func (r LogicAppStandardResource) tags(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + tags = { + environment = "production" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) tagsUpdated(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + tags = { + environment = "production" + hello = "Berlin" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) version(data acceptance.TestData, version string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + version = "%[4]s" + 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.RandomString, version) +} + +func (r LogicAppStandardResource) appSettings(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + app_settings = { + "hello" = "world" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) appSettingsUpdate(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_storage_account.test.primary_connection_string + "AzureWebJobsDashboard" = azurerm_storage_account.test.primary_connection_string + "AzureWebJobsStorage" = azurerm_storage_account.test.primary_connection_string + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) alwaysOn(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + site_config { + always_on = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) healthCheck(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + site_config { + health_check_path = "/" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) connectionStrings(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + connection_string { + name = "Example" + value = "some-postgresql-connection-string" + type = "PostgreSQL" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) appSettingsAlwaysOn(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + app_settings = { + "hello" = "world" + } + + site_config { + always_on = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) app64bit(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + use_32_bit_worker_process = false + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) httpsOnly(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + https_only = true +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) dailyMemoryTimeQuota(data acceptance.TestData, dailyMemoryTimeQuota int) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + kind = "FunctionApp" + + sku { + tier = "Dynamic" + size = "Y1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + daily_memory_time_quota = %d +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger, dailyMemoryTimeQuota) +} + +func (r LogicAppStandardResource) basicIdentity(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + identity { + type = "SystemAssigned" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) userAssignedIdentity(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_user_assigned_identity" "first" { + name = "acctest1%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.first.id] + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) userAssignedIdentityUpdated(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_user_assigned_identity" "first" { + name = "acctest1%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_user_assigned_identity" "second" { + name = "acctest2%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.first.id, azurerm_user_assigned_identity.second.id] + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) corsSettings(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + site_config { + cors { + allowed_origins = [ + "http://www.contoso.com", + "www.contoso.com", + "contoso.com", + "http://localhost:4201", + ] + + support_credentials = true + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) enableHttp2(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + http2_enabled = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) minTlsVersion(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + min_tls_version = "1.2" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) ftpsState(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + ftps_state = "AllAllowed" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) preWarmedInstanceCount(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + kind = "elastic" + + sku { + tier = "ElasticPremium" + size = "EP1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + pre_warmed_instance_count = 1 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) computedPreWarmedInstanceCount(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + kind = "elastic" + + sku { + tier = "ElasticPremium" + size = "EP1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) oneIpRestriction(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + ip_restriction { + ip_address = "10.10.10.10/32" + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) oneServiceTagIpRestriction(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + ip_restriction { + service_tag = "AzureEventGrid" + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) oneVNetSubnetIpRestriction(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.0.2.0/24" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + ip_restriction { + virtual_network_subnet_id = azurerm_subnet.test.id + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) manyIpRestrictions(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + ip_restriction { + ip_address = "10.10.10.10/32" + } + + ip_restriction { + ip_address = "20.20.20.0/24" + } + + ip_restriction { + ip_address = "30.30.0.0/16" + } + + ip_restriction { + ip_address = "192.168.1.2/24" + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) ipRestrictionRemoved(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + ip_restriction = [] + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) updateStorageAccountKey(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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.secondary_access_key +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) clientCertMode(data acceptance.TestData, modeValue string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + client_cert_mode = "%[4]s" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, modeValue) +} + +func (r LogicAppStandardResource) elasticInstanceMinimum(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + kind = "elastic" + + sku { + tier = "ElasticPremium" + size = "EP1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + elastic_instance_minimum = 1 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) appScaleLimit(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + kind = "elastic" + + sku { + tier = "ElasticPremium" + size = "EP1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + site_config { + app_scale_limit = 1 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) runtimeScaleMonitoringEnabled(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + kind = "elastic" + + sku { + tier = "ElasticPremium" + size = "EP1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + version = "~3" + + site_config { + pre_warmed_instance_count = 1 + runtime_scale_monitoring_enabled = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func (r LogicAppStandardResource) dotnetVersion(data acceptance.TestData, functionVersion string, version string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%d-func" + 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 + + version = "%s" + + site_config { + dotnet_framework_version = "%s" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger, functionVersion, version) +} diff --git a/internal/services/logic/registration.go b/internal/services/logic/registration.go index 60bfc4ca2c24..e736363b6622 100644 --- a/internal/services/logic/registration.go +++ b/internal/services/logic/registration.go @@ -39,6 +39,7 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { "azurerm_logic_app_trigger_http_request": resourceLogicAppTriggerHttpRequest(), "azurerm_logic_app_trigger_recurrence": resourceLogicAppTriggerRecurrence(), "azurerm_logic_app_workflow": resourceLogicAppWorkflow(), + "azurerm_logic_app_standard": resourceLogicAppStandard(), "azurerm_integration_service_environment": resourceIntegrationServiceEnvironment(), } } From 882974703498b93481405177c50b25d2533dafa5 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Sat, 28 Aug 2021 09:54:24 +0100 Subject: [PATCH 02/16] validate and parse support for logic app standard --- .../logic/logic_app_standard_resource.go | 63 +++------- .../logic/logic_app_standard_resource_test.go | 101 +--------------- .../logic/parse/logic_app_standard.go | 69 +++++++++++ .../logic/parse/logic_app_standard_test.go | 112 ++++++++++++++++++ internal/services/logic/resourceids.go | 1 + .../logic/validate/logic_app_standard_id.go | 23 ++++ .../validate/logic_app_standard_id_test.go | 76 ++++++++++++ .../logic/validate/logic_app_standard_name.go | 16 +++ 8 files changed, 315 insertions(+), 146 deletions(-) create mode 100644 internal/services/logic/parse/logic_app_standard.go create mode 100644 internal/services/logic/parse/logic_app_standard_test.go create mode 100644 internal/services/logic/validate/logic_app_standard_id.go create mode 100644 internal/services/logic/validate/logic_app_standard_id_test.go create mode 100644 internal/services/logic/validate/logic_app_standard_name.go diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index 1cb0e33192b1..1e2260841058 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -1,7 +1,6 @@ package logic import ( - "context" "fmt" "log" "strings" @@ -11,11 +10,11 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/logic/parse" + logicValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/logic/validate" msiParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/validate" storageValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/storage/validate" - "github.com/hashicorp/terraform-provider-azurerm/internal/services/web/parse" - webValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/web/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tags" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress" @@ -31,7 +30,7 @@ func resourceLogicAppStandard() *pluginsdk.Resource { Update: resourceLogicAppStandardUpdate, Delete: resourceLogicAppStandardDelete, Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { - _, err := parse.FunctionAppID(id) + _, err := parse.LogicAppStandardID(id) return err }), @@ -47,7 +46,7 @@ func resourceLogicAppStandard() *pluginsdk.Resource { Type: pluginsdk.TypeString, Required: true, ForceNew: true, - ValidateFunc: webValidate.AppServiceName, + ValidateFunc: logicValidate.LogicAppStandardName, }, "resource_group_name": azure.SchemaResourceGroupName(), @@ -166,7 +165,6 @@ func resourceLogicAppStandard() *pluginsdk.Resource { "tags": tags.Schema(), // Computed Only - "custom_domain_verification_id": { Type: pluginsdk.TypeString, Computed: true, @@ -232,7 +230,7 @@ func resourceLogicAppStandardCreate(d *pluginsdk.ResourceData, meta interface{}) } if existing.ID != nil && *existing.ID != "" { - return tf.ImportAsExistsError("azurerm_function_app", *existing.ID) + return tf.ImportAsExistsError("azurerm_logic_app_standard", *existing.ID) } availabilityRequest := web.ResourceNameAvailabilityRequest{ @@ -258,12 +256,8 @@ func resourceLogicAppStandardCreate(d *pluginsdk.ResourceData, meta interface{}) httpsOnly := d.Get("https_only").(bool) dailyMemoryTimeQuota := d.Get("daily_memory_time_quota").(int) t := d.Get("tags").(map[string]interface{}) - appServiceTier, err := getLogicAppStandardServiceTier(ctx, appServicePlanID, meta) - if err != nil { - return err - } - basicAppSettings, err := getBasicLogicAppSettings(d, appServiceTier, endpointSuffix) + basicAppSettings, err := getBasicLogicAppSettings(d, endpointSuffix) if err != nil { return err } @@ -316,7 +310,7 @@ func resourceLogicAppStandardCreate(d *pluginsdk.ResourceData, meta interface{}) } if read.ID == nil || *read.ID == "" { - return fmt.Errorf("Cannot read Logic App Standard %s (resource group %s) ID", name, resourceGroup) + return fmt.Errorf("cannot read Logic App Standard %s (resource group %s) ID", name, resourceGroup) } d.SetId(*read.ID) @@ -330,7 +324,7 @@ func resourceLogicAppStandardUpdate(d *pluginsdk.ResourceData, meta interface{}) ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := parse.FunctionAppID(d.Id()) + id, err := parse.LogicAppStandardID(d.Id()) if err != nil { return err } @@ -346,12 +340,7 @@ func resourceLogicAppStandardUpdate(d *pluginsdk.ResourceData, meta interface{}) dailyMemoryTimeQuota := d.Get("daily_memory_time_quota").(int) t := d.Get("tags").(map[string]interface{}) - appServiceTier, err := getLogicAppStandardServiceTier(ctx, appServicePlanID, meta) - if err != nil { - return err - } - - basicAppSettings, err := getBasicLogicAppSettings(d, appServiceTier, endpointSuffix) + basicAppSettings, err := getBasicLogicAppSettings(d, endpointSuffix) if err != nil { return err } @@ -397,7 +386,7 @@ func resourceLogicAppStandardUpdate(d *pluginsdk.ResourceData, meta interface{}) return fmt.Errorf("waiting for update of Logic App Standard %q (Resource Group %q): %+v", id.SiteName, id.ResourceGroup, err) } - appSettings, err := expandLogicAppStandardSettings(d, appServiceTier, endpointSuffix) + appSettings, err := expandLogicAppStandardSettings(d, endpointSuffix) if err != nil { return err } @@ -442,7 +431,7 @@ func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) e ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := parse.FunctionAppID(d.Id()) + id, err := parse.LogicAppStandardID(d.Id()) if err != nil { return err } @@ -572,7 +561,7 @@ func resourceLogicAppStandardDelete(d *pluginsdk.ResourceData, meta interface{}) ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := parse.FunctionAppID(d.Id()) + id, err := parse.LogicAppStandardID(d.Id()) if err != nil { return err } @@ -591,29 +580,7 @@ func resourceLogicAppStandardDelete(d *pluginsdk.ResourceData, meta interface{}) return nil } -func getLogicAppStandardServiceTier(ctx context.Context, appServicePlanId string, meta interface{}) (string, error) { - id, err := parse.AppServicePlanID(appServicePlanId) - if err != nil { - return "", fmt.Errorf("[ERROR] Unable to parse App Service Plan ID %q: %+v", appServicePlanId, err) - } - - log.Printf("[DEBUG] Retrieving Service Plan %q (Resource Group %q)", id.ServerfarmName, id.ResourceGroup) - - appServicePlansClient := meta.(*clients.Client).Web.AppServicePlansClient - appServicePlan, err := appServicePlansClient.Get(ctx, id.ResourceGroup, id.ServerfarmName) - if err != nil { - return "", fmt.Errorf("[ERROR] Could not retrieve App Service Plan ID %q: %+v", appServicePlanId, err) - } - - if sku := appServicePlan.Sku; sku != nil { - if tier := sku.Tier; tier != nil { - return *tier, nil - } - } - return "", fmt.Errorf("No `sku` block was returned for App Service Plan ID %q", appServicePlanId) -} - -func getBasicLogicAppSettings(d *pluginsdk.ResourceData, appServiceTier, endpointSuffix string) ([]web.NameValuePair, error) { +func getBasicLogicAppSettings(d *pluginsdk.ResourceData, endpointSuffix string) ([]web.NameValuePair, error) { storagePropName := "AzureWebJobsStorage" functionVersionPropName := "FUNCTIONS_EXTENSION_VERSION" contentSharePropName := "WEBSITE_CONTENTSHARE" @@ -1282,10 +1249,10 @@ func expandLogicAppStandardIdentity(input []interface{}) *web.ManagedServiceIden return &managedServiceIdentity } -func expandLogicAppStandardSettings(d *pluginsdk.ResourceData, appServiceTier, endpointSuffix string) (map[string]*string, error) { +func expandLogicAppStandardSettings(d *pluginsdk.ResourceData, endpointSuffix string) (map[string]*string, error) { output := expandAppSettings(d) - basicAppSettings, err := getBasicLogicAppSettings(d, appServiceTier, endpointSuffix) + basicAppSettings, err := getBasicLogicAppSettings(d, endpointSuffix) if err != nil { return nil, err } diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go index a9dc0a89c56a..b71a5b3671fe 100644 --- a/internal/services/logic/logic_app_standard_resource_test.go +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -26,8 +26,8 @@ func TestAccLogicAppStandard_basic(t *testing.T) { Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - data.CheckWithClient(r.hasContentShareAppSetting(false)), - check.That(data.ResourceName).Key("version").HasValue("~1"), + data.CheckWithClient(r.hasContentShareAppSetting(true)), + check.That(data.ResourceName).Key("version").HasValue("~3"), check.That(data.ResourceName).Key("outbound_ip_addresses").Exists(), check.That(data.ResourceName).Key("possible_outbound_ip_addresses").Exists(), check.That(data.ResourceName).Key("custom_domain_verification_id").Exists(), @@ -50,7 +50,7 @@ func TestAccLogicAppStandard_requiresImport(t *testing.T) { Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - data.CheckWithClient(r.hasContentShareAppSetting(false)), + data.CheckWithClient(r.hasContentShareAppSetting(true)), ), }, data.RequiresImportErrorStep(r.requiresImport), @@ -177,9 +177,6 @@ func TestAccLogicAppStandard_connectionStrings(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("connection_string.#").HasValue("1"), - check.That(data.ResourceName).Key("connection_string.163594034.name").HasValue("Example"), - check.That(data.ResourceName).Key("connection_string.163594034.type").HasValue("PostgreSQL"), - check.That(data.ResourceName).Key("connection_string.163594034.value").HasValue("some-postgresql-connection-string"), ), }, data.ImportStep(), @@ -1168,55 +1165,6 @@ resource "azurerm_logic_app_standard" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } -func (r LogicAppStandardResource) appSettingsAlwaysOn(data acceptance.TestData) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%[1]d" - location = "%[2]s" -} - -resource "azurerm_storage_account" "test" { - name = "acctestsa%[3]s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - account_tier = "Standard" - account_replication_type = "LRS" -} - -resource "azurerm_app_service_plan" "test" { - name = "acctestASP-%[1]d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - - sku { - tier = "Standard" - size = "S1" - } -} - -resource "azurerm_logic_app_standard" "test" { - name = "acctest-%[1]d-func" - 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 - - app_settings = { - "hello" = "world" - } - - site_config { - always_on = true - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomString) -} - func (r LogicAppStandardResource) app64bit(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -1304,49 +1252,6 @@ resource "azurerm_logic_app_standard" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) } -func (r LogicAppStandardResource) dailyMemoryTimeQuota(data acceptance.TestData, dailyMemoryTimeQuota int) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -resource "azurerm_storage_account" "test" { - name = "acctestsa%s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - account_tier = "Standard" - account_replication_type = "LRS" -} - -resource "azurerm_app_service_plan" "test" { - name = "acctestASP-%d" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - kind = "FunctionApp" - - sku { - tier = "Dynamic" - size = "Y1" - } -} - -resource "azurerm_logic_app_standard" "test" { - name = "acctest-%d-func" - 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 - daily_memory_time_quota = %d -} -`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger, dailyMemoryTimeQuota) -} - func (r LogicAppStandardResource) basicIdentity(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/internal/services/logic/parse/logic_app_standard.go b/internal/services/logic/parse/logic_app_standard.go new file mode 100644 index 000000000000..0b9395116e59 --- /dev/null +++ b/internal/services/logic/parse/logic_app_standard.go @@ -0,0 +1,69 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" +) + +type LogicAppStandardId struct { + SubscriptionId string + ResourceGroup string + SiteName string +} + +func NewLogicAppStandardID(subscriptionId, resourceGroup, siteName string) LogicAppStandardId { + return LogicAppStandardId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + SiteName: siteName, + } +} + +func (id LogicAppStandardId) String() string { + segments := []string{ + fmt.Sprintf("Site Name %q", id.SiteName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Logic App Standard", segmentsStr) +} + +func (id LogicAppStandardId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Web/sites/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.SiteName) +} + +// LogicAppStandardID parses a LogicAppStandard ID into an LogicAppStandardId struct +func LogicAppStandardID(input string) (*LogicAppStandardId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := LogicAppStandardId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.SiteName, err = id.PopSegment("sites"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/logic/parse/logic_app_standard_test.go b/internal/services/logic/parse/logic_app_standard_test.go new file mode 100644 index 000000000000..d66f1c31e149 --- /dev/null +++ b/internal/services/logic/parse/logic_app_standard_test.go @@ -0,0 +1,112 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = LogicAppStandardId{} + +func TestLogicAppStandardIDFormatter(t *testing.T) { + actual := NewLogicAppStandardID("12345678-1234-9876-4563-123456789012", "resGroup1", "site1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestLogicAppStandardID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *LogicAppStandardId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing SiteName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/", + Error: true, + }, + + { + // missing value for SiteName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1", + Expected: &LogicAppStandardId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + SiteName: "site1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.WEB/SITES/SITE1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := LogicAppStandardID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.SiteName != v.Expected.SiteName { + t.Fatalf("Expected %q but got %q for SiteName", v.Expected.SiteName, actual.SiteName) + } + } +} diff --git a/internal/services/logic/resourceids.go b/internal/services/logic/resourceids.go index fc09276e1014..f23f98637179 100644 --- a/internal/services/logic/resourceids.go +++ b/internal/services/logic/resourceids.go @@ -5,3 +5,4 @@ package logic //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=IntegrationAccountSchema -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Logic/integrationAccounts/integrationAccount1/schemas/schema1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=IntegrationAccountSession -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Logic/integrationAccounts/integrationAccount1/sessions/session1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=IntegrationServiceEnvironment -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Logic/integrationServiceEnvironments/ise1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LogicAppStandard -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1 diff --git a/internal/services/logic/validate/logic_app_standard_id.go b/internal/services/logic/validate/logic_app_standard_id.go new file mode 100644 index 000000000000..1a0b693ffb84 --- /dev/null +++ b/internal/services/logic/validate/logic_app_standard_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/logic/parse" +) + +func LogicAppStandardID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.LogicAppStandardID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/internal/services/logic/validate/logic_app_standard_id_test.go b/internal/services/logic/validate/logic_app_standard_id_test.go new file mode 100644 index 000000000000..009d1f7c22d6 --- /dev/null +++ b/internal/services/logic/validate/logic_app_standard_id_test.go @@ -0,0 +1,76 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestLogicAppStandardID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing SiteName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/", + Valid: false, + }, + + { + // missing value for SiteName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.WEB/SITES/SITE1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := LogicAppStandardID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/internal/services/logic/validate/logic_app_standard_name.go b/internal/services/logic/validate/logic_app_standard_name.go new file mode 100644 index 000000000000..e17317260a1a --- /dev/null +++ b/internal/services/logic/validate/logic_app_standard_name.go @@ -0,0 +1,16 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func LogicAppStandardName(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if matched := regexp.MustCompile(`^[0-9a-zA-Z-]{1,60}$`).Match([]byte(value)); !matched { + errors = append(errors, fmt.Errorf("%q may only contain alphanumeric characters and dashes and up to 60 characters in length", k)) + } + + return warnings, errors +} From 6fe47b7bc09d59997465ea31a3f54170e16956ba Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Sat, 28 Aug 2021 15:16:37 +0100 Subject: [PATCH 03/16] Linted, formatting and docs --- .../logic/logic_app_standard_resource.go | 5 +- .../logic/logic_app_standard_resource_test.go | 155 +++-------- .../docs/r/logic_app_standard.html.markdown | 248 ++++++++++++++++++ 3 files changed, 283 insertions(+), 125 deletions(-) create mode 100644 website/docs/r/logic_app_standard.html.markdown diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index 1e2260841058..0a01925b1ad6 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -598,14 +598,11 @@ func getBasicLogicAppSettings(d *pluginsdk.ResourceData, endpointSuffix string) {Name: &storagePropName, Value: &storageConnection}, {Name: &functionVersionPropName, Value: &functionVersion}, {Name: &appKindPropName, Value: &appKindPropValue}, - } - - consumptionSettings := []web.NameValuePair{ {Name: &contentSharePropName, Value: &contentShare}, {Name: &contentFileConnStringPropName, Value: &storageConnection}, } - return append(basicSettings, consumptionSettings...), nil + return basicSettings, nil } func schemaLogicAppStandardSiteConfig() *pluginsdk.Schema { diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go index b71a5b3671fe..18c31b114e22 100644 --- a/internal/services/logic/logic_app_standard_resource_test.go +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -3,7 +3,6 @@ package logic_test import ( "context" "fmt" - "strings" "testing" "github.com/hashicorp/terraform-provider-azurerm/helpers/validate" @@ -26,15 +25,12 @@ func TestAccLogicAppStandard_basic(t *testing.T) { Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - data.CheckWithClient(r.hasContentShareAppSetting(true)), + acceptance.TestCheckResourceAttr(data.ResourceName, "kind", "functionapp,workflowapp"), check.That(data.ResourceName).Key("version").HasValue("~3"), check.That(data.ResourceName).Key("outbound_ip_addresses").Exists(), check.That(data.ResourceName).Key("possible_outbound_ip_addresses").Exists(), check.That(data.ResourceName).Key("custom_domain_verification_id").Exists(), - check.That(data.ResourceName).Key("identity.#").HasValue("1"), - check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), - check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), - check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), + check.That(data.ResourceName).Key("app_settings.APP_KIND").HasValue("workflowApp"), ), }, data.ImportStep(), @@ -50,7 +46,6 @@ func TestAccLogicAppStandard_requiresImport(t *testing.T) { Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - data.CheckWithClient(r.hasContentShareAppSetting(true)), ), }, data.RequiresImportErrorStep(r.requiresImport), @@ -118,20 +113,6 @@ func TestAccLogicAppStandard_appSettings(t *testing.T) { ), }, data.ImportStep(), - { - Config: r.appSettingsUpdate(data), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep("app_settings.%", "app_settings.AzureWebJobsDashboard", "app_settings.AzureWebJobsStorage"), - { - Config: r.basic(data), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), }) } @@ -719,33 +700,6 @@ func (r LogicAppStandardResource) Exists(ctx context.Context, clients *clients.C return utils.Bool(true), nil } -func (r LogicAppStandardResource) hasContentShareAppSetting(shouldExist bool) func(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) error { - return func(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) error { - id, err := parse.FunctionAppID(state.ID) - if err != nil { - return err - } - - appSettingsResp, err := clients.Web.AppServicesClient.ListApplicationSettings(ctx, id.ResourceGroup, id.SiteName) - if err != nil { - return fmt.Errorf("listing AppSettings: %+v", err) - } - - exists := false - for k := range appSettingsResp.Properties { - if strings.EqualFold("WEBSITE_CONTENTSHARE", k) { - exists = true - break - } - } - if exists != shouldExist { - return fmt.Errorf("expected %t but got %t", shouldExist, exists) - } - - return nil - } -} - func (r LogicAppStandardResource) basic(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -793,10 +747,10 @@ func (r LogicAppStandardResource) requiresImport(data acceptance.TestData) strin %s resource "azurerm_logic_app_standard" "import" { - name = azurerm_function_app.test.name - location = azurerm_function_app.test.location - resource_group_name = azurerm_function_app.test.resource_group_name - app_service_plan_id = azurerm_function_app.test.app_service_plan_id + name = azurerm_logic_app_standard.test.name + location = azurerm_logic_app_standard.test.location + resource_group_name = azurerm_logic_app_standard.test.resource_group_name + app_service_plan_id = azurerm_logic_app_standard.test.app_service_plan_id storage_account_name = azurerm_storage_account.test.name storage_account_access_key = azurerm_storage_account.test.primary_access_key } @@ -975,54 +929,13 @@ resource "azurerm_logic_app_standard" "test" { storage_account_access_key = azurerm_storage_account.test.primary_access_key app_settings = { - "hello" = "world" - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomString) -} - -func (r LogicAppStandardResource) appSettingsUpdate(data acceptance.TestData) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%[1]d" - location = "%[2]s" -} - -resource "azurerm_storage_account" "test" { - name = "acctestsa%[3]s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - account_tier = "Standard" - account_replication_type = "LRS" -} - -resource "azurerm_app_service_plan" "test" { - name = "acctestASP-%[1]d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - - sku { - tier = "Standard" - size = "S1" - } -} - -resource "azurerm_logic_app_standard" "test" { - name = "acctest-%[1]d-func" - 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 - - app_settings = { - "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_storage_account.test.primary_connection_string - "AzureWebJobsDashboard" = azurerm_storage_account.test.primary_connection_string - "AzureWebJobsStorage" = azurerm_storage_account.test.primary_connection_string + "hello" = "world" + "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_storage_account.test.primary_connection_string + "FUNCTIONS_EXTENSION_VERSION" = "~3" + "AzureWebJobsStorage" = azurerm_storage_account.test.primary_connection_string + "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.test.primary_connection_string + "WEBSITE_CONTENTSHARE" = "acctest-%[1]d-func-content" + "APP_KIND" = "workflowApp" } } `, data.RandomInteger, data.Locations.Primary, data.RandomString) @@ -1628,10 +1541,10 @@ resource "azurerm_app_service_plan" "test" { } resource "azurerm_logic_app_standard" "test" { - name = "acctest-%d-func" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - app_service_plan_id = azurerm_app_service_plan.test.id + name = "acctest-%d-func" + 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 @@ -1674,10 +1587,10 @@ resource "azurerm_app_service_plan" "test" { } resource "azurerm_logic_app_standard" "test" { - name = "acctest-%d-func" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - app_service_plan_id = azurerm_app_service_plan.test.id + name = "acctest-%d-func" + 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 } @@ -2058,10 +1971,10 @@ resource "azurerm_app_service_plan" "test" { } resource "azurerm_logic_app_standard" "test" { - name = "acctest-%d-func" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - app_service_plan_id = azurerm_app_service_plan.test.id + name = "acctest-%d-func" + 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 @@ -2104,10 +2017,10 @@ resource "azurerm_app_service_plan" "test" { } resource "azurerm_logic_app_standard" "test" { - name = "acctest-%d-func" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - app_service_plan_id = azurerm_app_service_plan.test.id + name = "acctest-%d-func" + 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 @@ -2150,13 +2063,13 @@ resource "azurerm_app_service_plan" "test" { } resource "azurerm_logic_app_standard" "test" { - name = "acctest-%d-func" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - app_service_plan_id = azurerm_app_service_plan.test.id + name = "acctest-%d-func" + 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 - version = "~3" + version = "~3" site_config { pre_warmed_instance_count = 1 diff --git a/website/docs/r/logic_app_standard.html.markdown b/website/docs/r/logic_app_standard.html.markdown new file mode 100644 index 000000000000..293fdbe45caa --- /dev/null +++ b/website/docs/r/logic_app_standard.html.markdown @@ -0,0 +1,248 @@ +--- +subcategory: "App Service (Web Apps)" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_logic_app_standard" +description: |- + Manages a Logic Application (Standard / Single Tenant). + +--- + +# azurerm_logic_app_standard + +Manages a Logic App (Standard / Single Tenant) + +~> **Note:** To connect an Azure Logic App and a subnet within the same region `azurerm_app_service_virtual_network_swift_connection` can be used. +For an example, check the `azurerm_app_service_virtual_network_swift_connection` documentation. + +## Example Usage (with App Service Plan) + +```hcl +resource "azurerm_resource_group" "example" { + name = "azure-functions-test-rg" + location = "West Europe" +} + +resource "azurerm_storage_account" "example" { + name = "functionsapptestsa" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "example" { + name = "azure-functions-test-service-plan" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + + sku { + tier = "ElasticPremium" + size = "EP1" + } +} + +resource "azurerm_logic_app_standard" "example" { + name = "test-azure-functions" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + app_service_plan_id = azurerm_app_service_plan.example.id + storage_account_name = azurerm_storage_account.example.name + storage_account_access_key = azurerm_storage_account.example.primary_access_key +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Logic App Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which to create the Logic App + +* `location` - (Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. + +* `app_service_plan_id` - (Required) The ID of the App Service Plan within which to create this Logic App + +* `app_settings` - (Optional) A map of key-value pairs for [App Settings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings) and custom values. + +~> **NOTE:** The values for `AzureWebJobsStorage` and `FUNCTIONS_EXTENSION_VERSION` will be filled by other input arguments and shouldn't be configured separately. `AzureWebJobsStorage` is filled based on `storage_account_name` and `storage_account_access_key`. `FUNCTIONS_EXTENSION_VERSION` is filled based on `version`. + +* `auth_settings` - (Optional) A `auth_settings` block as defined below. + +* `connection_string` - (Optional) An `connection_string` block as defined below. + +* `client_affinity_enabled` - (Optional) Should the Logic App send session affinity cookies, which route client requests in the same session to the same instance? + +* `client_cert_mode` - (Optional) The mode of the Logic App's client certificates requirement for incoming requests. Possible values are `Required` and `Optional`. + +* `enabled` - (Optional) Is the Logic App enabled? + +* `https_only` - (Optional) Can the Logic App only be accessed via HTTPS? Defaults to `false`. + +* `identity` - (Optional) An `identity` block as defined below. + +* `site_config` - (Optional) A `site_config` object as defined below. + +* `storage_account_name` - (Required) The backend storage account name which will be used by this Logic App (such as the dashboard, logs). + +* `storage_account_access_key` - (Required) The access key which will be used to access the backend storage account for the Logic App + +~> **Note:** When integrating a `CI/CD pipeline` and expecting to run from a deployed package in `Azure` you must seed your `app settings` as part of terraform code for Logic App to be successfully deployed. `Important Default key pairs`: (`"WEBSITE_RUN_FROM_PACKAGE" = ""`, `"FUNCTIONS_WORKER_RUNTIME" = "node"` (or python, etc), `"WEBSITE_NODE_DEFAULT_VERSION" = "10.14.1"`, `"APPINSIGHTS_INSTRUMENTATIONKEY" = ""`). + +~> **Note:** When using an App Service Plan in the `Free` or `Shared` Tiers `use_32_bit_worker_process` must be set to `true`. + +* `version` - (Optional) The runtime version associated with the Logic App Defaults to `~1`. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +--- + +`connection_string` supports the following: + +* `name` - (Required) The name of the Connection String. + +* `type` - (Required) The type of the Connection String. Possible values are `APIHub`, `Custom`, `DocDb`, `EventHub`, `MySQL`, `NotificationHub`, `PostgreSQL`, `RedisCache`, `ServiceBus`, `SQLAzure` and `SQLServer`. + +* `value` - (Required) The value for the Connection String. + +--- + +`site_config` supports the following: + +* `always_on` - (Optional) Should the Logic App be loaded at all times? Defaults to `false`. + +* `app_scale_limit` - (Optional) The number of workers this Logic App can scale out to. Only applicable to apps on the Consumption and Premium plan. + +* `cors` - (Optional) A `cors` block as defined below. + +* `dotnet_framework_version` - (Optional) The version of the .net framework's CLR used in this Logic App Possible values are `v4.0` (including .NET Core 2.1 and 3.1), `v5.0` and `v6.0`. [For more information on which .net Framework version to use based on the runtime version you're targeting - please see this table](https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-class-library#supported-versions). Defaults to `v4.0`. + +* `elastic_instance_minimum` - (Optional) The number of minimum instances for this Logic App Only affects apps on the Premium plan. + +* `ftps_state` - (Optional) State of FTP / FTPS service for this Logic App Possible values include: `AllAllowed`, `FtpsOnly` and `Disabled`. Defaults to `AllAllowed`. + +* `health_check_path` - (Optional) Path which will be checked for this Logic App health. + +* `http2_enabled` - (Optional) Specifies whether or not the http2 protocol should be enabled. Defaults to `false`. + +* `ip_restriction` - (Optional) A [List of objects](/docs/configuration/attr-as-blocks.html) representing ip restrictions as defined below. + +-> **NOTE** User has to explicitly set `ip_restriction` to empty slice (`[]`) to remove it. + +* `min_tls_version` - (Optional) The minimum supported TLS version for the Logic App Possible values are `1.0`, `1.1`, and `1.2`. Defaults to `1.2` for new Logic Apps. + +* `pre_warmed_instance_count` - (Optional) The number of pre-warmed instances for this Logic App Only affects apps on the Premium plan. + +* `runtime_scale_monitoring_enabled` - (Optional) Should Runtime Scale Monitoring be enabled?. Only applicable to apps on the Premium plan. Defaults to `false`. + +* `use_32_bit_worker_process` - (Optional) Should the Logic App run in 32 bit mode, rather than 64 bit mode? Defaults to `true`. + +~> **Note:** when using an App Service Plan in the `Free` or `Shared` Tiers `use_32_bit_worker_process` must be set to `true`. + +* `websockets_enabled` - (Optional) Should WebSockets be enabled? + +--- + +A `cors` block supports the following: + +* `allowed_origins` - (Optional) A list of origins which should be able to make cross-origin calls. `*` can be used to allow all calls. + +* `support_credentials` - (Optional) Are credentials supported? + +--- + +An `identity` block supports the following: + +* `type` - (Required) Specifies the identity type of the Logic App Possible values are `SystemAssigned` (where Azure will generate a Service Principal for you), `UserAssigned` where you can specify the Service Principal IDs in the `identity_ids` field, and `SystemAssigned, UserAssigned` which assigns both a system managed identity as well as the specified user assigned identities. + +~> **NOTE:** When `type` is set to `SystemAssigned`, The assigned `principal_id` and `tenant_id` can be retrieved after the Logic App has been created. More details are available below. + +* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned. Required if `type` is `UserAssigned`. + +--- + +A `ip_restriction` block supports the following: + +* `ip_address` - (Optional) The IP Address used for this IP Restriction in CIDR notation. + +* `service_tag` - (Optional) The Service Tag used for this IP Restriction. + +* `virtual_network_subnet_id` - (Optional) The Virtual Network Subnet ID used for this IP Restriction. + +-> **NOTE:** One of either `ip_address`, `service_tag` or `virtual_network_subnet_id` must be specified + +* `name` - (Optional) The name for this IP Restriction. + +* `priority` - (Optional) The priority for this IP Restriction. Restrictions are enforced in priority order. By default, the priority is set to 65000 if not specified. + +* `action` - (Optional) Does this restriction `Allow` or `Deny` access for this IP range. Defaults to `Allow`. + +* `headers` - (Optional) The headers for this specific `ip_restriction` as defined below. + +--- + +A `headers` block supports the following: + +* `x_azure_fdid` - (Optional) A list of allowed Azure FrontDoor IDs in UUID notation with a maximum of 8. + +* `x_fd_health_probe` - (Optional) A list to allow the Azure FrontDoor health probe header. Only allowed value is "1". + +* `x_forwarded_for` - (Optional) A list of allowed 'X-Forwarded-For' IPs in CIDR notation with a maximum of 8 + +* `x_forwarded_host` - (Optional) A list of allowed 'X-Forwarded-Host' domains with a maximum of 8. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Logic App + +* `custom_domain_verification_id` - An identifier used by App Service to perform domain ownership verification via DNS TXT record. + +* `default_hostname` - The default hostname associated with the Logic App - such as `mysite.azurewebsites.net` + +* `outbound_ip_addresses` - A comma separated list of outbound IP addresses - such as `52.23.25.3,52.143.43.12` + +* `possible_outbound_ip_addresses` - A comma separated list of outbound IP addresses - such as `52.23.25.3,52.143.43.12,52.143.43.17` - not all of which are necessarily in use. Superset of `outbound_ip_addresses`. + +* `identity` - An `identity` block as defined below, which contains the Managed Service Identity information for this App Service. + +* `site_credential` - A `site_credential` block as defined below, which contains the site-level credentials used to publish to this App Service. + +* `kind` - The Logic App kind - will be `functionapp,workflowapp` + +--- + +The `identity` block exports the following: + +* `principal_id` - The Principal ID for the Service Principal associated with the Managed Service Identity of this App Service. + +* `tenant_id` - The Tenant ID for the Service Principal associated with the Managed Service Identity of this App Service. + +-> You can access the Principal ID via `azurerm_app_service.example.identity.0.principal_id` and the Tenant ID via `azurerm_app_service.example.identity.0.tenant_id` + +--- + +The `site_credential` block exports the following: + +* `username` - The username which can be used to publish to this App Service + +* `password` - The password associated with the username, which can be used to publish to this App Service. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Logic App +* `update` - (Defaults to 30 minutes) Used when updating the Logic App +* `read` - (Defaults to 5 minutes) Used when retrieving the Logic App +* `delete` - (Defaults to 30 minutes) Used when deleting the Logic App + +## Import + +Logic Apps can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_logic_app_standard.logicapp1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Web/sites/logicapp1 +``` From 72d613c4a18da8a155ea1bdded1177565f66924d Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Sat, 28 Aug 2021 15:22:09 +0100 Subject: [PATCH 04/16] Favour the logic validate package over MSI --- internal/services/logic/logic_app_standard_resource.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index 0a01925b1ad6..b0cc9bf88693 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -11,9 +11,9 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/services/logic/parse" - logicValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/logic/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/logic/validate" msiParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/parse" - "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/validate" + msiValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/validate" storageValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/storage/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tags" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -46,7 +46,7 @@ func resourceLogicAppStandard() *pluginsdk.Resource { Type: pluginsdk.TypeString, Required: true, ForceNew: true, - ValidateFunc: logicValidate.LogicAppStandardName, + ValidateFunc: validate.LogicAppStandardName, }, "resource_group_name": azure.SchemaResourceGroupName(), @@ -731,7 +731,7 @@ func schemaLogicAppStandardIdentity() *pluginsdk.Schema { MinItems: 1, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, - ValidateFunc: validate.UserAssignedIdentityID, + ValidateFunc: msiValidate.UserAssignedIdentityID, }, }, From 30eecfb77519811773b38d7aeb6857ade82e45f3 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Sat, 28 Aug 2021 15:42:56 +0100 Subject: [PATCH 05/16] Added support for passing a custom content share name --- .../logic/logic_app_standard_resource.go | 5 ++ .../logic/logic_app_standard_resource_test.go | 71 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index b0cc9bf88693..6472a8e350e6 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -592,7 +592,12 @@ func getBasicLogicAppSettings(d *pluginsdk.ResourceData, endpointSuffix string) accountKey := d.Get("storage_account_access_key").(string) storageConnection := fmt.Sprintf("DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;EndpointSuffix=%s", storageAccount, accountKey, endpointSuffix) functionVersion := d.Get("version").(string) + contentShare := strings.ToLower(d.Get("name").(string)) + "-content" + appSettings := expandAppSettings(d) + if val, ok := appSettings["WEBSITE_CONTENTSHARE"]; ok { + contentShare = *val + } basicSettings := []web.NameValuePair{ {Name: &storagePropName, Value: &storageConnection}, diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go index 18c31b114e22..d4dd13f84d88 100644 --- a/internal/services/logic/logic_app_standard_resource_test.go +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -116,6 +116,21 @@ func TestAccLogicAppStandard_appSettings(t *testing.T) { }) } +func TestAccLogicAppStandard_customShare(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.customShare(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccLogicAppStandard_siteConfig(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") r := LogicAppStandardResource{} @@ -941,6 +956,62 @@ resource "azurerm_logic_app_standard" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } +func (r LogicAppStandardResource) customShare(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_storage_share" "custom" { + name = "customshare" + storage_account_name = azurerm_storage_account.test.name +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + app_settings = { + "hello" = "world" + "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_storage_account.test.primary_connection_string + "FUNCTIONS_EXTENSION_VERSION" = "~3" + "AzureWebJobsStorage" = azurerm_storage_account.test.primary_connection_string + "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.test.primary_connection_string + "WEBSITE_CONTENTSHARE" = azurerm_storage_share.custom.name + "APP_KIND" = "workflowApp" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + func (r LogicAppStandardResource) alwaysOn(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { From e6d4dec772e77f6bfd8917a19a5931d9e4fa6d54 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Sat, 28 Aug 2021 15:51:27 +0100 Subject: [PATCH 06/16] Removed not relevant parameters for logic apps --- .../logic/logic_app_standard_resource.go | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index 6472a8e350e6..bac49286cd87 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -82,11 +82,6 @@ func resourceLogicAppStandard() *pluginsdk.Resource { }, false), }, - "daily_memory_time_quota": { - Type: pluginsdk.TypeInt, - Optional: true, - }, - "enabled": { Type: pluginsdk.TypeBool, Optional: true, @@ -254,7 +249,6 @@ func resourceLogicAppStandardCreate(d *pluginsdk.ResourceData, meta interface{}) clientCertMode := d.Get("client_cert_mode").(string) clientCertEnabled := clientCertMode != "" httpsOnly := d.Get("https_only").(bool) - dailyMemoryTimeQuota := d.Get("daily_memory_time_quota").(int) t := d.Get("tags").(map[string]interface{}) basicAppSettings, err := getBasicLogicAppSettings(d, endpointSuffix) @@ -337,7 +331,6 @@ func resourceLogicAppStandardUpdate(d *pluginsdk.ResourceData, meta interface{}) clientCertMode := d.Get("client_cert_mode").(string) clientCertEnabled := clientCertMode != "" httpsOnly := d.Get("https_only").(bool) - dailyMemoryTimeQuota := d.Get("daily_memory_time_quota").(int) t := d.Get("tags").(map[string]interface{}) basicAppSettings, err := getBasicLogicAppSettings(d, endpointSuffix) @@ -486,7 +479,6 @@ func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) e d.Set("enabled", props.Enabled) d.Set("default_hostname", props.DefaultHostName) d.Set("https_only", props.HTTPSOnly) - d.Set("daily_memory_time_quota", props.DailyMemoryTimeQuota) d.Set("outbound_ip_addresses", props.OutboundIPAddresses) d.Set("possible_outbound_ip_addresses", props.PossibleOutboundIPAddresses) d.Set("client_affinity_enabled", props.ClientAffinityEnabled) @@ -680,12 +672,6 @@ func schemaLogicAppStandardSiteConfig() *pluginsdk.Schema { Optional: true, }, - "java_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"1.8", "11"}, false), - }, - "elastic_instance_minimum": { Type: pluginsdk.TypeInt, Optional: true, @@ -1004,10 +990,6 @@ func flattenLogicAppStandardSiteConfig(input *web.SiteConfig) []interface{} { result["health_check_path"] = *input.HealthCheckPath } - if input.JavaVersion != nil { - result["java_version"] = *input.JavaVersion - } - if input.MinimumElasticInstanceCount != nil { result["elastic_instance_minimum"] = *input.MinimumElasticInstanceCount } @@ -1205,10 +1187,6 @@ func expandLogicAppStandardSiteConfig(d *pluginsdk.ResourceData) (web.SiteConfig siteConfig.HealthCheckPath = utils.String(v.(string)) } - if v, ok := config["java_version"]; ok { - siteConfig.JavaVersion = utils.String(v.(string)) - } - if v, ok := config["elastic_instance_minimum"]; ok { siteConfig.MinimumElasticInstanceCount = utils.Int32(int32(v.(int))) } From 70ac4aae32f868a9bfab003975bca1e503bebc85 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Sat, 28 Aug 2021 16:10:22 +0100 Subject: [PATCH 07/16] removed not used setting --- internal/services/logic/logic_app_standard_resource.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index bac49286cd87..0ee62fcb5d59 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -273,7 +273,6 @@ func resourceLogicAppStandardCreate(d *pluginsdk.ResourceData, meta interface{}) ClientAffinityEnabled: utils.Bool(clientAffinityEnabled), ClientCertEnabled: utils.Bool(clientCertEnabled), HTTPSOnly: utils.Bool(httpsOnly), - DailyMemoryTimeQuota: utils.Int32(int32(dailyMemoryTimeQuota)), SiteConfig: &siteConfig, }, } @@ -355,7 +354,6 @@ func resourceLogicAppStandardUpdate(d *pluginsdk.ResourceData, meta interface{}) ClientAffinityEnabled: utils.Bool(clientAffinityEnabled), ClientCertEnabled: utils.Bool(clientCertEnabled), HTTPSOnly: utils.Bool(httpsOnly), - DailyMemoryTimeQuota: utils.Int32(int32(dailyMemoryTimeQuota)), SiteConfig: &siteConfig, }, } From 13ef0c0283d2d0c381a5cb951e3fb1a34a3b1925 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Tue, 31 Aug 2021 18:50:37 +0100 Subject: [PATCH 08/16] supporting bundle settings + container mode --- .../logic/logic_app_standard_resource.go | 79 +++++++- .../logic/logic_app_standard_resource_test.go | 171 ++++++++++++++++-- .../docs/r/logic_app_standard.html.markdown | 61 ++++++- 3 files changed, 292 insertions(+), 19 deletions(-) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index 0ee62fcb5d59..b488feb321da 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -67,6 +67,18 @@ func resourceLogicAppStandard() *pluginsdk.Resource { }, }, + "use_extension_bundle": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + + "bundle_version_range": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "[1.*, 2.0.0)", + }, + "client_affinity_enabled": { Type: pluginsdk.TypeBool, Optional: true, @@ -242,7 +254,7 @@ func resourceLogicAppStandardCreate(d *pluginsdk.ResourceData, meta interface{}) } location := azure.NormalizeLocation(d.Get("location").(string)) - kind := "functionapp,workflowapp" + appServicePlanID := d.Get("app_service_plan_id").(string) enabled := d.Get("enabled").(bool) clientAffinityEnabled := d.Get("client_affinity_enabled").(bool) @@ -261,6 +273,11 @@ func resourceLogicAppStandardCreate(d *pluginsdk.ResourceData, meta interface{}) return fmt.Errorf("expanding `site_config` for Logic App Standard %q (Resource Group %q): %s", name, resourceGroup, err) } + kind := "functionapp,workflowapp" + if siteConfig.LinuxFxVersion != nil && len(*siteConfig.LinuxFxVersion) > 0 { + kind = "functionapp,linux,container,workflowapp" + } + siteConfig.AppSettings = &basicAppSettings siteEnvelope := web.Site{ @@ -323,7 +340,6 @@ func resourceLogicAppStandardUpdate(d *pluginsdk.ResourceData, meta interface{}) } location := azure.NormalizeLocation(d.Get("location").(string)) - kind := "functionapp,workflowapp" appServicePlanID := d.Get("app_service_plan_id").(string) enabled := d.Get("enabled").(bool) clientAffinityEnabled := d.Get("client_affinity_enabled").(bool) @@ -342,6 +358,11 @@ func resourceLogicAppStandardUpdate(d *pluginsdk.ResourceData, meta interface{}) return fmt.Errorf("expanding `site_config` for Logic App Standard %q (Resource Group %q): %s", id.SiteName, id.ResourceGroup, err) } + kind := "functionapp,workflowapp" + if siteConfig.LinuxFxVersion != nil && len(*siteConfig.LinuxFxVersion) > 0 { + kind = "functionapp,linux,container,workflowapp" + } + siteConfig.AppSettings = &basicAppSettings siteEnvelope := web.Site{ @@ -468,6 +489,7 @@ func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) e d.Set("name", id.SiteName) d.Set("resource_group_name", id.ResourceGroup) d.Set("kind", resp.Kind) + if location := resp.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) } @@ -516,6 +538,26 @@ func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) e d.Set("version", appSettings["FUNCTIONS_EXTENSION_VERSION"]) + if _, ok := appSettings["AzureFunctionsJobHost__extensionBundle__id"]; ok { + d.Set("use_extension_bundle", true) + if val, ok := appSettings["AzureFunctionsJobHost__extensionBundle__version"]; ok { + d.Set("bundle_version_range", val) + } + } else { + d.Set("use_extension_bundle", false) + d.Set("bundle_version_range", "[1.*, 2.0.0)") + } + + // Remove all the settings that are created by this resource so we don't to have to specify in app_settings + // block whenever we use azurerm_logic_app_standard. + delete(appSettings, "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING") + delete(appSettings, "APP_KIND") + delete(appSettings, "AzureFunctionsJobHost__extensionBundle__id") + delete(appSettings, "AzureFunctionsJobHost__extensionBundle__version") + delete(appSettings, "AzureWebJobsDashboard") + delete(appSettings, "AzureWebJobsStorage") + delete(appSettings, "FUNCTIONS_EXTENSION_VERSION") + if err = d.Set("app_settings", appSettings); err != nil { return err } @@ -597,6 +639,25 @@ func getBasicLogicAppSettings(d *pluginsdk.ResourceData, endpointSuffix string) {Name: &contentFileConnStringPropName, Value: &storageConnection}, } + useExtensionBundle := d.Get("use_extension_bundle").(bool) + if useExtensionBundle { + extensionBundlePropName := "AzureFunctionsJobHost__extensionBundle__id" + extensionBundleName := "Microsoft.Azure.Functions.ExtensionBundle.Workflows" + extensionBundleVersionPropName := "AzureFunctionsJobHost__extensionBundle__version" + extensionBundleVersion := d.Get("bundle_version_range").(string) + + if extensionBundleVersion == "" { + return nil, fmt.Errorf("when `use_extension_bundle` is true, `bundle_version_range` must be specified") + } + + bundleSettings := []web.NameValuePair{ + {Name: &extensionBundlePropName, Value: &extensionBundleName}, + {Name: &extensionBundleVersionPropName, Value: &extensionBundleVersion}, + } + + return append(basicSettings, bundleSettings...), nil + } + return basicSettings, nil } @@ -635,6 +696,12 @@ func schemaLogicAppStandardSiteConfig() *pluginsdk.Schema { "ip_restriction": schemaLogicAppStandardIpRestriction(), + "linux_fx_version": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + }, + "min_tls_version": { Type: pluginsdk.TypeString, Optional: true, @@ -965,6 +1032,10 @@ func flattenLogicAppStandardSiteConfig(input *web.SiteConfig) []interface{} { result["websockets_enabled"] = *input.WebSocketsEnabled } + if input.LinuxFxVersion != nil { + result["linux_fx_version"] = *input.LinuxFxVersion + } + if input.HTTP20Enabled != nil { result["http2_enabled"] = *input.HTTP20Enabled } @@ -1152,6 +1223,10 @@ func expandLogicAppStandardSiteConfig(d *pluginsdk.ResourceData) (web.SiteConfig siteConfig.WebSocketsEnabled = utils.Bool(v.(bool)) } + if v, ok := config["linux_fx_version"]; ok { + siteConfig.LinuxFxVersion = utils.String(v.(string)) + } + if v, ok := config["cors"]; ok { expand := expandLogicAppStandardCorsSettings(v) siteConfig.Cors = &expand diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go index d4dd13f84d88..1f9cb36b5ae4 100644 --- a/internal/services/logic/logic_app_standard_resource_test.go +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -3,6 +3,7 @@ package logic_test import ( "context" "fmt" + "strings" "testing" "github.com/hashicorp/terraform-provider-azurerm/helpers/validate" @@ -30,7 +31,38 @@ func TestAccLogicAppStandard_basic(t *testing.T) { check.That(data.ResourceName).Key("outbound_ip_addresses").Exists(), check.That(data.ResourceName).Key("possible_outbound_ip_addresses").Exists(), check.That(data.ResourceName).Key("custom_domain_verification_id").Exists(), - check.That(data.ResourceName).Key("app_settings.APP_KIND").HasValue("workflowApp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_containerized(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.containerized(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + acceptance.TestCheckResourceAttr(data.ResourceName, "kind", "functionapp,linux,container,workflowapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLogicAppStandard_extensionBundle(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.extensionBundle(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + data.CheckWithClient(r.hasExtensionBundleAppSetting(true)), ), }, data.ImportStep(), @@ -715,6 +747,33 @@ func (r LogicAppStandardResource) Exists(ctx context.Context, clients *clients.C return utils.Bool(true), nil } +func (r LogicAppStandardResource) hasExtensionBundleAppSetting(shouldExist bool) func(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) error { + return func(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) error { + id, err := parse.FunctionAppID(state.ID) + if err != nil { + return err + } + + appSettingsResp, err := clients.Web.AppServicesClient.ListApplicationSettings(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("listing AppSettings: %+v", err) + } + + exists := false + for k := range appSettingsResp.Properties { + if strings.EqualFold("AzureFunctionsJobHost__extensionBundle__id", k) { + exists = true + break + } + } + if exists != shouldExist { + return fmt.Errorf("expected %t but got %t", shouldExist, exists) + } + + return nil + } +} + func (r LogicAppStandardResource) basic(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -756,6 +815,96 @@ resource "azurerm_logic_app_standard" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } +func (r LogicAppStandardResource) containerized(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + kind = "Linux" + reserved = true + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + + site_config { + linux_fx_version = "DOCKER|mcr.microsoft.com/azure-functions/dotnet:3.0-appservice" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r LogicAppStandardResource) extensionBundle(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + use_extension_bundle = true + bundle_version_range = "[1.*, 2.0.0)" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + func (r LogicAppStandardResource) requiresImport(data acceptance.TestData) string { template := r.basic(data) return fmt.Sprintf(` @@ -944,13 +1093,9 @@ resource "azurerm_logic_app_standard" "test" { storage_account_access_key = azurerm_storage_account.test.primary_access_key app_settings = { - "hello" = "world" - "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_storage_account.test.primary_connection_string - "FUNCTIONS_EXTENSION_VERSION" = "~3" - "AzureWebJobsStorage" = azurerm_storage_account.test.primary_connection_string - "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.test.primary_connection_string - "WEBSITE_CONTENTSHARE" = "acctest-%[1]d-func-content" - "APP_KIND" = "workflowApp" + "hello" = "world" + "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_storage_account.test.primary_connection_string + "WEBSITE_CONTENTSHARE" = "acctest-%[1]d-func-content" } } `, data.RandomInteger, data.Locations.Primary, data.RandomString) @@ -1000,13 +1145,9 @@ resource "azurerm_logic_app_standard" "test" { storage_account_access_key = azurerm_storage_account.test.primary_access_key app_settings = { - "hello" = "world" - "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_storage_account.test.primary_connection_string - "FUNCTIONS_EXTENSION_VERSION" = "~3" - "AzureWebJobsStorage" = azurerm_storage_account.test.primary_connection_string - "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.test.primary_connection_string - "WEBSITE_CONTENTSHARE" = azurerm_storage_share.custom.name - "APP_KIND" = "workflowApp" + "hello" = "world" + "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_storage_account.test.primary_connection_string + "WEBSITE_CONTENTSHARE" = azurerm_storage_share.custom.name } } `, data.RandomInteger, data.Locations.Primary, data.RandomString) diff --git a/website/docs/r/logic_app_standard.html.markdown b/website/docs/r/logic_app_standard.html.markdown index 293fdbe45caa..27153ded2281 100644 --- a/website/docs/r/logic_app_standard.html.markdown +++ b/website/docs/r/logic_app_standard.html.markdown @@ -51,6 +51,59 @@ resource "azurerm_logic_app_standard" "example" { } ``` +## Example Usage (for container mode) + +~> **Note:** You must set `azurerm_app_service_plan` `kind` to `Linux` and `reserved` to `true` when used with `linux_fx_version` + +```hcl +resource "azurerm_resource_group" "example" { + name = "azure-functions-test-rg" + location = "West Europe" +} + +resource "azurerm_storage_account" "example" { + name = "functionsapptestsa" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "example" { + name = "azure-functions-test-service-plan" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + kind = "Linux" + reserved = true + + sku { + tier = "ElasticPremium" + size = "EP1" + } +} + +resource "azurerm_logic_app_standard" "example" { + name = "test-azure-functions" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + app_service_plan_id = azurerm_app_service_plan.example.id + storage_account_name = azurerm_storage_account.example.name + storage_account_access_key = azurerm_storage_account.example.primary_access_key + + site_config { + linux_fx_version = "DOCKER|mcr.microsoft.com/azure-functions/dotnet:3.0-appservice" + } + + app_settings = { + "DOCKER_REGISTRY_SERVER_URL" = "https://.azurecr.io" + "DOCKER_REGISTRY_SERVER_USERNAME" = "username" + "DOCKER_REGISTRY_SERVER_PASSWORD" = "password" + } + +} +``` + + ## Argument Reference The following arguments are supported: @@ -65,9 +118,11 @@ The following arguments are supported: * `app_settings` - (Optional) A map of key-value pairs for [App Settings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings) and custom values. -~> **NOTE:** The values for `AzureWebJobsStorage` and `FUNCTIONS_EXTENSION_VERSION` will be filled by other input arguments and shouldn't be configured separately. `AzureWebJobsStorage` is filled based on `storage_account_name` and `storage_account_access_key`. `FUNCTIONS_EXTENSION_VERSION` is filled based on `version`. +~> **NOTE:** There are a number of application settings that will be managed for you by this resource type and shouldn't be configured separately as part of the app_settings you specify. `AzureWebJobsStorage` is filled based on `storage_account_name` and `storage_account_access_key`. `FUNCTIONS_EXTENSION_VERSION` is filled based on `version`. `APP_KIND` is set to workflowApp and `AzureFunctionsJobHost__extensionBundle__id` and `AzureFunctionsJobHost__extensionBundle__version` are set as detailed below. -* `auth_settings` - (Optional) A `auth_settings` block as defined below. +* `use_extension_bundle` - (Optional) Should the logic app use the bundled extension package? If true, then application settings for `AzureFunctionsJobHost__extensionBundle__id` and `AzureFunctionsJobHost__extensionBundle__version` will be created. Default true + +* `bundle_version_range` - (Optional) If `use_extension_bundle` then controls the allowed range for bundle versions. Default `[1.*, 2.0.0)` * `connection_string` - (Optional) An `connection_string` block as defined below. @@ -129,6 +184,8 @@ The following arguments are supported: -> **NOTE** User has to explicitly set `ip_restriction` to empty slice (`[]`) to remove it. +* `linux_fx_version` - (Optional) Linux App Framework and version for the AppService, e.g. `DOCKER|(golang:latest)`. Setting this value will also set the `kind` of application deployed to `functionapp,linux,container,workflowapp` + * `min_tls_version` - (Optional) The minimum supported TLS version for the Logic App Possible values are `1.0`, `1.1`, and `1.2`. Defaults to `1.2` for new Logic Apps. * `pre_warmed_instance_count` - (Optional) The number of pre-warmed instances for this Logic App Only affects apps on the Premium plan. From 25ecfce93dc69ea107301621ed95782281a27625 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Tue, 31 Aug 2021 18:52:39 +0100 Subject: [PATCH 09/16] fixed up page sub category --- website/docs/r/logic_app_standard.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/logic_app_standard.html.markdown b/website/docs/r/logic_app_standard.html.markdown index 27153ded2281..6e3073d1037f 100644 --- a/website/docs/r/logic_app_standard.html.markdown +++ b/website/docs/r/logic_app_standard.html.markdown @@ -1,5 +1,5 @@ --- -subcategory: "App Service (Web Apps)" +subcategory: "Logic App" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_logic_app_standard" description: |- From 979686a4a40f30827676e3b029e8888d25e79c29 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Mon, 13 Sep 2021 10:39:42 +0100 Subject: [PATCH 10/16] Support vnet route all --- .../logic/logic_app_standard_resource.go | 16 ++++++ .../logic/logic_app_standard_resource_test.go | 54 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index b488feb321da..182f992445dd 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -768,6 +768,12 @@ func schemaLogicAppStandardSiteConfig() *pluginsdk.Schema { }, true), DiffSuppressFunc: suppress.CaseDifference, }, + + "vnet_route_all_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, }, }, } @@ -1075,6 +1081,12 @@ func flattenLogicAppStandardSiteConfig(input *web.SiteConfig) []interface{} { result["dotnet_framework_version"] = *input.NetFrameworkVersion } + vnetRouteAllEnabled := false + if input.VnetRouteAllEnabled != nil { + vnetRouteAllEnabled = *input.VnetRouteAllEnabled + } + result["vnet_route_all_enabled"] = vnetRouteAllEnabled + results = append(results, result) return results } @@ -1276,6 +1288,10 @@ func expandLogicAppStandardSiteConfig(d *pluginsdk.ResourceData) (web.SiteConfig siteConfig.NetFrameworkVersion = utils.String(v.(string)) } + if v, ok := config["vnet_route_all_enabled"]; ok { + siteConfig.VnetRouteAllEnabled = utils.Bool(v.(bool)) + } + return siteConfig, nil } diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go index 1f9cb36b5ae4..2e8f10600bc1 100644 --- a/internal/services/logic/logic_app_standard_resource_test.go +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -69,6 +69,21 @@ func TestAccLogicAppStandard_extensionBundle(t *testing.T) { }) } +func TestAccFunctionApp_siteConfigVnetRouteAllEnabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.siteConfigVnetRouteAllEnabled(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.vnet_route_all_enabled").HasValue("true"), + ), + }, + data.ImportStep(), + }) +} func TestAccLogicAppStandard_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") r := LogicAppStandardResource{} @@ -905,6 +920,45 @@ resource "azurerm_logic_app_standard" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } +func (r LogicAppStandardResource) siteConfigVnetRouteAllEnabled(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku { + tier = "Standard" + size = "S1" + } +} +resource "azurerm_function_app" "test" { + name = "acctest-%[1]d-func" + 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 + site_config { + vnet_route_all_enabled = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + func (r LogicAppStandardResource) requiresImport(data acceptance.TestData) string { template := r.basic(data) return fmt.Sprintf(` From ca66164858120b3c2a6c33c0ef61c4551059facf Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Fri, 17 Sep 2021 09:47:22 +0100 Subject: [PATCH 11/16] Updating tests and fixing content share setting --- .../logic/logic_app_standard_resource.go | 22 +++++-- .../logic/logic_app_standard_resource_test.go | 59 ++++++++++++++++++- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index 182f992445dd..f4ecfa5386b0 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -3,6 +3,7 @@ package logic import ( "fmt" "log" + "strconv" "strings" "time" @@ -365,6 +366,18 @@ func resourceLogicAppStandardUpdate(d *pluginsdk.ResourceData, meta interface{}) siteConfig.AppSettings = &basicAppSettings + // WEBSITE_VNET_ROUTE_ALL is superseded by a setting in site_config that defaults to false from 2021-02-01 + appSettings, err := expandLogicAppStandardSettings(d, endpointSuffix) + if err != nil { + return fmt.Errorf("expanding `app_settings` for Function App %q (Resource Group %q): %+v", id.SiteName, id.ResourceGroup, err) + } + if vnetRouteAll, ok := appSettings["WEBSITE_VNET_ROUTE_ALL"]; ok { + if !d.HasChange("site_config.0.vnet_route_all_enabled") { + vnetRouteAllEnabled, _ := strconv.ParseBool(*vnetRouteAll) + siteConfig.VnetRouteAllEnabled = &vnetRouteAllEnabled + } + } + siteEnvelope := web.Site{ Kind: &kind, Location: &location, @@ -398,10 +411,6 @@ func resourceLogicAppStandardUpdate(d *pluginsdk.ResourceData, meta interface{}) return fmt.Errorf("waiting for update of Logic App Standard %q (Resource Group %q): %+v", id.SiteName, id.ResourceGroup, err) } - appSettings, err := expandLogicAppStandardSettings(d, endpointSuffix) - if err != nil { - return err - } settings := web.StringDictionary{ Properties: appSettings, } @@ -558,6 +567,11 @@ func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) e delete(appSettings, "AzureWebJobsStorage") delete(appSettings, "FUNCTIONS_EXTENSION_VERSION") + configSettings := expandAppSettings(d) + if _, ok := configSettings["WEBSITE_CONTENTSHARE"]; !ok { + delete(appSettings, "WEBSITE_CONTENTSHARE") + } + if err = d.Set("app_settings", appSettings); err != nil { return err } diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go index 2e8f10600bc1..d9e6d24a6408 100644 --- a/internal/services/logic/logic_app_standard_resource_test.go +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -84,6 +84,24 @@ func TestAccFunctionApp_siteConfigVnetRouteAllEnabled(t *testing.T) { data.ImportStep(), }) } + +func TestAccFunctionApp_appSettingsVnetRouteAllEnabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") + r := LogicAppStandardResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appSettingsVnetRouteAllEnabled(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("app_settings.WEBSITE_VNET_ROUTE_ALL").HasValue("true"), + check.That(data.ResourceName).Key("site_config.0.vnet_route_all_enabled").HasValue("true"), + ), + }, + data.ImportStep(), + }) +} + func TestAccLogicAppStandard_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") r := LogicAppStandardResource{} @@ -945,7 +963,7 @@ resource "azurerm_app_service_plan" "test" { size = "S1" } } -resource "azurerm_function_app" "test" { +resource "azurerm_logic_app_standard" "test" { name = "acctest-%[1]d-func" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name @@ -959,6 +977,45 @@ resource "azurerm_function_app" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } +func (r LogicAppStandardResource) appSettingsVnetRouteAllEnabled(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku { + tier = "Standard" + size = "S1" + } +} +resource "azurerm_logic_app_standard" "test" { + name = "acctest-%[1]d-func" + 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 + app_settings = { + "WEBSITE_VNET_ROUTE_ALL" = "true" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + func (r LogicAppStandardResource) requiresImport(data acceptance.TestData) string { template := r.basic(data) return fmt.Sprintf(` From 171936455402168b2f0d4350c8fd17b8fb10421c Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Sat, 18 Sep 2021 14:06:38 +0100 Subject: [PATCH 12/16] Added setting storage_account_share_name for controlling share name --- .../logic/logic_app_standard_resource.go | 19 +++++++++++-------- .../logic/logic_app_standard_resource_test.go | 4 ++-- .../docs/r/logic_app_standard.html.markdown | 6 ++++-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index f4ecfa5386b0..32af36aaa5fa 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -164,6 +164,12 @@ func resourceLogicAppStandard() *pluginsdk.Resource { ValidateFunc: validation.NoZeroValues, }, + "storage_account_share_name": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + }, + "version": { Type: pluginsdk.TypeString, Optional: true, @@ -557,6 +563,8 @@ func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) e d.Set("bundle_version_range", "[1.*, 2.0.0)") } + d.Set("storage_account_share_name", appSettings["WEBSITE_CONTENTSHARE"]) + // Remove all the settings that are created by this resource so we don't to have to specify in app_settings // block whenever we use azurerm_logic_app_standard. delete(appSettings, "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING") @@ -566,11 +574,7 @@ func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) e delete(appSettings, "AzureWebJobsDashboard") delete(appSettings, "AzureWebJobsStorage") delete(appSettings, "FUNCTIONS_EXTENSION_VERSION") - - configSettings := expandAppSettings(d) - if _, ok := configSettings["WEBSITE_CONTENTSHARE"]; !ok { - delete(appSettings, "WEBSITE_CONTENTSHARE") - } + delete(appSettings, "WEBSITE_CONTENTSHARE") if err = d.Set("app_settings", appSettings); err != nil { return err @@ -640,9 +644,8 @@ func getBasicLogicAppSettings(d *pluginsdk.ResourceData, endpointSuffix string) functionVersion := d.Get("version").(string) contentShare := strings.ToLower(d.Get("name").(string)) + "-content" - appSettings := expandAppSettings(d) - if val, ok := appSettings["WEBSITE_CONTENTSHARE"]; ok { - contentShare = *val + if _, ok := d.GetOk("storage_account_share_name"); ok { + contentShare = d.Get("storage_account_share_name").(string) } basicSettings := []web.NameValuePair{ diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go index d9e6d24a6408..8673298d3c88 100644 --- a/internal/services/logic/logic_app_standard_resource_test.go +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -1202,11 +1202,11 @@ resource "azurerm_logic_app_standard" "test" { 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 + storage_account_share_name = "acctest-%[1]d-func-content" app_settings = { "hello" = "world" "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_storage_account.test.primary_connection_string - "WEBSITE_CONTENTSHARE" = "acctest-%[1]d-func-content" } } `, data.RandomInteger, data.Locations.Primary, data.RandomString) @@ -1254,11 +1254,11 @@ resource "azurerm_logic_app_standard" "test" { 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 + storage_account_share_name = azurerm_storage_share.custom.name app_settings = { "hello" = "world" "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_storage_account.test.primary_connection_string - "WEBSITE_CONTENTSHARE" = azurerm_storage_share.custom.name } } `, data.RandomInteger, data.Locations.Primary, data.RandomString) diff --git a/website/docs/r/logic_app_standard.html.markdown b/website/docs/r/logic_app_standard.html.markdown index 6e3073d1037f..eb989e9f2b61 100644 --- a/website/docs/r/logic_app_standard.html.markdown +++ b/website/docs/r/logic_app_standard.html.markdown @@ -118,7 +118,7 @@ The following arguments are supported: * `app_settings` - (Optional) A map of key-value pairs for [App Settings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings) and custom values. -~> **NOTE:** There are a number of application settings that will be managed for you by this resource type and shouldn't be configured separately as part of the app_settings you specify. `AzureWebJobsStorage` is filled based on `storage_account_name` and `storage_account_access_key`. `FUNCTIONS_EXTENSION_VERSION` is filled based on `version`. `APP_KIND` is set to workflowApp and `AzureFunctionsJobHost__extensionBundle__id` and `AzureFunctionsJobHost__extensionBundle__version` are set as detailed below. +~> **NOTE:** There are a number of application settings that will be managed for you by this resource type and *shouldn't* be configured separately as part of the app_settings you specify. `AzureWebJobsStorage` is filled based on `storage_account_name` and `storage_account_access_key`. `WEBSITE_CONTENTSHARE` is detailed below. `FUNCTIONS_EXTENSION_VERSION` is filled based on `version`. `APP_KIND` is set to workflowApp and `AzureFunctionsJobHost__extensionBundle__id` and `AzureFunctionsJobHost__extensionBundle__version` are set as detailed below. * `use_extension_bundle` - (Optional) Should the logic app use the bundled extension package? If true, then application settings for `AzureFunctionsJobHost__extensionBundle__id` and `AzureFunctionsJobHost__extensionBundle__version` will be created. Default true @@ -138,10 +138,12 @@ The following arguments are supported: * `site_config` - (Optional) A `site_config` object as defined below. -* `storage_account_name` - (Required) The backend storage account name which will be used by this Logic App (such as the dashboard, logs). +* `storage_account_name` - (Required) The backend storage account name which will be used by this Logic App (e.g. for Stateful workflows data) * `storage_account_access_key` - (Required) The access key which will be used to access the backend storage account for the Logic App +* `storage_account_share_name` - (Optional) The name of the share used by the logic app, if you want to use a custom name. This corresponds to the WEBSITE_CONTENTSHARE appsetting, which this resource will create for you. If you don't specify a name, then this resource will generate a dynamic name. This setting is useful if you want to provision a storage account and create a share using azurerm_storage_share + ~> **Note:** When integrating a `CI/CD pipeline` and expecting to run from a deployed package in `Azure` you must seed your `app settings` as part of terraform code for Logic App to be successfully deployed. `Important Default key pairs`: (`"WEBSITE_RUN_FROM_PACKAGE" = ""`, `"FUNCTIONS_WORKER_RUNTIME" = "node"` (or python, etc), `"WEBSITE_NODE_DEFAULT_VERSION" = "10.14.1"`, `"APPINSIGHTS_INSTRUMENTATIONKEY" = ""`). ~> **Note:** When using an App Service Plan in the `Free` or `Shared` Tiers `use_32_bit_worker_process` must be set to `true`. From f83854e1b43d93eef9a81577e3239f7a9622ed10 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Sat, 18 Sep 2021 14:34:58 +0100 Subject: [PATCH 13/16] Added vet setting to docs. Renamed test method names to follow LogicApp naming --- .../services/logic/logic_app_standard_resource_test.go | 10 +++++----- website/docs/r/logic_app_standard.html.markdown | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go index 8673298d3c88..c87d1a3f5e36 100644 --- a/internal/services/logic/logic_app_standard_resource_test.go +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" - "github.com/hashicorp/terraform-provider-azurerm/internal/services/web/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/logic/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" ) @@ -69,7 +69,7 @@ func TestAccLogicAppStandard_extensionBundle(t *testing.T) { }) } -func TestAccFunctionApp_siteConfigVnetRouteAllEnabled(t *testing.T) { +func TestAccLogicAppStandard_siteConfigVnetRouteAllEnabled(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") r := LogicAppStandardResource{} @@ -85,7 +85,7 @@ func TestAccFunctionApp_siteConfigVnetRouteAllEnabled(t *testing.T) { }) } -func TestAccFunctionApp_appSettingsVnetRouteAllEnabled(t *testing.T) { +func TestAccLogicAppStandard_appSettingsVnetRouteAllEnabled(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") r := LogicAppStandardResource{} @@ -759,7 +759,7 @@ func TestAccLogicAppStandard_dotnetVersion6(t *testing.T) { } func (r LogicAppStandardResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { - id, err := parse.FunctionAppID(state.ID) + id, err := parse.LogicAppStandardID(state.ID) if err != nil { return nil, err } @@ -782,7 +782,7 @@ func (r LogicAppStandardResource) Exists(ctx context.Context, clients *clients.C func (r LogicAppStandardResource) hasExtensionBundleAppSetting(shouldExist bool) func(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) error { return func(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) error { - id, err := parse.FunctionAppID(state.ID) + id, err := parse.LogicAppStandardID(state.ID) if err != nil { return err } diff --git a/website/docs/r/logic_app_standard.html.markdown b/website/docs/r/logic_app_standard.html.markdown index eb989e9f2b61..2d452b29201b 100644 --- a/website/docs/r/logic_app_standard.html.markdown +++ b/website/docs/r/logic_app_standard.html.markdown @@ -198,6 +198,8 @@ The following arguments are supported: ~> **Note:** when using an App Service Plan in the `Free` or `Shared` Tiers `use_32_bit_worker_process` must be set to `true`. +* `vnet_route_all_enabled` - (Optional) Should all outbound traffic to have Virtual Network Security Groups and User Defined Routes applied. + * `websockets_enabled` - (Optional) Should WebSockets be enabled? --- From b6187e134656f7b033019b635348a1781395adf2 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Sat, 18 Sep 2021 19:20:50 +0100 Subject: [PATCH 14/16] Removing user identity support - Logic Apps currently only support System Identity --- .../logic/logic_app_standard_resource.go | 37 ----- .../logic/logic_app_standard_resource_test.go | 140 ------------------ 2 files changed, 177 deletions(-) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index 32af36aaa5fa..d3b1022292ed 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -13,8 +13,6 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/services/logic/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/services/logic/validate" - msiParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/parse" - msiValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/validate" storageValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/storage/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tags" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -804,28 +802,14 @@ func schemaLogicAppStandardIdentity() *pluginsdk.Schema { MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ - "identity_ids": { - Type: pluginsdk.TypeList, - Optional: true, - MinItems: 1, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateFunc: msiValidate.UserAssignedIdentityID, - }, - }, - "type": { Type: pluginsdk.TypeString, Required: true, ValidateFunc: validation.StringInSlice([]string{ - string(web.ManagedServiceIdentityTypeNone), string(web.ManagedServiceIdentityTypeSystemAssigned), - string(web.ManagedServiceIdentityTypeSystemAssignedUserAssigned), - string(web.ManagedServiceIdentityTypeUserAssigned), }, true), DiffSuppressFunc: suppress.CaseDifference, }, - "principal_id": { Type: pluginsdk.TypeString, Computed: true, @@ -1013,20 +997,8 @@ func flattenLogicAppStandardIdentity(identity *web.ManagedServiceIdentity) ([]in tenantId = *identity.TenantID } - identityIds := make([]string, 0) - if identity.UserAssignedIdentities != nil { - for key := range identity.UserAssignedIdentities { - parsedId, err := msiParse.UserAssignedIdentityID(key) - if err != nil { - return nil, err - } - identityIds = append(identityIds, parsedId.ID()) - } - } - return []interface{}{ map[string]interface{}{ - "identity_ids": identityIds, "principal_id": principalId, "tenant_id": tenantId, "type": string(identity.Type), @@ -1319,19 +1291,10 @@ func expandLogicAppStandardIdentity(input []interface{}) *web.ManagedServiceIden identity := input[0].(map[string]interface{}) identityType := web.ManagedServiceIdentityType(identity["type"].(string)) - identityIds := make(map[string]*web.UserAssignedIdentity) - for _, id := range identity["identity_ids"].([]interface{}) { - identityIds[id.(string)] = &web.UserAssignedIdentity{} - } - managedServiceIdentity := web.ManagedServiceIdentity{ Type: identityType, } - if managedServiceIdentity.Type == web.ManagedServiceIdentityTypeUserAssigned || managedServiceIdentity.Type == web.ManagedServiceIdentityTypeSystemAssignedUserAssigned { - managedServiceIdentity.UserAssignedIdentities = identityIds - } - return &managedServiceIdentity } diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go index c87d1a3f5e36..da8deb36d5a0 100644 --- a/internal/services/logic/logic_app_standard_resource_test.go +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -346,36 +346,6 @@ func TestAccLogicAppStandard_updateIdentity(t *testing.T) { }) } -func TestAccLogicAppStandard_userAssignedIdentity(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") - r := LogicAppStandardResource{} - - data.ResourceTest(t, r, []acceptance.TestStep{ - { - Config: r.userAssignedIdentity(data), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("identity.0.type").HasValue("UserAssigned"), - check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("1"), - check.That(data.ResourceName).Key("identity.0.principal_id").IsEmpty(), - check.That(data.ResourceName).Key("identity.0.tenant_id").IsEmpty(), - ), - }, - data.ImportStep(), - { - Config: r.userAssignedIdentityUpdated(data), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("identity.0.type").HasValue("UserAssigned"), - check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("2"), - check.That(data.ResourceName).Key("identity.0.principal_id").IsEmpty(), - check.That(data.ResourceName).Key("identity.0.tenant_id").IsEmpty(), - ), - }, - data.ImportStep(), - }) -} - func TestAccLogicAppStandard_corsSettings(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_logic_app_standard", "test") r := LogicAppStandardResource{} @@ -1533,116 +1503,6 @@ resource "azurerm_logic_app_standard" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } -func (r LogicAppStandardResource) userAssignedIdentity(data acceptance.TestData) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%[1]d" - location = "%[2]s" -} - -resource "azurerm_storage_account" "test" { - name = "acctestsa%[3]s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - account_tier = "Standard" - account_replication_type = "LRS" -} - -resource "azurerm_app_service_plan" "test" { - name = "acctestASP-%[1]d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - - sku { - tier = "Standard" - size = "S1" - } -} - -resource "azurerm_user_assigned_identity" "first" { - name = "acctest1%[1]d" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location -} - -resource "azurerm_logic_app_standard" "test" { - name = "acctest-%[1]d-func" - 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 - - identity { - type = "UserAssigned" - identity_ids = [azurerm_user_assigned_identity.first.id] - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomString) -} - -func (r LogicAppStandardResource) userAssignedIdentityUpdated(data acceptance.TestData) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%[1]d" - location = "%[2]s" -} - -resource "azurerm_storage_account" "test" { - name = "acctestsa%[3]s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - account_tier = "Standard" - account_replication_type = "LRS" -} - -resource "azurerm_app_service_plan" "test" { - name = "acctestASP-%[1]d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - - sku { - tier = "Standard" - size = "S1" - } -} - -resource "azurerm_user_assigned_identity" "first" { - name = "acctest1%[1]d" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location -} - -resource "azurerm_user_assigned_identity" "second" { - name = "acctest2%[1]d" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location -} - -resource "azurerm_logic_app_standard" "test" { - name = "acctest-%[1]d-func" - 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 - - identity { - type = "UserAssigned" - identity_ids = [azurerm_user_assigned_identity.first.id, azurerm_user_assigned_identity.second.id] - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomString) -} - func (r LogicAppStandardResource) corsSettings(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { From d898347bb957064e4d8ec5b2cfec7a7c5233dbfb Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Sat, 18 Sep 2021 20:43:39 +0100 Subject: [PATCH 15/16] fixed up lint / remove not needed error --- .../services/logic/logic_app_standard_resource.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index d3b1022292ed..058aa37c98b4 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -578,10 +578,7 @@ func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) e return err } - identity, err := flattenLogicAppStandardIdentity(resp.Identity) - if err != nil { - return err - } + identity := flattenLogicAppStandardIdentity(resp.Identity) if err := d.Set("identity", identity); err != nil { return fmt.Errorf("setting `identity`: %s", err) } @@ -982,9 +979,9 @@ func flattenLogicAppStandardConnectionStrings(input map[string]*web.ConnStringVa return results } -func flattenLogicAppStandardIdentity(identity *web.ManagedServiceIdentity) ([]interface{}, error) { +func flattenLogicAppStandardIdentity(identity *web.ManagedServiceIdentity) []interface{} { if identity == nil { - return make([]interface{}, 0), nil + return make([]interface{}, 0) } principalId := "" @@ -1003,7 +1000,7 @@ func flattenLogicAppStandardIdentity(identity *web.ManagedServiceIdentity) ([]in "tenant_id": tenantId, "type": string(identity.Type), }, - }, nil + } } func flattenLogicAppStandardSiteConfig(input *web.SiteConfig) []interface{} { From e3508ffefa084206d6465acad5fdd718a1304749 Mon Sep 17 00:00:00 2001 From: Dylan Morley <5038454+dylanmorley@users.noreply.github.com> Date: Mon, 27 Sep 2021 20:58:08 +0100 Subject: [PATCH 16/16] Removed client_cert_mode abbreviation and renamed bundle_version_range --- .../logic/logic_app_standard_resource.go | 18 +++++++++--------- .../logic/logic_app_standard_resource_test.go | 12 ++++++------ .../docs/r/logic_app_standard.html.markdown | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/internal/services/logic/logic_app_standard_resource.go b/internal/services/logic/logic_app_standard_resource.go index 058aa37c98b4..2c168b508b5a 100644 --- a/internal/services/logic/logic_app_standard_resource.go +++ b/internal/services/logic/logic_app_standard_resource.go @@ -72,7 +72,7 @@ func resourceLogicAppStandard() *pluginsdk.Resource { Default: true, }, - "bundle_version_range": { + "bundle_version": { Type: pluginsdk.TypeString, Optional: true, Default: "[1.*, 2.0.0)", @@ -84,7 +84,7 @@ func resourceLogicAppStandard() *pluginsdk.Resource { Computed: true, }, - "client_cert_mode": { + "client_certificate_mode": { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{ @@ -263,7 +263,7 @@ func resourceLogicAppStandardCreate(d *pluginsdk.ResourceData, meta interface{}) appServicePlanID := d.Get("app_service_plan_id").(string) enabled := d.Get("enabled").(bool) clientAffinityEnabled := d.Get("client_affinity_enabled").(bool) - clientCertMode := d.Get("client_cert_mode").(string) + clientCertMode := d.Get("client_certificate_mode").(string) clientCertEnabled := clientCertMode != "" httpsOnly := d.Get("https_only").(bool) t := d.Get("tags").(map[string]interface{}) @@ -348,7 +348,7 @@ func resourceLogicAppStandardUpdate(d *pluginsdk.ResourceData, meta interface{}) appServicePlanID := d.Get("app_service_plan_id").(string) enabled := d.Get("enabled").(bool) clientAffinityEnabled := d.Get("client_affinity_enabled").(bool) - clientCertMode := d.Get("client_cert_mode").(string) + clientCertMode := d.Get("client_certificate_mode").(string) clientCertEnabled := clientCertMode != "" httpsOnly := d.Get("https_only").(bool) t := d.Get("tags").(map[string]interface{}) @@ -521,7 +521,7 @@ func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) e if props.ClientCertEnabled != nil && *props.ClientCertEnabled { clientCertMode = string(props.ClientCertMode) } - d.Set("client_cert_mode", clientCertMode) + d.Set("client_certificate_mode", clientCertMode) } appSettings := flattenLogicAppStandardAppSettings(appSettingsResp.Properties) @@ -554,11 +554,11 @@ func resourceLogicAppStandardRead(d *pluginsdk.ResourceData, meta interface{}) e if _, ok := appSettings["AzureFunctionsJobHost__extensionBundle__id"]; ok { d.Set("use_extension_bundle", true) if val, ok := appSettings["AzureFunctionsJobHost__extensionBundle__version"]; ok { - d.Set("bundle_version_range", val) + d.Set("bundle_version", val) } } else { d.Set("use_extension_bundle", false) - d.Set("bundle_version_range", "[1.*, 2.0.0)") + d.Set("bundle_version", "[1.*, 2.0.0)") } d.Set("storage_account_share_name", appSettings["WEBSITE_CONTENTSHARE"]) @@ -656,10 +656,10 @@ func getBasicLogicAppSettings(d *pluginsdk.ResourceData, endpointSuffix string) extensionBundlePropName := "AzureFunctionsJobHost__extensionBundle__id" extensionBundleName := "Microsoft.Azure.Functions.ExtensionBundle.Workflows" extensionBundleVersionPropName := "AzureFunctionsJobHost__extensionBundle__version" - extensionBundleVersion := d.Get("bundle_version_range").(string) + extensionBundleVersion := d.Get("bundle_version").(string) if extensionBundleVersion == "" { - return nil, fmt.Errorf("when `use_extension_bundle` is true, `bundle_version_range` must be specified") + return nil, fmt.Errorf("when `use_extension_bundle` is true, `bundle_version` must be specified") } bundleSettings := []web.NameValuePair{ diff --git a/internal/services/logic/logic_app_standard_resource_test.go b/internal/services/logic/logic_app_standard_resource_test.go index da8deb36d5a0..6ad33fa31693 100644 --- a/internal/services/logic/logic_app_standard_resource_test.go +++ b/internal/services/logic/logic_app_standard_resource_test.go @@ -604,28 +604,28 @@ func TestAccLogicAppStandard_clientCertMode(t *testing.T) { Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("client_cert_mode").HasValue(""), + check.That(data.ResourceName).Key("client_certificate_mode").HasValue(""), ), }, { Config: r.clientCertMode(data, "Required"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("client_cert_mode").HasValue("Required"), + check.That(data.ResourceName).Key("client_certificate_mode").HasValue("Required"), ), }, { Config: r.clientCertMode(data, "Optional"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("client_cert_mode").HasValue("Optional"), + check.That(data.ResourceName).Key("client_certificate_mode").HasValue("Optional"), ), }, { Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("client_cert_mode").HasValue(""), + check.That(data.ResourceName).Key("client_certificate_mode").HasValue(""), ), }, data.ImportStep(), @@ -903,7 +903,7 @@ resource "azurerm_logic_app_standard" "test" { storage_account_name = azurerm_storage_account.test.name storage_account_access_key = azurerm_storage_account.test.primary_access_key use_extension_bundle = true - bundle_version_range = "[1.*, 2.0.0)" + bundle_version = "[1.*, 2.0.0)" } `, data.RandomInteger, data.Locations.Primary, data.RandomString) } @@ -2117,7 +2117,7 @@ resource "azurerm_logic_app_standard" "test" { 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 - client_cert_mode = "%[4]s" + client_certificate_mode = "%[4]s" } `, data.RandomInteger, data.Locations.Primary, data.RandomString, modeValue) } diff --git a/website/docs/r/logic_app_standard.html.markdown b/website/docs/r/logic_app_standard.html.markdown index 2d452b29201b..cb4975b6bd8c 100644 --- a/website/docs/r/logic_app_standard.html.markdown +++ b/website/docs/r/logic_app_standard.html.markdown @@ -122,13 +122,13 @@ The following arguments are supported: * `use_extension_bundle` - (Optional) Should the logic app use the bundled extension package? If true, then application settings for `AzureFunctionsJobHost__extensionBundle__id` and `AzureFunctionsJobHost__extensionBundle__version` will be created. Default true -* `bundle_version_range` - (Optional) If `use_extension_bundle` then controls the allowed range for bundle versions. Default `[1.*, 2.0.0)` +* `bundle_version` - (Optional) If `use_extension_bundle` then controls the allowed range for bundle versions. Default `[1.*, 2.0.0)` * `connection_string` - (Optional) An `connection_string` block as defined below. * `client_affinity_enabled` - (Optional) Should the Logic App send session affinity cookies, which route client requests in the same session to the same instance? -* `client_cert_mode` - (Optional) The mode of the Logic App's client certificates requirement for incoming requests. Possible values are `Required` and `Optional`. +* `client_certificate_mode` - (Optional) The mode of the Logic App's client certificates requirement for incoming requests. Possible values are `Required` and `Optional`. * `enabled` - (Optional) Is the Logic App enabled?