Skip to content

Commit 591b8ae

Browse files
authored
Flex Deployment Changes (#3625)
* fixing bug where auth parameter was ignored by Python v2. * Flex - Public preview changes. * Update runtime for flex. * Update default .NET version to 8.0 in tests. * Flex runtime update changes. * Added timeout to a test. * Updated the python default to 3.10. * Changed the python default back to 3.10. * Added 3.11 back in the condition.
1 parent 01d17db commit 591b8ae

File tree

8 files changed

+221
-18
lines changed

8 files changed

+221
-18
lines changed

src/Azure.Functions.Cli/Actions/AzureActions/PublishFunctionAppAction.cs

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,20 @@
88
using System.Net.Http.Headers;
99
using System.Threading;
1010
using System.Threading.Tasks;
11+
using Azure.Core;
1112
using Azure.Functions.Cli.Actions.LocalActions;
12-
using Azure.Functions.Cli.Arm.Models;
1313
using Azure.Functions.Cli.Common;
14-
using Azure.Functions.Cli.Diagnostics;
1514
using Azure.Functions.Cli.Extensions;
1615
using Azure.Functions.Cli.Helpers;
1716
using Azure.Functions.Cli.Interfaces;
1817
using Azure.Functions.Cli.StacksApi;
1918
using Colors.Net;
2019
using Fclp;
21-
using Microsoft.Build.Framework;
2220
using Microsoft.WindowsAzure.Storage;
2321
using Microsoft.WindowsAzure.Storage.Blob;
2422
using Newtonsoft.Json;
25-
using NuGet.Common;
2623
using static Azure.Functions.Cli.Common.OutputTheme;
24+
using Site = Azure.Functions.Cli.Arm.Models.Site;
2725

2826
namespace Azure.Functions.Cli.Actions.AzureActions
2927
{
@@ -34,7 +32,7 @@ internal class PublishFunctionAppAction : BaseFunctionAppAction
3432

3533
private readonly ISettings _settings;
3634
private readonly ISecretsManager _secretsManager;
37-
private static string _requiredNetFrameworkVersion = "6.0";
35+
private static string _requiredNetFrameworkVersion = "8.0";
3836

3937
public bool PublishLocalSettings { get; set; }
4038
public bool OverwriteSettings { get; set; }
@@ -268,7 +266,14 @@ private async Task<IDictionary<string, string>> ValidateFunctionAppPublish(Site
268266
}
269267
}
270268

271-
if (functionApp.AzureAppSettings.TryGetValue(Constants.FunctionsWorkerRuntime, out string workerRuntimeStr))
269+
string workerRuntimeStr = null;
270+
if (functionApp.IsFlex)
271+
{
272+
workerRuntimeStr = functionApp.FunctionAppConfig.runtime.name;
273+
}
274+
275+
if ((functionApp.IsFlex && !string.IsNullOrEmpty(workerRuntimeStr) ||
276+
(!functionApp.IsFlex && functionApp.AzureAppSettings.TryGetValue(Constants.FunctionsWorkerRuntime, out workerRuntimeStr))))
272277
{
273278
var resolution = $"You can pass --force to update your Azure app with '{workerRuntime}' as a '{Constants.FunctionsWorkerRuntime}'";
274279
try
@@ -312,7 +317,18 @@ private async Task<IDictionary<string, string>> ValidateFunctionAppPublish(Site
312317
throw new CliException($"Azure Functions Core Tools does not support this deployment path. Please configure the app to deploy from a remote package using the steps here: https://aka.ms/deployfromurl");
313318
}
314319

315-
await UpdateFrameworkVersions(functionApp, workerRuntime, DotnetFrameworkVersion, Force, azureHelperService);
320+
if (functionApp.IsFlex)
321+
{
322+
if (result.ContainsKey(Constants.FunctionsWorkerRuntime))
323+
{
324+
await UpdateRuntimeConfigForFlex(functionApp, WorkerRuntimeLanguageHelper.GetRuntimeMoniker(workerRuntime), null, azureHelperService);
325+
result.Remove(Constants.FunctionsWorkerRuntime);
326+
}
327+
}
328+
else
329+
{
330+
await UpdateFrameworkVersions(functionApp, workerRuntime, DotnetFrameworkVersion, Force, azureHelperService);
331+
}
316332

317333
// Special checks for python dependencies
318334
if (workerRuntime == WorkerRuntime.python)
@@ -321,10 +337,12 @@ private async Task<IDictionary<string, string>> ValidateFunctionAppPublish(Site
321337
await PythonHelpers.WarnIfAzureFunctionsWorkerInRequirementsTxt();
322338
// Check if remote LinuxFxVersion exists and is different from local version
323339
var localVersion = await PythonHelpers.GetEnvironmentPythonVersion();
324-
if (!PythonHelpers.IsLinuxFxVersionRuntimeVersionMatched(functionApp.LinuxFxVersion, localVersion.Major, localVersion.Minor))
340+
341+
if ((!functionApp.IsFlex && !PythonHelpers.IsLinuxFxVersionRuntimeVersionMatched(functionApp.LinuxFxVersion, localVersion.Major, localVersion.Minor)) ||
342+
(functionApp.IsFlex && !PythonHelpers.IsFlexPythonRuntimeVersionMatched(functionApp.FunctionAppConfig?.runtime?.name, functionApp.FunctionAppConfig?.runtime?.version, localVersion.Major, localVersion.Minor)))
325343
{
326344
ColoredConsole.WriteLine(WarningColor($"Local python version '{localVersion.Version}' is different from the version expected for your deployed Function App." +
327-
$" This may result in 'ModuleNotFound' errors in Azure Functions. Please create a Python Function App for version {localVersion.Major}.{localVersion.Minor} or change the virtual environment on your local machine to match '{functionApp.LinuxFxVersion}'."));
345+
$" This may result in 'ModuleNotFound' errors in Azure Functions. Please create a Python Function App for version {localVersion.Major}.{localVersion.Minor} or change the virtual environment on your local machine to match '{(functionApp.IsFlex? functionApp.FunctionAppConfig.runtime.version: functionApp.LinuxFxVersion)}'."));
328346
}
329347
}
330348

