Skip to content

Commit 889f65f

Browse files
committed
replace importing external DF SDK for simply validating it is in session
1 parent 6f7fa8a commit 889f65f

File tree

6 files changed

+97
-79
lines changed

6 files changed

+97
-79
lines changed

src/DurableSDK/IPowerShellServices.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ internal interface IPowerShellServices
1515

1616
bool HasExternalDurableSDK();
1717

18-
void tryEnablingExternalDurableSDK();
18+
bool isExternalDurableSdkLoaded();
19+
20+
void EnableExternalDurableSDK();
1921

2022
void SetDurableClient(object durableClient);
2123

src/DurableSDK/PowerShellServices.cs

Lines changed: 33 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -25,93 +25,66 @@ internal class PowerShellServices : IPowerShellServices
2525
private readonly ILogger _logger;
2626

2727
private const string _setFunctionInvocationContextCommandTemplate = "{0}\\Set-FunctionInvocationContext";
28-
private const string _internalDurableSdkName = "Microsoft.Azure.Functions.PowerShellWorker";
29-
private const string _externalDurableSdkName = "AzureFunctions.PowerShell.Durable.SDK";
3028

3129
// uses built-in SDK by default
3230
private string SetFunctionInvocationContextCommand = string.Format(
3331
_setFunctionInvocationContextCommandTemplate,
34-
_internalDurableSdkName);
32+
Utils.InternalDurableSdkName);
3533

3634
public PowerShellServices(PowerShell pwsh, ILogger logger)
3735
{
3836
_pwsh = pwsh;
3937
_logger = logger;
4038

4139
// Configure FunctionInvocationContext command, based on the select DF SDK
42-
var prefix = _internalDurableSdkName;
40+
var prefix = Utils.InternalDurableSdkName;
4341
SetFunctionInvocationContextCommand = string.Format(_setFunctionInvocationContextCommandTemplate, prefix);
4442
}
4543

