Skip to content

Commit

Permalink
Flex: Stacks API Support. (#3636)
Browse files Browse the repository at this point in the history
* fixing bug where auth parameter was ignored by Python v2.

* Flex: Stacks API support.

* Remove unnecessary import

---------

Co-authored-by: Kirstyn Joy Amperiadis <kamperiadis@microsoft.com>
  • Loading branch information
khkh-ms and kamperiadis committed Apr 22, 2024
1 parent c6a4269 commit 7a895ef
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Azure.Functions.Cli.StacksApi;
using Colors.Net;
using Fclp;
using Microsoft.IdentityModel.Tokens;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Newtonsoft.Json;
Expand Down Expand Up @@ -361,50 +362,59 @@ public static async Task UpdateRuntimeConfigForFlex(Site site, string runtimeNam
return;
}

List<FlexSku> skuList = await GetFlexSkus(site, runtimeName, helperService);

if (!skuList.Any())
{
throw new CliException($"We couldn't validate '{runtimeName}' runtime for Flex SKU in '{site.Location}'.");
}

if (string.IsNullOrEmpty(runtimeVersion))
{
if (runtimeName.Equals("python", StringComparison.OrdinalIgnoreCase))
{
var localVersion = await PythonHelpers.GetEnvironmentPythonVersion();
runtimeVersion = $"{localVersion.Major}.{localVersion.Minor}";
if (runtimeVersion != "3.10" && runtimeVersion != "3.11")
{
// todo: default will be 3.11 after 3.11 support is added.
runtimeVersion = "3.10";
}
}
else if (runtimeName.Equals("dotnet-isolated", StringComparison.OrdinalIgnoreCase))
{
// Only .NET 8.0 is supported in flex.
if (runtimeVersion != "8.0")
runtimeVersion = "8.0";
}
else if (runtimeName.Equals("node", StringComparison.OrdinalIgnoreCase))
var defaultSku = skuList.FirstOrDefault(s => s.IsDefault);
if (defaultSku == null)
{
// Only Node 18 is supported.
if (runtimeVersion != "18")
runtimeVersion = "18";
defaultSku = skuList.FirstOrDefault();
}
else if (runtimeName.Equals("powershell", StringComparison.OrdinalIgnoreCase))
{
// Only Python 7.2 is supported.
if (runtimeVersion != "7.2")
runtimeVersion = "7.2";
}
else if (runtimeName.Equals("java", StringComparison.OrdinalIgnoreCase))
{
// Warning: Java is not supported by core tools at the moment.
ColoredConsole.WriteLine(WarningColor($"Java is not supported in core tools at the moment. Please use az cli to update the runtime information."));
}
else

runtimeVersion = defaultSku?.functionAppConfigProperties.Runtime.Version;
}

ColoredConsole.WriteLine($"Updating function app runtime setting with '{runtimeName} {runtimeVersion}'.");
await helperService.UpdateFlexRuntime(site, runtimeName, runtimeVersion);
}

private static async Task<List<FlexSku>> GetFlexSkus(Site site, string runtimeName, AzureHelperService helperService)
{
var flexStacks = await helperService.GetFlexFunctionsStacks(runtimeName, site.Location);
var skuList = new List<FlexSku>();

if (flexStacks == null)
{
return skuList;
}

var languageProperties = flexStacks.Languages.FirstOrDefault()?.LanguageProperties;
foreach (var majorVersion in languageProperties.MajorVersions)
{
var minorVersionSkuList = majorVersion?.MinorVersions?
.Where(m => m.StackSettings?.LinuxRuntimeSettings?.Sku != null)
.Select(s => { return new { skus = s.StackSettings.LinuxRuntimeSettings.Sku, isDefault = s.StackSettings.LinuxRuntimeSettings.IsDefault }; });

foreach (var minorVersionSkus in minorVersionSkuList)
{
// Warning: Runtime name is unknown.
ColoredConsole.WriteLine(WarningColor($"Runtime is not updated. Only dotnet-isolated, node, java, and powershell is supported in core tools for Flex SKU."));
return;
foreach (var sku in minorVersionSkus.skus)
{
if (sku.SkuCode.Equals("FC1", StringComparison.OrdinalIgnoreCase))
{
sku.IsDefault = minorVersionSkus.isDefault;
skuList.Add(sku);
}
}
}
}

await helperService.UpdateFlexRuntime(site, runtimeName, runtimeVersion);
return skuList;
}

