Skip to content

Commit fc93cca

Browse files
authored
Stacks API Support (#3546)
* Stacks API * eol changes * Upgrading the vulnerably packages. * fixing bug where auth parameter was ignored by Python v2. * Dotnet 8 as default. * Changed the stacks API version. * Changes for the change in stacks api version. * Upgraded a vulnerable package.
1 parent 2f6887e commit fc93cca

File tree

12 files changed

+2252
-3
lines changed

12 files changed

+2252
-3
lines changed

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

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
using Azure.Functions.Cli.Extensions;
1616
using Azure.Functions.Cli.Helpers;
1717
using Azure.Functions.Cli.Interfaces;
18+
using Azure.Functions.Cli.StacksApi;
1819
using Colors.Net;
1920
using Fclp;
21+
using Microsoft.Build.Framework;
2022
using Microsoft.WindowsAzure.Storage;
2123
using Microsoft.WindowsAzure.Storage.Blob;
2224
using Newtonsoft.Json;
@@ -169,9 +171,24 @@ public override async Task RunAsync()
169171
{
170172
var projectRoot = ProjectHelpers.GetProject(projectFilePath);
171173
var targetFramework = ProjectHelpers.GetPropertyValue(projectRoot, Constants.TargetFrameworkElementName);
172-
if (targetFramework.Equals("net7.0", StringComparison.InvariantCultureIgnoreCase))
174+
175+
var majorDotnetVersion = StacksApiHelper.GetMajorDotnetVersionFromDotnetVersionInProject(targetFramework);
176+
177+
if (majorDotnetVersion != null)
173178
{
174-
_requiredNetFrameworkVersion = "7.0";
179+
// Get Stacks
180+
var stacks = await AzureHelper.GetFunctionsStacks(AccessToken, ManagementURL);
181+
var runtimeSettings = stacks.GetRuntimeSettings(majorDotnetVersion.Value, out bool isLTS);
182+
183+
ShowEolMessage(stacks, runtimeSettings, majorDotnetVersion.Value);
184+
185+
// This is for future proofing with stacks API for future dotnet versions.
186+
if (runtimeSettings != null &&
187+
(runtimeSettings.IsDeprecated == null || runtimeSettings.IsDeprecated == false) &&
188+
(runtimeSettings.IsDeprecatedForRuntime == null || runtimeSettings.IsDeprecatedForRuntime == false))
189+
{
190+
_requiredNetFrameworkVersion = $"{majorDotnetVersion}.0";
191+
}
175192
}
176193
else if (targetFramework.Equals("net8.0", StringComparison.InvariantCultureIgnoreCase))
177194
{
@@ -1272,5 +1289,34 @@ public AzureHelperService(string accessToken, string managementUrl)
12721289
public virtual Task<HttpResult<string, string>> UpdateWebSettings(Site functionApp, Dictionary<string, string> updatedSettings) =>
12731290
AzureHelper.UpdateWebSettings(functionApp, updatedSettings, _accessToken, _managementUrl);
12741291
}
1292+
1293+
private void ShowEolMessage(FunctionsStacks stacks, WindowsRuntimeSettings currentRuntimeSettings, int? majorDotnetVersion)
1294+
{
1295+
try
1296+
{
1297+
if (currentRuntimeSettings.IsDeprecated == true || currentRuntimeSettings.IsDeprecatedForRuntime == true)
1298+
{
1299+
var nextDotnetVersion = stacks.GetNextDotnetVersion(majorDotnetVersion.Value);
1300+
if (nextDotnetVersion != null)
1301+
{
1302+
var warningMessage = EolMessages.GetAfterEolUpdateMessageDotNet(majorDotnetVersion.ToString(), nextDotnetVersion.ToString(), currentRuntimeSettings.EndOfLifeDate.Value);
1303+
ColoredConsole.WriteLine(WarningColor(warningMessage));
1304+
}
1305+
}
1306+
else if (StacksApiHelper.IsInNextSixMonths(currentRuntimeSettings.EndOfLifeDate))
1307+
{
1308+
var nextDotnetVersion = stacks.GetNextDotnetVersion(majorDotnetVersion.Value);
1309+
if (nextDotnetVersion != null)
1310+
{
1311+
var warningMessage = EolMessages.GetEarlyEolUpdateMessageDotNet(majorDotnetVersion.ToString(), nextDotnetVersion.ToString(), currentRuntimeSettings.EndOfLifeDate.Value);
1312+
ColoredConsole.WriteLine(WarningColor(warningMessage));
1313+
}
1314+
}
1315+
}
1316+
catch (Exception)
1317+
{
1318+
// ignore. Failure to show the EOL message should not fail the deployment.
1319+
}
1320+
}
12751321
}
12761322
}

