Browse Source

Add support for Azure Functions v4 (#1285)

* Extract Azure Functions version from project file.

* Push Azure Functions version into run info.

* Get version-specific path.

* Switch to executable.

* Create shell scripts for installing/uninstalling Tye.

* Updates per PR feedback.
pull/1290/head
Phillip Hoff 5 years ago
committed by GitHub
parent
commit
6f9454e7b5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      install-tye.cmd
  2. 4
      install-tye.ps1
  3. 5
      install-tye.sh
  4. 2
      remove-tye.cmd
  5. 3
      remove-tye.sh
  6. 74
      src/Microsoft.Tye.Core/ApplicationFactory.cs
  7. 1
      src/Microsoft.Tye.Core/AzureFunctionServiceBuilder.cs
  8. 45
      src/Microsoft.Tye.Core/ProjectReader.cs
  9. 35
      src/Microsoft.Tye.Hosting/FuncFinder.cs
  10. 2
      src/Microsoft.Tye.Hosting/Model/AzureFunctionRunInfo.cs
  11. 1
      src/tye/ProjectEvaluation.targets

3
install-tye.cmd

@ -0,0 +1,3 @@
@ECHO OFF
SETLOCAL
PowerShell -NoProfile -NoLogo -ExecutionPolicy ByPass -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = ''; try { & '%~dp0install-tye.ps1' %*; exit $LASTEXITCODE } catch { write-host $_; exit 1 }"

4
install-tye.ps1

@ -0,0 +1,4 @@
$versionPrefix = Select-Xml -Path .\eng\Versions.props -XPath "/Project/PropertyGroup/VersionPrefix" | ForEach-Object { $_.Node.InnerXml }
dotnet tool install microsoft.tye -g --version "$versionPrefix-dev" --add-source ./artifacts/packages/Debug/Shipping

5
install-tye.sh

@ -0,0 +1,5 @@
#!/usr/bin/env bash
versionprefix=`awk -F'[<>]' '/VersionPrefix.*VersionPrefix/{print $3}' ./eng/Versions.props`
dotnet tool install microsoft.tye -g --version "$versionprefix-dev" --add-source ./artifacts/packages/Debug/Shipping

2
remove-tye.cmd

@ -0,0 +1,2 @@
@echo off
dotnet tool uninstall microsoft.tye -g

3
remove-tye.sh

@ -0,0 +1,3 @@
#!/usr/bin/env bash
dotnet tool uninstall microsoft.tye -g

74
src/Microsoft.Tye.Core/ApplicationFactory.cs

@ -73,10 +73,31 @@ namespace Microsoft.Tye
root.Extensions.Add(extension);
}
bool IsAzureFunctionService(ConfigService service)
{
return !string.IsNullOrEmpty(service.AzureFunction);
}
var services = filter?.ServicesFilter != null ?
config.Services.Where(filter.ServicesFilter).ToList() :
config.Services;
// Infer project file for Azure Function services so they will be evaluated.
foreach (var service in services.Where(IsAzureFunctionService))
{
var azureFunctionDirectory = Path.Combine(config.Source.DirectoryName!, service.AzureFunction!);
foreach (var proj in Directory.EnumerateFiles(azureFunctionDirectory))
{
var fileInfo = new FileInfo(proj);
if (fileInfo.Extension == ".csproj" || fileInfo.Extension == ".fsproj")
{
service.Project = fileInfo.FullName;
break;
}
}
}
var sw = Stopwatch.StartNew();
// Project services will be restored and evaluated before resolving all other services.
// This batching will mitigate the performance cost of running MSBuild out of process.
@ -162,7 +183,31 @@ namespace Microsoft.Tye
continue;
}
if (!string.IsNullOrEmpty(configService.Project))
// NOTE: Evaluate Azure Function services before project services as both use Project.
if (IsAzureFunctionService(configService))
{
var azureFunctionDirectory = Path.Combine(config.Source.DirectoryName!, configService.AzureFunction!);
var functionBuilder = new AzureFunctionServiceBuilder(
configService.Name,
azureFunctionDirectory,
ServiceSource.Configuration)
{
Args = configService.Args,
Replicas = configService.Replicas ?? 1,
FuncExecutablePath = configService.FuncExecutable,
ProjectFile = configService.Project
};
if (functionBuilder.ProjectFile != null)
{
ProjectReader.ReadAzureFunctionProjectDetails(output, functionBuilder, projectMetadata[configService.Name]);
}
// TODO liveness?
service = functionBuilder;
}
else if (!string.IsNullOrEmpty(configService.Project))
{
// TODO: Investigate possible null.
var project = new DotnetProjectServiceBuilder(configService.Name!, new FileInfo(configService.ProjectFullPath!), ServiceSource.Configuration);
@ -305,33 +350,6 @@ namespace Microsoft.Tye
continue;
}
else if (!string.IsNullOrEmpty(configService.AzureFunction))
{
var azureFunctionDirectory = Path.Combine(config.Source.DirectoryName!, configService.AzureFunction);
var functionBuilder = new AzureFunctionServiceBuilder(
configService.Name,
azureFunctionDirectory,
ServiceSource.Configuration)
{
Args = configService.Args,
Replicas = configService.Replicas ?? 1,
FuncExecutablePath = configService.FuncExecutable,
};
foreach (var proj in Directory.EnumerateFiles(azureFunctionDirectory))
{
var fileInfo = new FileInfo(proj);
if (fileInfo.Extension == ".csproj" || fileInfo.Extension == ".fsproj")
{
functionBuilder.ProjectFile = fileInfo.FullName;
break;
}
}
// TODO liveness?
service = functionBuilder;
}
else if (configService.External)
{
var external = new ExternalServiceBuilder(configService.Name, ServiceSource.Configuration);

1
src/Microsoft.Tye.Core/AzureFunctionServiceBuilder.cs

@ -19,6 +19,7 @@ namespace Microsoft.Tye
public string FunctionPath { get; }
public string? FuncExecutablePath { get; set; }
public string? ProjectFile { get; set; }
public string? AzureFunctionsVersion { get; set; }
public List<EnvironmentVariableBuilder> EnvironmentVariables { get; } = new List<EnvironmentVariableBuilder>();
}
}