46-
private bool tryImportingDurableSDK()
44+
public bool isExternalDurableSdkLoaded()
4745
{
48-
// Try to load/import the external Durable Functions SDK. If an error occurs, it is logged.
49-
var importSucceeded = false;
50-
try
51-
{
52-
// attempt to import SDK
53-
_logger.Log(isUserOnlyLog: false, LogLevel.Trace, String.Format(
54-
PowerShellWorkerStrings.LoadingDurableSDK, _externalDurableSdkName));
55-
56-
var results = _pwsh.AddCommand(Utils.ImportModuleCmdletInfo)
57-
.AddParameter("FullyQualifiedName", _externalDurableSdkName)
58-
.AddParameter("ErrorAction", ActionPreference.Stop)
59-
.AddParameter("PassThru")
60-
.InvokeAndClearCommands<PSModuleInfo>();
61-
62-
// Given how the command above is constructed, only 1 result should be possible
63-
var moduleInfo = results[0];
64-
_logger.Log(isUserOnlyLog: false, LogLevel.Trace, String.Format(
65-
PowerShellWorkerStrings.ImportSucceeded, moduleInfo.Name, moduleInfo.Version));
66-
67-
importSucceeded = true;
68-
}
69-
catch (Exception e)
70-
{
71-
// If an error ocurred, we try to log the exception.
72-
var errorMessage = e.ToString();
46+
// Search for the external DF SDK in the current session
47+
var matchingModules = _pwsh.AddCommand(Utils.GetModuleCmdletInfo)
48+
.AddParameter("FullyQualifiedName", Utils.ExternalDurableSdkName)
49+
.InvokeAndClearCommands<PSModuleInfo>();
7350

74-
// If a PowerShell error record is available through Get-Error, we log that instead.
75-
if (e.InnerException is IContainsErrorRecord inner)
51+
// If we get at least one result, we know the external SDK was imported
52+
var numCandidates = matchingModules.Count();
53+
var isModuleInCurrentSession = numCandidates > 0;
54+
55+
if (isModuleInCurrentSession)
56+
{
57+
var candidatesInfo = matchingModules.Select(module => string.Format(
58+
PowerShellWorkerStrings.FoundExternalDurableSdkInSession, module.Name, module.Version, module.Path));
59+
var externalSDKModuleInfo = string.Join('\n', candidatesInfo);
60+
61+
if (numCandidates > 1)
62+
{
63+
// If there's more than 1 result, there may be runtime conflicts
64+
// warn user of potential conflicts
65+
_logger.Log(isUserOnlyLog: false, LogLevel.Warning, String.Format(
66+
PowerShellWorkerStrings.MultipleExternalSDKsInSession,
67+
numCandidates, Utils.ExternalDurableSdkName, externalSDKModuleInfo));
68+
}
69+
else
7670
{
77-
errorMessage = _errorRecordFormatter.Format(inner.ErrorRecord);
71+
// a single external SDK is in session. Report its metadata
72+
_logger.Log(isUserOnlyLog: false, LogLevel.Trace, externalSDKModuleInfo);
7873
}
79-
_logger.Log(isUserOnlyLog: false, LogLevel.Error, string.Format(
80-
PowerShellWorkerStrings.ErrorImportingDurableSDK,
81-
_externalDurableSdkName, errorMessage));
8274

8375
}
84-
return importSucceeded;
76+
77+
return isModuleInCurrentSession;
8578
}
8679

87-
public void tryEnablingExternalDurableSDK()
80+
public void EnableExternalDurableSDK()
8881
{
89-
// Search for the external DF SDK in the available modules
90-
var matchingModules = _pwsh.AddCommand(Utils.GetModuleCmdletInfo)
91-
.AddParameter("ListAvailable")
92-
.AddParameter("FullyQualifiedName", _externalDurableSdkName)
93-
.InvokeAndClearCommands<PSModuleInfo>();
94-
95-
// If we get at least one result, we attempt to load it
96-
var numCandidates = matchingModules.Count();
97-
if (numCandidates > 0)
98-
{
99-
// try to import the external DF SDK
100-
_usesExternalDurableSDK = tryImportingDurableSDK();
101-
}
102-
else
103-
{
104-
// Log that the module was not found in worker path
105-
var workerPathContents = PowerShellWorkerConfiguration.GetString("PSModulePath");
106-
_logger.Log(isUserOnlyLog: false, LogLevel.Trace, string.Format(
107-
PowerShellWorkerStrings.DurableNotInWorkerPath, _externalDurableSdkName,
108-
workerPathContents));
109-
}
82+
_usesExternalDurableSDK = true;
11083

11184
// assign SetFunctionInvocationContextCommand to the corresponding external SDK's CmdLet
11285
SetFunctionInvocationContextCommand = string.Format(
11386
_setFunctionInvocationContextCommandTemplate,
114-
_externalDurableSdkName);
87+
Utils.ExternalDurableSdkName);
11588
}
11689

11790
public bool HasExternalDurableSDK()

src/DurableWorker/DurableController.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
1111
using System.Linq;
1212
using System.Management.Automation;
1313

14-
using Newtonsoft.Json;
1514
using WebJobs.Script.Grpc.Messages;
1615

1716
using PowerShellWorker.Utility;
1817
using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker;
1918
using System;
19+
using LogLevel = WebJobs.Script.Grpc.Messages.RpcLog.Types.Level;
20+
2021

2122
/// <summary>
2223
/// The main entry point for durable functions support.
@@ -27,8 +28,9 @@ internal class DurableController
2728
private readonly IPowerShellServices _powerShellServices;
2829
private readonly IOrchestrationInvoker _orchestrationInvoker;
2930
private OrchestrationBindingInfo _orchestrationBindingInfo;
31+
private readonly ILogger _logger;
3032

31-
private bool EnableExternalDurableSDK { get; } =
33+
private bool isExternalDFSdkEnabled { get; } =
3234
PowerShellWorkerConfiguration.GetBoolean("ExternalDurablePowerShellSDK") ?? false;
3335

