diff --git a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs index 641231cf3e..0820922179 100644 --- a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs +++ b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs @@ -36,7 +36,7 @@ public void Configure(HttpWorkerOptions options) { throw new HostConfigurationException($"Missing WorkerDescription for HttpWorker"); } - httpWorkerDescription.ApplyDefaultsAndValidate(_scriptJobHostOptions.RootScriptPath); + httpWorkerDescription.ApplyDefaultsAndValidate(_scriptJobHostOptions.RootScriptPath, _logger); options.Arguments = new WorkerProcessArguments() { ExecutablePath = options.Description.DefaultExecutablePath, diff --git a/src/WebJobs.Script/Workers/Http/HttpWorkerDescription.cs b/src/WebJobs.Script/Workers/Http/HttpWorkerDescription.cs index 922720e21f..ae89ea9e44 100644 --- a/src/WebJobs.Script/Workers/Http/HttpWorkerDescription.cs +++ b/src/WebJobs.Script/Workers/Http/HttpWorkerDescription.cs @@ -5,12 +5,13 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; +using Microsoft.Extensions.Logging; namespace Microsoft.Azure.WebJobs.Script.Workers.Http { public class HttpWorkerDescription : WorkerDescription { - public override void ApplyDefaultsAndValidate(string workerDirectory) + public override void ApplyDefaultsAndValidate(string workerDirectory, ILogger logger) { if (workerDirectory == null) { diff --git a/src/WebJobs.Script/Workers/ProcessManagement/WorkerDescription.cs b/src/WebJobs.Script/Workers/ProcessManagement/WorkerDescription.cs index f1ba151724..48ec42ddec 100644 --- a/src/WebJobs.Script/Workers/ProcessManagement/WorkerDescription.cs +++ b/src/WebJobs.Script/Workers/ProcessManagement/WorkerDescription.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; +using Microsoft.Extensions.Logging; namespace Microsoft.Azure.WebJobs.Script.Workers { @@ -27,6 +28,6 @@ public abstract class WorkerDescription /// public List Arguments { get; set; } - public abstract void ApplyDefaultsAndValidate(string workerDirectory); + public abstract void ApplyDefaultsAndValidate(string workerDirectory, ILogger logger); } } \ No newline at end of file diff --git a/src/WebJobs.Script/Workers/Rpc/Configuration/RpcWorkerConfigFactory.cs b/src/WebJobs.Script/Workers/Rpc/Configuration/RpcWorkerConfigFactory.cs index 2de8ef0eb0..310b31e200 100644 --- a/src/WebJobs.Script/Workers/Rpc/Configuration/RpcWorkerConfigFactory.cs +++ b/src/WebJobs.Script/Workers/Rpc/Configuration/RpcWorkerConfigFactory.cs @@ -140,7 +140,7 @@ internal void AddProvider(string workerDir) { workerDescription.DefaultWorkerPath = GetHydratedWorkerPath(workerDescription); } - workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory()); + workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory(), _logger); _workerDescripionDictionary[workerDescription.Language] = workerDescription; } catch (Exception ex) diff --git a/src/WebJobs.Script/Workers/Rpc/RpcWorkerDescription.cs b/src/WebJobs.Script/Workers/Rpc/RpcWorkerDescription.cs index ab2a1b82f8..f2d3409c61 100644 --- a/src/WebJobs.Script/Workers/Rpc/RpcWorkerDescription.cs +++ b/src/WebJobs.Script/Workers/Rpc/RpcWorkerDescription.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; namespace Microsoft.Azure.WebJobs.Script.Workers.Rpc @@ -65,7 +66,10 @@ public List Extensions } } - public override void ApplyDefaultsAndValidate(string workerDirectory) + // Can be replaced for testing purposes + internal Func FileExists { private get; set; } = File.Exists; + + public override void ApplyDefaultsAndValidate(string workerDirectory, ILogger logger) { if (workerDirectory == null) { @@ -94,7 +98,7 @@ public override void ApplyDefaultsAndValidate(string workerDirectory) throw new FileNotFoundException($"Did not find {nameof(DefaultWorkerPath)} for language: {Language}"); } - ResolveDotNetDefaultExecutablePath(); + ResolveDotNetDefaultExecutablePath(logger); } public void ValidateWorkerPath(string workerPath, OSPlatform os, Architecture architecture, string version) @@ -139,14 +143,29 @@ private void ValidateRuntimeVersion(string version) } } - private void ResolveDotNetDefaultExecutablePath() + private void ResolveDotNetDefaultExecutablePath(ILogger logger) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && (DefaultExecutablePath.Equals(RpcWorkerConstants.DotNetExecutableName, StringComparison.OrdinalIgnoreCase) || DefaultExecutablePath.Equals(RpcWorkerConstants.DotNetExecutableNameWithExtension, StringComparison.OrdinalIgnoreCase))) { var programFilesFolder = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); - DefaultExecutablePath = Path.Combine(programFilesFolder, RpcWorkerConstants.DotNetFolderName, DefaultExecutablePath); + + var fullPath = Path.Combine( + programFilesFolder, + RpcWorkerConstants.DotNetFolderName, + RpcWorkerConstants.DotNetExecutableNameWithExtension); + + if (FileExists(fullPath)) + { + DefaultExecutablePath = fullPath; + } + else + { + logger.Log( + LogLevel.Warning, + $"File '{fullPath}' is not found, '{DefaultExecutablePath}' invocation will rely on the PATH environment variable."); + } } } } diff --git a/test/WebJobs.Script.Tests/Workers/Rpc/RpcWorkerConfigTests.cs b/test/WebJobs.Script.Tests/Workers/Rpc/RpcWorkerConfigTests.cs index 0c0c4bad1e..0b4491fa26 100644 --- a/test/WebJobs.Script.Tests/Workers/Rpc/RpcWorkerConfigTests.cs +++ b/test/WebJobs.Script.Tests/Workers/Rpc/RpcWorkerConfigTests.cs @@ -244,17 +244,21 @@ public void ReadWorkerProviderFromConfig_OverrideDefaultExePath() [MemberData(nameof(InvalidWorkerDescriptions))] public void InvalidWorkerDescription_Throws(WorkerDescription workerDescription) { - Assert.Throws(() => workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory())); + var testLogger = new TestLogger(testLanguage); + + Assert.Throws(() => workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory(), testLogger)); } [Theory] [MemberData(nameof(ValidWorkerDescriptions))] public void ValidateWorkerDescription_Succeeds(WorkerDescription workerDescription) { + var testLogger = new TestLogger(testLanguage); + try { RpcWorkerConfigTestUtilities.CreateTestWorkerFileInCurrentDir(); - workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory()); + workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory(), testLogger); } finally { @@ -267,26 +271,88 @@ public void ValidateWorkerDescription_Succeeds(WorkerDescription workerDescripti [InlineData("DotNet")] [InlineData("dotnet.exe")] [InlineData("DOTNET.EXE")] - public void ValidateWorkerDescription_ResolvesDotNetDefaultWorkerExecutablePath(string defaultExecutablePath) + public void ValidateWorkerDescription_ResolvesDotNetDefaultWorkerExecutablePath_WhenExpectedFileExists( + string defaultExecutablePath) { + var testLogger = new TestLogger(testLanguage); + var expectedExecutablePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet", defaultExecutablePath) + ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet", "dotnet.exe") : defaultExecutablePath; - var workerDescription = new RpcWorkerDescription { Language = testLanguage, Extensions = new List(), DefaultExecutablePath = defaultExecutablePath }; - workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory()); + var workerDescription = new RpcWorkerDescription + { + Language = testLanguage, + Extensions = new List(), + DefaultExecutablePath = defaultExecutablePath, + FileExists = path => + { + Assert.Equal(expectedExecutablePath, path); + return true; + } + }; + + workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory(), testLogger); Assert.Equal(expectedExecutablePath, workerDescription.DefaultExecutablePath); } + [Theory] + [InlineData("dotnet")] + [InlineData("DotNet")] + [InlineData("dotnet.exe")] + [InlineData("DOTNET.EXE")] + public void ValidateWorkerDescription_DoesNotModifyDefaultWorkerExecutablePathAndWarns_WhenExpectedFileDoesNotExist( + string defaultExecutablePath) + { + var testLogger = new TestLogger(testLanguage); + + var expectedExecutablePath = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet", "dotnet.exe") + : defaultExecutablePath; + + var workerDescription = new RpcWorkerDescription + { + Language = testLanguage, + Extensions = new List(), + DefaultExecutablePath = defaultExecutablePath, + FileExists = path => + { + Assert.Equal(expectedExecutablePath, path); + return false; + } + }; + + workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory(), testLogger); + Assert.Equal(defaultExecutablePath, workerDescription.DefaultExecutablePath); + Assert.True(testLogger.GetLogMessages().Any(message => message.Level == LogLevel.Warning + && message.FormattedMessage.Contains(defaultExecutablePath) + && message.FormattedMessage.Contains(expectedExecutablePath))); + } + [Theory] [InlineData(@"D:\CustomExecutableFolder\dotnet")] [InlineData(@"/CustomExecutableFolder/dotnet")] [InlineData("AnythingElse")] - public void ValidateWorkerDescription_DoesNotModifyDefaultWorkerExecutablePath_WhenDoesNotStrictlyMatchDotNet(string defaultExecutablePath) + public void ValidateWorkerDescription_DoesNotModifyDefaultWorkerExecutablePath_WhenDoesNotStrictlyMatchDotNet( + string defaultExecutablePath) { - var workerDescription = new RpcWorkerDescription { Language = testLanguage, Extensions = new List(), DefaultExecutablePath = defaultExecutablePath }; - workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory()); + var testLogger = new TestLogger(testLanguage); + + var workerDescription = new RpcWorkerDescription + { + Language = testLanguage, + Extensions = new List(), + DefaultExecutablePath = defaultExecutablePath, + FileExists = path => + { + Assert.True(false, "FileExists should not be called"); + return false; + } + }; + + workerDescription.ApplyDefaultsAndValidate(Directory.GetCurrentDirectory(), testLogger); Assert.Equal(defaultExecutablePath, workerDescription.DefaultExecutablePath); }