Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic PyStein support V4 #3071

Merged
merged 31 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
aca1abf
Add static function_app.py file
michaelpeng36 May 10, 2022
83f6801
Add function_app.py to .csproj
michaelpeng36 May 10, 2022
7e8873d
Add Dockerfile and allow modification of static resources
michaelpeng36 May 10, 2022
86d8568
Add a programming-model parameter and print PyStein messages with fun…
michaelpeng36 May 10, 2022
b4fa774
Include host version to support PyStein
michaelpeng36 May 11, 2022
47695b9
Altered WebHost version to support PyStein
michaelpeng36 May 15, 2022
cbdc212
Moved ResolveProgrammingModel to a dedicated helper class
michaelpeng36 May 15, 2022
fc98955
Add handling of func new cases
michaelpeng36 May 15, 2022
c730df0
Fixed issue where the preview programming model is supported on langu…
michaelpeng36 May 15, 2022
8bd280a
Update WebHost version to match v4.x
michaelpeng36 May 18, 2022
096a394
Update host version
michaelpeng36 Jun 6, 2022
d4fd006
Reverted .csproj to be compatible with PyStein and added unit tets fo…
michaelpeng36 Jun 7, 2022
5c77112
Add https:// before links
michaelpeng36 Jun 16, 2022
48a3f53
Add friendlier information message
michaelpeng36 Jun 16, 2022
c9421e1
Separate messages printed out for PyStein awareness and reference
michaelpeng36 Jun 16, 2022
103d998
Making code more modular
michaelpeng36 Jun 16, 2022
6e326f4
Corrected small typo
michaelpeng36 Jun 16, 2022
d3a3d70
Correct error messages and display strings
michaelpeng36 Jun 16, 2022
88c350a
Do not generate getting_started.md in case of the new programming model
michaelpeng36 Jun 17, 2022
943f109
Add HTTP Trigger to the function_app.py template
michaelpeng36 Jun 21, 2022
8c1a9c4
Update host version to 4.9.0
michaelpeng36 Jul 21, 2022
b3f307a
Add ConsoleLogCategoryName
michaelpeng36 Jul 21, 2022
e0f22c9
Modified requirements.txt warning and corresponding E2E test
michaelpeng36 Jul 22, 2022
6e9c540
Alter programming model names
michaelpeng36 Aug 24, 2022
fedbdfb
Resolve merge conflicts
michaelpeng36 Aug 25, 2022
7819054
Merge branch 'v4.x' into michaelpeng/pystein-v4
michaelpeng36 Aug 25, 2022
b1e4fce
Correct merge errors
michaelpeng36 Aug 26, 2022
20a4a14
Removed PyStein Dockerfiles and added custom bundle configuration
michaelpeng36 Aug 29, 2022
49e395d
Removed newline
michaelpeng36 Aug 29, 2022
eff642d
Correct unit tests
michaelpeng36 Aug 29, 2022
4825d5a
Made the error message for func new more verbose
michaelpeng36 Aug 29, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,17 @@ public async override Task RunAsync()
workerRuntime = WorkerRuntimeLanguageHelper.SetWorkerRuntime(_secretsManager, Language);
}

// Check if the programming model is PyStein
if (isNewPythonProgrammingModel())
{
// TODO: Remove these messages once creating new functions in the new programming model is supported
ColoredConsole.WriteLine(WarningColor("When using the new Python programming model, triggers and bindings are created as decorators within the Python file itself."));
ColoredConsole.Write(AdditionalInfoColor("For information on how to create a new function with the new programming model, see "));
PythonHelpers.PrintPySteinWikiLink();
throw new CliException(
"Function not created!");
}

if (WorkerRuntimeLanguageHelper.IsDotnet(workerRuntime) && !Csx)
{
SelectionMenuHelper.DisplaySelectionWizardPrompt("template");
Expand Down Expand Up @@ -205,6 +216,10 @@ public async override Task RunAsync()
}
}
ColoredConsole.WriteLine($"The function \"{FunctionName}\" was created successfully from the \"{TemplateName}\" template.");
if (!isNewPythonProgrammingModel())
{
PythonHelpers.PrintPySteinAwarenessMessage();
}
}