3436
public DurableController(
@@ -52,6 +54,7 @@ internal DurableController(
5254
_durableFunctionInfo = durableDurableFunctionInfo;
5355
_powerShellServices = powerShellServices;
5456
_orchestrationInvoker = orchestrationInvoker;
57+
_logger = logger;
5558
}
5659

5760
public string GetOrchestrationParameterName()
@@ -61,12 +64,28 @@ public string GetOrchestrationParameterName()
6164

6265
public void InitializeBindings(IList<ParameterBinding> inputData, out bool hasExternalSDK)
6366
{
64-
// Enable external SDK only when customer has opted-in
65-
if (EnableExternalDurableSDK)
67+
var isExternalSdkLoaded = _powerShellServices.isExternalDurableSdkLoaded();
68+
69+
if (isExternalDFSdkEnabled)
6670
{
67-
_powerShellServices.tryEnablingExternalDurableSDK();
71+
if (isExternalSdkLoaded)
72+
{
73+
// Enable external SDK only when customer has opted-in
74+
_powerShellServices.EnableExternalDurableSDK();
75+
}
76+
else
77+
{
78+
// Customer attempted to enable external SDK but the module not in session. Default to built-in SDK.
79+
_logger.Log(isUserOnlyLog: false, LogLevel.Error, string.Format(PowerShellWorkerStrings.ExternalSDKWasNotLoaded, Utils.ExternalDurableSdkName));
80+
}
81+
}
82+
else if (isExternalSdkLoaded)
83+
{
84+
// External SDK is in session, but customer does not mean to enable it. Report potential clashes
85+
_logger.Log(isUserOnlyLog: false, LogLevel.Error, String.Format(PowerShellWorkerStrings.PotentialDurableSDKClash, Utils.ExternalDurableSdkName));
6886
}
6987

88+
7089
// If the function is an durable client, then we set the DurableClient
7190
// in the module context for the 'Start-DurableOrchestration' function to use.
7291
if (_durableFunctionInfo.IsDurableClient)

src/Utility/Utils.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ internal class Utils
2323
internal readonly static CmdletInfo RemoveJobCmdletInfo = new CmdletInfo("Remove-Job", typeof(RemoveJobCommand));
2424
internal readonly static CmdletInfo OutStringCmdletInfo = new CmdletInfo("Out-String", typeof(OutStringCommand));
2525
internal readonly static CmdletInfo WriteInformationCmdletInfo = new CmdletInfo("Write-Information", typeof(WriteInformationCommand));
26+
27+
internal const string InternalDurableSdkName = "Microsoft.Azure.Functions.PowerShellWorker";
28+
internal const string ExternalDurableSdkName = "AzureFunctions.PowerShell.Durable.SDK";
2629

2730
internal readonly static object BoxedTrue = (object)true;
2831
internal readonly static object BoxedFalse = (object)false;

src/resources/PowerShellWorkerStrings.resx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,8 @@
352352
<data name="DependencySnapshotDoesNotContainAcceptableModuleVersions" xml:space="preserve">
353353
<value>Dependency snapshot '{0}' does not contain acceptable module versions.</value>
354354
</data>
355-
<data name="UtilizingExternalDurableSDK" xml:space="preserve">
356-
<value>Utilizing external Durable Functions SDK: '{0}'.</value>
355+
<data name="FoundExternalDurableSdkInSession" xml:space="preserve">
356+
<value>Found External Durable Functions SDK in session: Name='{0}', Version='{1}', Path='{2}'.</value>
357357
</data>
358358
<data name="IsOrchestrationFailureKey" xml:space="preserve">
359359
<value>IsOrchestrationFailure</value>
@@ -379,4 +379,16 @@
379379
<data name="UnexpectedResultCount" xml:space="preserve">
380380
<value>Operation '{0}' expected '{1}' result(s) but received '{2}'.</value>
381381
</data>
382+
<data name="ExternalSDKWasNotLoaded" xml:space="preserve">
383+
<value>The external Durable Functions SDK is enabled but it cannot be used because it was not loaded onto the current PowerShell session. Please add `Import-Module`'{0}' to your profile.ps1 so it may be used. Defaulting to built-in SDK.</value>
384+
</data>
385+
<data name="MultipleExternalSDKsInSession" xml:space="preserve">
386+
<value>Get-Module returned '{0}' instances of '{1}' in the PowerShell session, but only 1 or 0 are expected. This may create runtime errors. Please ensure your script only imports a single version of '{0}'. The modules currently in session are:\n '{2}'.</value>
387+
</data>
388+
<data name="PotentialDurableSDKClash" xml:space="preserve">
389+
<value>The external Durable Functions SDK is not enabled but '{0}' has been imported to the PowerShell session. This may create runtime conflicts between the built-in and external Durable Functions CommandLets. If you mean to use the external Durable Functions SDK, please set the enviroment variable '{1}' to "true"</value>
390+
</data>
391+
<data name="UtilizingExternalDurableSDK" xml:space="preserve">
392+
<value>Utilizing external Durable Functions SDK: '{0}'.</value>
393+
</data>
382394
</root>

test/Unit/Durable/DurableControllerTests.cs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ public void InitializeBindings_SetsDurableClient_ForDurableClientFunction()
4545

4646
_mockPowerShellServices.Setup(_ => _.SetDurableClient(It.IsAny<object>()));
4747
_mockPowerShellServices.Setup(_ => _.HasExternalDurableSDK()).Returns(false);
48-
_mockPowerShellServices.Setup(_ => _.tryEnablingExternalDurableSDK());
48+
_mockPowerShellServices.Setup(_ => _.EnableExternalDurableSDK());
49+
_mockPowerShellServices.Setup(_ => _.isExternalDurableSdkLoaded()).Returns(false);
4950

5051

5152
durableController.InitializeBindings(inputData, out bool hasExternalSDK);
@@ -70,7 +71,8 @@ public void InitializeBindings_SetsOrchestrationContext_ForOrchestrationFunction
7071
.Returns(_orchestrationBindingInfo);
7172
_mockOrchestrationInvoker.Setup(_ => _.SetExternalInvoker(It.IsAny<IExternalOrchestrationInvoker>()));
7273
_mockPowerShellServices.Setup(_ => _.HasExternalDurableSDK()).Returns(false);
73-
_mockPowerShellServices.Setup(_ => _.tryEnablingExternalDurableSDK());
74+
_mockPowerShellServices.Setup(_ => _.EnableExternalDurableSDK());
75+
_mockPowerShellServices.Setup(_ => _.isExternalDurableSdkLoaded()).Returns(false);
7476

7577
durableController.InitializeBindings(inputData, out bool hasExternalSDK);
7678

@@ -87,7 +89,8 @@ public void InitializeBindings_Throws_OnOrchestrationFunctionWithoutContextParam
8789
var durableController = CreateDurableController(DurableFunctionType.OrchestrationFunction);
8890
var inputData = new ParameterBinding[0];
8991
_mockPowerShellServices.Setup(_ => _.HasExternalDurableSDK()).Returns(false);
90-
_mockPowerShellServices.Setup(_ => _.tryEnablingExternalDurableSDK());
92+
_mockPowerShellServices.Setup(_ => _.EnableExternalDurableSDK());
93+
_mockPowerShellServices.Setup(_ => _.isExternalDurableSdkLoaded()).Returns(false);
9194

9295
Assert.ThrowsAny<ArgumentException>(() => durableController.InitializeBindings(inputData, out bool hasExternalSDK));
9396
}
@@ -104,7 +107,8 @@ internal void InitializeBindings_DoesNothing_ForNonOrchestrationFunction(Durable
104107
CreateParameterBinding("ParameterName", _orchestrationContext)
105108
};
106109
_mockPowerShellServices.Setup(_ => _.HasExternalDurableSDK()).Returns(false);
107-
_mockPowerShellServices.Setup(_ => _.tryEnablingExternalDurableSDK());
110+
_mockPowerShellServices.Setup(_ => _.EnableExternalDurableSDK());
111+
_mockPowerShellServices.Setup(_ => _.isExternalDurableSdkLoaded()).Returns(false);
108112

109113
durableController.InitializeBindings(inputData, out bool hasExternalSDK);
110114
}
@@ -136,7 +140,8 @@ public void TryGetInputBindingParameterValue_RetrievesOrchestrationContextParame
136140
out It.Ref<IExternalOrchestrationInvoker>.IsAny))
137141
.Returns(_orchestrationBindingInfo);
138142
_mockPowerShellServices.Setup(_ => _.HasExternalDurableSDK()).Returns(false);
139-
_mockPowerShellServices.Setup(_ => _.tryEnablingExternalDurableSDK());
143+
_mockPowerShellServices.Setup(_ => _.EnableExternalDurableSDK());
144+
_mockPowerShellServices.Setup(_ => _.isExternalDurableSdkLoaded()).Returns(false);
140145