internal static async Task UpdateFrameworkVersions(Site functionApp, WorkerRuntime workerRuntime, string requestedDotNetVersion, bool force, AzureHelperService helperService)
Expand Down Expand Up @@ -760,9 +770,14 @@ private async Task<bool> HandleFlexConsumptionPublish(Site functionApp, Func<Tas
// Get the WorkerRuntime
var workerRuntime = GlobalCoreToolsSettings.CurrentWorkerRuntime;

if (workerRuntime == WorkerRuntime.dotnetIsolated && _requiredNetFrameworkVersion != "8.0")
if (workerRuntime == WorkerRuntime.dotnetIsolated)
{
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.");
var flexSkus = await GetFlexSkus(functionApp, WorkerRuntimeLanguageHelper.GetRuntimeMoniker(workerRuntime), new AzureHelperService(AccessToken, ManagementURL));
if (!flexSkus.Any(s => s.functionAppConfigProperties.Runtime.Version == _requiredNetFrameworkVersion))
{
var versions = string.Join(", ", flexSkus.Select(s => s.functionAppConfigProperties.Runtime.Version));
throw new CliException($"You are deploying .NET Isolated {_requiredNetFrameworkVersion} to Flex consumption. Flex consumpton supports .NET {versions}. Please upgrade your app to an appropriate .NET version and try the deployment again.");
}
}

Task<DeployStatus> pollDeploymentStatusTask(HttpClient client) => KuduLiteDeploymentHelpers.WaitForFlexDeployment(client, functionApp);
Expand Down Expand Up @@ -1398,6 +1413,9 @@ public AzureHelperService(string accessToken, string managementUrl)

public virtual Task UpdateFlexRuntime(Site functionApp, string runtimeName, string runtimeVersion) =>
AzureHelper.UpdateFlexRuntime(functionApp, runtimeName, runtimeVersion, _accessToken, _managementUrl);

public virtual Task<FlexFunctionsStacks> GetFlexFunctionsStacks(string runtime, string region) =>
AzureHelper.GetFlexFunctionsStacks(_accessToken, _managementUrl, runtime, region);
}

private void ShowEolMessage(FunctionsStacks stacks, WindowsRuntimeSettings currentRuntimeSettings, int? majorDotnetVersion)
Expand Down
1 change: 1 addition & 0 deletions src/Azure.Functions.Cli/Arm/ArmUriTemplates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal static class ArmUriTemplates
public const string ManagedEnvironmentApiVersion = "2022-10-01";
public const string BasicAuthCheckApiVersion = "2022-03-01";
public const string FunctionsStacksApiVersion = "2020-10-01";
public const string FlexFunctionsStacksApiVersion = "2020-10-01";

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

Expand Down
22 changes: 22 additions & 0 deletions src/Azure.Functions.Cli/Helpers/AzureHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -722,5 +722,27 @@ public static async Task<FunctionsStacks> GetFunctionsStacks(string accessToken,
return null;
}
}

public static async Task<FlexFunctionsStacks> GetFlexFunctionsStacks(string accessToken, string managementURL, string runtime, string region)
{
// API only supports dotnet as runtime. The dotnet-isolated is part of the dotnet for this API.
if (runtime.Equals("dotnet-isolated", StringComparison.OrdinalIgnoreCase))
{
runtime = "dotnet";
}

var url = new Uri($"{managementURL}//providers/Microsoft.Web/locations/{region}/functionAppStacks?api-version={ArmUriTemplates.FlexFunctionsStacksApiVersion}&removeHiddenStacks=true&removeDeprecatedStacks=true&stack={runtime}");
var response = await ArmClient.HttpInvoke(HttpMethod.Get, url, accessToken);
var content = await response.Content.ReadAsStringAsync();

if (response.IsSuccessStatusCode)
{
return JsonConvert.DeserializeObject<FlexFunctionsStacks>(content);
}
else
{
return null;
}
}
}
}
224 changes: 224 additions & 0 deletions src/Azure.Functions.Cli/StacksApi/FlexFunctionsStacks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Azure.Functions.Cli.StacksApi
{
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);

public class FlexFunctionsStacks
{
[JsonProperty("value")]
public List<FlexLanguage> Languages { get; set; }

[JsonProperty("nextLink")]
public object NextLink { get; set; }

[JsonProperty("id")]
public object Id { get; set; }
}

public class FlexLanguage
{
[JsonProperty("id")]
public object Id { get; set; }

[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("type")]
public string Type { get; set; }

[JsonProperty("location")]
public string Location { get; set; }

[JsonProperty("properties")]
public FlexLanguageProperties LanguageProperties { get; set; }
}

public class FlexLanguageProperties
{
[JsonProperty("displayText")]
public string DisplayText { get; set; }

[JsonProperty("value")]
public string Value { get; set; }

[JsonProperty("preferredOs")]
public string PreferredOs { get; set; }

[JsonProperty("majorVersions")]
public List<FlexMajorVersion> MajorVersions { get; set; }
}

