From 4c6411b82c1c03bb5b76a57c4bf89d33849444fe Mon Sep 17 00:00:00 2001 From: Francisco Gamino Date: Sat, 9 Sep 2023 11:34:57 -0700 Subject: [PATCH] Update language worker to support parsing command-line arguments prefix with functions- (#993) * Add support to parsing command-line arguments prefix with functions- * Configure parser to ignore unknown arguments * Remove deprecated option grpcMaxMessageLength --- src/Worker.cs | 94 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/src/Worker.cs b/src/Worker.cs index d580e79..e11b501 100644 --- a/src/Worker.cs +++ b/src/Worker.cs @@ -31,10 +31,53 @@ namespace Microsoft.Azure.Functions.PowerShellWorker LogLevel.Information, string.Format(PowerShellWorkerStrings.PowerShellWorkerVersion, typeof(Worker).Assembly.GetName().Version)); - WorkerArguments arguments = null; - Parser.Default.ParseArguments(args) - .WithParsed(ops => arguments = ops) - .WithNotParsed(err => Environment.Exit(1)); + var workerOptions = new WorkerOptions(); + + var parser = new Parser(settings => + { + settings.EnableDashDash = true; + settings.IgnoreUnknownArguments = true; + }); + parser.ParseArguments(args) + .WithParsed(workerArgs => + { + // TODO: Remove parsing old command-line arguments that are not prefixed with functions- + // for more information, see https://github.com/Azure/azure-functions-powershell-worker/issues/995 + workerOptions.WorkerId = workerArgs.FunctionsWorkerId ?? workerArgs.WorkerId; + workerOptions.RequestId = workerArgs.FunctionsRequestId ?? workerArgs.RequestId; + + if (!string.IsNullOrWhiteSpace(workerArgs.FunctionsUri)) + { + try + { + // TODO: Update WorkerOptions to have a URI property instead of host name and port number + // for more information, see https://github.com/Azure/azure-functions-powershell-worker/issues/994 + var uri = new Uri(workerArgs.FunctionsUri); + workerOptions.Host = uri.Host; + workerOptions.Port = uri.Port; + } + catch (UriFormatException formatEx) + { + var message = $"Invalid URI format: {workerArgs.FunctionsUri}. Error message: {formatEx.Message}"; + throw new ArgumentException(message, nameof(workerArgs.FunctionsUri)); + } + } + else + { + workerOptions.Host = workerArgs.Host; + workerOptions.Port = workerArgs.Port; + } + + // Validate workerOptions + ValidateProperty("WorkerId", workerOptions.WorkerId); + ValidateProperty("RequestId", workerOptions.RequestId); + ValidateProperty("Host", workerOptions.Host); + + if (workerOptions.Port <= 0) + { + throw new ArgumentException("Port number has not been initialized", nameof(workerOptions.Port)); + } + }); // Create the very first Runspace so the debugger has the target to attach to. // This PowerShell instance is shared by the first PowerShellManager instance created in the pool, @@ -44,14 +87,14 @@ namespace Microsoft.Azure.Functions.PowerShellWorker LogPowerShellVersion(pwshVersion); WarmUpPowerShell(firstPowerShellInstance); - var msgStream = new MessagingStream(arguments.Host, arguments.Port); + var msgStream = new MessagingStream(workerOptions.Host, workerOptions.Port); var requestProcessor = new RequestProcessor(msgStream, firstPowerShellInstance, pwshVersion); // Send StartStream message var startedMessage = new StreamingMessage() { - RequestId = arguments.RequestId, - StartStream = new StartStream() { WorkerId = arguments.WorkerId } + RequestId = workerOptions.RequestId, + StartStream = new StartStream() { WorkerId = workerOptions.WorkerId } }; msgStream.Write(startedMessage); @@ -81,23 +124,48 @@ namespace Microsoft.Azure.Functions.PowerShellWorker var message = string.Format(PowerShellWorkerStrings.PowerShellVersion, pwshVersion); RpcLogger.WriteSystemLog(LogLevel.Information, message); } + + private static void ValidateProperty(string name, string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException($"{name} is null or empty", name); + } + } } internal class WorkerArguments { - [Option("host", Required = true, HelpText = "IP Address used to connect to the Host via gRPC.")] + [Option("host", Required = false, 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.")] + [Option("port", Required = false, 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.")] + [Option("workerId", Required = false, 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.")] + [Option("requestId", Required = false, HelpText = "Request ID used for gRPC communication with the Host.")] public string RequestId { get; set; } - [Option("grpcMaxMessageLength", Required = false, HelpText = "[Deprecated and ignored] gRPC Maximum message size.")] - public int MaxMessageLength { get; set; } + [Option("functions-uri", Required = false, HelpText = "URI with IP Address and Port used to connect to the Host via gRPC.")] + public string FunctionsUri { get; set; } + + [Option("functions-workerid", Required = false, HelpText = "Worker ID assigned to this language worker.")] + public string FunctionsWorkerId { get; set; } + + [Option("functions-requestid", Required = false, HelpText = "Request ID used for gRPC communication with the Host.")] + public string FunctionsRequestId { get; set; } + } + + internal class WorkerOptions + { + public string Host { get; set; } + + public int Port { get; set; } + + public string WorkerId { get; set; } + + public string RequestId { get; set; } } }