diff --git a/create_new_worker_instructions.md b/create_new_worker_instructions.md new file mode 100644 index 00000000..b3824a12 --- /dev/null +++ b/create_new_worker_instructions.md @@ -0,0 +1,6 @@ +## Instructions for Upgrading the PowerShell Language Worker to a New PowerShell SDK Minor Version (7.6+) +Once a new PowerShell SDK version is released on [GiHub](https://github.com/PowerShell/PowerShell/releases), follow these steps to upgrade the PowerShell SDK reference used by the language worker: + +- Update the solution targets as needed for whatever .NET version is targeted by the new PowerShell +- Follow instructions in upgrade_ps_sdk_instructions.md to update loosely linked dependencies in project files +- Update the Managed Dependency shutoff date in src/DependencyManagement/WorkerEnvironment.cs diff --git a/src/DependencyManagement/BackgroundDependencySnapshotMaintainer.cs b/src/DependencyManagement/BackgroundDependencySnapshotMaintainer.cs index deba2e90..642ac624 100644 --- a/src/DependencyManagement/BackgroundDependencySnapshotMaintainer.cs +++ b/src/DependencyManagement/BackgroundDependencySnapshotMaintainer.cs @@ -20,6 +20,8 @@ internal class BackgroundDependencySnapshotMaintainer : IBackgroundDependencySna private TimeSpan MaxBackgroundUpgradePeriod { get; } = PowerShellWorkerConfiguration.GetTimeSpan("MDMaxBackgroundUpgradePeriod") ?? TimeSpan.FromDays(7); + private Func _getShouldPerformManagedDependencyUpgrades; + private readonly IDependencyManagerStorage _storage; private readonly IDependencySnapshotInstaller _installer; private readonly IDependencySnapshotPurger _purger; @@ -29,11 +31,14 @@ internal class BackgroundDependencySnapshotMaintainer : IBackgroundDependencySna public BackgroundDependencySnapshotMaintainer( IDependencyManagerStorage storage, IDependencySnapshotInstaller installer, - IDependencySnapshotPurger purger) + IDependencySnapshotPurger purger, + Func getShouldPerformManagedDependencyUpgrades) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _installer = installer ?? throw new ArgumentNullException(nameof(installer)); _purger = purger ?? throw new ArgumentNullException(nameof(purger)); + _getShouldPerformManagedDependencyUpgrades = getShouldPerformManagedDependencyUpgrades; + } public void Start(string currentSnapshotPath, DependencyManifestEntry[] dependencyManifest, ILogger logger) @@ -56,6 +61,23 @@ public string InstallAndPurgeSnapshots(Func pwshFactory, ILogger log { try { + if (!_getShouldPerformManagedDependencyUpgrades()) + { + logger.Log( + isUserOnlyLog: false, + RpcLog.Types.Level.Warning, + PowerShellWorkerStrings.AutomaticUpgradesAreDisabled); + + // Shutdown the timer that calls this method after the EOL date + if (_installAndPurgeTimer is not null) + { + _installAndPurgeTimer.Dispose(); + _installAndPurgeTimer = null; + } + + return null; + } + // Purge before installing a new snapshot, as we may be able to free some space. _purger.Purge(logger); diff --git a/src/DependencyManagement/DependencyManager.cs b/src/DependencyManagement/DependencyManager.cs index 71caf2bd..a2bbe57f 100644 --- a/src/DependencyManagement/DependencyManager.cs +++ b/src/DependencyManagement/DependencyManager.cs @@ -41,6 +41,9 @@ internal class DependencyManager : IDisposable private Task _dependencyInstallationTask; + private bool EnableAutomaticUpgrades { get; } = + PowerShellWorkerConfiguration.GetBoolean("MDEnableAutomaticUpgrades") ?? false; + #endregion public DependencyManager( @@ -67,7 +70,8 @@ public DependencyManager( maintainer ?? new BackgroundDependencySnapshotMaintainer( _storage, _installer, - new DependencySnapshotPurger(_storage)); + new DependencySnapshotPurger(_storage), + ShouldEnableManagedDpendencyUpgrades); _currentSnapshotContentLogger = currentSnapshotContentLogger ?? new BackgroundDependencySnapshotContentLogger(snapshotContentLogger); } @@ -126,6 +130,18 @@ internal string Initialize(ILogger logger) } } + /// + /// Determines whether the function app should enable automatic upgrades for managed dependencies + /// + /// + /// True if Managed Dependencies should be upgraded (SDK is not past it's deprecation date OR user has configured this behavior via MDEnableAutomaticUpgrades env var + /// False if Managed Dependencies should not be upgraded + /// + private bool ShouldEnableManagedDpendencyUpgrades() + { + return !WorkerEnvironment.IsPowerShellSDKDeprecated() || EnableAutomaticUpgrades; + } + /// /// Start dependency installation if needed. /// firstPowerShell is the first PowerShell instance created in this process (which this is important for local debugging), diff --git a/src/DependencyManagement/WorkerEnvironment.cs b/src/DependencyManagement/WorkerEnvironment.cs index ba6cbe9e..fedeaaed 100644 --- a/src/DependencyManagement/WorkerEnvironment.cs +++ b/src/DependencyManagement/WorkerEnvironment.cs @@ -16,6 +16,8 @@ internal static class WorkerEnvironment private const string ContainerName = "CONTAINER_NAME"; private const string LegionServiceHost = "LEGION_SERVICE_HOST"; + private static readonly DateTime PowerShellSDKDeprecationDate = new DateTime(2024, 11, 8); + public static bool IsAppService() { return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(AzureWebsiteInstanceId)); @@ -32,5 +34,10 @@ public static bool IsLinuxConsumptionOnLegion() !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ContainerName)) && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LegionServiceHost)); } + + public static bool IsPowerShellSDKDeprecated() + { + return DateTime.Now > PowerShellSDKDeprecationDate; + } } } diff --git a/src/resources/PowerShellWorkerStrings.resx b/src/resources/PowerShellWorkerStrings.resx index 53f0c801..3c83bbab 100644 --- a/src/resources/PowerShellWorkerStrings.resx +++ b/src/resources/PowerShellWorkerStrings.resx @@ -352,6 +352,9 @@ Dependency snapshot '{0}' does not contain acceptable module versions. + + Worker init request completed in {0} ms. + Found external Durable Functions SDK in session: Name='{0}', Version='{1}', Path='{2}'. @@ -361,9 +364,6 @@ Unable to initialize orchestrator function due to presence of other bindings. Total number of bindings found is '{0}'. Orchestrator Functions should never use any input or output bindings other than the orchestration trigger itself. See: aka.ms/df-bindings - - Worker init request completed in {0} ms. - Operation '{0}' expected '{1}' result(s) but received '{2}'. @@ -388,4 +388,7 @@ The app is configured to use OpenTelemetry but the TraceContext passed from host was null. + + Automatic upgrades are disabled in PowerShell 7.2 function apps. This warning should not be emitted until PowerShell 7.2's End of Life date. For more information, please see https://azure.microsoft.com/en-us/updates/v2/powershell72-azure-functions-retirement + \ No newline at end of file diff --git a/test/Unit/DependencyManagement/BackgroundDependencySnapshotMaintainerTests.cs b/test/Unit/DependencyManagement/BackgroundDependencySnapshotMaintainerTests.cs index aac937d9..b4a2619f 100644 --- a/test/Unit/DependencyManagement/BackgroundDependencySnapshotMaintainerTests.cs +++ b/test/Unit/DependencyManagement/BackgroundDependencySnapshotMaintainerTests.cs @@ -13,8 +13,8 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Test.DependencyManagement using Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement; using Microsoft.Azure.Functions.PowerShellWorker.Utility; - using LogLevel = Microsoft.Azure.WebJobs.Script.Grpc.Messages.RpcLog.Types.Level; + using Microsoft.Azure.WebJobs.Script.Grpc.Messages; public class BackgroundDependencySnapshotMaintainerTests { @@ -30,7 +30,7 @@ public class BackgroundDependencySnapshotMaintainerTests [Fact] public void SetsCurrentlyUsedSnapshotOnPurger() { - using (var maintainer = CreateMaintainerWithMocks()) + using (var maintainer = CreateMaintainerWithMocks(true)) { maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object); } @@ -56,7 +56,7 @@ public void InstallsSnapshotIfNoRecentlyInstalledSnapshotFound() It.IsAny())); using (var dummyPowerShell = PowerShell.Create()) - using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod)) + using (var maintainer = CreateMaintainerWithMocks(true, _minBackgroundUpgradePeriod)) { maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object); @@ -73,6 +73,41 @@ public void InstallsSnapshotIfNoRecentlyInstalledSnapshotFound() } } + [Fact] + public void DoesNothingIfManagedDependenciesUpgradesAreDisabled() + { + _mockStorage.Setup(_ => _.GetInstalledAndInstallingSnapshots()).Returns(new[] { "older snapshot" }); + _mockStorage.Setup(_ => _.GetSnapshotCreationTimeUtc("older snapshot")) + .Returns(DateTime.UtcNow - _minBackgroundUpgradePeriod - TimeSpan.FromSeconds(1)); + + _mockStorage.Setup(_ => _.CreateNewSnapshotPath()).Returns("new snapshot path"); + + _mockInstaller.Setup( + _ => _.InstallSnapshot( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())); + + using (var dummyPowerShell = PowerShell.Create()) + using (var maintainer = CreateMaintainerWithMocks(false, _minBackgroundUpgradePeriod)) + { + maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object); + + // ReSharper disable once AccessToDisposedClosure + var installedSnapshotPath = maintainer.InstallAndPurgeSnapshots(() => dummyPowerShell, _mockLogger.Object); + Assert.Equal(null, installedSnapshotPath); + + // ReSharper disable once AccessToDisposedClosure + _mockInstaller.Verify( + _ => _.InstallSnapshot(_dependencyManifest, "new snapshot path", dummyPowerShell, DependencySnapshotInstallationMode.Optional, _mockLogger.Object), + Times.Never); + + _mockLogger.Verify(_ => _.Log(false, LogLevel.Warning, PowerShellWorkerStrings.AutomaticUpgradesAreDisabled, null), Times.Once); + } + } + [Fact] public void DoesNotInstallSnapshotIfRecentlyInstalledSnapshotFound() { @@ -80,7 +115,7 @@ public void DoesNotInstallSnapshotIfRecentlyInstalledSnapshotFound() _mockStorage.Setup(_ => _.GetSnapshotCreationTimeUtc("older snapshot")) .Returns(DateTime.UtcNow - _minBackgroundUpgradePeriod + TimeSpan.FromSeconds(1)); - using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod)) + using (var maintainer = CreateMaintainerWithMocks(true, _minBackgroundUpgradePeriod)) { maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object); @@ -112,7 +147,7 @@ public void LogsWarningIfCannotInstallSnapshot() .Throws(injectedException); using (var dummyPowerShell = PowerShell.Create()) - using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod)) + using (var maintainer = CreateMaintainerWithMocks(true, _minBackgroundUpgradePeriod)) { maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object); @@ -129,12 +164,13 @@ public void LogsWarningIfCannotInstallSnapshot() Times.Once); } - private BackgroundDependencySnapshotMaintainer CreateMaintainerWithMocks(TimeSpan? minBackgroundUpgradePeriod = null) + private BackgroundDependencySnapshotMaintainer CreateMaintainerWithMocks(bool shouldPerformManagedDependenciesUpgrades, TimeSpan? minBackgroundUpgradePeriod = null) { var maintainer = new BackgroundDependencySnapshotMaintainer( _mockStorage.Object, _mockInstaller.Object, - _mockPurger.Object); + _mockPurger.Object, + () => { return shouldPerformManagedDependenciesUpgrades; }); if (minBackgroundUpgradePeriod != null) {