@@ -336,6 +354,59 @@ private async Task<IDictionary<string, string>> ValidateFunctionAppPublish(Site
336354
return result;
337355
}
338356

357+
public static async Task UpdateRuntimeConfigForFlex(Site site, string runtimeName, string runtimeVersion, AzureHelperService helperService)
358+
{
359+
if (string.IsNullOrEmpty(runtimeName))
360+
{
361+
return;
362+
}
363+
364+
if (string.IsNullOrEmpty(runtimeVersion))
365+
{
366+
if (runtimeName.Equals("python", StringComparison.OrdinalIgnoreCase))
367+
{
368+
var localVersion = await PythonHelpers.GetEnvironmentPythonVersion();
369+
runtimeVersion = $"{localVersion.Major}.{localVersion.Minor}";
370+
if (runtimeVersion != "3.10" && runtimeVersion != "3.11")
371+
{
372+
// todo: default will be 3.11 after 3.11 support is added.
373+
runtimeVersion = "3.10";
374+
}
375+
}
376+
else if (runtimeName.Equals("dotnet-isolated", StringComparison.OrdinalIgnoreCase))
377+
{
378+
// Only .NET 8.0 is supported in flex.
379+
if (runtimeVersion != "8.0")
380+
runtimeVersion = "8.0";
381+
}
382+
else if (runtimeName.Equals("node", StringComparison.OrdinalIgnoreCase))
383+
{
384+
// Only Node 18 is supported.
385+
if (runtimeVersion != "18")
386+
runtimeVersion = "18";
387+
}
388+
else if (runtimeName.Equals("powershell", StringComparison.OrdinalIgnoreCase))
389+
{
390+
// Only Python 7.2 is supported.
391+
if (runtimeVersion != "7.2")
392+
runtimeVersion = "7.2";
393+
}
394+
else if (runtimeName.Equals("java", StringComparison.OrdinalIgnoreCase))
395+
{
396+
// Warning: Java is not supported by core tools at the moment.
397+
ColoredConsole.WriteLine(WarningColor($"Java is not supported in core tools at the moment. Please use az cli to update the runtime information."));
398+
}
399+
else
400+
{
401+
// Warning: Runtime name is unknown.
402+
ColoredConsole.WriteLine(WarningColor($"Runtime is not updated. Only dotnet-isolated, node, java, and powershell is supported in core tools for Flex SKU."));
403+
return;
404+
}
405+
}
406+
407+
await helperService.UpdateFlexRuntime(site, runtimeName, runtimeVersion);
408+
}
409+
339410
internal static async Task UpdateFrameworkVersions(Site functionApp, WorkerRuntime workerRuntime, string requestedDotNetVersion, bool force, AzureHelperService helperService)
340411
{
341412
if (workerRuntime == WorkerRuntime.dotnetIsolated)
@@ -686,6 +757,14 @@ await WaitForAppSettingUpdateSCM(functionApp, shouldHaveSettings: functionApp.Az
686757
/// <returns>ShouldSyncTrigger value</returns>
687758
private async Task<bool> HandleFlexConsumptionPublish(Site functionApp, Func<Task<Stream>> zipFileFactory)
688759
{
760+
// Get the WorkerRuntime
761+
var workerRuntime = GlobalCoreToolsSettings.CurrentWorkerRuntime;
762+
763+
if (workerRuntime == WorkerRuntime.dotnetIsolated && _requiredNetFrameworkVersion != "8.0")
764+
{
765+
throw new CliException($"You are deploying .NET Isolated {_requiredNetFrameworkVersion} to Flex consumption. Flex consumpton only supports .NET 8. Please upgrade your app to .NET 8 and try the deployment again.");
766+
}
767+
689768
Task<DeployStatus> pollDeploymentStatusTask(HttpClient client) => KuduLiteDeploymentHelpers.WaitForFlexDeployment(client, functionApp);
690769
var deploymentParameters = new Dictionary<string, string>();
691770

@@ -704,7 +783,7 @@ public async Task<DeployStatus> PerformFlexDeployment(Site functionApp, Func<Tas
704783
using (var handler = new ProgressMessageHandler(new HttpClientHandler()))
705784
using (var client = GetRemoteZipClient(functionApp, handler))
706785
using (var request = new HttpRequestMessage(HttpMethod.Post, new Uri(
707-
$"api/Deploy/Zip?isAsync=true&author={Environment.MachineName}&Deployer=core_tools&{string.Join("&", deploymentParameters?.Select(kvp => $"{kvp.Key}={kvp.Value}")) ?? string.Empty}", UriKind.Relative)))
786+
$"api/publish?isAsync=true&author={Environment.MachineName}&Deployer=core_tools&{string.Join("&", deploymentParameters?.Select(kvp => $"{kvp.Key}={kvp.Value}")) ?? string.Empty}", UriKind.Relative)))
708787
{
709788
ColoredConsole.WriteLine(GetLogMessage("Creating archive for current directory..."));
710789

@@ -1141,7 +1220,29 @@ private async Task<bool> PublishLocalAppSettings(Site functionApp, IDictionary<s
11411220

11421221
private async Task<bool> PublishAppSettings(Site functionApp, IDictionary<string, string> local, IDictionary<string, string> additional)
11431222
{
1223+
string flexRuntimeName = null;
1224+
string flexRuntimeVersion = null;
1225+
if (functionApp.IsFlex)
1226+
{
1227+
// if the additiona keys has runtime, it would mean that runtime is already updated.
1228+
if (!additional.ContainsKey(Constants.FunctionsWorkerRuntime))
1229+
{
1230+
if (local.ContainsKey(Constants.FunctionsWorkerRuntime))
1231+
{
1232+
flexRuntimeName = local[Constants.FunctionsWorkerRuntime];
1233+
local.Remove(Constants.FunctionsWorkerRuntime);
1234+
}
1235+
1236+
if (local.ContainsKey(Constants.FunctionsWorkerRuntimeVersion))
1237+
{
1238+
flexRuntimeVersion = local[Constants.FunctionsWorkerRuntimeVersion];
1239+
local.Remove(Constants.FunctionsWorkerRuntimeVersion);
1240+
}
1241+
}
1242+
}
1243+
11441244
functionApp.AzureAppSettings = MergeAppSettings(functionApp.AzureAppSettings, local, additional);
1245+
11451246
var result = await AzureHelper.UpdateFunctionAppAppSettings(functionApp, AccessToken, ManagementURL);
11461247
if (!result.IsSuccessful)
11471248
{
@@ -1151,6 +1252,12 @@ private async Task<bool> PublishAppSettings(Site functionApp, IDictionary<string
11511252
.WriteLine(ErrorColor(result.ErrorResult));
11521253
return false;
11531254
}
1255+
1256+
if (functionApp.IsFlex && !string.IsNullOrEmpty(flexRuntimeName))
1257+
{
1258+
await UpdateRuntimeConfigForFlex(functionApp, flexRuntimeName, flexRuntimeVersion, new AzureHelperService(AccessToken, ManagementURL));
1259+
}
1260+
11541261
return true;
11551262
}
11561263

@@ -1288,6 +1395,9 @@ public AzureHelperService(string accessToken, string managementUrl)
12881395

12891396
public virtual Task<HttpResult<string, string>> UpdateWebSettings(Site functionApp, Dictionary<string, string> updatedSettings) =>
12901397
AzureHelper.UpdateWebSettings(functionApp, updatedSettings, _accessToken, _managementUrl);
1398+
1399+
public virtual Task UpdateFlexRuntime(Site functionApp, string runtimeName, string runtimeVersion) =>
1400+
AzureHelper.UpdateFlexRuntime(functionApp, runtimeName, runtimeVersion, _accessToken, _managementUrl);
12911401
}
12921402

12931403
private void ShowEolMessage(FunctionsStacks stacks, WindowsRuntimeSettings currentRuntimeSettings, int? majorDotnetVersion)

src/Azure.Functions.Cli/Arm/ArmUriTemplates.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ internal static class ArmUriTemplates
88
{
99
public const string ArmApiVersion = "2018-09-01";
1010
public const string WebsitesApiVersion = "2015-08-01";
11+
public const string FlexApiVersion = "2023-12-01";
1112
public const string SyncTriggersApiVersion = "2016-08-01";
1213
public const string ArgApiVersion = "2019-04-01";
1314
public const string FunctionAppOnContainerAppsApiVersion = "2022-09-01";

src/Azure.Functions.Cli/Arm/Models/ArmWebsite.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,47 @@ internal class ArmWebsite
77
public IEnumerable<string> enabledHostNames { get; set; }
88

99
public string sku { get; set; }
10+
11+
public FunctionAppConfig functionAppConfig { get; set; }
12+
}
13+
14+
public class Authentication
15+
{
16+
public string type { get; set; }
17+
public object userAssignedIdentityResourceId { get; set; }
18+
public string storageAccountConnectionStringName { get; set; }
19+
}
20+
21+
public class Deployment
22+
{
23+
public Storage storage { get; set; }
24+
}
25+
26+
public class FunctionAppConfig
27+
{
28+
public Deployment deployment { get; set; }
29+
public Runtime runtime { get; set; }
30+
public ScaleAndConcurrency scaleAndConcurrency { get; set; }
31+
}
32+
33+
public class Runtime
34+
{
35+
public string name { get; set; }
36+
public string version { get; set; }
37+
}
38+
39+
public class ScaleAndConcurrency
40+
{
41+
public List<object> alwaysReady { get; set; }
42+
public int maximumInstanceCount { get; set; }
43+
public int instanceMemoryMB { get; set; }
44+
public object triggers { get; set; }
45+
}
46+
47+
public class Storage
48+
{
49+
public string type { get; set; }
50+
public string value { get; set; }
51+
public Authentication authentication { get; set; }
1052
}
1153
}

src/Azure.Functions.Cli/Arm/Models/Site.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,7 @@ public Site(string siteId)
5353
{
5454
SiteId = siteId;
5555
}
56+
57+
public FunctionAppConfig FunctionAppConfig { get; set; }
5658
}
5759
}