private void ConfigureAuthorizationLevel(Template template)
Expand Down Expand Up @@ -261,5 +276,11 @@ private void PerformPostDeployTasks(string functionName, string language)
FileSystemHelpers.WriteAllTextToFile(funcJsonFile, JsonConvert.SerializeObject(funcObj, Formatting.Indented));
}
}

private bool isNewPythonProgrammingModel()
{
return string.Equals(Language, Languages.Python, StringComparison.InvariantCultureIgnoreCase)
&& FileSystemHelpers.FileExists(Path.Combine(Environment.CurrentDirectory, Constants.PySteinFunctionAppPy));
}
}
}
56 changes: 46 additions & 10 deletions src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,14 @@ internal class InitAction : BaseAction

public bool? ManagedDependencies { get; set; }

public string ProgrammingModel { get; set; }

public WorkerRuntime ResolvedWorkerRuntime { get; set; }

public string ResolvedLanguage { get; set; }

public ProgrammingModel ResolvedProgrammingModel { get; set; }

internal static readonly Dictionary<Lazy<string>, Task<string>> fileToContentMap = new Dictionary<Lazy<string>, Task<string>>
{
{ new Lazy<string>(() => ".gitignore"), StaticResources.GitIgnore }
Expand Down Expand Up @@ -118,6 +122,11 @@ public override ICommandLineParserResult ParseArgs(string[] args)
.Setup<bool>("managed-dependencies")
.WithDescription("Installs managed dependencies. Currently, only the PowerShell worker runtime supports this functionality.")
.Callback(f => ManagedDependencies = f);

Parser
.Setup<string>('m', "model")
.WithDescription($"Selects the programming model for the function app. Options are {EnumerationHelper.Join(", ", ProgrammingModelHelper.GetProgrammingModels())}. Currently, only the Python worker runtime supports the preview programming model")
.Callback(m => ProgrammingModel = m);

Parser
.Setup<bool>("no-bundle")
Expand Down Expand Up @@ -174,6 +183,16 @@ private async Task InitFunctionAppProject()
else
{
(ResolvedWorkerRuntime, ResolvedLanguage) = ResolveWorkerRuntimeAndLanguage(WorkerRuntime, Language);
// Order here is important: each language may have multiple runtimes, and each unique (language, worker-runtime) pair
// may have its own programming model. Thus, we assume that ResolvedLanguage and ResolvedWorkerRuntime are properly set
// before attempting to resolve the programming model.
var supportedProgrammingModels = ProgrammingModelHelper.GetSupportedProgrammingModels(ResolvedWorkerRuntime);
ResolvedProgrammingModel = ProgrammingModelHelper.ResolveProgrammingModel(ProgrammingModel, ResolvedWorkerRuntime, ResolvedLanguage);
if (!supportedProgrammingModels.Contains(ResolvedProgrammingModel))
{
throw new CliArgumentsException(
$"The {ResolvedProgrammingModel.ToString()} programming model is not supported for worker runtime {ResolvedWorkerRuntime.ToString()}. Supported programming models for worker runtime {ResolvedWorkerRuntime.ToString()} are:\n{EnumerationHelper.Join<ProgrammingModel>("\n", supportedProgrammingModels)}");
}
}

TelemetryHelpers.AddCommandEventToDictionary(TelemetryCommandEvents, "WorkerRuntime", ResolvedWorkerRuntime.ToString());
Expand All @@ -186,10 +205,10 @@ private async Task InitFunctionAppProject()
else
{
bool managedDependenciesOption = ResolveManagedDependencies(ResolvedWorkerRuntime, ManagedDependencies);
await InitLanguageSpecificArtifacts(ResolvedWorkerRuntime, ResolvedLanguage, managedDependenciesOption, GeneratePythonDocumentation);
await InitLanguageSpecificArtifacts(ResolvedWorkerRuntime, ResolvedLanguage, ResolvedProgrammingModel, managedDependenciesOption, GeneratePythonDocumentation);
await WriteFiles();
await WriteHostJson(ResolvedWorkerRuntime, managedDependenciesOption, ExtensionBundle);
await WriteLocalSettingsJson(ResolvedWorkerRuntime);
await WriteLocalSettingsJson(ResolvedWorkerRuntime, ResolvedProgrammingModel);
}

await WriteExtensionsJson();
Expand Down Expand Up @@ -251,11 +270,16 @@ private static string LanguageSelectionIfRelevant(WorkerRuntime workerRuntime)
return string.Empty;
}

