sdk/FunctionMetadataLoaderExtension/Startup.cs (129 lines of code) (raw):

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader; using Microsoft.Azure.WebJobs.Hosting; using Microsoft.Azure.WebJobs.Script.Description; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; [assembly: WebJobsStartup(typeof(Startup))] namespace Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader { internal class Startup : IWebJobsStartup2, IWebJobsConfigurationStartup { private const string WorkerConfigFile = "worker.config.json"; private const string ExePathPropertyName = "defaultExecutablePath"; private const string WorkerPathPropertyName = "defaultWorkerPath"; private const string WorkerRootToken = "{WorkerRoot}"; private const string WorkerIndexingPropertyName = "workerIndexing"; private static readonly string _dotnetIsolatedWorkerConfigPath = ConfigurationPath.Combine("languageWorkers", "dotnet-isolated", "workerDirectory"); private static readonly string _dotnetIsolatedWorkerExePath = ConfigurationPath.Combine("languageWorkers", "dotnet-isolated", ExePathPropertyName); private WorkerConfigDescription? newWorkerDescription; public void Configure(WebJobsBuilderContext context, IWebJobsConfigurationBuilder builder) { string appRootPath = context.ApplicationRootPath; // We need to adjust the path to the worker exe based on the root, if WorkerRootToken is found. // Both Configure methods need the updated worker description and the following line will check if it already exists or call GetUpdatedWorkerDescription() to create it newWorkerDescription ??= GetUpdatedWorkerDescription(appRootPath); builder.ConfigurationBuilder.AddInMemoryCollection(new Dictionary<string, string> { { _dotnetIsolatedWorkerConfigPath, appRootPath }, { _dotnetIsolatedWorkerExePath, newWorkerDescription.DefaultExecutablePath! } }); Environment.SetEnvironmentVariable("DOTNET_NOLOGO", "true"); Environment.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "true"); Environment.SetEnvironmentVariable("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "true"); } public void Configure(WebJobsBuilderContext context, IWebJobsBuilder builder) { string appRootPath = context.ApplicationRootPath; newWorkerDescription ??= GetUpdatedWorkerDescription(appRootPath); bool enableIndexing = newWorkerDescription.EnableWorkerIndexing ?? false; if (!enableIndexing) { builder.Services.AddOptions<FunctionMetadataJsonReaderOptions>().Configure(o => o.FunctionMetadataFileDrectory = appRootPath); builder.Services.AddSingleton<FunctionMetadataJsonReader>(); builder.Services.AddSingleton<IFunctionProvider, JsonFunctionProvider>(); } } public void Configure(IWebJobsBuilder builder) { // This will not be called. } private static WorkerConfigDescription GetUpdatedWorkerDescription(string appRootPath) { string fullPathToWorkerConfig = Path.Combine(appRootPath, WorkerConfigFile); WorkerConfigDescription workerDescription = GetWorkerConfigDescription(fullPathToWorkerConfig); if (string.IsNullOrEmpty(workerDescription.DefaultExecutablePath)) { throw new InvalidOperationException($"The property '{ExePathPropertyName}' is required in '{fullPathToWorkerConfig}'."); } UpdateExecutablePath(workerDescription, appRootPath); return workerDescription; } private static void UpdateExecutablePath(WorkerConfigDescription workerDescription, string appRootPath) { string exePath = workerDescription.DefaultExecutablePath!; // Translate '{WorkerRoot}myExe' to '<app-path>\myExe.exe' for Windows, and '<app-path>/myExe' for Unix. if (HasWorkerRootToken(exePath)) { exePath = AddWorkerRootPath(exePath, appRootPath); if (!File.Exists(exePath)) { throw new FileNotFoundException($"The file '{exePath}' was not found."); } } workerDescription.DefaultExecutablePath = exePath; } private static WorkerConfigDescription GetWorkerConfigDescription(string workerConfigPath) { if (!File.Exists(workerConfigPath)) { throw new FileNotFoundException($"The file '{workerConfigPath}' was not found."); } WorkerConfigDescription? workerDescription; using (var fs = File.OpenText(workerConfigPath)) using (var js = new JsonTextReader(fs)) { JObject? workerDescriptionJObject = JToken.ReadFrom(js)["description"] as JObject; workerDescription = workerDescriptionJObject?.ToObject<WorkerConfigDescription>(); if (workerDescription is null) { throw new InvalidOperationException($"The property 'description' is required in '{workerConfigPath}'."); } } return workerDescription; } private static bool HasWorkerRootToken(string exe) { return exe.Contains(WorkerRootToken); } private static string AddWorkerRootPath(string exe, string appRootPath) { string execName = exe.Replace(WorkerRootToken, string.Empty); if (string.IsNullOrEmpty(execName)) { throw new InvalidOperationException($"The property '{ExePathPropertyName}' in '{WorkerConfigFile}' does not contain the executable file name."); } string execPath = Path.Combine(appRootPath, execName); return AddExeExtensionIfWindows(execPath); } private static string AddExeExtensionIfWindows(string file) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !file.EndsWith(".exe")) { file += ".exe"; } return file; } private class WorkerConfigDescription { [JsonProperty(ExePathPropertyName)] public string? DefaultExecutablePath { get; set; } [JsonProperty(WorkerPathPropertyName)] public string? DefaultWorkerPath { get; set; } [JsonProperty(WorkerIndexingPropertyName)] public bool? EnableWorkerIndexing { get; set; } } } }