Skip to content

Commit

Permalink
Merge pull request #19 from bacongobbler/connection-string
Browse files Browse the repository at this point in the history
implement connection string support
  • Loading branch information
bacongobbler authored Jun 8, 2022
2 parents 72497f7 + 45e087c commit 9711c2f
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 40 deletions.
60 changes: 29 additions & 31 deletions src/Bindle/BindleClient.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Tomlyn;
using Tomlyn.Syntax;
Expand All @@ -14,35 +12,41 @@ namespace Deislabs.Bindle;

public class BindleClient
{
public BindleClient(
string baseUri
)
{
_baseUri = new Uri(SlashSafe(baseUri));
_httpClient = new HttpClient();
}
public BindleClient(string connectionString) : this(new ConnectionInfo(connectionString)) { }

public BindleClient(
string baseUri,
HttpMessageHandler messageHandler
)
public BindleClient(ConnectionInfo connectionInfo)
{
_baseUri = new Uri(SlashSafe(baseUri));
_httpClient = new HttpClient(messageHandler);
if (string.IsNullOrEmpty(connectionInfo.BaseUri))
throw new ArgumentException("base URI cannot be empty");

var handler = new HttpClientHandler();

if (connectionInfo.SslMode == SslMode.Disable)
{
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};
}

_httpClient = new HttpClient(handler) { BaseAddress = new Uri(SlashSafe(connectionInfo.BaseUri)) };

if (!string.IsNullOrEmpty(connectionInfo.UserName) && !string.IsNullOrEmpty(connectionInfo.Password))
{
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", $"{connectionInfo.UserName}:{connectionInfo.Password}");
}
}

private const string INVOICE_PATH = "_i";
private const string QUERY_PATH = "_q";
private const string RELATIONSHIP_PATH = "_r";

private readonly Uri _baseUri;
private readonly HttpClient _httpClient;

public async Task<Invoice> GetInvoice(string invoiceId, GetInvoiceOptions options = GetInvoiceOptions.None)
{
var query = GetInvoiceQueryString(options);
var uri = new Uri(_baseUri, $"{INVOICE_PATH}/{invoiceId}{query}");
var response = await _httpClient.GetAsync(uri);
var response = await _httpClient.GetAsync($"{INVOICE_PATH}/{invoiceId}{query}");
await ExpectResponseCode(response, HttpStatusCode.OK, HttpStatusCode.Forbidden);

if (response.StatusCode == HttpStatusCode.Forbidden)
Expand Down Expand Up @@ -74,8 +78,7 @@ public async Task<Matches> QueryInvoices(string? queryString = null,
bool? yanked = null)
{
var query = GetDistinctInvoicesNamesQueryString(queryString, offset, limit, strict, semVer, yanked);
var uri = new Uri(_baseUri, $"{QUERY_PATH}?{query}");
var response = await _httpClient.GetAsync(uri);
var response = await _httpClient.GetAsync($"{QUERY_PATH}?{query}");
await ExpectResponseCode(response, HttpStatusCode.OK, HttpStatusCode.Forbidden);

if (response.StatusCode == HttpStatusCode.Forbidden)
Expand All @@ -99,9 +102,8 @@ public async Task<CreateInvoiceResult> CreateInvoice(Invoice invoice)
ConvertPropertyName = name => TomlNamingHelper.PascalToCamelCase(name)
});

var uri = new Uri(_baseUri, INVOICE_PATH);
var requestContent = new StringContent(invoiceToml, null, "application/toml");
var response = await _httpClient.PostAsync(uri, requestContent);
var response = await _httpClient.PostAsync(INVOICE_PATH, requestContent);
await ExpectResponseCode(response, HttpStatusCode.Created, HttpStatusCode.Accepted);

var content = await response.Content.ReadAsStringAsync();
Expand All @@ -115,15 +117,13 @@ public async Task<CreateInvoiceResult> CreateInvoice(Invoice invoice)

public async Task YankInvoice(string invoiceId)
{
var uri = new Uri(_baseUri, $"{INVOICE_PATH}/{invoiceId}");
var response = await _httpClient.DeleteAsync(uri);
var response = await _httpClient.DeleteAsync($"{INVOICE_PATH}/{invoiceId}");
await ExpectResponseCode(response, HttpStatusCode.OK);
}