src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
using Azure.Functions.Cli.Extensions;
1010
using Azure.Functions.Cli.Helpers;
1111
using Azure.Functions.Cli.Interfaces;
12+
using Azure.Functions.Cli.StacksApi;
1213
using Colors.Net;
14+
using DurableTask.Core;
1315
using Dynamitey;
1416
using Fclp;
17+
using Microsoft.Azure.AppService.Proxy.Common.Constants;
1518
using Newtonsoft.Json;
1619
using Newtonsoft.Json.Linq;
1720
using static Azure.Functions.Cli.Common.OutputTheme;
@@ -21,6 +24,7 @@ namespace Azure.Functions.Cli.Actions.LocalActions
2124
[Action(Name = "init", HelpText = "Create a new Function App in the current folder. Initializes git repo.")]
2225
internal class InitAction : BaseAction
2326
{
27+
2428
public SourceControl SourceControl { get; set; } = SourceControl.Git;
2529

2630
public bool InitSourceControl { get; set; }
@@ -55,6 +59,9 @@ internal class InitAction : BaseAction
5559

5660
public ProgrammingModel ResolvedProgrammingModel { get; set; }
5761

62+
// Default to .NET 8 if the target framework is not specified
63+
private const string DefaultTargetFramework = Common.TargetFramework.net8;
64+
5865
internal static readonly Dictionary<Lazy<string>, Task<string>> fileToContentMap = new Dictionary<Lazy<string>, Task<string>>
5966
{
6067
{ new Lazy<string>(() => ".gitignore"), StaticResources.GitIgnore }
@@ -201,6 +208,7 @@ private async Task InitFunctionAppProject()
201208
ValidateTargetFramework();
202209
if (WorkerRuntimeLanguageHelper.IsDotnet(ResolvedWorkerRuntime) && !Csx)
203210
{
211+
await ShowEolMessage();
204212
await DotnetHelpers.DeployDotnetProject(Utilities.SanitizeLiteral(Path.GetFileName(Environment.CurrentDirectory), allowed: "-"), Force, ResolvedWorkerRuntime, TargetFramework);
205213
}
206214
else
@@ -333,7 +341,7 @@ private void ValidateTargetFramework()
333341
{
334342
// Default to .NET 8 if the target framework is not specified
335343
// NOTE: we must have TargetFramework be non-empty for a dotnet-isolated project, even if it is not specified by the user, due to the structure of the new templates
336-
TargetFramework = Common.TargetFramework.net8;
344+
TargetFramework = DefaultTargetFramework;
337345
}
338346
if (!TargetFrameworkHelper.GetSupportedTargetFrameworks().Contains(TargetFramework, StringComparer.InvariantCultureIgnoreCase))
339347
{
@@ -556,5 +564,42 @@ public async Task FetchPackages(WorkerRuntime workerRuntime, ProgrammingModel pr
556564
}
557565
}
558566
}
567+
568+
private async Task ShowEolMessage()
569+
{
570+
try
571+
{
572+
if (!WorkerRuntimeLanguageHelper.IsDotnetIsolated(ResolvedWorkerRuntime) || TargetFramework == DefaultTargetFramework)
573+
return;
574+
575+
var majorDotnetVersion = StacksApiHelper.GetMajorDotnetVersionFromDotnetVersionInProject(TargetFramework);
576+
577+
if (majorDotnetVersion == null)
578+
return;
579+
580+
var stacksContent = await StaticResources.StacksJson;
581+
var stacks = JsonConvert.DeserializeObject<FunctionsStacks>(stacksContent);
582+
583+
var currentRuntimeSettings = stacks.GetRuntimeSettings(majorDotnetVersion.Value, out bool isLTS);
584+
585+
if (currentRuntimeSettings == null)
586+
return;
587+
588+
if (currentRuntimeSettings.IsDeprecated == true || currentRuntimeSettings.IsDeprecatedForRuntime == true)
589+
{
590+
var warningMessage = EolMessages.GetAfterEolCreateMessageDotNet(majorDotnetVersion.ToString(), currentRuntimeSettings.EndOfLifeDate.Value);
591+
ColoredConsole.WriteLine(WarningColor(warningMessage));
592+
}
593+
else if (StacksApiHelper.IsInNextSixMonths(currentRuntimeSettings.EndOfLifeDate))
594+
{
595+
var warningMessage = EolMessages.GetEarlyEolCreateMessageForDotNet(majorDotnetVersion.ToString(), currentRuntimeSettings.EndOfLifeDate.Value);
596+
ColoredConsole.WriteLine(WarningColor(warningMessage));
597+
}
598+
}
599+
catch (Exception)
600+
{
601+
// ignore. Failure to show the EOL message should not fail the init command.
602+
}
603+
}
559604
}
560605
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ internal static class ArmUriTemplates
1313
public const string FunctionAppOnContainerAppsApiVersion = "2022-09-01";
1414
public const string ManagedEnvironmentApiVersion = "2022-10-01";
1515
public const string BasicAuthCheckApiVersion = "2022-03-01";
16+
public const string FunctionsStacksApiVersion = "2020-10-01";
1617