45
src/Microsoft.Tye.Core/ProjectReader.cs

@ -77,6 +77,26 @@ namespace Microsoft.Tye
}
}
public static void ReadAzureFunctionProjectDetails(OutputContext output, AzureFunctionServiceBuilder project, string metadataFile)
{
if (output is null)
{
throw new ArgumentNullException(nameof(output));
}
if (project is null)
{
throw new ArgumentNullException(nameof(project));
}
if (metadataFile is null)
{
throw new ArgumentNullException(nameof(metadataFile));
}
EvaluateAzureFunctionProject(output, project, metadataFile);
}
// Do not load MSBuild types before using EnsureMSBuildRegistered.
[MethodImpl(MethodImplOptions.NoInlining)]
private static void EvaluateProject(OutputContext output, DotnetProjectServiceBuilder project, string metadataFile)
@ -159,6 +179,31 @@ namespace Microsoft.Tye
bool MetadataIsTrue(string key) => metadata!.TryGetValue(key, out var value) && bool.Parse(value);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void EvaluateAzureFunctionProject(OutputContext output, AzureFunctionServiceBuilder project, string metadataFile)
{
var sw = Stopwatch.StartNew();
var metadata = new Dictionary<string, string>();
var metadataKVPs = File.ReadLines(metadataFile).Select(l => l.Split(new[] { ':' }, 2));
foreach (var metadataKVP in metadataKVPs)
{
if (!string.IsNullOrEmpty(metadataKVP[1]))
{
metadata.Add(metadataKVP[0], metadataKVP[1].Trim());
}
}
project.AzureFunctionsVersion = GetMetadataValueOrNull("AzureFunctionsVersion");
output.WriteDebugLine($"AzureFunctionsVersion={project.AzureFunctionsVersion}");
output.WriteDebugLine($"Evaluation Took: {sw.Elapsed.TotalMilliseconds}ms");
string? GetMetadataValueOrNull(string key) => metadata!.TryGetValue(key, out var value) ? value : null;
}
private static string NormalizePath(string path)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))