public async Task<HttpContent> GetParcel(string invoiceId, string parcelId)
{
var uri = new Uri(_baseUri, $"{INVOICE_PATH}/{invoiceId}@{parcelId}");
var response = await _httpClient.GetAsync(uri);
var response = await _httpClient.GetAsync($"{INVOICE_PATH}/{invoiceId}@{parcelId}");
await ExpectResponseCode(response, HttpStatusCode.OK);

return response.Content;
Expand All @@ -146,15 +146,13 @@ public async Task CreateParcel(string invoiceId, string parcelId, byte[] content

public async Task CreateParcel(string invoiceId, string parcelId, HttpContent content)
{
var uri = new Uri(_baseUri, $"{INVOICE_PATH}/{invoiceId}@{parcelId}");
var response = await _httpClient.PostAsync(uri, content);
var response = await _httpClient.PostAsync($"{INVOICE_PATH}/{invoiceId}@{parcelId}", content);
await ExpectResponseCode(response, HttpStatusCode.OK, HttpStatusCode.Created);
}

public async Task<MissingParcelsResponse> ListMissingParcels(string invoiceId)
{
var uri = new Uri(_baseUri, $"{RELATIONSHIP_PATH}/missing/{invoiceId}");
var response = await _httpClient.GetAsync(uri);
var response = await _httpClient.GetAsync($"{RELATIONSHIP_PATH}/missing/{invoiceId}");
await ExpectResponseCode(response, HttpStatusCode.OK);

var content = await response.Content.ReadAsStringAsync();
Expand Down
93 changes: 93 additions & 0 deletions src/Bindle/ConnectionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Deislabs.Bindle;

public class ConnectionInfo
{
static readonly string[] serverAliases = {
"server",
"host",
"data source",
"datasource",
"address",
"addr",
"network address",
};

static readonly string[] sslModeAliases = {
"sslmode",
"ssl mode"
};

static readonly string[] usernameAliases = {
"user",
"username"
};

static readonly string[] passwordAliases = {
"pass",
"passwd",
"password"
};

private readonly Dictionary<string, string> keyValuePairs;

public string BaseUri;

public string UserName;

public string Password;

public SslMode? SslMode;

public ConnectionInfo()
{
keyValuePairs = new Dictionary<string, string>();
BaseUri = "http://localhost:8080/v1/";
UserName = String.Empty;
Password = String.Empty;
}

public ConnectionInfo(string connectionString)
{
keyValuePairs = GetKeyValuePairs(connectionString);

BaseUri = GetValue(serverAliases);

UserName = GetValue(usernameAliases);

Password = GetValue(passwordAliases);

try
{
SslMode = Enum.Parse<SslMode>(GetValue(sslModeAliases), true);
}
catch (Exception)
{
SslMode = null;
}
}

private Dictionary<string, string> GetKeyValuePairs(string connectionString)
{
return connectionString.Split(';')
.Where(kvp => kvp.Contains('='))
.Select(kvp => kvp.Split(new char[] { '=' }, 2))
.ToDictionary(kvp => kvp[0].Trim(),
kvp => kvp[1].Trim(),
StringComparer.InvariantCultureIgnoreCase);
}

private string GetValue(string[] keyAliases)
{
foreach (var alias in keyAliases)
{
string? value;
if (keyValuePairs.TryGetValue(alias, out value))
return value;
}
return string.Empty;
}
}
22 changes: 22 additions & 0 deletions src/Bindle/SslMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Deislabs.Bindle;

public enum SslMode
{
// I don't care about security, and I don't want to pay the overhead of encryption.
Disable,
// I don't care about security, but I will pay the overhead of encryption if the server insists on it.
// TODO(bacongobbler): not implemented
Allow,
// I don't care about encryption, but I wish to pay the overhead of encryption if the server supports it.
// TODO(bacongobbler): not implemented
Prefer,
// I want my data to be encrypted, and I accept the overhead. I trust that the network will make sure I always connect to the server I want.
// TODO(bacongobbler): not implemented
Require,
// I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server that I trust.
// TODO(bacongobbler): not implemented
VerifyCA,
// I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server I trust, and that it's the one I specify.
// TODO(bacongobbler): not implemented
VerifyFull,
}
16 changes: 8 additions & 8 deletions tests/Bindle.IntegrationTests/Integration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class Integration : IClassFixture<IntegrationFixture>
[Fact]
public async Task CanFetchInvoice()
{
var client = new BindleClient(DEMO_SERVER_URL);
var client = new BindleClient($"host={DEMO_SERVER_URL}");
var invoice = await client.GetInvoice("your/fancy/bindle/0.3.0");
Assert.Equal("1.0.0", invoice.BindleVersion);
Assert.Equal("your/fancy/bindle", invoice.Bindle.Name);
Expand All @@ -37,15 +37,15 @@ public async Task CanFetchInvoice()
[Fact]
public async Task CanQueryInvoices()
{
var client = new BindleClient(DEMO_SERVER_URL);
var client = new BindleClient($"host={DEMO_SERVER_URL}");
var matches = await client.QueryInvoices(queryString: "my");
Assert.True(matches.Invoices.All(i => i.Bindle.Name.Contains("my")));
}

[Fact]
public async Task CanFetchYankedInvoices()
{
var client = new BindleClient(DEMO_SERVER_URL);
var client = new BindleClient($"host={DEMO_SERVER_URL}");
var invoice = await client.GetInvoice("yourbindle/0.1.1", IncludeYanked);
Assert.Equal("1.0.0", invoice.BindleVersion);
Assert.Equal("yourbindle", invoice.Bindle.Name);
Expand All @@ -55,7 +55,7 @@ public async Task CanFetchYankedInvoices()
[Fact]
public async Task CanCreateInvoices()
{
var client = new BindleClient(DEMO_SERVER_URL);
var client = new BindleClient($"host={DEMO_SERVER_URL}");
var invoice = new Invoice
{
BindleVersion = "1.0.0",
Expand Down Expand Up @@ -98,7 +98,7 @@ public async Task CanCreateInvoices()
[TestPriority(10)]
public async Task CanYankInvoice()
{
var client = new BindleClient(DEMO_SERVER_URL);
var client = new BindleClient($"host={DEMO_SERVER_URL}");
await client.YankInvoice("your/fancy/bindle/0.3.0");
await Assert.ThrowsAsync<BindleYankedException>(async () =>
{
Expand All @@ -111,15 +111,15 @@ await Assert.ThrowsAsync<BindleYankedException>(async () =>
[Fact]
public async Task CanFetchParcel()
{
var client = new BindleClient(DEMO_SERVER_URL);
var client = new BindleClient($"host={DEMO_SERVER_URL}");
var parcel = await client.GetParcel("mybindle/0.1.0", "f7f3b33707fb76d208f5839a40e770452dcf9f348bfd7faf2c524e0fa6710ed6");
Assert.Equal("Fie on you Gary", await parcel.ReadAsStringAsync());
}

[Fact]
public async Task CanCreateParcel()
{
var client = new BindleClient(DEMO_SERVER_URL);
var client = new BindleClient($"host={DEMO_SERVER_URL}");
await client.CreateParcel("mybindle/0.1.0", "460d5965e4d1909e8c7a3748a414956b7038ab5fd79937c9fcb2b214e6b0160a", "The front fell off");
var fetched = await client.GetParcel("mybindle/0.1.0", "460d5965e4d1909e8c7a3748a414956b7038ab5fd79937c9fcb2b214e6b0160a");
Assert.Equal("The front fell off", await fetched.ReadAsStringAsync());
Expand All @@ -128,7 +128,7 @@ public async Task CanCreateParcel()
[Fact]
public async Task CanListMissingParcels()
{
var client = new BindleClient(DEMO_SERVER_URL);
var client = new BindleClient($"host={DEMO_SERVER_URL}");
var resp = await client.ListMissingParcels("mybindle/0.3.0");
Assert.Contains(resp.Missing, (label) => label.Sha256 == "e1706ab0a39ac88094b6d54a3f5cdba41fe5a901");
Assert.DoesNotContain(resp.Missing, (label) => label.Sha256 == "f7f3b33707fb76d208f5839a40e770452dcf9f348bfd7faf2c524e0fa6710ed6");
Expand Down
Loading

0 comments on commit 9711c2f

Please sign in to comment.