Skip to content

Commit

Permalink
Merge pull request #2 from DotNetNomads/dotnet7-update
Browse files Browse the repository at this point in the history
WIP: .NET 7 + update api
  • Loading branch information
binali-rustamov authored Sep 26, 2023
2 parents 238e045 + 1fac271 commit 8d52482
Show file tree
Hide file tree
Showing 31 changed files with 640 additions and 110 deletions.
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Dockerfile
**/*/
!PortainerClient/bin/release/netcoreapp3.1/linux-x64/publish/PortainerClient
!PortainerClient/bin/Release/net7.0/linux-x64/publish/PortainerClient
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
FROM alpine:3.7
FROM ubuntu:20.04

WORKDIR /
COPY PortainerClient/bin/release/netcoreapp3.1/linux-x64/publish/PortainerClient /usr/bin/portainerctl
COPY "PortainerClient/bin/Release/net7.0/linux-x64/publish/PortainerClient" "/usr/bin/portainerctl"

RUN chmod +x /usr/bin/portainerctl
18 changes: 11 additions & 7 deletions PortainerClient/Api/Base/BaseApiHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ public static class BaseApiHelpers
/// </summary>
/// <param name="request">API request</param>
/// <param name="parameters">List with request parameters</param>
public static void AddParameters(this IRestRequest request,
public static void AddParameters(this RestRequest request,
IEnumerable<(string paramName, object paramValue)> parameters)
{
foreach (var (paramName, paramValue) in parameters) request.AddParameter(paramName, paramValue);
foreach (var (paramName, paramValue) in parameters)
request.AddParameter(paramName, paramValue, ParameterType.QueryString);
}

/// <summary>
Expand All @@ -26,21 +27,24 @@ public static void AddParameters(this IRestRequest request,
/// <param name="request">API request</param>
/// <param name="parameters">List with request parameters</param>
/// <exception cref="InvalidOperationException">Occurs when parameter type is not implemented</exception>
public static void AddParameters(this IRestRequest request,
IEnumerable<(string paramName, object value, ParamType type)> parameters)
public static void AddParameters(this RestRequest request,
(string? paramName, object value, ParamType type)[] parameters)
{
foreach (var (paramName, value, type) in parameters)
{
switch (type)
{
case ParamType.File:
request.AddFile(paramName, value.ToString());
request.AddFile(paramName ?? throw new InvalidOperationException("File name required"),
value.ToString()!);
break;
case ParamType.QueryParam:
request.AddQueryParameter(paramName, value.ToString());
request.AddQueryParameter(
paramName ?? throw new InvalidOperationException("Query parameter name required"),
value.ToString());
break;
case ParamType.BodyParam:
request.AddParameter(paramName, value);
request.AddParameter(paramName, value, ParameterType.GetOrPost);
break;
case ParamType.JsonBody:
request.RequestFormat = DataFormat.Json;
Expand Down
74 changes: 50 additions & 24 deletions PortainerClient/Api/Base/BaseApiService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Diagnostics;
using System.Text.Json;
using PortainerClient.Api.Model;
using PortainerClient.Config;
using RestSharp;
Expand All @@ -11,91 +13,115 @@ namespace PortainerClient.Api.Base
/// </summary>
public abstract class BaseApiService
{
private RestClient _client;
private RestClient? _client;

private RestClient ResolveClient()
{
if (_client != null)
return _client;
var config = ConfigModel.Load();
_client ??= new RestClient(config.Url);
_client.Authenticator = new JwtAuthenticator(config.Token);
var options = new RestClientOptions
{
BaseUrl = new Uri(config.Url ?? throw new InvalidOperationException("Invalid URL from config")),
Authenticator =
new JwtAuthenticator(config.Token ?? throw new InvalidOperationException("Invalid Token from URL"))
};
_client ??= new RestClient(options);
return _client;
}

/// <summary>
/// Do Get request and return parsed response
/// </summary>
/// <param name="resource">API resource</param>
/// <param name="debug"></param>
/// <param name="parameters">List of request parameters</param>
/// <typeparam name="T">Response type</typeparam>
/// <returns>Parsed instance of T</returns>
protected T Get<T>(string resource, params (string paramName, object paramValue)[] parameters)
protected T Get<T>(string resource, bool debug = false,
params (string paramName, object paramValue)[] parameters)
where T : new() =>
ExecuteRequest<T>(
resource,
Method.GET,
request => request.AddParameters(parameters));
Method.Get,
request => request.AddParameters(parameters), debug);

/// <summary>
/// Do Post request and return parsed response
/// </summary>
/// <param name="resource">API resource</param>
/// <param name="debug">Print content of request and response</param>
/// <param name="parameters">List of request parameters</param>
/// <typeparam name="T">Parsed instance of T</typeparam>
/// <returns>Parsed instance of T</returns>
protected T Post<T>(string resource, params (string paramName, object value, ParamType type)[] parameters)
protected T Post<T>(string resource, bool debug = false,
params (string? paramName, object value, ParamType type)[] parameters)
where T : new() => ExecuteRequest<T>(
resource,
Method.POST,
request => request.AddParameters(parameters));
Method.Post,
request => request.AddParameters(parameters), debug);

/// <summary>
/// Do Put request and return parsed response
/// </summary>
/// <param name="resource">API resource</param>
/// <param name="debug">Debug request / response to Portainer</param>
/// <param name="parameters">List of request parameters</param>
/// <typeparam name="T">Parsed instance of T</typeparam>
/// <returns>Parsed instance of T</returns>
protected T Put<T>(string resource,
params (string paramName, object value, ParamType type)[] parameters)
bool debug,
params (string? paramName, object value, ParamType type)[] parameters)
where T : new() => ExecuteRequest<T>(
resource,
Method.PUT,
request => request.AddParameters(parameters));
Method.Put,
request => request.AddParameters(parameters), debug);

/// <summary>
/// Do Delete request and return parsed response
/// </summary>
/// <param name="resource">API resource</param>
/// <param name="parameters">List of request parameters</param>
protected void Delete(string resource, params (string paramName, object paramValue)[] parameters) =>
ExecuteRequest(resource, Method.DELETE, request => request.AddParameters(parameters));
ExecuteRequest(resource, Method.Get, request => request.AddParameters(parameters));

private void ExecuteRequest(string resource, Method method, Action<IRestRequest> requestConfig = null)
private void ExecuteRequest(string resource, Method method, Action<RestRequest>? requestConfig = null)
{
var request = new RestRequest(resource, method);
var request = new TraceRequest(resource, method);
requestConfig?.Invoke(request);
var response = ResolveClient().Execute(request);
if (response.IsSuccessful) return;
throw ParseError(request.Resource, response.Content);
Debug.Assert(response.Content != null, "response.Content != null");
throw ParseError(request.Resource, response);
}

private T ExecuteRequest<T>(string resource, Method method, Action<IRestRequest> requestConfig = null)
private T ExecuteRequest<T>(string resource, Method method, Action<RestRequest>? requestConfig = null,
bool debug = false)
where T : new()
{
var request = new RestRequest(resource, method);
var request = new TraceRequest(resource, method, debug);
requestConfig?.Invoke(request);

var response = ResolveClient().Execute<T>(request);
if (response.IsSuccessful)
return response.Data;
throw ParseError(request.Resource, response.Content);
if (!response.IsSuccessful)
{
Debug.Assert(response.Content != null, "response.Content != null");
throw ParseError(request.Resource, response);
}

Debug.Assert(response.Data != null, "response.Data != null");
return response.Data;
}

private static InvalidOperationException ParseError(string resource, string responseData)
private static InvalidOperationException ParseError(string resource, RestResponse responseData)
{
ApiError errorInfo = null;
if (responseData != null) errorInfo = SimpleJson.DeserializeObject<ApiError>(responseData);
ApiError? errorInfo = null;
if (string.IsNullOrWhiteSpace(responseData.Content))
{
return new InvalidOperationException($"Request {resource}: {responseData.StatusDescription}");
}

if (responseData != null) errorInfo = JsonSerializer.Deserialize<ApiError>(responseData.Content);

return new InvalidOperationException(
$"Request {resource}: {(errorInfo != null ? $"{errorInfo.message}, details: {errorInfo.details}" : "no information")}");
Expand Down
171 changes: 171 additions & 0 deletions PortainerClient/Api/Base/TraceRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using RestSharp;

namespace PortainerClient.Api.Base;

/// <inheritdoc />
public class TraceRequest : RestRequest
{
#region Properties

private readonly bool _debug;

#endregion

#region Constructor

/// <inheritdoc />
public TraceRequest(string pResource, bool debug = false)
: base(pResource)
{
_debug = debug;
InitializeLogs();
}

/// <inheritdoc />
public TraceRequest(string pResource, Method method, bool debug = false) : base(pResource, method)
{
_debug = debug;
InitializeLogs();
}

#endregion

#region Methods

private void InitializeLogs()
{
if (!_debug)
return;
OnBeforeRequest = OnBeforeRequestMethod;
OnAfterRequest = OnAfterRequestMethod;
}

private ValueTask OnBeforeRequestMethod(HttpRequestMessage pMessage)
{
var builder = new StringBuilder();

builder.AppendLine("------------------------------");
builder.AppendLine($"REQUEST [{pMessage.Method}] {pMessage.RequestUri}");

foreach (var header in pMessage.Headers)
{
builder.AppendLine($" {header.Key}: {string.Join(';', header.Value)}");
}

var stream = pMessage.Content?.ReadAsStream();

ReadStream(stream, builder);


builder.AppendLine("------------------------------");

var content = builder.ToString();

Console.WriteLine(content);

return ValueTask.CompletedTask;
}

private void ReadContent(HttpContent? pContent, StringBuilder pBuilder)
{
if (pContent == null)
{
return;
}

foreach (var header in pContent.Headers)
{
pBuilder.AppendLine($" {header.Key}: {string.Join(';', header.Value)}");
}

ReadContent(pContent as StreamContent, pBuilder);
ReadContent(pContent as StringContent, pBuilder);
ReadContent(pContent as MultipartFormDataContent, pBuilder);

Console.WriteLine();
}

private void ReadContent(MultipartFormDataContent? pContent, StringBuilder pBuilder)
{
if (pContent == null) return;
foreach (var content in pContent)
{
pBuilder.AppendLine();
ReadContent(content, pBuilder);
}
}

private static void ReadContent(StreamContent? pContent, StringBuilder pBuilder)
{
if (pContent == null) return;
var stream = pContent.ReadAsStream();
pBuilder.AppendLine($" contains {stream.Length} bytes");
}

private void ReadContent(StringContent? pContent, StringBuilder pBuilder)
{
if (pContent == null) return;
var stream = pContent.ReadAsStream();
pBuilder.Append(" ");
ReadStream(stream, pBuilder);
}

private static void ReadStream(Stream? pStream, StringBuilder pBuilder)
{
if (pStream == null)
{
return;
}

var index = 0L;
var length = pStream.Length;
var buffer = new byte[1024];

while (index < length - 1)
{
var read = pStream.Read(buffer, 0, 1024);
var result = Encoding.UTF8.GetString(buffer, 0, read);

pBuilder.Append(result);

index += read;
}

pBuilder.AppendLine();

pStream.Seek(0L, SeekOrigin.Begin);
}

private ValueTask OnAfterRequestMethod(HttpResponseMessage pMessage)
{
var builder = new StringBuilder();

builder.AppendLine("------------------------------");
builder.AppendLine(
$"RESPONSE {pMessage.RequestMessage.Method} [{pMessage.RequestMessage.RequestUri}] {pMessage.StatusCode}");

foreach (var header in pMessage.Headers)
{
builder.AppendLine($" {header.Key}: {string.Join(';', header.Value)}");
}

var stream = pMessage.Content.ReadAsStream();

ReadStream(stream, builder);

builder.AppendLine("------------------------------");

var content = builder.ToString();

Console.WriteLine(content);

return ValueTask.CompletedTask;
}

#endregion
}
4 changes: 2 additions & 2 deletions PortainerClient/Api/Model/ApiError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ public class ApiError
/// <summary>
/// Message about occured error
/// </summary>
public string message { get; set; }
public string? message { get; set; }
/// <summary>
/// Occured error details
/// </summary>
public string details { get; set; }
public string? details { get; set; }
}
}
Loading

0 comments on commit 8d52482

Please sign in to comment.