diff --git a/azure-functions-powershell-worker.sln b/azure-functions-powershell-worker.sln index 1b8c5d61..06985175 100644 --- a/azure-functions-powershell-worker.sln +++ b/azure-functions-powershell-worker.sln @@ -5,11 +5,11 @@ VisualStudioVersion = 15.0.26124.0 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8C758288-3909-4CE1-972D-1BE966628D6C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Functions.PowerShell.Worker", "src\Azure.Functions.PowerShell.Worker\Azure.Functions.PowerShell.Worker.csproj", "{939262BA-4823-405E-81CD-436C0B77D524}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Functions.PowerShell.Worker", "src\Azure.Functions.PowerShell.Worker.csproj", "{939262BA-4823-405E-81CD-436C0B77D524}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{12092936-4F2A-4B40-9AF2-56C840D44FEA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Functions.PowerShell.Worker.Test", "test\Azure.Functions.PowerShell.Worker.Test\Azure.Functions.PowerShell.Worker.Test.csproj", "{535C8DA3-479D-42BF-B1AF-5B03ECAF67A4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Functions.PowerShell.Worker.Test", "test\Azure.Functions.PowerShell.Worker.Test.csproj", "{535C8DA3-479D-42BF-B1AF-5B03ECAF67A4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Azure.Functions.PowerShell.Worker/Properties/AssemblyInfo.cs b/src/AssemblyInfo.cs similarity index 99% rename from src/Azure.Functions.PowerShell.Worker/Properties/AssemblyInfo.cs rename to src/AssemblyInfo.cs index dd8b6b66..8b0dcdf5 100644 --- a/src/Azure.Functions.PowerShell.Worker/Properties/AssemblyInfo.cs +++ b/src/AssemblyInfo.cs @@ -6,3 +6,4 @@ using System.Runtime.CompilerServices; [assembly:InternalsVisibleTo("Azure.Functions.PowerShell.Worker.Test")] + diff --git a/src/Azure.Functions.PowerShell.Worker/Azure.Functions.PowerShell.Worker.csproj b/src/Azure.Functions.PowerShell.Worker.csproj similarity index 93% rename from src/Azure.Functions.PowerShell.Worker/Azure.Functions.PowerShell.Worker.csproj rename to src/Azure.Functions.PowerShell.Worker.csproj index 06608836..8773f1fc 100644 --- a/src/Azure.Functions.PowerShell.Worker/Azure.Functions.PowerShell.Worker.csproj +++ b/src/Azure.Functions.PowerShell.Worker.csproj @@ -14,6 +14,7 @@ true en-US + @@ -27,7 +28,8 @@ PreserveNewest - + + Modules\%(RecursiveDir)\%(FileName)%(Extension) PreserveNewest diff --git a/src/Azure.Functions.PowerShell.Worker/Requests/HandleFunctionLoadRequest.cs b/src/Azure.Functions.PowerShell.Worker/Requests/HandleFunctionLoadRequest.cs deleted file mode 100644 index b5a45f29..00000000 --- a/src/Azure.Functions.PowerShell.Worker/Requests/HandleFunctionLoadRequest.cs +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using System; - -using Microsoft.Azure.Functions.PowerShellWorker.Utility; -using Microsoft.Azure.WebJobs.Script.Grpc.Messages; - -namespace Microsoft.Azure.Functions.PowerShellWorker.Requests -{ - using System.Management.Automation; - using Microsoft.Azure.Functions.PowerShellWorker.PowerShell; - - internal static class HandleFunctionLoadRequest - { - public static StreamingMessage Invoke( - PowerShellManager powerShellManager, - FunctionLoader functionLoader, - StreamingMessage request, - RpcLogger logger) - { - FunctionLoadRequest functionLoadRequest = request.FunctionLoadRequest; - - // Assume success unless something bad happens - StatusResult status = new StatusResult() - { - Status = StatusResult.Types.Status.Success - }; - - // Try to load the functions - try - { - functionLoader.Load(functionLoadRequest.FunctionId, functionLoadRequest.Metadata); - } - catch (Exception e) - { - status.Status = StatusResult.Types.Status.Failure; - status.Exception = e.ToRpcException(); - } - - return new StreamingMessage() - { - RequestId = request.RequestId, - FunctionLoadResponse = new FunctionLoadResponse() - { - FunctionId = functionLoadRequest.FunctionId, - Result = status - } - }; - } - } -} diff --git a/src/Azure.Functions.PowerShell.Worker/Requests/HandleInvocationRequest.cs b/src/Azure.Functions.PowerShell.Worker/Requests/HandleInvocationRequest.cs deleted file mode 100644 index 8f3a15ab..00000000 --- a/src/Azure.Functions.PowerShell.Worker/Requests/HandleInvocationRequest.cs +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using System; -using System.Collections; -using System.Collections.Generic; - -using Microsoft.Azure.Functions.PowerShellWorker.Utility; -using Microsoft.Azure.Functions.PowerShellWorker.PowerShell; -using Microsoft.Azure.WebJobs.Script.Grpc.Messages; - -namespace Microsoft.Azure.Functions.PowerShellWorker.Requests -{ - internal static class HandleInvocationRequest - { - public static StreamingMessage Invoke( - PowerShellManager powerShellManager, - FunctionLoader functionLoader, - StreamingMessage request, - RpcLogger logger) - { - InvocationRequest invocationRequest = request.InvocationRequest; - - // Set the RequestId and InvocationId for logging purposes - logger.SetContext(request.RequestId, invocationRequest.InvocationId); - - // Load information about the function - var functionInfo = functionLoader.GetInfo(invocationRequest.FunctionId); - (string scriptPath, string entryPoint) = functionLoader.GetFunc(invocationRequest.FunctionId); - - // Bundle all TriggerMetadata into Hashtable to send down to PowerShell - Hashtable triggerMetadata = new Hashtable(); - foreach (var dataItem in invocationRequest.TriggerMetadata) - { - triggerMetadata.Add(dataItem.Key, dataItem.Value.ToObject()); - } - - // Assume success unless something bad happens - var status = new StatusResult() { Status = StatusResult.Types.Status.Success }; - var response = new StreamingMessage() - { - RequestId = request.RequestId, - InvocationResponse = new InvocationResponse() - { - InvocationId = invocationRequest.InvocationId, - Result = status - } - }; - - // Invoke powershell logic and return hashtable of out binding data - Hashtable result = null; - try - { - result = powerShellManager - .InvokeFunctionAndSetGlobalReturn(scriptPath, entryPoint, triggerMetadata, invocationRequest.InputData) - .ReturnBindingHashtable(functionInfo.OutputBindings); - } - catch (Exception e) - { - status.Status = StatusResult.Types.Status.Failure; - status.Exception = e.ToRpcException(); - return response; - } - - // Set out binding data and return response to be sent back to host - foreach (KeyValuePair binding in functionInfo.OutputBindings) - { - ParameterBinding paramBinding = new ParameterBinding() - { - Name = binding.Key, - Data = result[binding.Key].ToTypedData() - }; - - response.InvocationResponse.OutputData.Add(paramBinding); - - // if one of the bindings is $return we need to also set the ReturnValue - if(binding.Key == "$return") - { - response.InvocationResponse.ReturnValue = paramBinding.Data; - } - } - - return response; - } - } -} diff --git a/src/Azure.Functions.PowerShell.Worker/Requests/HandleWorkerInitRequest.cs b/src/Azure.Functions.PowerShell.Worker/Requests/HandleWorkerInitRequest.cs deleted file mode 100644 index 28402d69..00000000 --- a/src/Azure.Functions.PowerShell.Worker/Requests/HandleWorkerInitRequest.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using Microsoft.Azure.Functions.PowerShellWorker.PowerShell; -using Microsoft.Azure.Functions.PowerShellWorker.Utility; -using Microsoft.Azure.WebJobs.Script.Grpc.Messages; - -namespace Microsoft.Azure.Functions.PowerShellWorker.Requests -{ - internal static class HandleWorkerInitRequest - { - public static StreamingMessage Invoke( - PowerShellManager powerShellManager, - FunctionLoader functionLoader, - StreamingMessage request, - RpcLogger logger) - { - return new StreamingMessage() - { - RequestId = request.RequestId, - WorkerInitResponse = new WorkerInitResponse() - { - Result = new StatusResult() - { - Status = StatusResult.Types.Status.Success - } - } - }; - } - } -} diff --git a/src/Azure.Functions.PowerShell.Worker/Worker.cs b/src/Azure.Functions.PowerShell.Worker/Worker.cs deleted file mode 100644 index 75e92e5b..00000000 --- a/src/Azure.Functions.PowerShell.Worker/Worker.cs +++ /dev/null @@ -1,114 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using System; -using System.Threading.Tasks; - -using CommandLine; -using Microsoft.Azure.Functions.PowerShellWorker.PowerShell; -using Microsoft.Azure.Functions.PowerShellWorker.Messaging; -using Microsoft.Azure.Functions.PowerShellWorker.Requests; -using Microsoft.Azure.Functions.PowerShellWorker.Utility; -using Microsoft.Azure.WebJobs.Script.Grpc.Messages; - -namespace Microsoft.Azure.Functions.PowerShellWorker -{ - /// - /// The PowerShell language worker for Azure Function - /// - public static class Worker - { - static readonly FunctionLoader s_functionLoader = new FunctionLoader(); - static FunctionMessagingClient s_client; - static RpcLogger s_logger; - static PowerShellManager s_powershellManager; - - /// - /// Entry point of the language worker. - /// - public async static Task Main(string[] args) - { - WorkerArguments arguments = null; - Parser.Default.ParseArguments(args) - .WithParsed(ops => arguments = ops) - .WithNotParsed(err => Environment.Exit(1)); - - // Initialize Rpc client, logger, and PowerShellManager - s_client = new FunctionMessagingClient(arguments.Host, arguments.Port); - s_logger = new RpcLogger(s_client); - s_powershellManager = PowerShellManager.Create(s_logger); - - // Send StartStream message - var streamingMessage = new StreamingMessage() { - RequestId = arguments.RequestId, - StartStream = new StartStream() { WorkerId = arguments.WorkerId } - }; - - await s_client.WriteAsync(streamingMessage); - await ProcessEvent(); - } - - static async Task ProcessEvent() - { - using (s_client) - { - while (await s_client.MoveNext()) - { - var message = s_client.GetCurrentMessage(); - StreamingMessage response; - switch (message.ContentCase) - { - case StreamingMessage.ContentOneofCase.WorkerInitRequest: - response = HandleWorkerInitRequest.Invoke( - s_powershellManager, - s_functionLoader, - message, - s_logger); - break; - - case StreamingMessage.ContentOneofCase.FunctionLoadRequest: - response = HandleFunctionLoadRequest.Invoke( - s_powershellManager, - s_functionLoader, - message, - s_logger); - break; - - case StreamingMessage.ContentOneofCase.InvocationRequest: - response = HandleInvocationRequest.Invoke( - s_powershellManager, - s_functionLoader, - message, - s_logger); - break; - - default: - throw new InvalidOperationException($"Not supportted message type: {message.ContentCase}"); - } - - await s_client.WriteAsync(response); - } - } - } - } - - internal class WorkerArguments - { - [Option("host", Required = true, HelpText = "IP Address used to connect to the Host via gRPC.")] - public string Host { get; set; } - - [Option("port", Required = true, HelpText = "Port used to connect to the Host via gRPC.")] - public int Port { get; set; } - - [Option("workerId", Required = true, HelpText = "Worker ID assigned to this language worker.")] - public string WorkerId { get; set; } - - [Option("requestId", Required = true, HelpText = "Request ID used for gRPC communication with the Host.")] - public string RequestId { get; set; } - - [Option("grpcMaxMessageLength", Required = true, HelpText = "gRPC Maximum message size.")] - public int MaxMessageLength { get; set; } - } -} diff --git a/src/Azure.Functions.PowerShell.Worker/Function/FunctionInfo.cs b/src/Function/FunctionInfo.cs similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/Function/FunctionInfo.cs rename to src/Function/FunctionInfo.cs diff --git a/src/Azure.Functions.PowerShell.Worker/Function/FunctionLoader.cs b/src/Function/FunctionLoader.cs similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/Function/FunctionLoader.cs rename to src/Function/FunctionLoader.cs diff --git a/src/Azure.Functions.PowerShell.Worker/Http/HttpRequestContext.cs b/src/Http/HttpRequestContext.cs similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/Http/HttpRequestContext.cs rename to src/Http/HttpRequestContext.cs diff --git a/src/Azure.Functions.PowerShell.Worker/Http/HttpResponseContext.cs b/src/Http/HttpResponseContext.cs similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/Http/HttpResponseContext.cs rename to src/Http/HttpResponseContext.cs diff --git a/src/Azure.Functions.PowerShell.Worker/Messaging/FunctionRpc.cs b/src/Messaging/FunctionRpc.cs similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/Messaging/FunctionRpc.cs rename to src/Messaging/FunctionRpc.cs diff --git a/src/Azure.Functions.PowerShell.Worker/Messaging/FunctionRpcGrpc.cs b/src/Messaging/FunctionRpcGrpc.cs similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/Messaging/FunctionRpcGrpc.cs rename to src/Messaging/FunctionRpcGrpc.cs diff --git a/src/Azure.Functions.PowerShell.Worker/Messaging/FunctionMessagingClient.cs b/src/Messaging/MessagingStream.cs similarity index 83% rename from src/Azure.Functions.PowerShell.Worker/Messaging/FunctionMessagingClient.cs rename to src/Messaging/MessagingStream.cs index c99dfe7d..23b1fa24 100644 --- a/src/Azure.Functions.PowerShell.Worker/Messaging/FunctionMessagingClient.cs +++ b/src/Messaging/MessagingStream.cs @@ -12,13 +12,13 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Messaging { - internal class FunctionMessagingClient : IDisposable + internal class MessagingStream : IDisposable { - SemaphoreSlim _writeStreamHandle = new SemaphoreSlim(1, 1); - AsyncDuplexStreamingCall _call; - public bool isDisposed; + private SemaphoreSlim _writeStreamHandle = new SemaphoreSlim(1, 1); + private AsyncDuplexStreamingCall _call; + private bool isDisposed; - public FunctionMessagingClient(string host, int port) + public MessagingStream(string host, int port) { Channel channel = new Channel(host, port, ChannelCredentials.Insecure); _call = new FunctionRpc.FunctionRpcClient(channel).EventStream(); @@ -56,4 +56,4 @@ public async Task WriteAsync(StreamingMessage message) } } } -} \ No newline at end of file +} diff --git a/src/Azure.Functions.PowerShell.Worker/Messaging/RpcLogger.cs b/src/Messaging/RpcLogger.cs similarity index 88% rename from src/Azure.Functions.PowerShell.Worker/Messaging/RpcLogger.cs rename to src/Messaging/RpcLogger.cs index 12ea9a83..a790afb2 100644 --- a/src/Azure.Functions.PowerShell.Worker/Messaging/RpcLogger.cs +++ b/src/Messaging/RpcLogger.cs @@ -13,13 +13,13 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Utility { internal class RpcLogger : ILogger { - FunctionMessagingClient _client; - string _invocationId = ""; - string _requestId = ""; + private MessagingStream _msgStream; + private string _invocationId = ""; + private string _requestId = ""; - public RpcLogger(FunctionMessagingClient client) + public RpcLogger(MessagingStream msgStream) { - _client = client; + _msgStream = msgStream; } public IDisposable BeginScope(TState state) => @@ -51,7 +51,7 @@ public bool IsEnabled(LogLevel logLevel) => public async void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - if (_client != null) + if (_msgStream != null) { var logMessage = new StreamingMessage { @@ -65,7 +65,7 @@ public async void Log(LogLevel logLevel, EventId eventId, TState state, } }; - await _client.WriteAsync(logMessage); + await _msgStream.WriteAsync(logMessage); } } diff --git a/src/Azure.Functions.PowerShell.Worker/Azure.Functions.PowerShell.Worker.Module/Azure.Functions.PowerShell.Worker.Module.psd1 b/src/Modules/Azure.Functions.PowerShell.Worker.Module/Azure.Functions.PowerShell.Worker.Module.psd1 similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/Azure.Functions.PowerShell.Worker.Module/Azure.Functions.PowerShell.Worker.Module.psd1 rename to src/Modules/Azure.Functions.PowerShell.Worker.Module/Azure.Functions.PowerShell.Worker.Module.psd1 diff --git a/src/Azure.Functions.PowerShell.Worker/Azure.Functions.PowerShell.Worker.Module/Azure.Functions.PowerShell.Worker.Module.psm1 b/src/Modules/Azure.Functions.PowerShell.Worker.Module/Azure.Functions.PowerShell.Worker.Module.psm1 similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/Azure.Functions.PowerShell.Worker.Module/Azure.Functions.PowerShell.Worker.Module.psm1 rename to src/Modules/Azure.Functions.PowerShell.Worker.Module/Azure.Functions.PowerShell.Worker.Module.psm1 diff --git a/src/Azure.Functions.PowerShell.Worker/PowerShell/PowerShellManager.cs b/src/PowerShell/PowerShellManager.cs similarity index 98% rename from src/Azure.Functions.PowerShell.Worker/PowerShell/PowerShellManager.cs rename to src/PowerShell/PowerShellManager.cs index f4d005a2..53a7f514 100644 --- a/src/Azure.Functions.PowerShell.Worker/PowerShell/PowerShellManager.cs +++ b/src/PowerShell/PowerShellManager.cs @@ -46,7 +46,7 @@ internal class PowerShellManager PowerShellManager(RpcLogger logger) { - _pwsh = System.Management.Automation.PowerShell.Create(InitialSessionState.CreateDefault()); + _pwsh = PowerShell.Create(InitialSessionState.CreateDefault()); _logger = logger; // Setup Stream event listeners diff --git a/src/Azure.Functions.PowerShell.Worker/PowerShell/StreamHandler.cs b/src/PowerShell/StreamHandler.cs similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/PowerShell/StreamHandler.cs rename to src/PowerShell/StreamHandler.cs diff --git a/src/RequestProcessor.cs b/src/RequestProcessor.cs new file mode 100644 index 00000000..ab3a5de9 --- /dev/null +++ b/src/RequestProcessor.cs @@ -0,0 +1,177 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Microsoft.Azure.Functions.PowerShellWorker.Messaging; +using Microsoft.Azure.Functions.PowerShellWorker.PowerShell; +using Microsoft.Azure.Functions.PowerShellWorker.Utility; +using Microsoft.Azure.WebJobs.Script.Grpc.Messages; + +namespace Microsoft.Azure.Functions.PowerShellWorker +{ + internal class RequestProcessor + { + private readonly FunctionLoader _functionLoader; + private readonly RpcLogger _logger; + private readonly MessagingStream _msgStream; + private readonly PowerShellManager _powerShellManager; + + internal RequestProcessor(MessagingStream msgStream) + { + _msgStream = msgStream; + _logger = new RpcLogger(msgStream); + _powerShellManager = PowerShellManager.Create(_logger); + _functionLoader = new FunctionLoader(); + } + + internal async Task ProcessRequestLoop() + { + using (_msgStream) + { + StreamingMessage request, response; + while (await _msgStream.MoveNext()) + { + request = _msgStream.GetCurrentMessage(); + switch (request.ContentCase) + { + case StreamingMessage.ContentOneofCase.WorkerInitRequest: + response = ProcessWorkerInitRequest(request); + break; + + case StreamingMessage.ContentOneofCase.FunctionLoadRequest: + response = ProcessFunctionLoadRequest(request); + break; + + case StreamingMessage.ContentOneofCase.InvocationRequest: + response = ProcessInvocationRequest(request); + break; + + default: + throw new InvalidOperationException($"Not supportted message type: {request.ContentCase}"); + } + + await _msgStream.WriteAsync(response); + } + } + } + + internal StreamingMessage ProcessWorkerInitRequest(StreamingMessage request) + { + return new StreamingMessage() + { + RequestId = request.RequestId, + WorkerInitResponse = new WorkerInitResponse() + { + Result = new StatusResult() + { + Status = StatusResult.Types.Status.Success + } + } + }; + } + + internal StreamingMessage ProcessFunctionLoadRequest(StreamingMessage request) + { + FunctionLoadRequest functionLoadRequest = request.FunctionLoadRequest; + + // Assume success unless something bad happens + StatusResult status = new StatusResult() + { + Status = StatusResult.Types.Status.Success + }; + + // Try to load the functions + try + { + _functionLoader.Load(functionLoadRequest.FunctionId, functionLoadRequest.Metadata); + } + catch (Exception e) + { + status.Status = StatusResult.Types.Status.Failure; + status.Exception = e.ToRpcException(); + } + + return new StreamingMessage() + { + RequestId = request.RequestId, + FunctionLoadResponse = new FunctionLoadResponse() + { + FunctionId = functionLoadRequest.FunctionId, + Result = status + } + }; + } + + internal StreamingMessage ProcessInvocationRequest(StreamingMessage request) + { + InvocationRequest invocationRequest = request.InvocationRequest; + + // Set the RequestId and InvocationId for logging purposes + _logger.SetContext(request.RequestId, invocationRequest.InvocationId); + + // Load information about the function + var functionInfo = _functionLoader.GetInfo(invocationRequest.FunctionId); + (string scriptPath, string entryPoint) = _functionLoader.GetFunc(invocationRequest.FunctionId); + + // Bundle all TriggerMetadata into Hashtable to send down to PowerShell + Hashtable triggerMetadata = new Hashtable(); + foreach (var dataItem in invocationRequest.TriggerMetadata) + { + triggerMetadata.Add(dataItem.Key, dataItem.Value.ToObject()); + } + + // Assume success unless something bad happens + var status = new StatusResult() { Status = StatusResult.Types.Status.Success }; + var response = new StreamingMessage() + { + RequestId = request.RequestId, + InvocationResponse = new InvocationResponse() + { + InvocationId = invocationRequest.InvocationId, + Result = status + } + }; + + // Invoke powershell logic and return hashtable of out binding data + Hashtable result = null; + try + { + result = _powerShellManager + .InvokeFunctionAndSetGlobalReturn(scriptPath, entryPoint, triggerMetadata, invocationRequest.InputData) + .ReturnBindingHashtable(functionInfo.OutputBindings); + } + catch (Exception e) + { + status.Status = StatusResult.Types.Status.Failure; + status.Exception = e.ToRpcException(); + return response; + } + + // Set out binding data and return response to be sent back to host + foreach (KeyValuePair binding in functionInfo.OutputBindings) + { + ParameterBinding paramBinding = new ParameterBinding() + { + Name = binding.Key, + Data = result[binding.Key].ToTypedData() + }; + + response.InvocationResponse.OutputData.Add(paramBinding); + + // if one of the bindings is $return we need to also set the ReturnValue + if(binding.Key == "$return") + { + response.InvocationResponse.ReturnValue = paramBinding.Data; + } + } + + return response; + } + } +} diff --git a/src/Azure.Functions.PowerShell.Worker/Utility/ExecutionTimer.cs b/src/Utility/ExecutionTimer.cs similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/Utility/ExecutionTimer.cs rename to src/Utility/ExecutionTimer.cs diff --git a/src/Azure.Functions.PowerShell.Worker/Utility/TypeExtensions.cs b/src/Utility/TypeExtensions.cs similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/Utility/TypeExtensions.cs rename to src/Utility/TypeExtensions.cs diff --git a/src/Worker.cs b/src/Worker.cs new file mode 100644 index 00000000..15d4d7cf --- /dev/null +++ b/src/Worker.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Threading.Tasks; + +using CommandLine; +using Microsoft.Azure.Functions.PowerShellWorker.PowerShell; +using Microsoft.Azure.Functions.PowerShellWorker.Messaging; +using Microsoft.Azure.Functions.PowerShellWorker.Utility; +using Microsoft.Azure.WebJobs.Script.Grpc.Messages; + +namespace Microsoft.Azure.Functions.PowerShellWorker +{ + /// + /// The PowerShell language worker for Azure Function + /// + public static class Worker + { + /// + /// Entry point of the language worker. + /// + public async static Task Main(string[] args) + { + WorkerArguments arguments = null; + Parser.Default.ParseArguments(args) + .WithParsed(ops => arguments = ops) + .WithNotParsed(err => Environment.Exit(1)); + + var msgStream = new MessagingStream(arguments.Host, arguments.Port); + var requestProcessor = new RequestProcessor(msgStream); + + // Send StartStream message + var startedMessage = new StreamingMessage() { + RequestId = arguments.RequestId, + StartStream = new StartStream() { WorkerId = arguments.WorkerId } + }; + + await msgStream.WriteAsync(startedMessage); + await requestProcessor.ProcessRequestLoop(); + } + } + + internal class WorkerArguments + { + [Option("host", Required = true, HelpText = "IP Address used to connect to the Host via gRPC.")] + public string Host { get; set; } + + [Option("port", Required = true, HelpText = "Port used to connect to the Host via gRPC.")] + public int Port { get; set; } + + [Option("workerId", Required = true, HelpText = "Worker ID assigned to this language worker.")] + public string WorkerId { get; set; } + + [Option("requestId", Required = true, HelpText = "Request ID used for gRPC communication with the Host.")] + public string RequestId { get; set; } + + [Option("grpcMaxMessageLength", Required = true, HelpText = "gRPC Maximum message size.")] + public int MaxMessageLength { get; set; } + } +} diff --git a/src/Azure.Functions.PowerShell.Worker/worker.config.json b/src/worker.config.json similarity index 100% rename from src/Azure.Functions.PowerShell.Worker/worker.config.json rename to src/worker.config.json diff --git a/test/Azure.Functions.PowerShell.Worker.Test/Azure.Functions.PowerShell.Worker.Test.csproj b/test/Azure.Functions.PowerShell.Worker.Test.csproj similarity index 83% rename from test/Azure.Functions.PowerShell.Worker.Test/Azure.Functions.PowerShell.Worker.Test.csproj rename to test/Azure.Functions.PowerShell.Worker.Test.csproj index 79079dd5..2167776e 100644 --- a/test/Azure.Functions.PowerShell.Worker.Test/Azure.Functions.PowerShell.Worker.Test.csproj +++ b/test/Azure.Functions.PowerShell.Worker.Test.csproj @@ -15,7 +15,7 @@ - + diff --git a/test/Azure.Functions.PowerShell.Worker.Test/Function/FunctionLoaderTests.cs b/test/Function/FunctionLoaderTests.cs similarity index 100% rename from test/Azure.Functions.PowerShell.Worker.Test/Function/FunctionLoaderTests.cs rename to test/Function/FunctionLoaderTests.cs diff --git a/test/Azure.Functions.PowerShell.Worker.Test/Requests/HandleWorkerInitRequestTests.cs b/test/Requests/HandleWorkerInitRequestTests.cs similarity index 80% rename from test/Azure.Functions.PowerShell.Worker.Test/Requests/HandleWorkerInitRequestTests.cs rename to test/Requests/HandleWorkerInitRequestTests.cs index afd51f8c..36fd8873 100644 --- a/test/Azure.Functions.PowerShell.Worker.Test/Requests/HandleWorkerInitRequestTests.cs +++ b/test/Requests/HandleWorkerInitRequestTests.cs @@ -3,14 +3,14 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.Azure.Functions.PowerShellWorker.Requests; +using Microsoft.Azure.Functions.PowerShellWorker; using Microsoft.Azure.Functions.PowerShellWorker.Utility; using Microsoft.Azure.WebJobs.Script.Grpc.Messages; using Xunit; namespace Azure.Functions.PowerShell.Worker.Test { - public class HandleWorkerInitRequestTests + public class ProcessWorkerInitRequestTests { [Fact] public void HandleWorkerInitRequestSuccess() @@ -29,14 +29,12 @@ public void HandleWorkerInitRequestSuccess() } }; - StreamingMessage result = HandleWorkerInitRequest.Invoke( - null, - null, + var requestProcessor = new RequestProcessor(null); + StreamingMessage result = requestProcessor.ProcessWorkerInitRequest( new StreamingMessage() { RequestId = requestId - }, - new RpcLogger(null) + } ); Assert.Equal(requestId, result.RequestId); diff --git a/test/Azure.Functions.PowerShell.Worker.Test/Utility/TypeExtensionsTests.cs b/test/Utility/TypeExtensionsTests.cs similarity index 100% rename from test/Azure.Functions.PowerShell.Worker.Test/Utility/TypeExtensionsTests.cs rename to test/Utility/TypeExtensionsTests.cs