Skip to content

Commit acb5e60

Browse files
authored
Shut off MD module upgrades after 7.4 EOL (#1084)
* Shut off MD module upgrades after 7.4 EOL * Add instructions for new PowerShell versions
1 parent 22f00b5 commit acb5e60

File tree

6 files changed

+102
-12
lines changed

6 files changed

+102
-12
lines changed

create_new_worker_instructions.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
## Instructions for Upgrading the PowerShell Language Worker to a New PowerShell SDK Minor Version (7.6+)
2+
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:
3+
4+
- Update the solution targets as needed for whatever .NET version is targeted by the new PowerShell
5+
- Follow instructions in upgrade_ps_sdk_instructions.md to update loosely linked dependencies in project files
6+
- Update the Managed Dependency shutoff date in src/DependencyManagement/WorkerEnvironment.cs

src/DependencyManagement/BackgroundDependencySnapshotMaintainer.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ internal class BackgroundDependencySnapshotMaintainer : IBackgroundDependencySna
2020
private TimeSpan MaxBackgroundUpgradePeriod { get; } =
2121
PowerShellWorkerConfiguration.GetTimeSpan("MDMaxBackgroundUpgradePeriod") ?? TimeSpan.FromDays(7);
2222

23+
private Func<bool> _getShouldPerformManagedDependencyUpgrades;
24+
2325
private readonly IDependencyManagerStorage _storage;
2426
private readonly IDependencySnapshotInstaller _installer;
2527
private readonly IDependencySnapshotPurger _purger;
@@ -29,11 +31,14 @@ internal class BackgroundDependencySnapshotMaintainer : IBackgroundDependencySna
2931
public BackgroundDependencySnapshotMaintainer(
3032
IDependencyManagerStorage storage,
3133
IDependencySnapshotInstaller installer,
32-
IDependencySnapshotPurger purger)
34+
IDependencySnapshotPurger purger,
35+
Func<bool> getShouldPerformManagedDependencyUpgrades)
3336
{
3437
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
3538
_installer = installer ?? throw new ArgumentNullException(nameof(installer));
3639
_purger = purger ?? throw new ArgumentNullException(nameof(purger));
40+
_getShouldPerformManagedDependencyUpgrades = getShouldPerformManagedDependencyUpgrades;
41+
3742
}
3843

3944
public void Start(string currentSnapshotPath, DependencyManifestEntry[] dependencyManifest, ILogger logger)
@@ -56,6 +61,23 @@ public string InstallAndPurgeSnapshots(Func<PowerShell> pwshFactory, ILogger log
5661
{
5762
try
5863
{
64+
if (!_getShouldPerformManagedDependencyUpgrades())
65+
{
66+
logger.Log(
67+
isUserOnlyLog: false,
68+
RpcLog.Types.Level.Warning,
69+
PowerShellWorkerStrings.AutomaticUpgradesAreDisabled);
70+
71+
// Shutdown the timer that calls this method after the EOL date
72+
if (_installAndPurgeTimer is not null)
73+
{
74+
_installAndPurgeTimer.Dispose();
75+
_installAndPurgeTimer = null;
76+
}
77+
78+
return null;
79+
}
80+
5981
// Purge before installing a new snapshot, as we may be able to free some space.
6082
_purger.Purge(logger);
6183

src/DependencyManagement/DependencyManager.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ internal class DependencyManager : IDisposable
4141

4242
private Task _dependencyInstallationTask;
4343

44+
private bool EnableAutomaticUpgrades { get; } =
45+
PowerShellWorkerConfiguration.GetBoolean("MDEnableAutomaticUpgrades") ?? false;
46+
4447
#endregion
4548

4649
public DependencyManager(
@@ -67,7 +70,8 @@ public DependencyManager(
6770
maintainer ?? new BackgroundDependencySnapshotMaintainer(
6871
_storage,
6972
_installer,
70-
new DependencySnapshotPurger(_storage));
73+
new DependencySnapshotPurger(_storage),
74+
ShouldEnableManagedDpendencyUpgrades);
7175
_currentSnapshotContentLogger =
7276
currentSnapshotContentLogger ?? new BackgroundDependencySnapshotContentLogger(snapshotContentLogger);
7377
}
@@ -126,6 +130,18 @@ internal string Initialize(ILogger logger)
126130
}
127131
}
128132

133+
/// <summary>
134+
/// Determines whether the function app should enable automatic upgrades for managed dependencies
135+
/// </summary>
136+
/// <returns>
137+
/// 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
138+
/// False if Managed Dependencies should not be upgraded
139+
/// </returns>
140+
private bool ShouldEnableManagedDpendencyUpgrades()
141+
{
142+
return !WorkerEnvironment.IsPowerShellSDKDeprecated() || EnableAutomaticUpgrades;
143+
}
144+
129145
/// <summary>
130146
/// Start dependency installation if needed.
131147
/// firstPowerShell is the first PowerShell instance created in this process (which this is important for local debugging),

src/DependencyManagement/WorkerEnvironment.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ internal static class WorkerEnvironment
1616
private const string ContainerName = "CONTAINER_NAME";
1717
private const string LegionServiceHost = "LEGION_SERVICE_HOST";
1818

19+
private static readonly DateTime PowerShellSDKDeprecationDate = new DateTime(2026, 11, 10);
20+
1921
public static bool IsAppService()
2022
{
2123
return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(AzureWebsiteInstanceId));
@@ -32,5 +34,10 @@ public static bool IsLinuxConsumptionOnLegion()
3234
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ContainerName)) &&
3335
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LegionServiceHost));
3436
}
37+
38+
public static bool IsPowerShellSDKDeprecated()
39+
{
40+
return DateTime.Now > PowerShellSDKDeprecationDate;
41+
}
3542
}
3643
}