1718
public const string ArgUri = "providers/Microsoft.ResourceGraph/resources";
1819

src/Azure.Functions.Cli/Azure.Functions.Cli.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
<PublishReadyToRunShowWarnings>false</PublishReadyToRunShowWarnings>
3535
</PropertyGroup>
3636
<ItemGroup>
37+
<EmbeddedResource Include="StaticResources\stacks.json">
38+
<LogicalName>$(AssemblyName).stacks.json</LogicalName>
39+
</EmbeddedResource>
3740
<EmbeddedResource Include="StaticResources\bundleConfig.json">
3841
<LogicalName>$(AssemblyName).bundleConfig.json</LogicalName>
3942
</EmbeddedResource>
@@ -275,7 +278,10 @@
275278
<PackageReference Include="Microsoft.Azure.DurableTask.AzureStorage.Internal" Version="1.4.0" />
276279
<PackageReference Include="Microsoft.Azure.WebJobs.Script.WebHost" Version="4.28.5" />
277280
<PackageReference Include="Microsoft.Build" Version="17.0.0" />
281+
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.35.0" />
278282
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
283+
<PackageReference Include="NuGet.Packaging" Version="5.11.6" />
284+
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.35.0" />
279285
<PackageReference Include="WindowsAzure.Storage" Version="9.3.1" />
280286
<PackageReference Include="YamlDotNet" Version="6.0.0" />
281287
</ItemGroup>

src/Azure.Functions.Cli/Common/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ internal static class Constants
9090
public const string FunctionAppFailedToDeployOnContainerAppsMessage = "Failed to deploy function app to Container Apps.";
9191
public const string LocalSettingsJsonFileName = "local.settings.json";
9292
public const string EnableWorkerIndexEnvironmentVariableName = "FunctionsHostingConfig__WORKER_INDEXING_ENABLED";
93+
public const string Dotnet = "dotnet";
9394

9495