public class FlexMajorVersion
{
[JsonProperty("displayText")]
public string DisplayText { get; set; }

[JsonProperty("value")]
public string Value { get; set; }

[JsonProperty("minorVersions")]
public List<FlexMinorVersion> MinorVersions { get; set; }
}

public class FlexMinorVersion
{
[JsonProperty("displayText")]
public string DisplayText { get; set; }

[JsonProperty("value")]
public string Value { get; set; }

[JsonProperty("stackSettings")]
public FlexStackSettings StackSettings { get; set; }
}

public class FlexStackSettings
{
[JsonProperty("linuxRuntimeSettings")]
public FlexLinuxRuntimeSettings LinuxRuntimeSettings { get; set; }
}

public class FlexLinuxRuntimeSettings
{
[JsonProperty("runtimeVersion")]
public string RuntimeVersion { get; set; }

[JsonProperty("remoteDebuggingSupported")]
public bool RemoteDebuggingSupported { get; set; }

[JsonProperty("isPreview")]
public bool IsPreview { get; set; }

[JsonProperty("isDefault")]
public bool IsDefault { get; set; }

[JsonProperty("isHidden")]
public bool IsHidden { get; set; }

[JsonProperty("appInsightsSettings")]
public FlexAppInsightsSettings AppInsightsSettings { get; set; }

[JsonProperty("gitHubActionSettings")]
public FlexGitHubActionSettings GitHubActionSettings { get; set; }

[JsonProperty("appSettingsDictionary")]
public FlexAppSettingsDictionary AppSettingsDictionary { get; set; }

[JsonProperty("siteConfigPropertiesDictionary")]
public FlexSiteConfigPropertiesDictionary SiteConfigPropertiesDictionary { get; set; }

[JsonProperty("supportedFunctionsExtensionVersions")]
public List<string> SupportedFunctionsExtensionVersions { get; set; }

[JsonProperty("supportedFunctionsExtensionVersionsInfo")]
public List<FlexSupportedFunctionsExtensionVersionsInfo> SupportedFunctionsExtensionVersionsInfo { get; set; }

[JsonProperty("endOfLifeDate")]
public string EndOfLifeDate { get; set; }

[JsonProperty("Sku")]
public List<FlexSku> Sku { get; set; }
}

public class FlexAppInsightsSettings
{
[JsonProperty("isSupported")]
public bool IsSupported { get; set; }
}

public class FlexGitHubActionSettings
{
[JsonProperty("isSupported")]
public bool IsSupported { get; set; }

[JsonProperty("supportedVersion")]
public string SupportedVersion { get; set; }
}

public class FlexAppSettingsDictionary
{
[JsonProperty("FUNCTIONS_WORKER_RUNTIME")]
public string FUNCTIONS_WORKER_RUNTIME { get; set; }
}

public class FlexSiteConfigPropertiesDictionary
{
[JsonProperty("use32BitWorkerProcess")]
public bool Use32BitWorkerProcess { get; set; }

[JsonProperty("linuxFxVersion")]
public string LinuxFxVersion { get; set; }
}

public class FlexSupportedFunctionsExtensionVersionsInfo
{
[JsonProperty("version")]
public string Version { get; set; }

[JsonProperty("isDeprecated")]
public bool IsDeprecated { get; set; }

[JsonProperty("isDefault")]
public bool IsDefault { get; set; }
}

public class FlexSku
{
[JsonIgnore]
public bool IsDefault { get; set; }

[JsonProperty("skuCode")]
public string SkuCode { get; set; }

[JsonProperty("instanceMemoryMB")]
public List<FlexInstanceMemoryMB> InstanceMemoryMB { get; set; }

[JsonProperty("maximumInstanceCount")]
public FlexMaximumInstanceCount MaximumInstanceCount { get; set; }

[JsonProperty("FunctionAppConfigProperties")]
public FunctionAppConfigProperties functionAppConfigProperties { get; set; }
}

public class FunctionAppConfigProperties
{
[JsonProperty("runtime")]
public FlexRuntime Runtime { get; set; }
}

public class FlexInstanceMemoryMB
{
[JsonProperty("size")]
public int Size { get; set; }

[JsonProperty("isDefault")]
public bool IsDefault { get; set; }
}

public class FlexMaximumInstanceCount
{
[JsonProperty("lowestMaximumInstanceCount")]
public int LowestMaximumInstanceCount { get; set; }

[JsonProperty("highestMaximumInstanceCount")]
public int HighestMaximumInstanceCount { get; set; }

[JsonProperty("defaultValue")]
public int DefaultValue { get; set; }
}

public class FlexRuntime
{
[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("version")]
public string Version { get; set; }
}
}

1 comment on commit 7a895ef

@daisukei
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd like to use V4.x as soon as possible but it seems the pipeline is cancelled due to this commit. Any reason?

image

Please sign in to comment.