src/resources/PowerShellWorkerStrings.resx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,9 @@
352352
<data name="DependencySnapshotDoesNotContainAcceptableModuleVersions" xml:space="preserve">
353353
<value>Dependency snapshot '{0}' does not contain acceptable module versions.</value>
354354
</data>
355+
<data name="WorkerInitCompleted" xml:space="preserve">
356+
<value>Worker init request completed in {0} ms.</value>
357+
</data>
355358
<data name="FoundExternalDurableSdkInSession" xml:space="preserve">
356359
<value>Found external Durable Functions SDK in session: Name='{0}', Version='{1}', Path='{2}'.</value>
357360
</data>
@@ -361,9 +364,6 @@
361364
<data name="UnableToInitializeOrchestrator" xml:space="preserve">
362365
<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>
363366
</data>
364-
<data name="WorkerInitCompleted" xml:space="preserve">
365-
<value>Worker init request completed in {0} ms.</value>
366-
</data>
367367
<data name="UnexpectedResultCount" xml:space="preserve">
368368
<value>Operation '{0}' expected '{1}' result(s) but received '{2}'.</value>
369369
</data>
@@ -388,4 +388,7 @@
388388
<data name="InvalidOpenTelemetryContext" xml:space="preserve">
389389
<value>The app is configured to use OpenTelemetry but the TraceContext passed from host was null. </value>
390390
</data>
391+
<data name="AutomaticUpgradesAreDisabled" xml:space="preserve">
392+
<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>
393+
</data>
391394
</root>

test/Unit/DependencyManagement/BackgroundDependencySnapshotMaintainerTests.cs

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Test.DependencyManagement
1313

1414
using Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement;
1515
using Microsoft.Azure.Functions.PowerShellWorker.Utility;
16-
1716
using LogLevel = Microsoft.Azure.WebJobs.Script.Grpc.Messages.RpcLog.Types.Level;
17+
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
1818

1919
public class BackgroundDependencySnapshotMaintainerTests
2020
{
@@ -30,7 +30,7 @@ public class BackgroundDependencySnapshotMaintainerTests
3030
[Fact]
3131
public void SetsCurrentlyUsedSnapshotOnPurger()
3232
{
33-
using (var maintainer = CreateMaintainerWithMocks())
33+
using (var maintainer = CreateMaintainerWithMocks(true))
3434
{
3535
maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object);
3636
}
@@ -56,7 +56,7 @@ public void InstallsSnapshotIfNoRecentlyInstalledSnapshotFound()
5656
It.IsAny<ILogger>()));
5757

5858
using (var dummyPowerShell = PowerShell.Create())
59-
using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod))
59+
using (var maintainer = CreateMaintainerWithMocks(true, _minBackgroundUpgradePeriod))
6060
{
6161
maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object);
6262