src/Azure.Functions.Cli/Helpers/AzureHelper.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,9 +423,38 @@ public static async Task<Site> LoadSiteObjectAsync(Site site, string accessToken
423423
site.Kind = armSite.kind;
424424
site.Sku = armSite.properties.sku;
425425
site.SiteName = armSite.name;
426+
site.FunctionAppConfig = armSite.properties.functionAppConfig;
426427
return site;
427428
}
428429

430+
public static async Task<bool> UpdateFlexRuntime(Site site, string runtimeName, string runtimeValue, string accessToken, string managementURL)
431+
{
432+
var url = new Uri($"{managementURL}{site.SiteId}?api-version={ArmUriTemplates.WebsitesApiVersion}");
433+
var functionAppJson = await ArmHttpAsyncForFlex(HttpMethod.Get, url, accessToken);
434+
dynamic functionApp = JsonConvert.DeserializeObject(functionAppJson);
435+
var runtimeConfig = functionApp?.properties?.functionAppConfig?.runtime;
436+
437+
if (runtimeConfig == null)
438+
{
439+
return false;
440+
}
441+
442+
runtimeConfig.name = runtimeName;
443+
runtimeConfig.version = runtimeValue;
444+
445+
url = new Uri($"{managementURL}{site.SiteId}?api-version={ArmUriTemplates.FlexApiVersion}");
446+
var result = await ArmHttpAsyncForFlex(new HttpMethod("PUT"), url, accessToken, functionApp);
447+
return true;
448+
}
449+
450+
private static async Task<string> ArmHttpAsyncForFlex(HttpMethod method, Uri uri, string accessToken, object payload = null)
451+
{
452+
var response = await ArmClient.HttpInvoke(method, uri, accessToken, payload, retryCount: 3);
453+
response.EnsureSuccessStatusCode();
454+
455+
return await response.Content.ReadAsStringAsync();
456+
}
457+
429458
private static async Task<T> ArmHttpAsync<T>(HttpMethod method, Uri uri, string accessToken, object payload = null)
430459
{
431460
var response = await ArmClient.HttpInvoke(method, uri, accessToken, payload, retryCount: 3);
@@ -464,6 +493,7 @@ public static async Task<HttpResult<string, string>> UpdateWebSettings(Site site
464493
public static async Task<HttpResult<Dictionary<string, string>, string>> UpdateFunctionAppAppSettings(Site site, string accessToken, string managementURL)
465494
{
466495
var url = new Uri($"{managementURL}{site.SiteId}/config/AppSettings?api-version={ArmUriTemplates.WebsitesApiVersion}");
496+
467497
var response = await ArmClient.HttpInvoke(HttpMethod.Put, url, accessToken, new { properties = site.AzureAppSettings });
468498
if (response.IsSuccessStatusCode)
469499
{

src/Azure.Functions.Cli/Helpers/PythonHelpers.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,23 @@ public static bool IsLinuxFxVersionRuntimeVersionMatched(string linuxFxVersion,
621621
return string.Equals(linuxFxVersion, $"PYTHON|{major}.{minor}", StringComparison.OrdinalIgnoreCase);
622622
}
623623

624+
public static bool IsFlexPythonRuntimeVersionMatched(string flexRuntime, string flexRuntimeVersion, int? major, int? minor)
625+
{
626+
if (string.IsNullOrEmpty(flexRuntime) || string.IsNullOrEmpty(flexRuntimeVersion))
627+
{
628+
// Match if version is 3.10
629+
return major == 3 && minor == 10;
630+
}
631+
// Only validate for python.
632+
else if (!flexRuntime.Equals("python", StringComparison.OrdinalIgnoreCase))
633+
{
634+
return true;
635+
}
636+
637+
// Validate LinuxFxVersion that follows the pattern PYTHON|<major>.<minor>
638+
return flexRuntimeVersion.Equals($"{major}.{minor}", StringComparison.OrdinalIgnoreCase);
639+
}
640+
624641
public static bool IsNewPythonProgrammingModel(string language)
625642
{
626643
return string.Equals(language, Languages.Python, StringComparison.InvariantCultureIgnoreCase) && HasPySteinFile();

test/Azure.Functions.Cli.Tests/E2E/CreateFunctionTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ await CliTester.Run(new RunConfiguration
6060
OutputContains = new[]
6161
{
6262
"Authorization level is applicable to templates that use Http trigger, Allowed values: [function, anonymous, admin]. Authorization level is not enforced when running functions from core tools"
63-
}
63+
},
64+
CommandTimeout = TimeSpan.FromSeconds(120)
6465
}, _output);
6566
}
6667

0 commit comments

Comments
 (0)