141146
_mockOrchestrationInvoker.Setup(_ => _.SetExternalInvoker(It.IsAny<IExternalOrchestrationInvoker>()));
142147
durableController.InitializeBindings(inputData, out bool hasExternalSDK);
@@ -161,7 +166,8 @@ internal void TryGetInputBindingParameterValue_RetrievesNothing_ForNonOrchestrat
161166
out It.Ref<IExternalOrchestrationInvoker>.IsAny))
162167
.Returns(_orchestrationBindingInfo);
163168
_mockPowerShellServices.Setup(_ => _.HasExternalDurableSDK()).Returns(false);
164-
_mockPowerShellServices.Setup(_ => _.tryEnablingExternalDurableSDK());
169+
_mockPowerShellServices.Setup(_ => _.EnableExternalDurableSDK());
170+
_mockPowerShellServices.Setup(_ => _.isExternalDurableSdkLoaded()).Returns(false);
165171

166172
_mockOrchestrationInvoker.Setup(_ => _.SetExternalInvoker(It.IsAny<IExternalOrchestrationInvoker>()));
167173
durableController.InitializeBindings(inputData, out bool hasExternalSDK);
@@ -181,7 +187,8 @@ public void TryInvokeOrchestrationFunction_InvokesOrchestrationFunction()
181187
out It.Ref<IExternalOrchestrationInvoker>.IsAny))
182188
.Returns(_orchestrationBindingInfo);
183189
_mockPowerShellServices.Setup(_ => _.HasExternalDurableSDK()).Returns(false);
184-
_mockPowerShellServices.Setup(_ => _.tryEnablingExternalDurableSDK());
190+
_mockPowerShellServices.Setup(_ => _.EnableExternalDurableSDK());
191+
_mockPowerShellServices.Setup(_ => _.isExternalDurableSdkLoaded()).Returns(false);
185192

