From 2392ebee3626b987799b19c6261630683d37ffac Mon Sep 17 00:00:00 2001 From: Francisco-Gamino Date: Fri, 21 Apr 2023 10:46:41 -0700 Subject: [PATCH 1/8] Error out when a user tries to use Managed Dependencies on Legion --- .../ManagedDependenciesPathDetector.cs | 13 +++++++++++++ src/resources/PowerShellWorkerStrings.resx | 3 +++ 2 files changed, 16 insertions(+) diff --git a/src/DependencyManagement/ManagedDependenciesPathDetector.cs b/src/DependencyManagement/ManagedDependenciesPathDetector.cs index 0b575aaa..abbaf52b 100644 --- a/src/DependencyManagement/ManagedDependenciesPathDetector.cs +++ b/src/DependencyManagement/ManagedDependenciesPathDetector.cs @@ -13,6 +13,7 @@ internal static class ManagedDependenciesPathDetector // Environment variables to help figure out if we are running in Azure. private const string AzureWebsiteInstanceId = "WEBSITE_INSTANCE_ID"; private const string ContainerName = "CONTAINER_NAME"; + private const string LegionServiceHost = "LEGION_SERVICE_HOST"; private const string HomeDriveName = "HOME"; private const string DataFolderName = "data"; @@ -30,6 +31,11 @@ internal static class ManagedDependenciesPathDetector /// public static string GetManagedDependenciesPath(string functionAppRootPath) { + if (IsLinuxConsumptionOnLegion()) + { + throw new NotSupportedException(PowerShellWorkerStrings.ManagedDependenciesIsNotSupportedOnLegion); + } + // If we are running in Azure App Service or Linux Consumption use the 'HOME\data' path. if (IsAppService() || IsLinuxConsumption()) { @@ -58,5 +64,12 @@ private static bool IsLinuxConsumption() { return !IsAppService() && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ContainerName)); } + + public static bool IsLinuxConsumptionOnLegion() + { + return !IsAppService() && + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ContainerName)) && + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LegionServiceHost)); + } } } diff --git a/src/resources/PowerShellWorkerStrings.resx b/src/resources/PowerShellWorkerStrings.resx index 89563720..571ee710 100644 --- a/src/resources/PowerShellWorkerStrings.resx +++ b/src/resources/PowerShellWorkerStrings.resx @@ -376,4 +376,7 @@ Utilizing external Durable Functions SDK: '{0}'. + + Managed Dependencies is not supported in Linux Consumption on Legion. Please remove all module references from requirements.psd1 and include the function app dependencies with the function app content. For more information, please see https://aka.ms/functions-powershell-include-modules-with-app-content. + \ No newline at end of file From 0ecfa68fe177e9ba47b5a1ff4f7bc6cdaf811b13 Mon Sep 17 00:00:00 2001 From: Francisco-Gamino Date: Fri, 21 Apr 2023 10:47:26 -0700 Subject: [PATCH 2/8] Add Managed Dependencies is not supported on Legion test case --- .../ManagedDependenciesPathDetectorTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/Unit/DependencyManagement/ManagedDependenciesPathDetectorTests.cs b/test/Unit/DependencyManagement/ManagedDependenciesPathDetectorTests.cs index 2ee13523..6afb42a0 100644 --- a/test/Unit/DependencyManagement/ManagedDependenciesPathDetectorTests.cs +++ b/test/Unit/DependencyManagement/ManagedDependenciesPathDetectorTests.cs @@ -62,5 +62,36 @@ public void ValidateManagedDependenciesPath(string name, string value, bool setE } } } + + [Fact] + public void ManagedDependenciesIsNotSupportedOnLegion() + { + const string ContainerName = "CONTAINER_NAME"; + const string LegionServiceHost = "LEGION_SERVICE_HOST"; + const string AzureWebsiteInstanceId = "WEBSITE_INSTANCE_ID"; + + var functionAppRoot = Path.Join(Path.GetTempPath(), "MyFunction"); + + try + { + Environment.SetEnvironmentVariable(AzureWebsiteInstanceId, null); + Environment.SetEnvironmentVariable(ContainerName, "MY_CONTAINER_NAME"); + Environment.SetEnvironmentVariable(LegionServiceHost, "MY_LEGION_SERVICE_HOST"); + + var exception = Assert.Throws(() => ManagedDependenciesPathDetector.GetManagedDependenciesPath(functionAppRoot)); + Assert.Contains("Managed Dependencies is not supported in Linux Consumption on Legion.", exception.Message); + Assert.Contains("https://aka.ms/functions-powershell-include-modules-with-app-content", exception.Message); + } + finally + { + Environment.SetEnvironmentVariable(ContainerName, null); + Environment.SetEnvironmentVariable(LegionServiceHost, null); + + if (Directory.Exists(functionAppRoot)) + { + Directory.Delete(functionAppRoot); + } + } + } } } From 7a6af186d6e2f68a08a16ff5699c743dd65b83e3 Mon Sep 17 00:00:00 2001 From: Francisco-Gamino Date: Fri, 21 Apr 2023 21:29:14 -0700 Subject: [PATCH 3/8] Update error message --- src/resources/PowerShellWorkerStrings.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/PowerShellWorkerStrings.resx b/src/resources/PowerShellWorkerStrings.resx index 571ee710..0685d7b4 100644 --- a/src/resources/PowerShellWorkerStrings.resx +++ b/src/resources/PowerShellWorkerStrings.resx @@ -377,6 +377,6 @@ Utilizing external Durable Functions SDK: '{0}'. - Managed Dependencies is not supported in Linux Consumption on Legion. Please remove all module references from requirements.psd1 and include the function app dependencies with the function app content. For more information, please see https://aka.ms/functions-powershell-include-modules-with-app-content. + Managed Dependencies is not supported in Linux Consumption on Legion. Please remove all module references from requirements.psd1 and include the function app dependencies with the function app content. For more information, please see https://aka.ms/functions-powershell-include-modules. \ No newline at end of file From 9c6ca8b977f50d3f1d9b9669afae96a3cf91be03 Mon Sep 17 00:00:00 2001 From: Francisco-Gamino Date: Fri, 21 Apr 2023 21:32:20 -0700 Subject: [PATCH 4/8] Moved logic to help figure out if we are running in Azure to a helper class --- .../ManagedDependenciesPathDetector.cs | 29 +-------------- src/DependencyManagement/WorkerEnvironment.cs | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 28 deletions(-) create mode 100644 src/DependencyManagement/WorkerEnvironment.cs diff --git a/src/DependencyManagement/ManagedDependenciesPathDetector.cs b/src/DependencyManagement/ManagedDependenciesPathDetector.cs index abbaf52b..006f621d 100644 --- a/src/DependencyManagement/ManagedDependenciesPathDetector.cs +++ b/src/DependencyManagement/ManagedDependenciesPathDetector.cs @@ -10,11 +10,6 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement { internal static class ManagedDependenciesPathDetector { - // Environment variables to help figure out if we are running in Azure. - private const string AzureWebsiteInstanceId = "WEBSITE_INSTANCE_ID"; - private const string ContainerName = "CONTAINER_NAME"; - private const string LegionServiceHost = "LEGION_SERVICE_HOST"; - private const string HomeDriveName = "HOME"; private const string DataFolderName = "data"; @@ -31,13 +26,8 @@ internal static class ManagedDependenciesPathDetector /// public static string GetManagedDependenciesPath(string functionAppRootPath) { - if (IsLinuxConsumptionOnLegion()) - { - throw new NotSupportedException(PowerShellWorkerStrings.ManagedDependenciesIsNotSupportedOnLegion); - } - // If we are running in Azure App Service or Linux Consumption use the 'HOME\data' path. - if (IsAppService() || IsLinuxConsumption()) + if (WorkerEnvironment.IsAppService() || WorkerEnvironment.IsLinuxConsumption()) { var homeDriveVariable = Environment.GetEnvironmentVariable(HomeDriveName); if (string.IsNullOrEmpty(homeDriveVariable)) @@ -54,22 +44,5 @@ public static string GetManagedDependenciesPath(string functionAppRootPath) string appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.DoNotVerify); return Path.Combine(appDataFolder, AzureFunctionsFolderName, functionAppName, ManagedDependenciesFolderName); } - - private static bool IsAppService() - { - return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(AzureWebsiteInstanceId)); - } - - private static bool IsLinuxConsumption() - { - return !IsAppService() && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ContainerName)); - } - - public static bool IsLinuxConsumptionOnLegion() - { - return !IsAppService() && - !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ContainerName)) && - !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LegionServiceHost)); - } } } diff --git a/src/DependencyManagement/WorkerEnvironment.cs b/src/DependencyManagement/WorkerEnvironment.cs new file mode 100644 index 00000000..ba6cbe9e --- /dev/null +++ b/src/DependencyManagement/WorkerEnvironment.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement +{ + /// + /// Hold information about the environment. + /// + internal static class WorkerEnvironment + { + // Environment variables to help figure out if we are running in Azure. + private const string AzureWebsiteInstanceId = "WEBSITE_INSTANCE_ID"; + private const string ContainerName = "CONTAINER_NAME"; + private const string LegionServiceHost = "LEGION_SERVICE_HOST"; + + public static bool IsAppService() + { + return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(AzureWebsiteInstanceId)); + } + + public static bool IsLinuxConsumption() + { + return !IsAppService() && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ContainerName)); + } + + public static bool IsLinuxConsumptionOnLegion() + { + return !IsAppService() && + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ContainerName)) && + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LegionServiceHost)); + } + } +} From a1635af0043157aa6890129636518dc010ac43a1 Mon Sep 17 00:00:00 2001 From: Francisco-Gamino Date: Fri, 21 Apr 2023 21:33:49 -0700 Subject: [PATCH 5/8] Error out if we are running on Legion when initializing the DependencyManager --- src/DependencyManagement/DependencyManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/DependencyManagement/DependencyManager.cs b/src/DependencyManagement/DependencyManager.cs index 28f4c59b..2211ace5 100644 --- a/src/DependencyManagement/DependencyManager.cs +++ b/src/DependencyManagement/DependencyManager.cs @@ -99,6 +99,11 @@ internal string Initialize(ILogger logger) // Parse and process the function app dependencies defined in the manifest. _dependenciesFromManifest = _storage.GetDependencies().ToArray(); + if (_dependenciesFromManifest.Length >= 1 && WorkerEnvironment.IsLinuxConsumptionOnLegion()) + { + throw new NotSupportedException(PowerShellWorkerStrings.ManagedDependenciesIsNotSupportedOnLegion); + } + if (!_dependenciesFromManifest.Any()) { logger.Log(isUserOnlyLog: true, LogLevel.Warning, PowerShellWorkerStrings.FunctionAppDoesNotHaveRequiredModulesToInstall); From 178fe04c192c5cf06ed7ee4d4d4456526d770ce6 Mon Sep 17 00:00:00 2001 From: Francisco-Gamino Date: Fri, 21 Apr 2023 21:35:44 -0700 Subject: [PATCH 6/8] Moved Managed Dependencies is not supported on Legion test case to DependencyManagement --- .../DependencyManagerTests.cs | 32 +++++++++++++++++++ .../ManagedDependenciesPathDetectorTests.cs | 31 ------------------ 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/test/Unit/DependencyManagement/DependencyManagerTests.cs b/test/Unit/DependencyManagement/DependencyManagerTests.cs index e6249468..622848da 100644 --- a/test/Unit/DependencyManagement/DependencyManagerTests.cs +++ b/test/Unit/DependencyManagement/DependencyManagerTests.cs @@ -50,6 +50,38 @@ public void Initialize_ReturnsNewSnapshotPath_WhenNoAcceptableDependencyVersions } } + [Fact] + public void Initialize_Throws_WhenDependenciesAreDefinedInRequirementsPsd1_OnLegion() + { + const string ContainerName = "CONTAINER_NAME"; + const string LegionServiceHost = "LEGION_SERVICE_HOST"; + const string AzureWebsiteInstanceId = "WEBSITE_INSTANCE_ID"; + + try + { + Environment.SetEnvironmentVariable(AzureWebsiteInstanceId, null); + Environment.SetEnvironmentVariable(ContainerName, "MY_CONTAINER_NAME"); + Environment.SetEnvironmentVariable(LegionServiceHost, "MY_LEGION_SERVICE_HOST"); + + _mockStorage.Setup(_ => _.GetDependencies()).Returns(GetAnyNonEmptyDependencyManifestEntries()); + + using (var dependencyManager = CreateDependencyManagerWithMocks()) + { + var caughtException = Assert.Throws( + () => dependencyManager.Initialize(_mockLogger.Object)); + + Assert.Contains("Managed Dependencies is not supported in Linux Consumption on Legion.", caughtException.Message); + Assert.Contains("https://aka.ms/functions-powershell-include-modules", caughtException.Message); + } + } + + finally + { + Environment.SetEnvironmentVariable(ContainerName, null); + Environment.SetEnvironmentVariable(LegionServiceHost, null); + } + } + [Fact] public void Initialize_ReturnsExistingSnapshotPath_WhenAcceptableDependencyVersionsAlreadyInstalled() { diff --git a/test/Unit/DependencyManagement/ManagedDependenciesPathDetectorTests.cs b/test/Unit/DependencyManagement/ManagedDependenciesPathDetectorTests.cs index 6afb42a0..2ee13523 100644 --- a/test/Unit/DependencyManagement/ManagedDependenciesPathDetectorTests.cs +++ b/test/Unit/DependencyManagement/ManagedDependenciesPathDetectorTests.cs @@ -62,36 +62,5 @@ public void ValidateManagedDependenciesPath(string name, string value, bool setE } } } - - [Fact] - public void ManagedDependenciesIsNotSupportedOnLegion() - { - const string ContainerName = "CONTAINER_NAME"; - const string LegionServiceHost = "LEGION_SERVICE_HOST"; - const string AzureWebsiteInstanceId = "WEBSITE_INSTANCE_ID"; - - var functionAppRoot = Path.Join(Path.GetTempPath(), "MyFunction"); - - try - { - Environment.SetEnvironmentVariable(AzureWebsiteInstanceId, null); - Environment.SetEnvironmentVariable(ContainerName, "MY_CONTAINER_NAME"); - Environment.SetEnvironmentVariable(LegionServiceHost, "MY_LEGION_SERVICE_HOST"); - - var exception = Assert.Throws(() => ManagedDependenciesPathDetector.GetManagedDependenciesPath(functionAppRoot)); - Assert.Contains("Managed Dependencies is not supported in Linux Consumption on Legion.", exception.Message); - Assert.Contains("https://aka.ms/functions-powershell-include-modules-with-app-content", exception.Message); - } - finally - { - Environment.SetEnvironmentVariable(ContainerName, null); - Environment.SetEnvironmentVariable(LegionServiceHost, null); - - if (Directory.Exists(functionAppRoot)) - { - Directory.Delete(functionAppRoot); - } - } - } } } From 933f1c544fdfefb5a1bec12114a44148d9602e01 Mon Sep 17 00:00:00 2001 From: Francisco-Gamino Date: Mon, 24 Apr 2023 11:07:01 -0700 Subject: [PATCH 7/8] Update DependencyManager initialization logic --- src/DependencyManagement/DependencyManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/DependencyManagement/DependencyManager.cs b/src/DependencyManagement/DependencyManager.cs index 2211ace5..71caf2bd 100644 --- a/src/DependencyManagement/DependencyManager.cs +++ b/src/DependencyManagement/DependencyManager.cs @@ -99,17 +99,17 @@ internal string Initialize(ILogger logger) // Parse and process the function app dependencies defined in the manifest. _dependenciesFromManifest = _storage.GetDependencies().ToArray(); - if (_dependenciesFromManifest.Length >= 1 && WorkerEnvironment.IsLinuxConsumptionOnLegion()) - { - throw new NotSupportedException(PowerShellWorkerStrings.ManagedDependenciesIsNotSupportedOnLegion); - } - if (!_dependenciesFromManifest.Any()) { logger.Log(isUserOnlyLog: true, LogLevel.Warning, PowerShellWorkerStrings.FunctionAppDoesNotHaveRequiredModulesToInstall); return null; } + if (WorkerEnvironment.IsLinuxConsumptionOnLegion()) + { + throw new NotSupportedException(PowerShellWorkerStrings.ManagedDependenciesIsNotSupportedOnLegion); + } + _currentSnapshotPath = _installedDependenciesLocator.GetPathWithAcceptableDependencyVersionsInstalled() ?? _storage.CreateNewSnapshotPath(); From 96cd46f8d2e6305a56f47cde9a7056b3a1e0e0d8 Mon Sep 17 00:00:00 2001 From: Francisco-Gamino Date: Mon, 24 Apr 2023 11:15:29 -0700 Subject: [PATCH 8/8] Add test case to validate that we do not throw on Legion when requirements.psd1 has no entries --- .../DependencyManagerTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/Unit/DependencyManagement/DependencyManagerTests.cs b/test/Unit/DependencyManagement/DependencyManagerTests.cs index 622848da..fbe8a13c 100644 --- a/test/Unit/DependencyManagement/DependencyManagerTests.cs +++ b/test/Unit/DependencyManagement/DependencyManagerTests.cs @@ -82,6 +82,40 @@ public void Initialize_Throws_WhenDependenciesAreDefinedInRequirementsPsd1_OnLeg } } + [Fact] + public void Initialize_NoDependenciesOnRequirementsPsd1_OnLegion_DoesNotThrow() + { + const string ContainerName = "CONTAINER_NAME"; + const string LegionServiceHost = "LEGION_SERVICE_HOST"; + const string AzureWebsiteInstanceId = "WEBSITE_INSTANCE_ID"; + + try + { + Environment.SetEnvironmentVariable(AzureWebsiteInstanceId, null); + Environment.SetEnvironmentVariable(ContainerName, "MY_CONTAINER_NAME"); + Environment.SetEnvironmentVariable(LegionServiceHost, "MY_LEGION_SERVICE_HOST"); + + _mockStorage.Setup(_ => _.GetDependencies()).Returns(new DependencyManifestEntry[0]); + + using (var dependencyManager = CreateDependencyManagerWithMocks()) + { + var dependenciesPath = dependencyManager.Initialize(_mockLogger.Object); + + Assert.Null(dependenciesPath); + VerifyMessageLogged(LogLevel.Warning, PowerShellWorkerStrings.FunctionAppDoesNotHaveRequiredModulesToInstall, expectedIsUserLog: true); + + _mockBackgroundDependencySnapshotMaintainer.VerifyNoOtherCalls(); + _mockNewerDependencySnapshotDetector.VerifyNoOtherCalls(); + } + } + + finally + { + Environment.SetEnvironmentVariable(ContainerName, null); + Environment.SetEnvironmentVariable(LegionServiceHost, null); + } + } + [Fact] public void Initialize_ReturnsExistingSnapshotPath_WhenAcceptableDependencyVersionsAlreadyInstalled() {