Skip to content

Commit

Permalink
Shut off MD module upgrades after 7.4 EOL (#1084)
Browse files Browse the repository at this point in the history
* Shut off MD module upgrades after 7.4 EOL
* Add instructions for new PowerShell versions
  • Loading branch information
andystaples authored Sep 25, 2024
1 parent 22f00b5 commit acb5e60
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 12 deletions.
6 changes: 6 additions & 0 deletions create_new_worker_instructions.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ internal class BackgroundDependencySnapshotMaintainer : IBackgroundDependencySna
private TimeSpan MaxBackgroundUpgradePeriod { get; } =
PowerShellWorkerConfiguration.GetTimeSpan("MDMaxBackgroundUpgradePeriod") ?? TimeSpan.FromDays(7);

private Func<bool> _getShouldPerformManagedDependencyUpgrades;

private readonly IDependencyManagerStorage _storage;
private readonly IDependencySnapshotInstaller _installer;
private readonly IDependencySnapshotPurger _purger;
Expand All @@ -29,11 +31,14 @@ internal class BackgroundDependencySnapshotMaintainer : IBackgroundDependencySna
public BackgroundDependencySnapshotMaintainer(
IDependencyManagerStorage storage,
IDependencySnapshotInstaller installer,
IDependencySnapshotPurger purger)
IDependencySnapshotPurger purger,
Func<bool> 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)
Expand All @@ -56,6 +61,23 @@ public string InstallAndPurgeSnapshots(Func<PowerShell> 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);

Expand Down
18 changes: 17 additions & 1 deletion src/DependencyManagement/DependencyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ internal class DependencyManager : IDisposable

private Task _dependencyInstallationTask;

private bool EnableAutomaticUpgrades { get; } =
PowerShellWorkerConfiguration.GetBoolean("MDEnableAutomaticUpgrades") ?? false;

#endregion

public DependencyManager(
Expand All @@ -67,7 +70,8 @@ public DependencyManager(
maintainer ?? new BackgroundDependencySnapshotMaintainer(
_storage,
_installer,
new DependencySnapshotPurger(_storage));
new DependencySnapshotPurger(_storage),
ShouldEnableManagedDpendencyUpgrades);
_currentSnapshotContentLogger =
currentSnapshotContentLogger ?? new BackgroundDependencySnapshotContentLogger(snapshotContentLogger);
}
Expand Down Expand Up @@ -126,6 +130,18 @@ internal string Initialize(ILogger logger)
}
}

/// <summary>
/// Determines whether the function app should enable automatic upgrades for managed dependencies
/// </summary>
/// <returns>
/// 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
/// </returns>
private bool ShouldEnableManagedDpendencyUpgrades()
{
return !WorkerEnvironment.IsPowerShellSDKDeprecated() || EnableAutomaticUpgrades;
}

/// <summary>
/// Start dependency installation if needed.
/// firstPowerShell is the first PowerShell instance created in this process (which this is important for local debugging),
Expand Down
7 changes: 7 additions & 0 deletions src/DependencyManagement/WorkerEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(2026, 11, 10);

public static bool IsAppService()
{
return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(AzureWebsiteInstanceId));
Expand All @@ -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;
}
}
}
9 changes: 6 additions & 3 deletions src/resources/PowerShellWorkerStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@
<data name="DependencySnapshotDoesNotContainAcceptableModuleVersions" xml:space="preserve">
<value>Dependency snapshot '{0}' does not contain acceptable module versions.</value>
</data>
<data name="WorkerInitCompleted" xml:space="preserve">
<value>Worker init request completed in {0} ms.</value>
</data>
<data name="FoundExternalDurableSdkInSession" xml:space="preserve">
<value>Found external Durable Functions SDK in session: Name='{0}', Version='{1}', Path='{2}'.</value>
</data>
Expand All @@ -361,9 +364,6 @@
<data name="UnableToInitializeOrchestrator" xml:space="preserve">
<value>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</value>
</data>
<data name="WorkerInitCompleted" xml:space="preserve">
<value>Worker init request completed in {0} ms.</value>
</data>
<data name="UnexpectedResultCount" xml:space="preserve">
<value>Operation '{0}' expected '{1}' result(s) but received '{2}'.</value>
</data>
Expand All @@ -388,4 +388,7 @@
<data name="InvalidOpenTelemetryContext" xml:space="preserve">
<value>The app is configured to use OpenTelemetry but the TraceContext passed from host was null. </value>
</data>
<data name="AutomaticUpgradesAreDisabled" xml:space="preserve">
<value>Automatic upgrades are disabled in PowerShell 7.4 function apps. This warning should not be emitted until PowerShell 7.4's End of Life date, at which time, more guidance will be available regarding how to upgrade your function app to the latest version. </value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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);
}
Expand All @@ -56,7 +56,7 @@ public void InstallsSnapshotIfNoRecentlyInstalledSnapshotFound()
It.IsAny<ILogger>()));

using (var dummyPowerShell = PowerShell.Create())
using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod))
using (var maintainer = CreateMaintainerWithMocks(true, _minBackgroundUpgradePeriod))
{
maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object);

Expand All @@ -73,14 +73,49 @@ 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<DependencyManifestEntry[]>(),
It.IsAny<string>(),
It.IsAny<PowerShell>(),
It.IsAny<DependencySnapshotInstallationMode>(),
It.IsAny<ILogger>()));

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()
{
_mockStorage.Setup(_ => _.GetInstalledAndInstallingSnapshots()).Returns(new[] { "older snapshot" });
_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);

Expand Down Expand Up @@ -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);

Expand All @@ -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)
{
Expand Down

0 comments on commit acb5e60

Please sign in to comment.