From 86c9d6f65c2f1100a8ceb70f30ba9be9a97428af Mon Sep 17 00:00:00 2001 From: Maarten Balliauw Date: Tue, 19 Apr 2022 11:35:38 +0200 Subject: [PATCH] Azure Functions - Publishing enhancements (#587) --- .../resources/META-INF/platformPlugin.xml | 4 +- .../rider/resources/META-INF/plugin.xml | 4 +- .../messages/RiderAzureMessages.properties | 4 + .../functionapp/config/FunctionAppRunState.kt | 43 ++++++++- .../runstate/FunctionAppDeployStateUtil.kt | 4 +- .../ui/FunctionAppCreateNewComponent.kt | 63 +++++++++++-- .../config/ui/FunctionAppPublishComponent.kt | 91 +++++++++++++++++-- .../model/FunctionAppPublishModel.kt | 39 ++++++-- .../webapp/AzureDotNetWebAppMvpModel.kt | 2 +- .../config/ui/WebAppCreateNewComponent.kt | 6 +- .../appservice/AppServicePlanSelector.kt | 4 +- .../appservice/HostingPlanSelector.kt | 7 +- .../functionapp/CreateFunctionAppDialog.kt | 3 +- .../functionapp/AzureFunctionAppMvpModel.kt | 3 +- 14 files changed, 237 insertions(+), 40 deletions(-) diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/resources/META-INF/platformPlugin.xml b/PluginsAndFeatures/azure-toolkit-for-intellij/resources/META-INF/platformPlugin.xml index 43fce29e38..1609c4b049 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/resources/META-INF/platformPlugin.xml +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/resources/META-INF/platformPlugin.xml @@ -9,11 +9,13 @@
  • Compatibility with Rider 2022.1
  • Azure Functions: Run project against a function core tool version defined in project file (#294)
  • Azure Functions: Support NCrontab 5-field expressions (#571)
  • -
  • Azure Functions Host version can be selected from new project dialog (#568)
  • +
  • Azure Functions: Host version can be selected from new project dialog (#568)
  • +
  • Azure Functions: choose OS (Windows/Linux) when creating new function app (#585)
  • Fixed bugs:

    diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/resources/META-INF/plugin.xml b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/resources/META-INF/plugin.xml index b74e29ac7c..0fe4559ce8 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/resources/META-INF/plugin.xml +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/resources/META-INF/plugin.xml @@ -28,11 +28,13 @@
  • Compatibility with Rider 2022.1
  • Azure Functions: Run project against a function core tool version defined in project file (#294)
  • Azure Functions: Support NCrontab 5-field expressions (#571)
  • -
  • Azure Functions Host version can be selected from new project dialog (#568)
  • +
  • Azure Functions: Host version can be selected from new project dialog (#568)
  • +
  • Azure Functions: choose OS (Windows/Linux) when creating new function app (#585)
  • Fixed bugs:

    [3.50.0-2021.3]

    diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/resources/messages/RiderAzureMessages.properties b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/resources/messages/RiderAzureMessages.properties index 774f532274..b3ab827ffd 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/resources/messages/RiderAzureMessages.properties +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/resources/messages/RiderAzureMessages.properties @@ -210,6 +210,7 @@ run_config.publish.form.function_app.existing_new_selector=Function App run_config.publish.form.function_app.app_settings_panel_name=Run On Function App run_config.publish.form.function_app.app_config_tab_name=App Configuration run_config.publish.form.function_app.db_config_tab_name=Database Connection +run_config.publish.form.function_app.runtime_mismatch_warning=Selected Azure Function App runtime ''{0}'' mismatch with Project .Net Core Framework ''{1}'' run_config.publish.form.resource_group.header=Resource Group run_config.publish.form.hosting_plan.header=Hosting Plan run_config.publish.form.storage_account.header=Storage Account @@ -322,6 +323,9 @@ run_config.run_function_app.debug.ignore.externalconsole=The 'Use external conso # Process Events process_event.publish.url=URL: {0} +process_event.publish.updating_runtime=Updating runtime to ''{0}'' (version ''{1}'') +process_event.publish.updating_runtime.linux=Linux runtime: ''{0}'' +process_event.publish.updating_appsettings.scm_build=Updating application setting: SCM_DO_BUILD_DURING_DEPLOYMENT=false process_event.publish.deploy_succeeded=Deploy succeeded. process_event.publish.done=Done. process_event.publish.connection_string.creating=Creating connection string with name ''{0}''... diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/FunctionAppRunState.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/FunctionAppRunState.kt index 61914697e0..86aebae352 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/FunctionAppRunState.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/FunctionAppRunState.kt @@ -28,10 +28,11 @@ import com.intellij.notification.NotificationGroupManager import com.intellij.notification.NotificationType import com.intellij.notification.Notifications import com.intellij.openapi.project.Project -import com.microsoft.azure.management.appservice.WebAppBase +import com.microsoft.azure.management.appservice.* import com.microsoft.azure.management.sql.SqlDatabase import com.microsoft.azure.toolkit.intellij.common.AzureRunProfileState import com.microsoft.azuretools.core.mvp.model.AzureMvpModel +import com.microsoft.azuretools.core.mvp.model.appserviceplan.AzureAppServicePlanMvpModel import com.microsoft.azuretools.core.mvp.model.database.AzureSqlDatabaseMvpModel import com.microsoft.azuretools.core.mvp.model.functionapp.AzureFunctionAppMvpModel import com.microsoft.azuretools.core.mvp.model.storage.AzureStorageAccountMvpModel @@ -61,7 +62,6 @@ class FunctionAppRunState(project: Project, private val myModel: FunctionAppSett companion object { private const val TARGET_FUNCTION_NAME = "FunctionApp" private const val TARGET_FUNCTION_DEPLOYMENT_SLOT_NAME = "FunctionDeploymentSlot" - private const val URL_FUNCTION_APP_WWWROOT = "/home/site/wwwroot" } override fun getDeployTarget(): String = @@ -82,6 +82,7 @@ class FunctionAppRunState(project: Project, private val myModel: FunctionAppSett AzureRiderSettings.VALUE_COLLECT_ARTIFACTS_TIMEOUT_MINUTES_DEFAULT) * 60000L val app = getOrCreateFunctionAppFromConfiguration(myModel.functionAppModel, processHandler) + tryConfigureAzureFunctionRuntimeStack(app, subscriptionId, processHandler) deployToAzureFunctionApp(project, publishableProject, app, processHandler, collectArtifactsTimeoutMs) isFunctionAppCreated = true @@ -125,6 +126,42 @@ class FunctionAppRunState(project: Project, private val myModel: FunctionAppSett return FunctionAppDeployResult(app, database) } + private fun tryConfigureAzureFunctionRuntimeStack(app: WebAppBase, subscriptionId: String, processHandler: RunProcessHandler) { + if (app !is FunctionApp) return + + val functionRuntimeStack = myModel.functionAppModel.functionRuntimeStack + processHandler.setText(message("process_event.publish.updating_runtime", functionRuntimeStack.runtime(), functionRuntimeStack.version())) + + if (myModel.functionAppModel.operatingSystem == OperatingSystem.LINUX) { + val appServicePlan = AzureAppServicePlanMvpModel + .getAppServicePlanById(subscriptionId, app.appServicePlanId()) + + processHandler.setText(message("process_event.publish.updating_runtime.linux", functionRuntimeStack.linuxFxVersionForDedicatedPlan)) + + // For Linux, we have to set the correct FunctionRuntimeStack + app.update() + .withExistingLinuxAppServicePlan(appServicePlan) + .withBuiltInImage(functionRuntimeStack) + .apply() + + // For Linux dynamic (consumption) plan, we have to set SCM_DO_BUILD_DURING_DEPLOYMENT=false + if (appServicePlan.pricingTier() == FunctionAppPublishModel.dynamicPricingTier) { + + processHandler.setText(message("process_event.publish.updating_appsettings.scm_build")) + + app.update() + .withAppSetting("SCM_DO_BUILD_DURING_DEPLOYMENT", "false") + .apply() + } + } else { + // For Windows, we have to set the correct runtime and version + app.update() + .withRuntime(functionRuntimeStack.runtime()) + .withRuntimeVersion(functionRuntimeStack.version()) + .apply() + } + } + override fun onSuccess(result: FunctionAppDeployResult, processHandler: RunProcessHandler) { processHandler.notifyComplete() @@ -186,4 +223,4 @@ class FunctionAppRunState(project: Project, private val myModel: FunctionAppSett Notifications.Bus.notify(notification, project) } -} +} \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/runstate/FunctionAppDeployStateUtil.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/runstate/FunctionAppDeployStateUtil.kt index f56c5575df..fa688f3b39 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/runstate/FunctionAppDeployStateUtil.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/runstate/FunctionAppDeployStateUtil.kt @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2021 JetBrains s.r.o. + * Copyright (c) 2019-2022 JetBrains s.r.o. * * All rights reserved. * @@ -76,6 +76,7 @@ object FunctionAppDeployStateUtil { val functionModelLog = StringBuilder("Create a new Function App with name '${model.appName}', ") .append("isCreateResourceGroup: ").append(model.isCreatingResourceGroup).append(", ") .append("resourceGroupName: ").append(model.resourceGroupName).append(", ") + .append("operatingSystem: ").append(model.operatingSystem).append(", ") .append("isCreatingAppServicePlan: ").append(model.isCreatingAppServicePlan).append(", ") .append("appServicePlanId: ").append(model.appServicePlanId).append(", ") .append("appServicePlanName: ").append(model.appServicePlanName).append(", ") @@ -102,6 +103,7 @@ object FunctionAppDeployStateUtil { appName = model.appName, isCreateResourceGroup = model.isCreatingResourceGroup, resourceGroupName = model.resourceGroupName, + operatingSystem = model.operatingSystem, isCreateAppServicePlan = model.isCreatingAppServicePlan, appServicePlanId = model.appServicePlanId, appServicePlanName = model.appServicePlanName, diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/ui/FunctionAppCreateNewComponent.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/ui/FunctionAppCreateNewComponent.kt index f72bbfc9b4..671fa7551d 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/ui/FunctionAppCreateNewComponent.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/ui/FunctionAppCreateNewComponent.kt @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2020 JetBrains s.r.o. + * Copyright (c) 2019-2022 JetBrains s.r.o. * * All rights reserved. * @@ -40,6 +40,9 @@ import com.microsoft.intellij.ui.component.StorageAccountSelector import com.microsoft.intellij.ui.component.appservice.AppNameComponent import com.microsoft.intellij.ui.component.appservice.HostingPlanSelector import com.microsoft.intellij.runner.functionapp.model.FunctionAppPublishModel +import com.microsoft.intellij.ui.component.appservice.OperatingSystemSelector +import com.microsoft.intellij.ui.extension.fillComboBox +import com.microsoft.intellij.ui.extension.setComponentsEnabled import net.miginfocom.swing.MigLayout import org.jetbrains.plugins.azure.RiderAzureBundle.message import javax.swing.JPanel @@ -55,6 +58,9 @@ class FunctionAppCreateNewComponent(lifetime: Lifetime) : val pnlResourceGroup = ResourceGroupSelector(lifetime.createNested()) private val pnlResourceGroupHolder = HideableTitledPanel(message("run_config.publish.form.resource_group.header"), pnlResourceGroup, true) + val pnlOperatingSystem = OperatingSystemSelector() + private val pnlOperatingSystemHolder = HideableTitledPanel(message("run_config.publish.form.operating_system.header"), pnlOperatingSystem, true) + val pnlHostingPlan = HostingPlanSelector(lifetime.createNested()) private val pnlHostingPlanHolder = HideableTitledPanel(message("run_config.publish.form.hosting_plan.header"), pnlHostingPlan, true) @@ -62,13 +68,50 @@ class FunctionAppCreateNewComponent(lifetime: Lifetime) : private val pnlStorageAccountHolder = HideableTitledPanel(message("run_config.publish.form.storage_account.header"), pnlStorageAccount, true) init { + initButtonGroupsState() + add(pnlAppName, "growx") add(pnlSubscription, "growx") add(pnlResourceGroupHolder, "growx") + add(pnlOperatingSystemHolder, "growx") add(pnlHostingPlanHolder, "growx") add(pnlStorageAccountHolder, "growx") } + fun setOperatingSystemRadioButtons(isDotNetCore: Boolean) { + setComponentsEnabled(isDotNetCore, pnlOperatingSystem.rdoOperatingSystemLinux) + + if (!isDotNetCore) { + pnlOperatingSystem.rdoOperatingSystemWindows.doClick() + toggleOperatingSystem(OperatingSystem.WINDOWS) + } + } + + //region Button Group + + private fun initButtonGroupsState() { + initOperatingSystemButtonGroup() + } + + private fun initOperatingSystemButtonGroup() { + pnlOperatingSystem.rdoOperatingSystemWindows.addActionListener { toggleOperatingSystem(OperatingSystem.WINDOWS) } + pnlOperatingSystem.rdoOperatingSystemLinux.addActionListener { toggleOperatingSystem(OperatingSystem.LINUX) } + } + + fun toggleOperatingSystem(operatingSystem: OperatingSystem) { + pnlHostingPlan.cbHostingPlan.fillComboBox( + filterAppServicePlans(operatingSystem, pnlHostingPlan.cachedAppServicePlan), + pnlHostingPlan.lastSelectedAppServicePlan + ) + + pnlHostingPlan.cbPricingTier.fillComboBox( + filterPricingTiers(pnlHostingPlan.cachedPricingTier), + pnlHostingPlan.lastSelectedAppServicePlan?.pricingTier() + ) + } + + //endregion Button Group + fun fillSubscription(subscriptions: List, defaultSubscription: Subscription? = null) = pnlSubscription.fillSubscriptionComboBox(subscriptions, defaultSubscription) @@ -79,13 +122,16 @@ class FunctionAppCreateNewComponent(lifetime: Lifetime) : } fun fillAppServicePlan(appServicePlans: List, defaultAppServicePlanId: String? = null) = - pnlHostingPlan.fillAppServicePlan(filterAppServicePlans(appServicePlans), defaultAppServicePlanId) + pnlHostingPlan.fillAppServicePlan( + appServicePlans, + { filterAppServicePlans(pnlOperatingSystem.deployOperatingSystem, it) }, + defaultAppServicePlanId) fun fillLocation(locations: List, defaultLocation: Region? = null) = pnlHostingPlan.fillLocationComboBox(locations, defaultLocation) fun fillPricingTier(pricingTiers: List, defaultPricingTier: PricingTier? = null) = - pnlHostingPlan.fillPricingTier(updatePricingTiers(pricingTiers), defaultPricingTier) + pnlHostingPlan.fillPricingTier(filterPricingTiers(pricingTiers), defaultPricingTier) fun fillStorageAccount(storageAccounts: List, defaultStorageAccountId: String? = null) = pnlStorageAccount.fillStorageAccount(storageAccounts, defaultStorageAccountId) @@ -96,9 +142,10 @@ class FunctionAppCreateNewComponent(lifetime: Lifetime) : /** * Filter App Service Plans to Operating System related values */ - private fun filterAppServicePlans(appServicePlans: List): List { + private fun filterAppServicePlans(operatingSystem: OperatingSystem, + appServicePlans: List): List { return appServicePlans - .filter { it.operatingSystem() == OperatingSystem.WINDOWS } + .filter { it.operatingSystem() == operatingSystem } .sortedWith(compareBy({ it.operatingSystem() }, { it.name() })) } @@ -108,7 +155,7 @@ class FunctionAppCreateNewComponent(lifetime: Lifetime) : * Note: Function Apps cannot use "Free" and "Shared" Pricing Tiers. * Add Consumption plan for pricing on demand */ - private fun updatePricingTiers(prices: List) = - prices.filter { it != PricingTier.FREE_F1 && it != PricingTier.SHARED_D1 } + - FunctionAppPublishModel.consumptionPricingTier + private fun filterPricingTiers(prices: List) = + (prices.filter { it != PricingTier.FREE_F1 && it != PricingTier.SHARED_D1 } + + FunctionAppPublishModel.dynamicPricingTier).distinct() } diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/ui/FunctionAppPublishComponent.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/ui/FunctionAppPublishComponent.kt index 55362e431c..a450946ae1 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/ui/FunctionAppPublishComponent.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/config/ui/FunctionAppPublishComponent.kt @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2020 JetBrains s.r.o. + * Copyright (c) 2019-2022 JetBrains s.r.o. * * All rights reserved. * @@ -27,10 +27,10 @@ import com.intellij.openapi.project.Project import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rd.util.reactive.adviseOnce import com.jetbrains.rider.model.PublishableProjectModel +import com.jetbrains.rider.model.projectModelTasks +import com.jetbrains.rider.projectView.solution import com.microsoft.applicationinsights.web.dependencies.apachecommons.lang3.RandomStringUtils -import com.microsoft.azure.management.appservice.AppServicePlan -import com.microsoft.azure.management.appservice.FunctionApp -import com.microsoft.azure.management.appservice.PricingTier +import com.microsoft.azure.management.appservice.* import com.microsoft.azure.management.resources.Location import com.microsoft.azure.management.resources.ResourceGroup import com.microsoft.azure.management.resources.Subscription @@ -49,10 +49,14 @@ import com.microsoft.intellij.ui.extension.getSelectedValue import com.microsoft.intellij.ui.extension.setComponentsVisible import net.miginfocom.swing.MigLayout import org.jetbrains.plugins.azure.RiderAzureBundle.message +import org.jetbrains.plugins.azure.functions.coreTools.FunctionsCoreToolsMsBuild +import org.jetbrains.plugins.azure.functions.run.localsettings.FunctionLocalSettingsUtil +import org.jetbrains.plugins.azure.functions.run.localsettings.FunctionsWorkerRuntime +import java.io.File import javax.swing.JPanel class FunctionAppPublishComponent(private val lifetime: Lifetime, - project: Project, + private val project: Project, private val model: FunctionAppPublishModel) : JPanel(MigLayout("novisualpadding, ins 0, fillx, wrap 1, hidemode 3")), AzureComponent { @@ -61,6 +65,9 @@ class FunctionAppPublishComponent(private val lifetime: Lifetime, private const val DEFAULT_APP_NAME = "functionapp-" private const val DEFAULT_PLAN_NAME = "appsp-" private const val DEFAULT_RESOURCE_GROUP_NAME = "rg-" + + private val netCoreAppVersionRegex = Regex("\\.NETCoreApp,Version=v([0-9](?:\\.[0-9])*)", RegexOption.IGNORE_CASE) + private val netAppVersionRegex = Regex("net([0-9](?:\\.[0-9])*)", RegexOption.IGNORE_CASE) } private val pnlFunctionAppSelector = ExistingOrNewSelector(message("run_config.publish.form.function_app.existing_new_selector")) @@ -115,6 +122,10 @@ class FunctionAppPublishComponent(private val lifetime: Lifetime, if (config.isCreatingAppServicePlan) pnlCreateFunctionApp.pnlHostingPlan.rdoCreateNew.doClick() else pnlCreateFunctionApp.pnlHostingPlan.rdoUseExisting.doClick() + // Operating System + if (config.operatingSystem == OperatingSystem.WINDOWS) pnlCreateFunctionApp.pnlOperatingSystem.rdoOperatingSystemWindows.doClick() + else pnlCreateFunctionApp.pnlOperatingSystem.rdoOperatingSystemLinux.doClick() + // Storage Account if (config.isCreatingStorageAccount) pnlCreateFunctionApp.pnlStorageAccount.rdoCreateNew.doClick() else pnlCreateFunctionApp.pnlStorageAccount.rdoUseExisting.doClick() @@ -151,11 +162,12 @@ class FunctionAppPublishComponent(private val lifetime: Lifetime, model.isCreatingNewApp = pnlFunctionAppSelector.isCreateNew model.appName = pnlCreateFunctionApp.pnlAppName.appName - if (!model.isCreatingNewApp) { - val selectedResource = pnlExistingFunctionApp.pnlExistingAppTable.lastSelectedResource - val selectedApp = selectedResource?.resource + val selectedResource = pnlExistingFunctionApp.pnlExistingAppTable.lastSelectedResource + val selectedApp = selectedResource?.resource - model.appId = selectedApp?.id() ?: "" + model.appId = selectedApp?.id() ?: "" + + if (!model.isCreatingNewApp) { model.subscription = AzureMvpModel.getInstance() .selectedSubscriptions .find { it.subscriptionId() == selectedResource?.subscriptionId } @@ -168,6 +180,31 @@ class FunctionAppPublishComponent(private val lifetime: Lifetime, model.resourceGroupName = pnlCreateFunctionApp.pnlResourceGroup.cbResourceGroup.getSelectedValue()?.name() ?: "" } + if (pnlFunctionAppSelector.isCreateNew) { + if (pnlCreateFunctionApp.pnlOperatingSystem.isWindows) { + model.operatingSystem = OperatingSystem.WINDOWS + } else { + model.operatingSystem = OperatingSystem.LINUX + } + } else { + model.operatingSystem = selectedApp?.operatingSystem() ?: OperatingSystem.WINDOWS + } + + // Set runtime stack based on project config + val publishableProject = model.publishableProject + if (publishableProject != null && publishableProject.isDotNetCore) { + val functionLocalSettings = FunctionLocalSettingsUtil.readFunctionLocalSettings(project, File(publishableProject.projectFilePath).parent) + val workerRuntime = functionLocalSettings?.values?.workerRuntime ?: FunctionsWorkerRuntime.DotNetDefault + + val coreToolsVersion = FunctionsCoreToolsMsBuild.requestAzureFunctionsVersion(project, publishableProject.projectFilePath) ?: "V4" + val netCoreVersion = getProjectNetCoreFrameworkVersion(publishableProject) + model.functionRuntimeStack = FunctionRuntimeStack( + workerRuntime.value, + "~" + coreToolsVersion.trimStart('v', 'V'), + "${workerRuntime.value}|$netCoreVersion", + "${workerRuntime.value}|$netCoreVersion") + } + val hostingPlan = pnlCreateFunctionApp.pnlHostingPlan model.isCreatingAppServicePlan = hostingPlan.isCreatingNew model.appServicePlanId = hostingPlan.lastSelectedAppServicePlan?.id() ?: model.appServicePlanId @@ -216,6 +253,7 @@ class FunctionAppPublishComponent(private val lifetime: Lifetime, pnlProject.fillProjectComboBox(publishableProjects, model.publishableProject) if (model.publishableProject != null && publishableProjects.contains(model.publishableProject!!)) { + pnlCreateFunctionApp.setOperatingSystemRadioButtons(model.publishableProject!!.isDotNetCore) pnlProject.lastSelectedProject = model.publishableProject } } @@ -229,12 +267,47 @@ class FunctionAppPublishComponent(private val lifetime: Lifetime, pnlProject.canBePublishedAction = { publishableProject -> publishableProject.isAzureFunction } pnlProject.listenerAction = { publishableProject -> + pnlCreateFunctionApp.setOperatingSystemRadioButtons(publishableProject.isDotNetCore) pnlExistingFunctionApp.filterAppTableContent(publishableProject.isDotNetCore) + + val functionApp = pnlExistingFunctionApp.pnlExistingAppTable.lastSelectedResource?.resource + if (functionApp != null) + checkSelectedProjectAgainstFunctionAppRuntime(functionApp, publishableProject) } } + private fun checkSelectedProjectAgainstFunctionAppRuntime(functionApp: FunctionApp, publishableProject: PublishableProjectModel) { + if (functionApp.operatingSystem() == OperatingSystem.WINDOWS) { + pnlExistingFunctionApp.setRuntimeMismatchWarning(false) + return + } + + // DOTNETCORE|2.0 -> 2.0 + val functionAppFrameworkVersion = functionApp.linuxFxVersion().split('|').getOrNull(1) + + // .NETCoreApp,Version=v2.0 -> 2.0 + val projectNetCoreVersion = getProjectNetCoreFrameworkVersion(publishableProject) + pnlExistingFunctionApp.setRuntimeMismatchWarning( + functionAppFrameworkVersion != projectNetCoreVersion, + message("run_config.publish.form.function_app.runtime_mismatch_warning", functionAppFrameworkVersion.toString(), projectNetCoreVersion) + ) + } + //endregion Project + private fun getProjectNetCoreFrameworkVersion(publishableProject: PublishableProjectModel): String { + val defaultVersion = "6.0" + val currentFramework = getCurrentFrameworkId(publishableProject) ?: return defaultVersion + return netAppVersionRegex.find(currentFramework)?.groups?.get(1)?.value // netX.Y + ?: netCoreAppVersionRegex.find(currentFramework)?.groups?.get(1)?.value // .NETCoreApp,version=vX.Y + ?: defaultVersion + } + + private fun getCurrentFrameworkId(publishableProject: PublishableProjectModel): String? { + val targetFramework = project.solution.projectModelTasks.targetFrameworks[publishableProject.projectModelId] + return targetFramework?.currentTargetFrameworkId?.valueOrNull?.framework?.id + } + //region Button Group private fun initButtonGroupsState() { diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/model/FunctionAppPublishModel.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/model/FunctionAppPublishModel.kt index c757eddbb4..7a5752dc00 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/model/FunctionAppPublishModel.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/functionapp/model/FunctionAppPublishModel.kt @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2021 JetBrains s.r.o. + * Copyright (c) 2019-2022 JetBrains s.r.o. * * All rights reserved. * @@ -22,6 +22,7 @@ package com.microsoft.intellij.runner.functionapp.model +import com.intellij.database.util.isNotNullOrEmpty import com.intellij.openapi.project.Project import com.intellij.openapi.rd.createLifetime import com.intellij.openapi.util.JDOMExternalizerUtil @@ -29,10 +30,7 @@ import com.intellij.util.application import com.jetbrains.rider.model.PublishableProjectModel import com.jetbrains.rider.model.publishableProjectsModel import com.jetbrains.rider.projectView.solution -import com.microsoft.azure.management.appservice.FunctionDeploymentSlot -import com.microsoft.azure.management.appservice.PricingTier -import com.microsoft.azure.management.appservice.SkuDescription -import com.microsoft.azure.management.appservice.WebAppBase +import com.microsoft.azure.management.appservice.* import com.microsoft.azure.management.resources.Subscription import com.microsoft.azure.management.resources.fluentcore.arm.Region import com.microsoft.azure.management.storage.SkuName @@ -44,12 +42,16 @@ import org.jdom.Element class FunctionAppPublishModel { companion object { - val consumptionPricingTier = PricingTier("Consumption", "Y1") - val defaultPricingTier = consumptionPricingTier + val defaultOperatingSystem = OperatingSystem.WINDOWS + val dynamicPricingTier = PricingTier("Dynamic", "Y1") + val defaultPricingTier = dynamicPricingTier val standardLocalRedundantStorage = StorageAccountSkuType.STANDARD_LRS val defaultStorageAccountType = standardLocalRedundantStorage + val defaultFunctionRuntimeStack = FunctionRuntimeStack( + "dotnet","~4", "dotnet|6.0", "dotnet|6.0") + private const val AZURE_FUNCTION_APP_PROJECT = "AZURE_FUNCTION_APP_PROJECT" private const val AZURE_FUNCTION_APP_SUBSCRIPTION_ID = "AZURE_FUNCTION_APP_SUBSCRIPTION_ID" private const val AZURE_FUNCTION_APP_IS_CREATE_APP = "AZURE_FUNCTION_APP_IS_CREATE_APP" @@ -62,6 +64,10 @@ class FunctionAppPublishModel { private const val AZURE_FUNCTION_APP_SERVICE_PLAN_NAME = "AZURE_FUNCTION_APP_SERVICE_PLAN_NAME" private const val AZURE_FUNCTION_APP_LOCATION = "AZURE_FUNCTION_APP_LOCATION" private const val AZURE_FUNCTION_APP_PRICING_TIER = "AZURE_FUNCTION_APP_PRICING_TIER" + private const val AZURE_FUNCTION_APP_OPERATING_SYSTEM = "AZURE_FUNCTION_APP_OPERATING_SYSTEM" + private const val AZURE_FUNCTION_APP_RUNTIME = "AZURE_FUNCTION_APP_RUNTIME" + private const val AZURE_FUNCTION_APP_RUNTIMEVERSION = "AZURE_FUNCTION_APP_RUNTIMEVERSION" + private const val AZURE_FUNCTION_APP_FXVERSION = "AZURE_FUNCTION_APP_FXVERSION" private const val AZURE_FUNCTION_APP_IS_CREATE_STORAGE_ACCOUNT = "AZURE_FUNCTION_APP_IS_CREATE_STORAGE_ACCOUNT" private const val AZURE_FUNCTION_APP_STORAGE_ACCOUNT_ID = "AZURE_FUNCTION_APP_STORAGE_ACCOUNT_ID" private const val AZURE_FUNCTION_APP_STORAGE_ACCOUNT_NAME = "AZURE_FUNCTION_APP_STORAGE_ACCOUNT_NAME" @@ -84,9 +90,12 @@ class FunctionAppPublishModel { var isCreatingAppServicePlan = false var appServicePlanId: String = "" var appServicePlanName = "" + var operatingSystem = defaultOperatingSystem var location = AzureDefaults.location var pricingTier = defaultPricingTier + var functionRuntimeStack = defaultFunctionRuntimeStack + var isCreatingStorageAccount = false var storageAccountId = "" var storageAccountName = "" @@ -138,6 +147,9 @@ class FunctionAppPublishModel { isCreatingResourceGroup = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_IS_CREATE_RESOURCE_GROUP) == "1" resourceGroupName = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_RESOURCE_GROUP_NAME) ?: "" + val osString = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_OPERATING_SYSTEM) ?: defaultOperatingSystem.name + operatingSystem = OperatingSystem.fromString(osString) + isCreatingAppServicePlan = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_IS_CREATE_APP_SERVICE_PLAN) == "1" appServicePlanId = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_SERVICE_PLAN_ID) ?: "" appServicePlanName = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_SERVICE_PLAN_NAME) ?: "" @@ -157,6 +169,15 @@ class FunctionAppPublishModel { val skuName = SkuName.fromString(storageAccountTypeString) storageAccountType = StorageAccountSkuType.fromSkuName(skuName) + val runtime = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_RUNTIME) ?: "dotnet" + val runtimeVersion = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_RUNTIMEVERSION) + val runtimeFxVersion = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_FXVERSION) + if (runtimeVersion.isNotNullOrEmpty && runtimeFxVersion.isNotNullOrEmpty) { + functionRuntimeStack = FunctionRuntimeStack(runtime, runtimeVersion, runtimeFxVersion, runtimeFxVersion) + } else { + functionRuntimeStack = defaultFunctionRuntimeStack + } + isDeployToSlot = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_IS_DEPLOY_TO_SLOT) == "1" slotName = JDOMExternalizerUtil.readField(element, AZURE_FUNCTION_APP_SLOT_NAME) ?: "" } @@ -179,6 +200,10 @@ class FunctionAppPublishModel { JDOMExternalizerUtil.writeField(element, AZURE_FUNCTION_APP_LOCATION, location.name()) JDOMExternalizerUtil.writeField(element, AZURE_FUNCTION_APP_PRICING_TIER, pricingTier.toSkuDescription().name()) + JDOMExternalizerUtil.writeField(element, AZURE_FUNCTION_APP_OPERATING_SYSTEM, operatingSystem.name) + JDOMExternalizerUtil.writeField(element, AZURE_FUNCTION_APP_RUNTIME, functionRuntimeStack.runtime()) + JDOMExternalizerUtil.writeField(element, AZURE_FUNCTION_APP_RUNTIMEVERSION, functionRuntimeStack.version()) + JDOMExternalizerUtil.writeField(element, AZURE_FUNCTION_APP_FXVERSION, functionRuntimeStack.linuxFxVersionForDedicatedPlan) JDOMExternalizerUtil.writeField(element, AZURE_FUNCTION_APP_IS_CREATE_STORAGE_ACCOUNT, if (isCreatingStorageAccount) "1" else "0") JDOMExternalizerUtil.writeField(element, AZURE_FUNCTION_APP_STORAGE_ACCOUNT_ID, storageAccountId) diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/webapp/AzureDotNetWebAppMvpModel.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/webapp/AzureDotNetWebAppMvpModel.kt index 5fd0681362..605a1951b1 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/webapp/AzureDotNetWebAppMvpModel.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/webapp/AzureDotNetWebAppMvpModel.kt @@ -128,7 +128,7 @@ object AzureDotNetWebAppMvpModel { } val linuxDotNetRuntime = netCoreRuntime ?: let { - val version = WebAppPublishModel.defaultRuntime + val version = WebAppPublishModel.defaultRuntime logger.info("Net Core Runtime version is not provided. Use a default value: $version") version } diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/webapp/config/ui/WebAppCreateNewComponent.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/webapp/config/ui/WebAppCreateNewComponent.kt index 5bbbb3a29f..fce4512b82 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/webapp/config/ui/WebAppCreateNewComponent.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/runner/webapp/config/ui/WebAppCreateNewComponent.kt @@ -120,9 +120,9 @@ class WebAppCreateNewComponent(lifetime: Lifetime) : fun fillAppServicePlan(appServicePlans: List, defaultAppServicePlanId: String? = null) { pnlAppServicePlan.fillAppServicePlanComboBox( - filterAppServicePlans(pnlOperatingSystem.deployOperatingSystem, appServicePlans)) { - appServicePlan -> appServicePlan.id() == defaultAppServicePlanId - } + appServicePlans, + { filterAppServicePlans(pnlOperatingSystem.deployOperatingSystem, it) }, + { it.id() == defaultAppServicePlanId }) } fun fillLocation(locations: List, defaultLocation: Region? = null) { diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/component/appservice/AppServicePlanSelector.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/component/appservice/AppServicePlanSelector.kt index 7986945c48..d029970899 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/component/appservice/AppServicePlanSelector.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/component/appservice/AppServicePlanSelector.kt @@ -142,11 +142,11 @@ class AppServicePlanSelector(private val lifetime: Lifetime) : AppServicePlanValidator.checkAppServicePlanNameMinLength(txtName.text) }) } - fun fillAppServicePlanComboBox(appServicePlans: List, defaultComparator: (AppServicePlan) -> Boolean = { false }) { + fun fillAppServicePlanComboBox(appServicePlans: List, filterAppServicePlans: (List) -> List, defaultComparator: (AppServicePlan) -> Boolean = { false }) { cachedAppServicePlan = appServicePlans cbAppServicePlan.fillComboBox( - appServicePlans.sortedWith(compareBy({ it.operatingSystem() }, { it.name() })), + filterAppServicePlans(appServicePlans).sortedWith(compareBy({ it.operatingSystem() }, { it.name() })), defaultComparator) if (appServicePlans.isEmpty()) { diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/component/appservice/HostingPlanSelector.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/component/appservice/HostingPlanSelector.kt index 1f162e65cb..77ab19f9a6 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/component/appservice/HostingPlanSelector.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/component/appservice/HostingPlanSelector.kt @@ -64,6 +64,8 @@ class HostingPlanSelector(private val lifetime: Lifetime) : val cbPricingTier = ComboBox() var cachedAppServicePlan: List = emptyList() + var cachedPricingTier: List = emptyList() + var lastSelectedAppServicePlan: AppServicePlan? = null val isCreatingNew: Boolean @@ -119,11 +121,11 @@ class HostingPlanSelector(private val lifetime: Lifetime) : AppServicePlanValidator.checkAppServicePlanNameMinLength(txtName.text) }) } - fun fillAppServicePlan(appServicePlans: List, defaultAppServicePlanId: String? = null) { + fun fillAppServicePlan(appServicePlans: List, filterAppServicePlans: (List) -> List, defaultAppServicePlanId: String? = null) { cachedAppServicePlan = appServicePlans cbHostingPlan.fillComboBox( - elements = appServicePlans, + elements = filterAppServicePlans(appServicePlans), defaultComparator = { appServicePlan -> appServicePlan.id() == defaultAppServicePlanId }) } @@ -134,6 +136,7 @@ class HostingPlanSelector(private val lifetime: Lifetime) : } fun fillPricingTier(pricingTiers: List, defaultPricingTier: PricingTier? = null) { + cachedPricingTier = pricingTiers cbPricingTier.fillComboBox( elements = pricingTiers, defaultElement = defaultPricingTier) diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/forms/appservice/functionapp/CreateFunctionAppDialog.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/forms/appservice/functionapp/CreateFunctionAppDialog.kt index 97fb35b67e..02ddb311af 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/forms/appservice/functionapp/CreateFunctionAppDialog.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/rider/src/com/microsoft/intellij/ui/forms/appservice/functionapp/CreateFunctionAppDialog.kt @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 JetBrains s.r.o. + * Copyright (c) 2020-2022 JetBrains s.r.o. * * All rights reserved. * @@ -119,6 +119,7 @@ class CreateFunctionAppDialog(lifetimeDef: LifetimeDefinition, appName = appName, isCreateResourceGroup = pnlCreate.pnlResourceGroup.isCreateNew, resourceGroupName = resourceGroupName, + operatingSystem = pnlCreate.pnlOperatingSystem.deployOperatingSystem, isCreateAppServicePlan = pnlCreate.pnlHostingPlan.isCreatingNew, appServicePlanId = pnlCreate.pnlHostingPlan.lastSelectedAppServicePlan?.id() ?: "", appServicePlanName = pnlCreate.pnlHostingPlan.hostingPlanName, diff --git a/Utils/azuretools-core/src/com/microsoft/azuretools/core/mvp/model/functionapp/AzureFunctionAppMvpModel.kt b/Utils/azuretools-core/src/com/microsoft/azuretools/core/mvp/model/functionapp/AzureFunctionAppMvpModel.kt index dde4f95031..cdcdf1fa1a 100644 --- a/Utils/azuretools-core/src/com/microsoft/azuretools/core/mvp/model/functionapp/AzureFunctionAppMvpModel.kt +++ b/Utils/azuretools-core/src/com/microsoft/azuretools/core/mvp/model/functionapp/AzureFunctionAppMvpModel.kt @@ -124,6 +124,7 @@ object AzureFunctionAppMvpModel { appName: String, isCreateResourceGroup: Boolean, resourceGroupName: String, + operatingSystem: OperatingSystem, isCreateAppServicePlan: Boolean, appServicePlanId: String, appServicePlanName: String, @@ -155,7 +156,7 @@ object AzureFunctionAppMvpModel { val planCreatable = planWithResourceGroup .withPricingTier(pricingTier) - .withOperatingSystem(OperatingSystem.WINDOWS) + .withOperatingSystem(operatingSystem) withResourceGroup.withNewAppServicePlan(planCreatable) } else {