private static async Task InitLanguageSpecificArtifacts(WorkerRuntime workerRuntime, string language, bool managedDependenciesOption, bool generatePythonDocumentation=true)
private static async Task InitLanguageSpecificArtifacts(
WorkerRuntime workerRuntime,
string language,
ProgrammingModel programmingModel,
bool managedDependenciesOption,
bool generatePythonDocumentation=true)
{
switch (workerRuntime) {
case Helpers.WorkerRuntime.python:
await PythonHelpers.SetupPythonProject(generatePythonDocumentation);
await PythonHelpers.SetupPythonProject(programmingModel, generatePythonDocumentation);
break;
case Helpers.WorkerRuntime.powershell:
await WriteFiles("profile.ps1", await StaticResources.PowerShellProfilePs1);
Expand Down Expand Up @@ -327,7 +351,7 @@ private void ValidateTargetFramework()
}
}

private static async Task WriteLocalSettingsJson(WorkerRuntime workerRuntime)
private static async Task WriteLocalSettingsJson(WorkerRuntime workerRuntime, ProgrammingModel programmingModel)
{
var localSettingsJsonContent = await StaticResources.LocalSettingsJson;
localSettingsJsonContent = localSettingsJsonContent.Replace($"{{{Constants.FunctionsWorkerRuntime}}}", WorkerRuntimeLanguageHelper.GetRuntimeMoniker(workerRuntime));
Expand All @@ -340,7 +364,12 @@ private static async Task WriteLocalSettingsJson(WorkerRuntime workerRuntime)

if (workerRuntime == Helpers.WorkerRuntime.powershell)
{
localSettingsJsonContent = AddWorkerVersion(localSettingsJsonContent, Constants.PowerShellWorkerDefaultVersion);
localSettingsJsonContent = AddLocalSetting(localSettingsJsonContent, Constants.FunctionsWorkerRuntimeVersion, Constants.PowerShellWorkerDefaultVersion);
}

if (programmingModel == Common.ProgrammingModel.V2)
{
localSettingsJsonContent = AddLocalSetting(localSettingsJsonContent, Constants.AzureWebJobsFeatureFlags, Constants.EnableWorkerIndexing);
}

await WriteFiles("local.settings.json", localSettingsJsonContent);
Expand Down Expand Up @@ -473,7 +502,7 @@ private static bool ResolveManagedDependencies(WorkerRuntime workerRuntime, bool
return true;
}

private static async Task WriteHostJson(WorkerRuntime workerRuntime, bool managedDependenciesOption, bool extensionBundle = true)
private async Task WriteHostJson(WorkerRuntime workerRuntime, bool managedDependenciesOption, bool extensionBundle = true)
{
var hostJsonContent = await StaticResources.HostJson;

Expand All @@ -484,7 +513,14 @@ private static async Task WriteHostJson(WorkerRuntime workerRuntime, bool manage

if (extensionBundle)
{
hostJsonContent = await hostJsonContent.AppendContent(Constants.ExtensionBundleConfigPropertyName, StaticResources.BundleConfig);
if (ResolvedProgrammingModel == Common.ProgrammingModel.V2 && ResolvedWorkerRuntime == Helpers.WorkerRuntime.python)
{
hostJsonContent = await hostJsonContent.AppendContent(Constants.ExtensionBundleConfigPropertyName, StaticResources.BundleConfigPyStein);
}
else
{
hostJsonContent = await hostJsonContent.AppendContent(Constants.ExtensionBundleConfigPropertyName, StaticResources.BundleConfig);
}
}

if(workerRuntime == Helpers.WorkerRuntime.custom)
Expand All @@ -495,15 +531,15 @@ private static async Task WriteHostJson(WorkerRuntime workerRuntime, bool manage
await WriteFiles(Constants.HostJsonFileName, hostJsonContent);
}

private static string AddWorkerVersion(string localSettingsContent, string workerVersion)
private static string AddLocalSetting(string localSettingsContent, string key, string value)
{
var localSettingsObj = JsonConvert.DeserializeObject<JObject>(localSettingsContent);

if (localSettingsObj.TryGetValue("Values", StringComparison.OrdinalIgnoreCase, out var valuesContent))
{
var values = valuesContent as JObject;
values.Property(Constants.FunctionsWorkerRuntime).AddAfterSelf(
new JProperty(Constants.FunctionsWorkerRuntimeVersion, workerVersion));
new JProperty(key, value));
}

return JsonConvert.SerializeObject(localSettingsObj, Formatting.Indented);
Expand Down
10 changes: 8 additions & 2 deletions src/Azure.Functions.Cli/Azure.Functions.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
<EmbeddedResource Include="StaticResources\bundleConfig.json">
<LogicalName>$(AssemblyName).bundleConfig.json</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="StaticResources\bundleConfigPyStein.json">
<LogicalName>$(AssemblyName).bundleConfigPyStein.json</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="StaticResources\customHandlerConfig.json">
<LogicalName>$(AssemblyName).customHandlerConfig.json</LogicalName>
</EmbeddedResource>
Expand Down Expand Up @@ -118,6 +121,9 @@
<EmbeddedResource Include="StaticResources\funcignore">
<LogicalName>$(AssemblyName).funcignore</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="StaticResources\function_app.py.template">
<LogicalName>$(AssemblyName).function_app.py</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="StaticResources\requirements.psd1">
<LogicalName>$(AssemblyName).requirements.psd1</LogicalName>
</EmbeddedResource>
Expand Down Expand Up @@ -159,7 +165,7 @@
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.20.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="2.2.0" />
<PackageReference Include="Microsoft.Azure.DurableTask.AzureStorage.Internal" Version="1.4.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Script.WebHost" Version="4.8.1" />
<PackageReference Include="Microsoft.Azure.WebJobs.Script.WebHost" Version="4.9.1" />
<PackageReference Include="Microsoft.Build" Version="17.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="WindowsAzure.Storage" Version="9.3.1" />
Expand All @@ -170,7 +176,7 @@
<PackageReference Include="Microsoft.Azure.Functions.NodeJsWorker" Version="3.4.0" />
<PackageReference Include="Microsoft.Azure.Functions.PowerShellWorker.PS7.0" Version="4.0.2045" />
<PackageReference Include="Microsoft.Azure.Functions.PowerShellWorker.PS7.2" Version="4.0.2040" />
<PackageReference Include="Microsoft.Azure.Functions.PythonWorker" Version="4.2.0-hotfix" />
<PackageReference Include="Microsoft.Azure.Functions.PythonWorker" Version="4.4.0" />
</ItemGroup>
<Target Name="ExcludeWorkersFromReadyToRun">
<CreateItem Include="%(None.Filename)%(None.Extension)" Condition="$([System.String]::new('%(None.TargetPath)').StartsWith('workers'))" PreserveExistingMetadata="false">
Expand Down
3 changes: 3 additions & 0 deletions src/Azure.Functions.Cli/Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal static class Constants
public const string FunctionsWorkerRuntimeVersion = "FUNCTIONS_WORKER_RUNTIME_VERSION";
public const string RequirementsTxt = "requirements.txt";
public const string PythonGettingStarted = "getting_started.md";
public const string PySteinFunctionAppPy = "function_app.py";
public const string FunctionJsonFileName = "function.json";
public const string HostJsonFileName = "host.json";
public const string ProxiesJsonFileName = "proxies.json";
Expand All @@ -30,6 +31,7 @@ internal static class Constants
public const string FunctionsExtensionVersion = "FUNCTIONS_EXTENSION_VERSION";
public const string StorageEmulatorConnectionString = "UseDevelopmentStorage=true";
public const string AzureWebJobsStorage = "AzureWebJobsStorage";
public const string AzureWebJobsFeatureFlags = "AzureWebJobsFeatureFlags";
public const string PackageReferenceElementName = "PackageReference";
public const string LinuxFxVersion = "linuxFxVersion";
public const string DotnetFrameworkVersion = "netFrameworkVersion";
Expand All @@ -54,6 +56,7 @@ internal static class Constants
public const string AuthLevelErrorMessage = "Unable to configure Authorization level. The selected template does not use Http Trigger";
public const string HttpTriggerTemplateName = "HttpTrigger";
public const string PowerShellWorkerDefaultVersion = "7.2";
public const string EnableWorkerIndexing = "EnableWorkerIndexing";
public const string UserSecretsIdElementName = "UserSecretsId";
public const string TargetFrameworkElementName = "TargetFramework";
public const string DisplayLogo = "FUNCTIONS_CORE_TOOLS_DISPLAY_LOGO";
Expand Down
8 changes: 8 additions & 0 deletions src/Azure.Functions.Cli/Common/ProgrammingModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Azure.Functions.Cli.Common
{
public enum ProgrammingModel
{
V1,
V2
}
}
14 changes: 14 additions & 0 deletions src/Azure.Functions.Cli/Helpers/EnumerationHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Linq;

namespace Azure.Functions.Cli.Helpers
{
public static class EnumerationHelper
{
public static string Join<T>(string separator, IEnumerable<T> enumerable)
{
return enumerable.Select(t => t.ToString())
.Aggregate((total, next) => total + separator + next);
}
}
}
2 changes: 2 additions & 0 deletions src/Azure.Functions.Cli/Helpers/GlobalCoreToolsSettings.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Azure.Functions.Cli.Common;
using Azure.Functions.Cli.Interfaces;
using Colors.Net;
using static Azure.Functions.Cli.Common.OutputTheme;
Expand All @@ -9,6 +10,7 @@ namespace Azure.Functions.Cli.Helpers
public static class GlobalCoreToolsSettings
{
private static WorkerRuntime _currentWorkerRuntime;
public static ProgrammingModel? CurrentProgrammingModel { get; set; }
public static WorkerRuntime CurrentWorkerRuntime
{
get
Expand Down
50 changes: 50 additions & 0 deletions src/Azure.Functions.Cli/Helpers/ProgrammingModelHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Azure.Functions.Cli.Common;

namespace Azure.Functions.Cli.Helpers
{
public static class ProgrammingModelHelper
{
public static IEnumerable<ProgrammingModel> GetProgrammingModels()
{
return Enum.GetValues<ProgrammingModel>();
}

// Given a worker runtime, this function returns a collection of the programming models that are supported.
public static IEnumerable<ProgrammingModel> GetSupportedProgrammingModels(WorkerRuntime workerRuntime)
{
var allProgrammingModels = GetProgrammingModels();
if (workerRuntime != WorkerRuntime.python)
{
return allProgrammingModels.Where(pm => pm != ProgrammingModel.V2);
}
return allProgrammingModels;
}

public static ProgrammingModel ResolveProgrammingModel(string programmingModel, WorkerRuntime workerRuntime, string language)
{
if (GlobalCoreToolsSettings.CurrentProgrammingModel != null)
michaelpeng36 marked this conversation as resolved.
Show resolved Hide resolved
{
return GlobalCoreToolsSettings.CurrentProgrammingModel.Value;
}
// We default to the "Default" programming model if the model parameter is not specified
if (string.IsNullOrEmpty(programmingModel))
{
GlobalCoreToolsSettings.CurrentProgrammingModel = ProgrammingModel.V1;
}
else if (GetProgrammingModels().Any(pm => string.Equals(programmingModel, pm.ToString(), StringComparison.InvariantCultureIgnoreCase)))
{
GlobalCoreToolsSettings.CurrentProgrammingModel = GetProgrammingModels().First(pm => string.Equals(programmingModel, pm.ToString(), StringComparison.InvariantCultureIgnoreCase));
}
// If programmingModel is non-empty and does not match any progrmming model, then we raise an exception
else
{
// TODO: Explicitly define the association between language, worker-runtime, and programming model
throw new CliArgumentsException($"The programming model {programmingModel} is not supported. Valid options for language {language} and worker-runtime {workerRuntime.ToString()} are:\n{EnumerationHelper.Join("\n", GetSupportedProgrammingModels(workerRuntime))}");
}
return GlobalCoreToolsSettings.CurrentProgrammingModel.Value;
}
}
}
Loading