186193
_mockOrchestrationInvoker.Setup(_ => _.SetExternalInvoker(It.IsAny<IExternalOrchestrationInvoker>()));
187194

@@ -271,11 +278,12 @@ internal void ExternalDurableSdkIsNotConfiguredByDefault(DurableFunctionType dur
271278
}
272279

273280
_mockPowerShellServices.Setup(_ => _.HasExternalDurableSDK()).Returns(false);
274-
_mockPowerShellServices.Setup(_ => _.tryEnablingExternalDurableSDK()).Throws(new Exception("should not be called"));
281+
_mockPowerShellServices.Setup(_ => _.EnableExternalDurableSDK()).Throws(new Exception("should not be called"));
282+
_mockPowerShellServices.Setup(_ => _.isExternalDurableSdkLoaded()).Returns(false);
275283
durableController.InitializeBindings(inputData, out var hasExternalSDK);
276284

277285
Assert.False(hasExternalSDK);
278-
_mockPowerShellServices.Verify(_ => _.tryEnablingExternalDurableSDK(), Times.Never);
286+
_mockPowerShellServices.Verify(_ => _.EnableExternalDurableSDK(), Times.Never);
279287
}
280288

281289
[Theory]
@@ -304,11 +312,12 @@ internal void ExternalDurableSdkCanBeEnabled(DurableFunctionType durableFunction
304312
}
305313

306314
_mockPowerShellServices.Setup(_ => _.HasExternalDurableSDK()).Returns(true);
307-
_mockPowerShellServices.Setup(_ => _.tryEnablingExternalDurableSDK());
315+
_mockPowerShellServices.Setup(_ => _.EnableExternalDurableSDK());
316+
_mockPowerShellServices.Setup(_ => _.isExternalDurableSdkLoaded()).Returns(true);
308317
durableController.InitializeBindings(inputData, out var hasExternalSDK);
309318

310319
Assert.True(hasExternalSDK);
311-
_mockPowerShellServices.Verify(_ => _.tryEnablingExternalDurableSDK(), Times.Once);
320+
_mockPowerShellServices.Verify(_ => _.EnableExternalDurableSDK(), Times.Once);
312321

313322
}
314323
finally

0 commit comments

Comments
 (0)