From d8a81d94500e7d7174a9781db955042f8c196667 Mon Sep 17 00:00:00 2001 From: Lakshman Peethani Date: Mon, 19 Jun 2023 20:25:29 +0530 Subject: [PATCH 01/11] made few improvements. --- Commands/PublishCommand.cs | 50 ++++++++++++++++++++++----- Commands/SubscribeCommand.cs | 65 ++++++++++++++++++++++++------------ 2 files changed, 84 insertions(+), 31 deletions(-) diff --git a/Commands/PublishCommand.cs b/Commands/PublishCommand.cs index 115835f..8f50523 100644 --- a/Commands/PublishCommand.cs +++ b/Commands/PublishCommand.cs @@ -8,17 +8,29 @@ namespace mqcat.Commands; public class PublishCommand : Command { private const int ErrorExitCode = -1; - private readonly Option hostOption = new(aliases: new[] {"--host", "-h"}, description: "Host name to connect."); - private readonly Option messageOption = new(aliases: new[] {"--message", "-m"}, description: "Message to publish."); + private readonly Option hostOption = new(aliases: new[] {"--host", "--ho"}, description: "Host uri to connect."); + + private readonly Option serverOption = new(aliases: new[] {"--server", "-S", "-s"}, description: "Server name to connect."); + private readonly Option usernameOption = new(aliases: new[] {"--user", "-u"}, description: "Host name to connect."); + private readonly Option passwordOption = new(aliases: new[] {"--password", "-p"}, description: "Host name to connect."); + + private readonly Option messageOption = new(aliases: new[] {"--message", "-m"}, description: "Message to publish"); private readonly Option fileOption = new(aliases: new[] {"--file", "--FILE", "-f"}, description: "File path, contents of the file will be published."); - private readonly Option exchangeOption = new(aliases: new[] {"--exchange", "-e"}, description: "Name of exchange to publish into."); - private readonly Option routingKeyOption = new(aliases: new[] {"--routing-key", "-r"}, description: "Routing key to publish message."); + private readonly Option exchangeOption = new(aliases: new[] {"--exchange", "-e"}, description: "Message to publish"); + private readonly Option routingKeyOption = new(aliases: new[] {"--routing-key", "-r"}, description: "Message to publish"); + private readonly Option peerVerifyOption = + new(aliases: new[] { "--disable-ssl-verify", "--dsv", "-d" }, "Disable SSL peer verify."); public PublishCommand() : base("publish", "Publishes messages to queue") { - hostOption.IsRequired=true; - hostOption.AddCompletions(new[] { "amqp://guest:guest@127.0.0.1:5672" }); + // hostOption.IsRequired=true; + hostOption.AddCompletions(new[] { "amqp://guest:guest@127.0.0.1:5672[/vhost]" }); this.AddOption(hostOption); + + serverOption.AddCompletions(new[] { "localhost" }); + this.AddOption(serverOption); + this.AddOption(usernameOption); + this.AddOption(passwordOption); exchangeOption.IsRequired=true; this.AddOption(exchangeOption); @@ -31,15 +43,35 @@ public PublishCommand() : base("publish", "Publishes messages to queue") fileOption.IsRequired = false; this.AddOption(fileOption); + + peerVerifyOption.IsRequired = false; + peerVerifyOption.SetDefaultValue(false); + this.AddOption(peerVerifyOption); - this.SetHandler(Publish, hostOption, exchangeOption, routingKeyOption, messageOption, fileOption); + this.SetHandler(Publish, hostOption, exchangeOption, routingKeyOption, messageOption, fileOption, peerVerifyOption); } - void Publish(string host, string exchangeName, string routingKey, string message, FileInfo? fileInfo) + void Publish(string host, string exchangeName, string routingKey, string message, FileInfo? fileInfo, bool peerVerifyDisabled) { Utils.LogInfo($" {host}, {exchangeName}, {routingKey}"); + var hostUri = new Uri(host); + // var sslSettings = new SslOption + // { + // Enabled = true, + // ServerName = hostUri.DnsSafeHost, + // Version = SslProtocols.Tls12 + // }; + // + // if(peerVerifyDisabled) + // { + // sslSettings.CertificateValidationCallback += (sender, certificate, chain, errors) => + // { + // return true; + // }; + // } + + var factory = new ConnectionFactory() { Uri=hostUri }; - var factory = new ConnectionFactory() { Uri=new Uri(host) }; using var connection = factory.CreateConnection(); if(string.IsNullOrEmpty(message) && fileInfo==null && !Console.IsInputRedirected) diff --git a/Commands/SubscribeCommand.cs b/Commands/SubscribeCommand.cs index c4df3ad..8d3d553 100644 --- a/Commands/SubscribeCommand.cs +++ b/Commands/SubscribeCommand.cs @@ -7,11 +7,13 @@ namespace mqcat.Commands; public class SubscribeCommand : Command { - private readonly Option hostOption = new(aliases: new[] {"--host", "-h"}, description: "Host name to connect."); + private readonly Option hostOption = new(aliases: new[] {"--host", "--ho"}, description: "Host name to connect."); private readonly Option queueOption = new(aliases: new[] {"--queue", "-q"}, description: "Queue name to publish message into."); private readonly Option isDurableOption = new(aliases: new[] {"--durable", "-d"}, "Is queue durable."); - // private readonly Option waitOption = new(aliases: new[] {"--wait", "-w"}, "should command wait for new messages."); - + private readonly Option waitOption = new(aliases: new[] {"--wait", "-w"}, "should command wait for new messages."); + + private bool keepRunning=true; + public SubscribeCommand() : base("subscribe", "Subscribes to message queue.") { hostOption.IsRequired=true; @@ -22,32 +24,51 @@ public SubscribeCommand() : base("subscribe", "Subscribes to message queue.") isDurableOption.SetDefaultValue(value: false); this.AddOption(isDurableOption); - this.SetHandler(handle: Subscribe, hostOption, queueOption, isDurableOption); + waitOption.SetDefaultValue(false); + this.AddOption(waitOption); + + this.SetHandler(handle: Subscribe, hostOption, queueOption, isDurableOption, waitOption); } - private void Subscribe(string host, string queueName, bool isDurable) + private void Subscribe(string host, string queueName, bool isDurable, bool shouldWait) { var factory = new ConnectionFactory() { Uri = new Uri(host) }; using (var connection = factory.CreateConnection()) { - using var channel = connection.CreateModel(); - channel.QueueDeclare(queue: queueName, durable: isDurable, exclusive: false, autoDelete: false, arguments: null); - - // Set up a consumer to receive messages - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (_, ea) => + using (var channel = connection.CreateModel()) { - byte[] body = ea.Body.ToArray(); - string message = Encoding.UTF8.GetString(body); - Console.WriteLine("Received message: {0}", message); - }; - - // channel.QueueBind(queueName, "demo-exchange", "/temp/#"); - // Start consuming messages from the queue - channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); - } + channel.QueueDeclare(queue: queueName, durable: isDurable, exclusive: false, autoDelete: false, + arguments: null); + + // Set up a consumer to receive messages + var consumer = new EventingBasicConsumer(channel); + consumer.Received += (_, ea) => + { + byte[] body = ea.Body.ToArray(); + string message = Encoding.UTF8.GetString(body); + Console.WriteLine("Received message: {0}", message); + }; - Console.WriteLine("Press any key to exit..."); - Console.ReadLine(); + Console.CancelKeyPress += (sender, e) => + { + Console.WriteLine("Closing Connection."); + e.Cancel = true; + this.keepRunning = false; + }; + + // channel.QueueBind(queueName, "demo-exchange", "/temp/#"); + // Start consuming messages from the queue + channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); + + if (shouldWait) + { + Console.WriteLine("Waiting for new messages, Press Ctrl+C to exit..."); + while (keepRunning && channel.IsOpen) Thread.Sleep(2); + } + + Thread.Sleep(500); + connection.Close(); + } + } } } \ No newline at end of file From c2e5f442c02072ad131c7567e5602f6ce78f5fee Mon Sep 17 00:00:00 2001 From: Lakshman Peethani Date: Mon, 19 Jun 2023 21:35:25 +0530 Subject: [PATCH 02/11] implement parameter binding. --- Bindings/Host.cs | 9 ++++ Bindings/HostBinder.cs | 31 +++++++++++ Commands/PublishCommand.cs | 99 ++++++++++++++++++++++-------------- Commands/SubscribeCommand.cs | 39 +++++++------- 4 files changed, 122 insertions(+), 56 deletions(-) create mode 100644 Bindings/Host.cs create mode 100644 Bindings/HostBinder.cs diff --git a/Bindings/Host.cs b/Bindings/Host.cs new file mode 100644 index 0000000..99f2cae --- /dev/null +++ b/Bindings/Host.cs @@ -0,0 +1,9 @@ +namespace mqcat.Bindings; + +public class Host +{ + public string? ServerName { get; set; } + public string? UserName { get; set; } + public string? Password { get; set; } + public string? Vhost { get; set; } +} \ No newline at end of file diff --git a/Bindings/HostBinder.cs b/Bindings/HostBinder.cs new file mode 100644 index 0000000..e9ded17 --- /dev/null +++ b/Bindings/HostBinder.cs @@ -0,0 +1,31 @@ +using System.CommandLine; +using System.CommandLine.Binding; + +namespace mqcat.Bindings; + +public class HostBinder : BinderBase +{ + private readonly Option _serverOption; + private readonly Option _vhostOption; + private readonly Option _usernameOption; + private readonly Option _passwordOption; + + public HostBinder(Option server, Option vhost, Option username, Option password) + { + this._serverOption = server; + this._vhostOption = vhost; + this._usernameOption = username; + this._passwordOption = password; + } + + protected override Host GetBoundValue(BindingContext bindingContext) + { + return new Host + { + ServerName = bindingContext.ParseResult.GetValueForOption(_serverOption), + Vhost = bindingContext.ParseResult.GetValueForOption(_vhostOption), + UserName = bindingContext.ParseResult.GetValueForOption(_usernameOption), + Password = bindingContext.ParseResult.GetValueForOption(_passwordOption) + }; + } +} \ No newline at end of file diff --git a/Commands/PublishCommand.cs b/Commands/PublishCommand.cs index 8f50523..f6ce601 100644 --- a/Commands/PublishCommand.cs +++ b/Commands/PublishCommand.cs @@ -1,5 +1,6 @@ using System.CommandLine; using System.Text; +using mqcat.Bindings; using mqcat.Util; using RabbitMQ.Client; @@ -8,53 +9,57 @@ namespace mqcat.Commands; public class PublishCommand : Command { private const int ErrorExitCode = -1; - private readonly Option hostOption = new(aliases: new[] {"--host", "--ho"}, description: "Host uri to connect."); + private readonly Option _hostUriOption = new(aliases: new[] {"--host", "--ho"}, description: "Host uri to connect."); - private readonly Option serverOption = new(aliases: new[] {"--server", "-S", "-s"}, description: "Server name to connect."); - private readonly Option usernameOption = new(aliases: new[] {"--user", "-u"}, description: "Host name to connect."); - private readonly Option passwordOption = new(aliases: new[] {"--password", "-p"}, description: "Host name to connect."); + private readonly Option _serverOption = new(aliases: new[] {"--server", "-S", "-s"}, description: "Server name to connect."); + private readonly Option _vhostOption = new(aliases: new[] {"--vhost", "-V", "-v"}, description: "vhost to connect."); + private readonly Option _usernameOption = new(aliases: new[] {"--user", "-u"}, description: "Host name to connect."); + private readonly Option _passwordOption = new(aliases: new[] {"--password", "-p"}, description: "Host name to connect."); - private readonly Option messageOption = new(aliases: new[] {"--message", "-m"}, description: "Message to publish"); - private readonly Option fileOption = new(aliases: new[] {"--file", "--FILE", "-f"}, description: "File path, contents of the file will be published."); - private readonly Option exchangeOption = new(aliases: new[] {"--exchange", "-e"}, description: "Message to publish"); - private readonly Option routingKeyOption = new(aliases: new[] {"--routing-key", "-r"}, description: "Message to publish"); - private readonly Option peerVerifyOption = + private readonly Option _messageOption = new(aliases: new[] {"--message", "-m"}, description: "Message to publish"); + private readonly Option _fileOption = new(aliases: new[] {"--file", "--FILE", "-f"}, description: "File path, contents of the file will be published."); + private readonly Option _exchangeOption = new(aliases: new[] {"--exchange", "-e"}, description: "Message to publish"); + private readonly Option _routingKeyOption = new(aliases: new[] {"--routing-key", "-r"}, description: "Message to publish"); + private readonly Option _peerVerifyOption = new(aliases: new[] { "--disable-ssl-verify", "--dsv", "-d" }, "Disable SSL peer verify."); public PublishCommand() : base("publish", "Publishes messages to queue") { // hostOption.IsRequired=true; - hostOption.AddCompletions(new[] { "amqp://guest:guest@127.0.0.1:5672[/vhost]" }); - this.AddOption(hostOption); + _hostUriOption.AddCompletions(new[] { "amqp://guest:guest@127.0.0.1:5672[/vhost]" }); + this.AddOption(_hostUriOption); - serverOption.AddCompletions(new[] { "localhost" }); - this.AddOption(serverOption); - this.AddOption(usernameOption); - this.AddOption(passwordOption); + _serverOption.AddCompletions(new[] { "localhost" }); + this.AddOption(_serverOption); + this.AddOption(_vhostOption); + this.AddOption(_usernameOption); + this.AddOption(_passwordOption); - exchangeOption.IsRequired=true; - this.AddOption(exchangeOption); + _exchangeOption.IsRequired=true; + this.AddOption(_exchangeOption); - routingKeyOption.IsRequired=true; - this.AddOption(routingKeyOption); + _routingKeyOption.IsRequired=true; + this.AddOption(_routingKeyOption); - messageOption.IsRequired=false; - this.AddOption(messageOption); + _messageOption.IsRequired=false; + this.AddOption(_messageOption); - fileOption.IsRequired = false; - this.AddOption(fileOption); + _fileOption.IsRequired = false; + this.AddOption(_fileOption); - peerVerifyOption.IsRequired = false; - peerVerifyOption.SetDefaultValue(false); - this.AddOption(peerVerifyOption); + _peerVerifyOption.IsRequired = false; + _peerVerifyOption.SetDefaultValue(value: false); + this.AddOption(_peerVerifyOption); - this.SetHandler(Publish, hostOption, exchangeOption, routingKeyOption, messageOption, fileOption, peerVerifyOption); + this.SetHandler(Publish, _hostUriOption, new HostBinder(_serverOption, _vhostOption, _usernameOption, _passwordOption), _exchangeOption, _routingKeyOption, _messageOption, _fileOption, _peerVerifyOption); } - void Publish(string host, string exchangeName, string routingKey, string message, FileInfo? fileInfo, bool peerVerifyDisabled) + void Publish(string hostUri, Host server, string exchangeName, string routingKey, string message, FileInfo? fileInfo, bool peerVerifyDisabled) { - Utils.LogInfo($" {host}, {exchangeName}, {routingKey}"); - var hostUri = new Uri(host); + Utils.LogInfo($" {hostUri}, {exchangeName}, {routingKey}"); + + var factory = new ConnectionFactory(); + // var sslSettings = new SslOption // { // Enabled = true, @@ -62,15 +67,33 @@ void Publish(string host, string exchangeName, string routingKey, string message // Version = SslProtocols.Tls12 // }; // - // if(peerVerifyDisabled) - // { - // sslSettings.CertificateValidationCallback += (sender, certificate, chain, errors) => - // { - // return true; - // }; - // } - var factory = new ConnectionFactory() { Uri=hostUri }; + if(peerVerifyDisabled) + { + factory.Ssl.CertificateValidationCallback += (sender, certificate, chain, errors) => + { + return true; + }; + } + + + if (!string.IsNullOrEmpty(hostUri)) + { + factory.Uri = new Uri(hostUri); + } else if (!string.IsNullOrEmpty(server.ServerName)) + { + factory.HostName = server.ServerName; + } + else + { + Utils.LogError($"Error:: neither --host nor --server supplied, one of them is mandatory."); + } + + if (!string.IsNullOrEmpty(server.UserName) && !string.IsNullOrEmpty(server.Password)) + { + factory.UserName = server.UserName; + factory.Password = server.Password; + } using var connection = factory.CreateConnection(); diff --git a/Commands/SubscribeCommand.cs b/Commands/SubscribeCommand.cs index 8d3d553..788b582 100644 --- a/Commands/SubscribeCommand.cs +++ b/Commands/SubscribeCommand.cs @@ -7,27 +7,27 @@ namespace mqcat.Commands; public class SubscribeCommand : Command { - private readonly Option hostOption = new(aliases: new[] {"--host", "--ho"}, description: "Host name to connect."); - private readonly Option queueOption = new(aliases: new[] {"--queue", "-q"}, description: "Queue name to publish message into."); - private readonly Option isDurableOption = new(aliases: new[] {"--durable", "-d"}, "Is queue durable."); - private readonly Option waitOption = new(aliases: new[] {"--wait", "-w"}, "should command wait for new messages."); + private readonly Option _hostOption = new(aliases: new[] {"--host", "--ho"}, description: "Host name to connect."); + private readonly Option _queueOption = new(aliases: new[] {"--queue", "-q"}, description: "Queue name to publish message into."); + private readonly Option _isDurableOption = new(aliases: new[] {"--durable", "-d"}, "Is queue durable."); + private readonly Option _waitOption = new(aliases: new[] {"--wait", "-w"}, "should command wait for new messages."); - private bool keepRunning=true; + private bool _keepRunning=true; public SubscribeCommand() : base("subscribe", "Subscribes to message queue.") { - hostOption.IsRequired=true; - this.AddOption(hostOption); - queueOption.IsRequired=true; - this.AddOption(queueOption); + _hostOption.IsRequired=true; + this.AddOption(_hostOption); + _queueOption.IsRequired=true; + this.AddOption(_queueOption); - isDurableOption.SetDefaultValue(value: false); - this.AddOption(isDurableOption); + _isDurableOption.SetDefaultValue(value: false); + this.AddOption(_isDurableOption); - waitOption.SetDefaultValue(false); - this.AddOption(waitOption); + _waitOption.SetDefaultValue(value: false); + this.AddOption(_waitOption); - this.SetHandler(handle: Subscribe, hostOption, queueOption, isDurableOption, waitOption); + this.SetHandler(handle: Subscribe, _hostOption, _queueOption, _isDurableOption, _waitOption); } private void Subscribe(string host, string queueName, bool isDurable, bool shouldWait) @@ -53,7 +53,7 @@ private void Subscribe(string host, string queueName, bool isDurable, bool shoul { Console.WriteLine("Closing Connection."); e.Cancel = true; - this.keepRunning = false; + this._keepRunning = false; }; // channel.QueueBind(queueName, "demo-exchange", "/temp/#"); @@ -63,10 +63,13 @@ private void Subscribe(string host, string queueName, bool isDurable, bool shoul if (shouldWait) { Console.WriteLine("Waiting for new messages, Press Ctrl+C to exit..."); - while (keepRunning && channel.IsOpen) Thread.Sleep(2); + while (_keepRunning && channel.IsOpen) Thread.Sleep(2); + } + else + { + // wait for sometime before closing to complete reading messages. + Thread.Sleep(500); } - - Thread.Sleep(500); connection.Close(); } } From d145b95a15a9c30e3e321b3b9a5b291ed2446393 Mon Sep 17 00:00:00 2001 From: Lakshman Peethani Date: Mon, 19 Jun 2023 23:14:02 +0530 Subject: [PATCH 03/11] refactored code and added sonar analysis. --- .github/workflows/build.yml | 48 ++++++++++++++++++++++ Bindings/Host.cs | 1 + Bindings/HostBinder.cs | 6 ++- Commands/PublishCommand.cs | 80 ++++++++++++++++++++++--------------- README.md | 72 ++++++++++++++++++++++++++++++++- Util/Utils.cs | 54 ++++++++++++++++++++++++- 6 files changed, 225 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6e59baa --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,48 @@ +name: SonarCloud +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: Build and analyze + runs-on: windows-latest + steps: + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: 11 + distribution: 'zulu' # Alternative distribution options are available. + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Cache SonarCloud packages + uses: actions/cache@v3 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache SonarCloud scanner + id: cache-sonar-scanner + uses: actions/cache@v3 + with: + path: .\.sonar\scanner + key: ${{ runner.os }}-sonar-scanner + restore-keys: ${{ runner.os }}-sonar-scanner + - name: Install SonarCloud scanner + if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' + shell: powershell + run: | + New-Item -Path .\.sonar\scanner -ItemType Directory + dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + shell: powershell + run: | + .\.sonar\scanner\dotnet-sonarscanner begin /k:"plkumar_mqcat" /o:"plkumar" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" + dotnet build + .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file diff --git a/Bindings/Host.cs b/Bindings/Host.cs index 99f2cae..e9b30b3 100644 --- a/Bindings/Host.cs +++ b/Bindings/Host.cs @@ -3,6 +3,7 @@ namespace mqcat.Bindings; public class Host { public string? ServerName { get; set; } + public int Port { get; set; } public string? UserName { get; set; } public string? Password { get; set; } public string? Vhost { get; set; } diff --git a/Bindings/HostBinder.cs b/Bindings/HostBinder.cs index e9ded17..a42ef05 100644 --- a/Bindings/HostBinder.cs +++ b/Bindings/HostBinder.cs @@ -1,18 +1,21 @@ using System.CommandLine; using System.CommandLine.Binding; +using System.Reflection; namespace mqcat.Bindings; public class HostBinder : BinderBase { private readonly Option _serverOption; + private readonly Option _portOption; private readonly Option _vhostOption; private readonly Option _usernameOption; private readonly Option _passwordOption; - public HostBinder(Option server, Option vhost, Option username, Option password) + public HostBinder(Option server, Option port, Option vhost, Option username, Option password) { this._serverOption = server; + this._portOption = port; this._vhostOption = vhost; this._usernameOption = username; this._passwordOption = password; @@ -23,6 +26,7 @@ protected override Host GetBoundValue(BindingContext bindingContext) return new Host { ServerName = bindingContext.ParseResult.GetValueForOption(_serverOption), + Port = bindingContext.ParseResult.GetValueForOption(_portOption), Vhost = bindingContext.ParseResult.GetValueForOption(_vhostOption), UserName = bindingContext.ParseResult.GetValueForOption(_usernameOption), Password = bindingContext.ParseResult.GetValueForOption(_passwordOption) diff --git a/Commands/PublishCommand.cs b/Commands/PublishCommand.cs index f6ce601..ddee1b3 100644 --- a/Commands/PublishCommand.cs +++ b/Commands/PublishCommand.cs @@ -9,17 +9,18 @@ namespace mqcat.Commands; public class PublishCommand : Command { private const int ErrorExitCode = -1; - private readonly Option _hostUriOption = new(aliases: new[] {"--host", "--ho"}, description: "Host uri to connect."); - - private readonly Option _serverOption = new(aliases: new[] {"--server", "-S", "-s"}, description: "Server name to connect."); - private readonly Option _vhostOption = new(aliases: new[] {"--vhost", "-V", "-v"}, description: "vhost to connect."); - private readonly Option _usernameOption = new(aliases: new[] {"--user", "-u"}, description: "Host name to connect."); - private readonly Option _passwordOption = new(aliases: new[] {"--password", "-p"}, description: "Host name to connect."); - - private readonly Option _messageOption = new(aliases: new[] {"--message", "-m"}, description: "Message to publish"); - private readonly Option _fileOption = new(aliases: new[] {"--file", "--FILE", "-f"}, description: "File path, contents of the file will be published."); - private readonly Option _exchangeOption = new(aliases: new[] {"--exchange", "-e"}, description: "Message to publish"); - private readonly Option _routingKeyOption = new(aliases: new[] {"--routing-key", "-r"}, description: "Message to publish"); + private readonly Option _hostUriOption = new(aliases: new[] { "--host", "--ho" }, description: "Host uri to connect."); + + private readonly Option _serverOption = new(aliases: new[] { "--server", "-S", "-s" }, description: "Server name to connect."); + private readonly Option _portOption = new(aliases: new[] { "--port", "-P" }, description: "Port number to connect."); + private readonly Option _vhostOption = new(aliases: new[] { "--vhost", "-V", "-v" }, description: "vhost to connect."); + private readonly Option _usernameOption = new(aliases: new[] { "--user", "-u" }, description: "Host name to connect."); + private readonly Option _passwordOption = new(aliases: new[] { "--password", "-p" }, description: "Host name to connect."); + + private readonly Option _messageOption = new(aliases: new[] { "--message", "-m" }, description: "Message to publish"); + private readonly Option _fileOption = new(aliases: new[] { "--file", "--FILE", "-f" }, description: "File path, contents of the file will be published."); + private readonly Option _exchangeOption = new(aliases: new[] { "--exchange", "-e" }, description: "Message to publish"); + private readonly Option _routingKeyOption = new(aliases: new[] { "--routing-key", "-r" }, description: "Message to publish"); private readonly Option _peerVerifyOption = new(aliases: new[] { "--disable-ssl-verify", "--dsv", "-d" }, "Disable SSL peer verify."); @@ -31,17 +32,22 @@ public PublishCommand() : base("publish", "Publishes messages to queue") _serverOption.AddCompletions(new[] { "localhost" }); this.AddOption(_serverOption); + _portOption.SetDefaultValue(value: 5672); + this.AddOption(_portOption); + _vhostOption.AddCompletions(new[] { "/", "/vhost1" }); + _vhostOption.SetDefaultValue(value: "/"); this.AddOption(_vhostOption); this.AddOption(_usernameOption); this.AddOption(_passwordOption); - - _exchangeOption.IsRequired=true; + + + _exchangeOption.IsRequired = true; this.AddOption(_exchangeOption); - - _routingKeyOption.IsRequired=true; + + _routingKeyOption.IsRequired = true; this.AddOption(_routingKeyOption); - _messageOption.IsRequired=false; + _messageOption.IsRequired = false; this.AddOption(_messageOption); _fileOption.IsRequired = false; @@ -50,14 +56,14 @@ public PublishCommand() : base("publish", "Publishes messages to queue") _peerVerifyOption.IsRequired = false; _peerVerifyOption.SetDefaultValue(value: false); this.AddOption(_peerVerifyOption); - - this.SetHandler(Publish, _hostUriOption, new HostBinder(_serverOption, _vhostOption, _usernameOption, _passwordOption), _exchangeOption, _routingKeyOption, _messageOption, _fileOption, _peerVerifyOption); + + this.SetHandler(Publish, _hostUriOption, new HostBinder(_serverOption, _portOption, _vhostOption, _usernameOption, _passwordOption), _exchangeOption, _routingKeyOption, _messageOption, _fileOption, _peerVerifyOption); } - void Publish(string hostUri, Host server, string exchangeName, string routingKey, string message, FileInfo? fileInfo, bool peerVerifyDisabled) + Task Publish(string hostUri, Host server, string exchangeName, string routingKey, string message, FileInfo? fileInfo, bool peerVerifyDisabled) { Utils.LogInfo($" {hostUri}, {exchangeName}, {routingKey}"); - + var factory = new ConnectionFactory(); // var sslSettings = new SslOption @@ -67,8 +73,8 @@ void Publish(string hostUri, Host server, string exchangeName, string routingKey // Version = SslProtocols.Tls12 // }; // - - if(peerVerifyDisabled) + + if (peerVerifyDisabled) { factory.Ssl.CertificateValidationCallback += (sender, certificate, chain, errors) => { @@ -76,33 +82,38 @@ void Publish(string hostUri, Host server, string exchangeName, string routingKey }; } - if (!string.IsNullOrEmpty(hostUri)) { - factory.Uri = new Uri(hostUri); - } else if (!string.IsNullOrEmpty(server.ServerName)) + factory.Uri = new Uri(hostUri); + } + else if (!string.IsNullOrEmpty(server.ServerName)) { factory.HostName = server.ServerName; + factory.Port = server.Port; } else { - Utils.LogError($"Error:: neither --host nor --server supplied, one of them is mandatory."); + Utils.LogError("neither --host nor --server supplied, one of them is mandatory."); } - if (!string.IsNullOrEmpty(server.UserName) && !string.IsNullOrEmpty(server.Password)) + if (!string.IsNullOrEmpty(server.UserName)) { factory.UserName = server.UserName; + if(string.IsNullOrEmpty(server.Password)) + { + server.Password = Utils.ReadPassword(); + } factory.Password = server.Password; } using var connection = factory.CreateConnection(); - - if(string.IsNullOrEmpty(message) && fileInfo==null && !Console.IsInputRedirected) + + if (string.IsNullOrEmpty(message) && fileInfo == null && !Console.IsInputRedirected) { - Utils.LogError("ERROR :: Neither message nor file argument specified and no input redirection detected."); + Utils.LogError("Neither message nor file argument specified and no input redirection detected."); Environment.Exit(ErrorExitCode); } - + if (string.IsNullOrEmpty(message) && Console.IsInputRedirected) { Console.SetIn(new StreamReader(Console.OpenStandardInput(8192))); // This will allow input >256 chars @@ -110,7 +121,8 @@ void Publish(string hostUri, Host server, string exchangeName, string routingKey { message = Console.In.ReadToEnd(); } - } else if (fileInfo != null) + } + else if (fileInfo != null) { message = File.ReadAllText(fileInfo.FullName); } @@ -127,6 +139,8 @@ void Publish(string hostUri, Host server, string exchangeName, string routingKey basicProperties: null, body: body); - Utils.LogSuccess($"Message sent:\n {message}" ); + Utils.LogSuccess($"Message sent:\n {message}"); + + return Task.FromResult(100); } } \ No newline at end of file diff --git a/README.md b/README.md index f022b97..9cfbf37 100644 --- a/README.md +++ b/README.md @@ -1 +1,71 @@ -# mqcat +# mqcat - a simple tool to publish and subscribe messages from AMQP Broker queues + +## Build + +```bash +dotnet build +``` + +## Run + +```bash +dotnet run --project src/mqcat/mqcat.csproj -- --help +``` + +## Usage + +```bash +mqcat [command] [options] +``` + +### Commands + +#### publish + +Publish a message to a queue. + +```bash +mqcat publish [options] +``` + +##### Options + +| Option | Description | Default | +| --- | --- | --- | +| -h, --host | AMQP Broker host | localhost | +| -p, --port | AMQP Broker port | 5672 | +| -u, --user | AMQP Broker user | guest | +| -P, --password | AMQP Broker password | guest | +| -q, --queue | Queue name | | +| -m, --message | Message to publish | | +| -f, --file | File to publish | | +| -e, --exchange | Exchange name | | +| -r, --routing-key | Routing key | | +|--- | --- | --- | + +#### subscribe + +Subscribe to a queue and print messages to stdout. + +```bash +mqcat subscribe [options] +``` + +##### Options + +| Option | Description | Default | +| --- | --- | --- | +| -h, --host | AMQP Broker host | localhost | +| -p, --port | AMQP Broker port | 5672 | +| -u, --user | AMQP Broker user | guest | +| -P, --password | AMQP Broker password | guest | +| -q, --queue | Queue name | | +| -e, --exchange | Exchange name | | +| --- | --- | --- | + +## License + +MIT +``` +``` + diff --git a/Util/Utils.cs b/Util/Utils.cs index f8087ec..c1b3330 100644 --- a/Util/Utils.cs +++ b/Util/Utils.cs @@ -1,3 +1,5 @@ +using System.Security; + namespace mqcat.Util; public class Utils @@ -5,7 +7,7 @@ public class Utils public static void LogError(string text) { Console.ForegroundColor = ConsoleColor.Red; - Console.Error.WriteLine(text); + Console.Error.WriteLine($"ERROR :: {text}"); Console.ResetColor(); } @@ -22,4 +24,54 @@ public static void LogSuccess(string text) Console.WriteLine(text); Console.ResetColor(); } + + // public SecureString GetPassword() + // { + // var pwd = new SecureString(); + // while (true) + // { + // ConsoleKeyInfo i = Console.ReadKey(true); + // if (i.Key == ConsoleKey.Enter) + // { + // break; + // } + // else if (i.Key == ConsoleKey.Backspace) + // { + // if (pwd.Length > 0) + // { + // pwd.RemoveAt(pwd.Length - 1); + // Console.Write("\b \b"); + // } + // } + // else if (i.KeyChar != '\u0000' ) // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc + // { + // pwd.AppendChar(i.KeyChar); + // Console.Write("*"); + // } + // } + // return pwd; + // } + public static string ReadPassword() + { + var pass = string.Empty; + ConsoleKey key; + do + { + var keyInfo = Console.ReadKey(intercept: true); + key = keyInfo.Key; + + if (key == ConsoleKey.Backspace && pass.Length > 0) + { + Console.Write("\b \b"); + pass = pass[0..^1]; + } + else if (!char.IsControl(keyInfo.KeyChar) ) + { + Console.Write("*"); + pass += keyInfo.KeyChar; + } + } while (key != ConsoleKey.Enter); + + return pass; + } } \ No newline at end of file From 3d82bf199259ae23800f5f624dc446584f37fa5e Mon Sep 17 00:00:00 2001 From: Lakshman Peethani Date: Mon, 19 Jun 2023 23:18:13 +0530 Subject: [PATCH 04/11] updated global json --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index dad2db5..7cd6a1f 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.0", + "version": "7.0.0", "rollForward": "latestMajor", "allowPrerelease": true } From 9ded43112e487cfb469ecef715732adec852c2e8 Mon Sep 17 00:00:00 2001 From: Lakshman Peethani Date: Mon, 19 Jun 2023 23:21:10 +0530 Subject: [PATCH 05/11] updated sdk version to 7.0 --- mqcat.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mqcat.csproj b/mqcat.csproj index 8794153..b13cde9 100644 --- a/mqcat.csproj +++ b/mqcat.csproj @@ -5,7 +5,7 @@ Exe - net8.0 + net7.0 enable enable true From b0ab42b4224a792ac430c9d6d001fd3e1202fb06 Mon Sep 17 00:00:00 2001 From: Lakshman Peethani Date: Tue, 20 Jun 2023 19:34:06 +0530 Subject: [PATCH 06/11] Some minor refactoring. --- .github/workflows/build.yml | 8 ++- .vscode/settings.json | 6 +- Client/AMQPClient.cs | 33 +++++++++ Commands/PublishCommand.cs | 64 ++++++----------- Commands/SubscribeCommand.cs | 133 ++++++++++++++++++++++------------- 5 files changed, 149 insertions(+), 95 deletions(-) create mode 100644 Client/AMQPClient.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e59baa..448247b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,4 +45,10 @@ jobs: run: | .\.sonar\scanner\dotnet-sonarscanner begin /k:"plkumar_mqcat" /o:"plkumar" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" dotnet build - .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file + .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" + - name: Build Publish + run: | + dotnet restore + dotnet build --configuration Release --no-restore + dotnet publish -c Release -o ../mqcat-linux-x64 -r linux-x64 --self-contained true + \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index f931672..8bc32c1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,7 @@ { - "dotnet.defaultSolution": "mqcat.sln" + "dotnet.defaultSolution": "mqcat.sln", + "sonarlint.connectedMode.project": { + "connectionId": "plkumar", + "projectKey": "plkumar_mqcat" + } } \ No newline at end of file diff --git a/Client/AMQPClient.cs b/Client/AMQPClient.cs new file mode 100644 index 0000000..7378b0a --- /dev/null +++ b/Client/AMQPClient.cs @@ -0,0 +1,33 @@ + +using mqcat.Bindings; +using mqcat.Util; +using RabbitMQ.Client; + +namespace mqcat.Client; + +class AMQPClient +{ + public static ConnectionFactory GetConnectionFactory(string host) => new() { Uri = new Uri(host) }; + + public static ConnectionFactory GetConnectionFactory(Host host) + { + var factory = new ConnectionFactory(); + if (!string.IsNullOrEmpty(host.ServerName)) + { + factory.HostName = host.ServerName; + factory.Port = host.Port; + } + + if (!string.IsNullOrEmpty(host.UserName)) + { + factory.UserName = host.UserName; + if (string.IsNullOrEmpty(host.Password)) + { + host.Password = Utils.ReadPassword(); + } + factory.Password = host.Password; + } + + return factory; + } +} \ No newline at end of file diff --git a/Commands/PublishCommand.cs b/Commands/PublishCommand.cs index ddee1b3..24eccb2 100644 --- a/Commands/PublishCommand.cs +++ b/Commands/PublishCommand.cs @@ -1,12 +1,13 @@ using System.CommandLine; using System.Text; using mqcat.Bindings; +using mqcat.Client; using mqcat.Util; using RabbitMQ.Client; namespace mqcat.Commands; -public class PublishCommand : Command +public sealed class PublishCommand : Command { private const int ErrorExitCode = -1; private readonly Option _hostUriOption = new(aliases: new[] { "--host", "--ho" }, description: "Host uri to connect."); @@ -21,8 +22,8 @@ public class PublishCommand : Command private readonly Option _fileOption = new(aliases: new[] { "--file", "--FILE", "-f" }, description: "File path, contents of the file will be published."); private readonly Option _exchangeOption = new(aliases: new[] { "--exchange", "-e" }, description: "Message to publish"); private readonly Option _routingKeyOption = new(aliases: new[] { "--routing-key", "-r" }, description: "Message to publish"); - private readonly Option _peerVerifyOption = - new(aliases: new[] { "--disable-ssl-verify", "--dsv", "-d" }, "Disable SSL peer verify."); + // private readonly Option _peerVerifyOption = + // new(aliases: new[] { "--disable-ssl-verify", "--dsv", "-d" }, "Disable SSL peer verify."); public PublishCommand() : base("publish", "Publishes messages to queue") { @@ -53,57 +54,32 @@ public PublishCommand() : base("publish", "Publishes messages to queue") _fileOption.IsRequired = false; this.AddOption(_fileOption); - _peerVerifyOption.IsRequired = false; - _peerVerifyOption.SetDefaultValue(value: false); - this.AddOption(_peerVerifyOption); + // _peerVerifyOption.IsRequired = false; + // _peerVerifyOption.SetDefaultValue(value: false); + // this.AddOption(_peerVerifyOption); - this.SetHandler(Publish, _hostUriOption, new HostBinder(_serverOption, _portOption, _vhostOption, _usernameOption, _passwordOption), _exchangeOption, _routingKeyOption, _messageOption, _fileOption, _peerVerifyOption); + this.SetHandler(Publish, _hostUriOption, new HostBinder(_serverOption, _portOption, _vhostOption, _usernameOption, _passwordOption), _exchangeOption, _routingKeyOption, _messageOption, _fileOption); } - Task Publish(string hostUri, Host server, string exchangeName, string routingKey, string message, FileInfo? fileInfo, bool peerVerifyDisabled) + Task Publish(string hostUri, Host host, string exchangeName, string routingKey, string message, FileInfo? fileInfo) { - Utils.LogInfo($" {hostUri}, {exchangeName}, {routingKey}"); + ConnectionFactory? factory = null; - var factory = new ConnectionFactory(); - - // var sslSettings = new SslOption - // { - // Enabled = true, - // ServerName = hostUri.DnsSafeHost, - // Version = SslProtocols.Tls12 - // }; - // - - if (peerVerifyDisabled) + if (string.IsNullOrEmpty(hostUri)) { - factory.Ssl.CertificateValidationCallback += (sender, certificate, chain, errors) => + if (string.IsNullOrEmpty(host.ServerName)) { - return true; - }; - } - - if (!string.IsNullOrEmpty(hostUri)) - { - factory.Uri = new Uri(hostUri); - } - else if (!string.IsNullOrEmpty(server.ServerName)) - { - factory.HostName = server.ServerName; - factory.Port = server.Port; + Utils.LogError("neither --host nor --server supplied, one of them is mandatory."); + Task.FromResult(ErrorExitCode); + } + else + { + factory = AMQPClient.GetConnectionFactory(host); + } } else { - Utils.LogError("neither --host nor --server supplied, one of them is mandatory."); - } - - if (!string.IsNullOrEmpty(server.UserName)) - { - factory.UserName = server.UserName; - if(string.IsNullOrEmpty(server.Password)) - { - server.Password = Utils.ReadPassword(); - } - factory.Password = server.Password; + factory = AMQPClient.GetConnectionFactory(hostUri); } using var connection = factory.CreateConnection(); diff --git a/Commands/SubscribeCommand.cs b/Commands/SubscribeCommand.cs index 788b582..bfdceee 100644 --- a/Commands/SubscribeCommand.cs +++ b/Commands/SubscribeCommand.cs @@ -1,24 +1,45 @@ using System.CommandLine; using System.Text; +using mqcat.Bindings; +using mqcat.Client; +using mqcat.Util; using RabbitMQ.Client; using RabbitMQ.Client.Events; namespace mqcat.Commands; -public class SubscribeCommand : Command +public sealed class SubscribeCommand : Command { - private readonly Option _hostOption = new(aliases: new[] {"--host", "--ho"}, description: "Host name to connect."); - private readonly Option _queueOption = new(aliases: new[] {"--queue", "-q"}, description: "Queue name to publish message into."); - private readonly Option _isDurableOption = new(aliases: new[] {"--durable", "-d"}, "Is queue durable."); - private readonly Option _waitOption = new(aliases: new[] {"--wait", "-w"}, "should command wait for new messages."); - - private bool _keepRunning=true; + private readonly Option _hostUriOption = new(aliases: new[] { "--host", "--ho" }, description: "Host name to connect."); + private readonly Option _serverOption = new(aliases: new[] { "--server", "-S", "-s" }, description: "Server name to connect."); + private readonly Option _portOption = new(aliases: new[] { "--port", "-P" }, description: "Port number to connect."); + private readonly Option _vhostOption = new(aliases: new[] { "--vhost", "-V", "-v" }, description: "vhost to connect."); + private readonly Option _usernameOption = new(aliases: new[] { "--user", "-u" }, description: "Host name to connect."); + private readonly Option _passwordOption = new(aliases: new[] { "--password", "-p" }, description: "Host name to connect."); + + private readonly Option _queueOption = new(aliases: new[] { "--queue", "-q" }, description: "Queue name to publish message into."); + private readonly Option _isDurableOption = new(aliases: new[] { "--durable", "-d" }, "Is queue durable."); + private readonly Option _waitOption = new(aliases: new[] { "--wait", "-w" }, "should command wait for new messages."); + + private bool _keepRunning = true; + public SubscribeCommand() : base("subscribe", "Subscribes to message queue.") { - _hostOption.IsRequired=true; - this.AddOption(_hostOption); - _queueOption.IsRequired=true; + _hostUriOption.IsRequired = true; + this.AddOption(_hostUriOption); + + _serverOption.AddCompletions(new[] { "localhost" }); + this.AddOption(_serverOption); + _portOption.SetDefaultValue(value: 5672); + this.AddOption(_portOption); + _vhostOption.AddCompletions(new[] { "/", "/vhost1" }); + _vhostOption.SetDefaultValue(value: "/"); + this.AddOption(_vhostOption); + this.AddOption(_usernameOption); + this.AddOption(_passwordOption); + + _queueOption.IsRequired = true; this.AddOption(_queueOption); _isDurableOption.SetDefaultValue(value: false); @@ -26,52 +47,66 @@ public SubscribeCommand() : base("subscribe", "Subscribes to message queue.") _waitOption.SetDefaultValue(value: false); this.AddOption(_waitOption); - - this.SetHandler(handle: Subscribe, _hostOption, _queueOption, _isDurableOption, _waitOption); + + this.SetHandler(handle: Subscribe, _hostUriOption, new HostBinder(_serverOption, _portOption, _vhostOption, _usernameOption, _passwordOption), _queueOption, _isDurableOption, _waitOption); } - private void Subscribe(string host, string queueName, bool isDurable, bool shouldWait) + private Task Subscribe(string hostUri, Host host, string queueName, bool isDurable, bool shouldWait) { - var factory = new ConnectionFactory() { Uri = new Uri(host) }; + ConnectionFactory? factory = null; + + if (string.IsNullOrEmpty(hostUri)) + { + if (string.IsNullOrEmpty(host.ServerName)) + { + Utils.LogError("neither --host nor --server supplied, one of them is mandatory."); + Task.FromResult(-1); + } + else + { + factory = AMQPClient.GetConnectionFactory(host); + } + } + else + { + factory = AMQPClient.GetConnectionFactory(hostUri); + } + using (var connection = factory.CreateConnection()) { - using (var channel = connection.CreateModel()) + using var channel = connection.CreateModel(); + channel.QueueDeclare(queue: queueName, durable: isDurable, exclusive: false, autoDelete: false, + arguments: null); + + var consumer = new EventingBasicConsumer(channel); + consumer.Received += (_, ea) => + { + byte[] body = ea.Body.ToArray(); + string message = Encoding.UTF8.GetString(body); + Console.WriteLine("Received message: {0}", message); + }; + + Console.CancelKeyPress += (sender, e) => + { + Console.WriteLine("Closing Connection."); + e.Cancel = true; + this._keepRunning = false; + }; + + channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); + + if (shouldWait) + { + Console.WriteLine("Waiting for new messages, Press Ctrl+C to exit..."); + while (_keepRunning && channel.IsOpen) Thread.Sleep(2); + } + else { - channel.QueueDeclare(queue: queueName, durable: isDurable, exclusive: false, autoDelete: false, - arguments: null); - - // Set up a consumer to receive messages - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (_, ea) => - { - byte[] body = ea.Body.ToArray(); - string message = Encoding.UTF8.GetString(body); - Console.WriteLine("Received message: {0}", message); - }; - - Console.CancelKeyPress += (sender, e) => - { - Console.WriteLine("Closing Connection."); - e.Cancel = true; - this._keepRunning = false; - }; - - // channel.QueueBind(queueName, "demo-exchange", "/temp/#"); - // Start consuming messages from the queue - channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); - - if (shouldWait) - { - Console.WriteLine("Waiting for new messages, Press Ctrl+C to exit..."); - while (_keepRunning && channel.IsOpen) Thread.Sleep(2); - } - else - { - // wait for sometime before closing to complete reading messages. - Thread.Sleep(500); - } - connection.Close(); + // wait for sometime before closing to complete reading messages. + Thread.Sleep(500); } + connection.Close(); + return Task.FromResult(0); } } } \ No newline at end of file From 10b63c6de401f555c29f342891be96823c065488 Mon Sep 17 00:00:00 2001 From: Lakshman Peethani Date: Wed, 21 Jun 2023 09:24:46 +0530 Subject: [PATCH 07/11] fixed sonar bug. --- Commands/PublishCommand.cs | 2 +- Commands/SubscribeCommand.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Commands/PublishCommand.cs b/Commands/PublishCommand.cs index 24eccb2..f7ac4d2 100644 --- a/Commands/PublishCommand.cs +++ b/Commands/PublishCommand.cs @@ -70,7 +70,7 @@ Task Publish(string hostUri, Host host, string exchangeName, string routing if (string.IsNullOrEmpty(host.ServerName)) { Utils.LogError("neither --host nor --server supplied, one of them is mandatory."); - Task.FromResult(ErrorExitCode); + return Task.FromResult(ErrorExitCode); } else { diff --git a/Commands/SubscribeCommand.cs b/Commands/SubscribeCommand.cs index bfdceee..f226c41 100644 --- a/Commands/SubscribeCommand.cs +++ b/Commands/SubscribeCommand.cs @@ -60,7 +60,7 @@ private Task Subscribe(string hostUri, Host host, string queueName, bool is if (string.IsNullOrEmpty(host.ServerName)) { Utils.LogError("neither --host nor --server supplied, one of them is mandatory."); - Task.FromResult(-1); + return Task.FromResult(-1); } else { From febc429621a903bbdfe7a42fe92d1ddf7c050caf Mon Sep 17 00:00:00 2001 From: Lakshman Peethani Date: Wed, 21 Jun 2023 09:41:14 +0530 Subject: [PATCH 08/11] fixed Sonar issue. --- Program.cs | 2 +- README.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Program.cs b/Program.cs index 18f336b..f62378c 100644 --- a/Program.cs +++ b/Program.cs @@ -3,7 +3,7 @@ namespace mqcat; -class Program +public static class Program { static async Task Main(string[] args) { diff --git a/README.md b/README.md index 9cfbf37..8bf99fe 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=plkumar_mqcat&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=plkumar_mqcat) + # mqcat - a simple tool to publish and subscribe messages from AMQP Broker queues ## Build From 695289f5a64fe7f60fdcd197310b367e5ca2ba30 Mon Sep 17 00:00:00 2001 From: Lakshman Kumar Peethani Date: Wed, 21 Jun 2023 10:03:56 +0530 Subject: [PATCH 09/11] Create codeql.yml --- .github/workflows/codeql.yml | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..74eb5bd --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,77 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '27 18 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From 203e74315c1e1382b1d7ed415d3fe0feccae7878 Mon Sep 17 00:00:00 2001 From: Lakshman Peethani Date: Wed, 21 Jun 2023 11:30:13 +0530 Subject: [PATCH 10/11] some refactoring and fixing code smells --- Client/AMQPClient.cs | 2 +- Commands/PublishCommand.cs | 15 ++++++++++----- Commands/SubscribeCommand.cs | 8 +++++++- Constants.cs | 7 +++++++ 4 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 Constants.cs diff --git a/Client/AMQPClient.cs b/Client/AMQPClient.cs index 7378b0a..0b4f743 100644 --- a/Client/AMQPClient.cs +++ b/Client/AMQPClient.cs @@ -5,7 +5,7 @@ namespace mqcat.Client; -class AMQPClient +static class AMQPClient { public static ConnectionFactory GetConnectionFactory(string host) => new() { Uri = new Uri(host) }; diff --git a/Commands/PublishCommand.cs b/Commands/PublishCommand.cs index f7ac4d2..ed15a47 100644 --- a/Commands/PublishCommand.cs +++ b/Commands/PublishCommand.cs @@ -9,7 +9,6 @@ namespace mqcat.Commands; public sealed class PublishCommand : Command { - private const int ErrorExitCode = -1; private readonly Option _hostUriOption = new(aliases: new[] { "--host", "--ho" }, description: "Host uri to connect."); private readonly Option _serverOption = new(aliases: new[] { "--server", "-S", "-s" }, description: "Server name to connect."); @@ -63,14 +62,14 @@ public PublishCommand() : base("publish", "Publishes messages to queue") Task Publish(string hostUri, Host host, string exchangeName, string routingKey, string message, FileInfo? fileInfo) { - ConnectionFactory? factory = null; + ConnectionFactory? factory; if (string.IsNullOrEmpty(hostUri)) { if (string.IsNullOrEmpty(host.ServerName)) { Utils.LogError("neither --host nor --server supplied, one of them is mandatory."); - return Task.FromResult(ErrorExitCode); + return Task.FromResult(Constants.ErrorExitCode); } else { @@ -82,18 +81,24 @@ Task Publish(string hostUri, Host host, string exchangeName, string routing factory = AMQPClient.GetConnectionFactory(hostUri); } + if(factory == null) + { + Utils.LogError("Failed to create connection factory."); + return Task.FromResult(Constants.ErrorExitCode); + } + using var connection = factory.CreateConnection(); if (string.IsNullOrEmpty(message) && fileInfo == null && !Console.IsInputRedirected) { Utils.LogError("Neither message nor file argument specified and no input redirection detected."); - Environment.Exit(ErrorExitCode); + Environment.Exit(Constants.ErrorExitCode); } if (string.IsNullOrEmpty(message) && Console.IsInputRedirected) { Console.SetIn(new StreamReader(Console.OpenStandardInput(8192))); // This will allow input >256 chars - if (Console.In.Peek() != -1) + if (Console.In.Peek() != Constants.EOF) { message = Console.In.ReadToEnd(); } diff --git a/Commands/SubscribeCommand.cs b/Commands/SubscribeCommand.cs index f226c41..f6d1c4e 100644 --- a/Commands/SubscribeCommand.cs +++ b/Commands/SubscribeCommand.cs @@ -60,7 +60,7 @@ private Task Subscribe(string hostUri, Host host, string queueName, bool is if (string.IsNullOrEmpty(host.ServerName)) { Utils.LogError("neither --host nor --server supplied, one of them is mandatory."); - return Task.FromResult(-1); + return Task.FromResult(Constants.ErrorExitCode); } else { @@ -71,6 +71,12 @@ private Task Subscribe(string hostUri, Host host, string queueName, bool is { factory = AMQPClient.GetConnectionFactory(hostUri); } + + if(factory == null) + { + Utils.LogError("Failed to create connection factory."); + return Task.FromResult(Constants.ErrorExitCode); + } using (var connection = factory.CreateConnection()) { diff --git a/Constants.cs b/Constants.cs new file mode 100644 index 0000000..a037e01 --- /dev/null +++ b/Constants.cs @@ -0,0 +1,7 @@ +using System.Reflection.Metadata; + +static class Constants { + public const int ErrorExitCode = -1; + public const int SuccessExitCode = 0; + public const int EOF = -1; +} \ No newline at end of file From f441eabf2f6989eef23815acded58bf773e973d8 Mon Sep 17 00:00:00 2001 From: Lakshman Peethani Date: Wed, 21 Jun 2023 11:37:13 +0530 Subject: [PATCH 11/11] fixed sonar bug. --- Constants.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Constants.cs b/Constants.cs index a037e01..042a2bd 100644 --- a/Constants.cs +++ b/Constants.cs @@ -1,5 +1,4 @@ -using System.Reflection.Metadata; - +namespace mqcat; static class Constants { public const int ErrorExitCode = -1; public const int SuccessExitCode = 0;