@@ -73,14 +73,49 @@ public void InstallsSnapshotIfNoRecentlyInstalledSnapshotFound()
7373
}
7474
}
7575

76+
[Fact]
77+
public void DoesNothingIfManagedDependenciesUpgradesAreDisabled()
78+
{
79+
_mockStorage.Setup(_ => _.GetInstalledAndInstallingSnapshots()).Returns(new[] { "older snapshot" });
80+
_mockStorage.Setup(_ => _.GetSnapshotCreationTimeUtc("older snapshot"))
81+
.Returns(DateTime.UtcNow - _minBackgroundUpgradePeriod - TimeSpan.FromSeconds(1));
82+
83+
_mockStorage.Setup(_ => _.CreateNewSnapshotPath()).Returns("new snapshot path");
84+
85+
_mockInstaller.Setup(
86+
_ => _.InstallSnapshot(
87+
It.IsAny<DependencyManifestEntry[]>(),
88+
It.IsAny<string>(),
89+
It.IsAny<PowerShell>(),
90+
It.IsAny<DependencySnapshotInstallationMode>(),
91+
It.IsAny<ILogger>()));
92+
93+
using (var dummyPowerShell = PowerShell.Create())
94+
using (var maintainer = CreateMaintainerWithMocks(false, _minBackgroundUpgradePeriod))
95+
{
96+
maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object);
97+
98+
// ReSharper disable once AccessToDisposedClosure
99+
var installedSnapshotPath = maintainer.InstallAndPurgeSnapshots(() => dummyPowerShell, _mockLogger.Object);
100+
Assert.Equal(null, installedSnapshotPath);
101+
102+
// ReSharper disable once AccessToDisposedClosure
103+
_mockInstaller.Verify(
104+
_ => _.InstallSnapshot(_dependencyManifest, "new snapshot path", dummyPowerShell, DependencySnapshotInstallationMode.Optional, _mockLogger.Object),
105+
Times.Never);
106+
107+
_mockLogger.Verify(_ => _.Log(false, LogLevel.Warning, PowerShellWorkerStrings.AutomaticUpgradesAreDisabled, null), Times.Once);
108+
}
109+
}
110+
76111
[Fact]
77112
public void DoesNotInstallSnapshotIfRecentlyInstalledSnapshotFound()
78113
{
79114
_mockStorage.Setup(_ => _.GetInstalledAndInstallingSnapshots()).Returns(new[] { "older snapshot" });
80115
_mockStorage.Setup(_ => _.GetSnapshotCreationTimeUtc("older snapshot"))
81116
.Returns(DateTime.UtcNow - _minBackgroundUpgradePeriod + TimeSpan.FromSeconds(1));
82117

83-
using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod))
118+
using (var maintainer = CreateMaintainerWithMocks(true, _minBackgroundUpgradePeriod))
84119
{
85120
maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object);
86121

@@ -112,7 +147,7 @@ public void LogsWarningIfCannotInstallSnapshot()
112147
.Throws(injectedException);
113148

114149
using (var dummyPowerShell = PowerShell.Create())
115-
using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod))
150+
using (var maintainer = CreateMaintainerWithMocks(true, _minBackgroundUpgradePeriod))
116151
{
117152
maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object);
118153

@@ -129,12 +164,13 @@ public void LogsWarningIfCannotInstallSnapshot()
129164
Times.Once);
130165
}
131166

132-
private BackgroundDependencySnapshotMaintainer CreateMaintainerWithMocks(TimeSpan? minBackgroundUpgradePeriod = null)
167+
private BackgroundDependencySnapshotMaintainer CreateMaintainerWithMocks(bool shouldPerformManagedDependenciesUpgrades, TimeSpan? minBackgroundUpgradePeriod = null)
133168
{
134169
var maintainer = new BackgroundDependencySnapshotMaintainer(
135170
_mockStorage.Object,
136171
_mockInstaller.Object,
137-
_mockPurger.Object);
172+
_mockPurger.Object,
173+
() => { return shouldPerformManagedDependenciesUpgrades; });
138174

139175
if (minBackgroundUpgradePeriod != null)
140176
{

0 commit comments

Comments
 (0)