9596

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Azure.Functions.Cli.Common;
1212
using Azure.Functions.Cli.ContainerApps.Models;
1313
using Azure.Functions.Cli.Extensions;
14+
using Azure.Functions.Cli.StacksApi;
1415
using Colors.Net;
1516
using Microsoft.Azure.AppService.Middleware;
1617
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
@@ -675,5 +676,21 @@ public static async Task PrintFunctionsInfo(Site functionApp, string accessToken
675676
return (null, null);
676677
}
677678
}
679+
680+
public static async Task<FunctionsStacks> GetFunctionsStacks(string accessToken, string managementURL)
681+
{
682+
var url = new Uri($"{managementURL}/providers/Microsoft.Web/functionAppStacks?api-version={ArmUriTemplates.FunctionsStacksApiVersion}");
683+
var response = await ArmClient.HttpInvoke(HttpMethod.Get, url, accessToken);
684+
var content = await response.Content.ReadAsStringAsync();
685+
686+
if (response.IsSuccessStatusCode)
687+
{
688+
return JsonConvert.DeserializeObject<FunctionsStacks>(content);
689+
}
690+
else
691+
{
692+
return null;
693+
}
694+
}
678695
}
679696
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Globalization;
3+
4+
namespace Azure.Functions.Cli.Helpers
5+
{
6+
internal class EolMessages
7+
{
8+
public static string GetEarlyEolCreateMessageForDotNet(string stackVersion, DateTime eol, string link = "")
9+
{
10+
return $".NET {stackVersion} will reach EOL on {FormatDate(eol)} and will no longer be supported. {link}";
11+
}
12+
13+
public static string GetAfterEolCreateMessageDotNet(string stackVersion, DateTime eol, string link = "")
14+
{
15+
return $".NET {stackVersion} has reached EOL on {FormatDate(eol)} and is no longer supported. {link}";
16+
}
17+
18+
public static string GetEarlyEolUpdateMessageDotNet(string currentStackVersion, string nextStackVersion, DateTime eol, string link = "")
19+
{
20+
return $"Upgrade your app to .NET {nextStackVersion} as .NET {currentStackVersion} will reach EOL on {FormatDate(eol)} and will no longer be supported. {link}";
21+
}
22+
23+
public static string GetAfterEolUpdateMessageDotNet(string currentStackVersion, string nextStackVersion, DateTime eol, string link = "")
24+
{
25+
return $"Upgrade your app to .NET {nextStackVersion} as .NET {currentStackVersion} has reached EOL on {FormatDate(eol)} and is no longer supported. {link}";
26+
}
27+
28+
private static string FormatDate(DateTime dateTime)
29+
{
30+
return dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
31+
}
32+
}
33+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using Azure.Functions.Cli.Common;
2+
using Azure.Functions.Cli.StacksApi;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace Azure.Functions.Cli.Helpers
10+
{
11+
internal static class StacksApiHelper
12+
{
13+
public static int? GetNextDotnetVersion(this FunctionsStacks stacks, int currentMajorVersion)
14+
{
15+
WindowsRuntimeSettings runtimeSettings;
16+
bool isLTS;
17+
do
18+
{
19+
currentMajorVersion++;
20+
runtimeSettings = GetRuntimeSettings(stacks, currentMajorVersion, out isLTS);
21+
} while (runtimeSettings != null && (!isLTS && runtimeSettings.IsDeprecated != true && runtimeSettings.IsDeprecatedForRuntime != true));
22+
23+
return currentMajorVersion;
24+
}
25+
26+
public static WindowsRuntimeSettings GetRuntimeSettings(this FunctionsStacks stacks, int majorDotnetVersion, out bool isLTS)
27+
{
28+
var dotnetIsolatedStackKey = $"{Constants.Dotnet}{majorDotnetVersion}isolated";
29+
var minorVersion = stacks?.Languages.FirstOrDefault(x => x.Name.Equals(Constants.Dotnet, StringComparison.InvariantCultureIgnoreCase))
30+
?.Properties.MajorVersions?.FirstOrDefault(x => x.Value == dotnetIsolatedStackKey)
31+
?.MinorVersions.LastOrDefault();
32+
33+
34+
isLTS = minorVersion?.Value?.Contains("LTS") == true;
35+
return minorVersion?.StackSettings?.WindowsRuntimeSettings;
36+
}
37+
38+
public static int? GetMajorDotnetVersionFromDotnetVersionInProject(string dotnetVersionFromConfig)
39+
{
40+
if (string.IsNullOrEmpty(dotnetVersionFromConfig))
41+
{
42+
return null;
43+
}
44+
45+
var versionStr = dotnetVersionFromConfig?.ToLower()?.Replace("net", string.Empty);
46+
if (double.TryParse(versionStr, out double version))
47+
{
48+
return (int)version;
49+
}
50+
51+
return null;
52+
}
53+
54+
public static bool IsInNextSixMonths(this DateTime? date)
55+
{
56+
if (date == null)
57+
return false;
58+
else
59+
return date < DateTime.Now.AddMonths(6);
60+
}
61+
}
62+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,5 +199,10 @@ public static bool IsDotnet(WorkerRuntime worker)
199199
{
200200
return worker == WorkerRuntime.dotnet || worker == WorkerRuntime.dotnetIsolated;
201201
}
202+
203+
public static bool IsDotnetIsolated(WorkerRuntime worker)
204+
{
205+
return worker == WorkerRuntime.dotnetIsolated;
206+
}
202207
}
203208
}

0 commit comments

Comments
 (0)