35
src/Microsoft.Tye.Hosting/FuncFinder.cs

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Tye.Hosting.Model;
@ -50,50 +51,62 @@ namespace Microsoft.Tye.Hosting
private string? FindFuncForVersion(AzureFunctionRunInfo func)
{
var funcDllPath = FuncDllPath();
int version = 4; // Default to the latest version (v4).
if (!string.IsNullOrWhiteSpace(func.AzureFunctionsVersion))
{
var match = Regex.Match(func.AzureFunctionsVersion, @"^[vV]?(?<number>\d+)$");
if (match.Success && int.TryParse(match.Groups["number"].Value, out int parsedValue))
{
version = parsedValue;
}
}
var funcDllPath = FuncDllPath(version);
if (!File.Exists(funcDllPath))
{
throw new FileNotFoundException("Could not find func installation. Please install the azure function core tools with the installer: " +
"https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local or `npm install -g azure-functions-core-tools@3`");
$"https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local or `npm install -g azure-functions-core-tools@{version}`");
}
_logger.LogDebug("Using func for running azure functions located at {Func}.", funcDllPath);
return funcDllPath;
}
private string FuncDllPath()
private string FuncDllPath(int version)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var funcPathStandalone = Environment.ExpandEnvironmentVariables("%PROGRAMFILES%/Microsoft/Azure Functions Core Tools/func.dll");
var funcPathStandalone = Environment.ExpandEnvironmentVariables("%PROGRAMFILES%/Microsoft/Azure Functions Core Tools/func.exe");
if (File.Exists(funcPathStandalone))
{
return funcPathStandalone;
}
return Environment.ExpandEnvironmentVariables("%APPDATA%/npm/node_modules/azure-functions-core-tools/bin/func.dll");
return Environment.ExpandEnvironmentVariables("%APPDATA%/npm/node_modules/azure-functions-core-tools/bin/func.exe");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var funcPathStandalone = "/usr/lib/azure-functions-core-tools-3/func.dll";
var funcPathStandalone = $"/usr/lib/azure-functions-core-tools-{version}/func";
if (File.Exists(funcPathStandalone))
{
return funcPathStandalone;
}
return "/usr/local/lib/node_modules/azure-functions-core-tools/bin/func.dll";
return "/usr/local/lib/node_modules/azure-functions-core-tools/bin/func";
}
else
{
// For brew path, just find a folder that supports functions v3.
var funcDirectories = Directory.GetDirectories("/usr/local/Cellar/azure-functions-core-tools@3/");
var funcPathStandalone = Path.Combine(funcDirectories.LastOrDefault() ?? "", "func.dll");
// For brew path, just find a folder that supports functions v{version}.
var funcDirectories = Directory.GetDirectories($"/usr/local/Cellar/azure-functions-core-tools@{version}/");
var funcPathStandalone = Path.Combine(funcDirectories.LastOrDefault() ?? "", "func");
if (File.Exists(funcPathStandalone))
{
return funcPathStandalone;
}
return "/usr/local/lib/node_modules/azure-functions-core-tools/bin/func.dll";
return "/usr/local/lib/node_modules/azure-functions-core-tools/bin/func";
}
}

2
src/Microsoft.Tye.Hosting/Model/AzureFunctionRunInfo.cs

@ -11,12 +11,14 @@ namespace Microsoft.Tye.Hosting.Model
public AzureFunctionRunInfo(AzureFunctionServiceBuilder function)
{
Args = function.Args;
AzureFunctionsVersion = function.AzureFunctionsVersion;
FunctionPath = function.FunctionPath;
FuncExecutablePath = function.FuncExecutablePath;
ProjectFile = function.ProjectFile;
}
public string? Args { get; }
public string? AzureFunctionsVersion { get; }
public string FunctionPath { get; }
public string? FuncExecutablePath { get; set; }
public string? ProjectFile { get; set; }

1
src/tye/ProjectEvaluation.targets

@ -34,6 +34,7 @@
<_MicrosoftTye_ProjectMetadata Include="MicrosoftNETPlatformLibrary: $(MicrosoftNETPlatformLibrary)" />
<_MicrosoftTye_ProjectMetadata Include="_AspNetCoreAppSharedFxIsEnabled: $(_AspNetCoreAppSharedFxIsEnabled)" />
<_MicrosoftTye_ProjectMetadata Include="UsingMicrosoftNETSdkWeb: $(UsingMicrosoftNETSdkWeb)" />
<_MicrosoftTye_ProjectMetadata Include="AzureFunctionsVersion: $(AzureFunctionsVersion)" />
</ItemGroup>
<WriteLinesToFile

Loading…
Cancel
Save