Skip to content

Commit

Permalink
Merge branch 'main' of github.com:plkumar/mqcat
Browse files Browse the repository at this point in the history
  • Loading branch information
plkumar committed Oct 6, 2023
2 parents 1ae5929 + 953735b commit 371be78
Show file tree
Hide file tree
Showing 14 changed files with 520 additions and 61 deletions.
54 changes: 54 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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 }}"
- 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
77 changes: 77 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -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}}"
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"dotnet.defaultSolution": "mqcat.sln",
"sonarlint.connectedMode.project": {
"connectionId": "plkumar",
"projectKey": "plkumar_mqcat"
}
"dotnet.defaultSolution": "mqcat.sln",
"github.copilot.enable":{
"*": true,
Expand Down
10 changes: 10 additions & 0 deletions Bindings/Host.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
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; }
}
35 changes: 35 additions & 0 deletions Bindings/HostBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.CommandLine;
using System.CommandLine.Binding;
using System.Reflection;

namespace mqcat.Bindings;

public class HostBinder : BinderBase<Host>
{
private readonly Option<string> _serverOption;
private readonly Option<int> _portOption;
private readonly Option<string> _vhostOption;
private readonly Option<string> _usernameOption;
private readonly Option<string> _passwordOption;

public HostBinder(Option<string> server, Option<int> port, Option<string> vhost, Option<string> username, Option<string> password)
{
this._serverOption = server;
this._portOption = port;
this._vhostOption = vhost;
this._usernameOption = username;
this._passwordOption = password;
}

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)
};
}
}
33 changes: 33 additions & 0 deletions Client/AMQPClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

using mqcat.Bindings;
using mqcat.Util;
using RabbitMQ.Client;

namespace mqcat.Client;

static 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;
}
}
120 changes: 85 additions & 35 deletions Commands/PublishCommand.cs
Original file line number Diff line number Diff line change
@@ -1,61 +1,109 @@
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<string> hostOption = new(aliases: new[] {"--host", "-h"}, description: "Host name to connect.");
private readonly Option<string> messageOption = new(aliases: new[] {"--message", "-m"}, description: "Message to publish.");
private readonly Option<FileInfo> fileOption = new(aliases: new[] {"--file", "--FILE", "-f"}, description: "File path, contents of the file will be published.");
private readonly Option<string> exchangeOption = new(aliases: new[] {"--exchange", "-e"}, description: "Name of exchange to publish into.");
private readonly Option<string> routingKeyOption = new(aliases: new[] {"--routing-key", "-r"}, description: "Routing key to publish message.");
private readonly Option<string> _hostUriOption = new(aliases: new[] { "--host", "--ho" }, description: "Host uri to connect.");

private readonly Option<string> _serverOption = new(aliases: new[] { "--server", "-S", "-s" }, description: "Server name to connect.");
private readonly Option<int> _portOption = new(aliases: new[] { "--port", "-P" }, description: "Port number to connect.");
private readonly Option<string> _vhostOption = new(aliases: new[] { "--vhost", "-V", "-v" }, description: "vhost to connect.");
private readonly Option<string> _usernameOption = new(aliases: new[] { "--user", "-u" }, description: "Host name to connect.");
private readonly Option<string> _passwordOption = new(aliases: new[] { "--password", "-p" }, description: "Host name to connect.");

private readonly Option<string> _messageOption = new(aliases: new[] { "--message", "-m" }, description: "Message to publish");
private readonly Option<FileInfo> _fileOption = new(aliases: new[] { "--file", "--FILE", "-f" }, description: "File path, contents of the file will be published.");
private readonly Option<string> _exchangeOption = new(aliases: new[] { "--exchange", "-e" }, description: "Message to publish");
private readonly Option<string> _routingKeyOption = new(aliases: new[] { "--routing-key", "-r" }, description: "Message to publish");
// private readonly Option<Boolean> _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" });
this.AddOption(hostOption);

exchangeOption.IsRequired=true;
this.AddOption(exchangeOption);

routingKeyOption.IsRequired=true;
this.AddOption(routingKeyOption);

messageOption.IsRequired=false;
this.AddOption(messageOption);

fileOption.IsRequired = false;
this.AddOption(fileOption);

this.SetHandler(Publish, hostOption, exchangeOption, routingKeyOption, messageOption, fileOption);
// hostOption.IsRequired=true;
_hostUriOption.AddCompletions(new[] { "amqp://guest:guest@127.0.0.1:5672[/vhost]" });
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);


_exchangeOption.IsRequired = true;
this.AddOption(_exchangeOption);

_routingKeyOption.IsRequired = true;
this.AddOption(_routingKeyOption);

_messageOption.IsRequired = false;
this.AddOption(_messageOption);

_fileOption.IsRequired = false;
this.AddOption(_fileOption);

// _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);
}

void Publish(string host, string exchangeName, string routingKey, string message, FileInfo? fileInfo)
Task<int> Publish(string hostUri, Host host, string exchangeName, string routingKey, string message, FileInfo? fileInfo)
{
Utils.LogInfo($" {host}, {exchangeName}, {routingKey}");
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(Constants.ErrorExitCode);
}
else
{
factory = AMQPClient.GetConnectionFactory(host);
}
}
else
{
factory = AMQPClient.GetConnectionFactory(hostUri);
}

if(factory == null)
{
Utils.LogError("Failed to create connection factory.");
return Task.FromResult(Constants.ErrorExitCode);
}

var factory = new ConnectionFactory() { Uri=new Uri(host) };
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.");
Environment.Exit(ErrorExitCode);
Utils.LogError("Neither message nor file argument specified and no input redirection detected.");
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();
}
} else if (fileInfo != null)
}
else if (fileInfo != null)
{
message = File.ReadAllText(fileInfo.FullName);
}
Expand All @@ -72,6 +120,8 @@ void Publish(string host, string exchangeName, string routingKey, string message
basicProperties: null,
body: body);

Utils.LogSuccess($"Message sent:\n {message}" );
Utils.LogSuccess($"Message sent:\n {message}");

return Task.FromResult(100);
}
}
Loading

0 comments on commit 371be78

Please sign in to comment.