diff --git a/global.json b/global.json
index 79422f0..f5efc4a 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1,5 @@
{
"sdk": {
- "version": "3.0.100"
+ "version": "3.1.401"
}
}
diff --git a/src/AzureCloud/AzureResource.cs b/src/AzureCloud/AzureResource.cs
index c51873f..f1e7009 100644
--- a/src/AzureCloud/AzureResource.cs
+++ b/src/AzureCloud/AzureResource.cs
@@ -1,47 +1,37 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Squadron.AzureCloud
-{
- ///
- /// Base class to use with Azure Cloud resources
- ///
- ///
- public class AzureResource
- where TOptions : AzureResourceOptions, new()
- {
- ///
- /// Azure configuration to work with Azure management api
- ///
- protected AzureResourceConfiguration AzureConfig { get; private set; }
-
- ///
- /// Initialize the resource
- ///
- ///
- public virtual Task InitializeAsync()
- {
- //var options = new TOptions();
- //AzureResourceOptionsBuilder builder = new AzureResourceOptionsBuilder();
- //AzureResourceOptions options = builder.Build();
- //AzureConfig = options.ConfigResolver();
- Trace.WriteLine("Loading Azure Configuration");
- return Task.CompletedTask;
- }
-
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace Squadron.AzureCloud
+{
+ ///
+ /// Base class to use with Azure Cloud resources
+ ///
+ ///
+ public class AzureResource
+ where TOptions : AzureResourceOptions, new()
+ {
+ ///
+ /// Azure configuration to work with Azure management api
+ ///
+ protected AzureResourceConfiguration AzureConfig { get; private set; }
+
+ ///
+ /// Initialize the resource
+ ///
+ ///
+ public virtual Task InitializeAsync()
+ {
+ Trace.WriteLine("Loading Azure Configuration");
+ return Task.CompletedTask;
+ }
+
///
/// Loads the azure resource configuration.
///
/// The builder.
- protected void LoadResourceConfiguration(AzureResourceOptionsBuilder builder)
- {
- AzureConfig = builder.ConfigResolver();
- }
- }
-}
+ protected void LoadResourceConfiguration(AzureResourceOptionsBuilder builder)
+ {
+ AzureConfig = builder.ConfigResolver();
+ }
+ }
+}
diff --git a/src/AzureCloud/AzureResourceIdentifier.cs b/src/AzureCloud/AzureResourceIdentifier.cs
index 06f27a6..599a270 100644
--- a/src/AzureCloud/AzureResourceIdentifier.cs
+++ b/src/AzureCloud/AzureResourceIdentifier.cs
@@ -1,37 +1,36 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Squadron.AzureCloud
-{
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Squadron.AzureCloud
+{
///
/// Azure resource identifier
///
- public class AzureResourceIdentifier
- {
+ public class AzureResourceIdentifier
+ {
///
/// Gets or sets the Azure subscriptionId.
///
///
/// The subscriptionId.
///
- public string SubscriptionId { get; set; }
-
+ public string SubscriptionId { get; set; }
+
///
/// Gets or sets the name of the resource group.
///
///
/// The name of the resource group.
///
- public string ResourceGroupName { get; set; }
-
-
+ public string ResourceGroupName { get; set; }
+
///
/// Gets or sets the name of the resource
///
///
/// The name.
///
- public string Name { get; set; }
- }
-}
+ public string Name { get; set; }
+ }
+}
diff --git a/src/AzureCloud/Model/AzureResourceProvisioningMode.cs b/src/AzureCloud/Model/AzureResourceProvisioningMode.cs
new file mode 100644
index 0000000..ab2985c
--- /dev/null
+++ b/src/AzureCloud/Model/AzureResourceProvisioningMode.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Squadron.AzureCloud
+{
+ ///
+ /// Defines ServiceBUs provisioning modes
+ ///
+ public enum AzureResourceProvisioningMode
+ {
+ ///
+ /// Use an existing Azure resource
+ ///
+ UseExisting,
+
+ ///
+ /// Provision and delete resource
+ ///
+ CreateAndDelete
+ }
+}
diff --git a/src/AzureCloudServiceBus.Tests/AzureExistingServiceBusResourceTests.cs b/src/AzureCloudServiceBus.Tests/AzureExistingServiceBusResourceTests.cs
index 2a523fb..3ef492f 100644
--- a/src/AzureCloudServiceBus.Tests/AzureExistingServiceBusResourceTests.cs
+++ b/src/AzureCloudServiceBus.Tests/AzureExistingServiceBusResourceTests.cs
@@ -1,42 +1,42 @@
using System;
using System.IO;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.Azure.ServiceBus;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Squadron.AzureServiceBus.Tests
-{
- public class AzureExistingServiceBusResourceTests
- : IClassFixture>
- {
- private readonly AzureCloudServiceBusResource _resource;
-
- public AzureExistingServiceBusResourceTests(
- AzureCloudServiceBusResource resource,
- ITestOutputHelper outputHelper)
- {
- _resource = resource;
- }
-
-
- [Fact( Skip ="Can not run without Azure credentials")]
- public async Task PrepareAzureServiceBusResource_ExistingNamespace_NoError()
- {
- ITopicClient topicClient = _resource.GetTopicClient("foo");
- ISubscriptionClient subscriptionClient =
- _resource.GetSubscriptionClient("foo", "test1");
-
- IQueueClient queueClient = _resource.GetQueueClient("bar");
-
- //subscriptionClient.RegisterMessageHandler()
-
- await topicClient.SendAsync(new Message(Encoding.UTF8.GetBytes("Hello")));
-
- ITopicClient newTopic = await _resource.CreateTopicAsync(b => b
- .Name("adhoc")
- .AddSubscription("test1"));
- }
- }
-}
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Azure.ServiceBus;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Squadron.AzureServiceBus.Tests
+{
+ public class AzureExistingServiceBusResourceTests
+ : IClassFixture>
+ {
+ private readonly AzureCloudServiceBusResource _resource;
+
+ public AzureExistingServiceBusResourceTests(
+ AzureCloudServiceBusResource resource,
+ ITestOutputHelper outputHelper)
+ {
+ _resource = resource;
+ }
+
+
+ [Fact( Skip ="Can not run without Azure credentials")]
+ public async Task PrepareAzureServiceBusResource_ExistingNamespace_NoError()
+ {
+ ITopicClient topicClient = _resource.GetTopicClient("foo");
+ ISubscriptionClient subscriptionClient =
+ _resource.GetSubscriptionClient("foo", "test1");
+
+ IQueueClient queueClient = _resource.GetQueueClient("bar");
+
+ //subscriptionClient.RegisterMessageHandler()
+
+ await topicClient.SendAsync(new Message(Encoding.UTF8.GetBytes("Hello")));
+
+ ITopicClient newTopic = await _resource.CreateTopicAsync(b => b
+ .Name("adhoc")
+ .AddSubscription("test1"));
+ }
+ }
+}
diff --git a/src/AzureCloudServiceBus.Tests/AzureNewServiceBusResourceTests.cs b/src/AzureCloudServiceBus.Tests/AzureNewServiceBusResourceTests.cs
index c01a5e3..ca4b097 100644
--- a/src/AzureCloudServiceBus.Tests/AzureNewServiceBusResourceTests.cs
+++ b/src/AzureCloudServiceBus.Tests/AzureNewServiceBusResourceTests.cs
@@ -1,28 +1,27 @@
using System.Text;
-using System.Threading.Tasks;
-using Microsoft.Azure.ServiceBus;
-using Xunit;
-
+using System.Threading.Tasks;
+using Microsoft.Azure.ServiceBus;
+using Xunit;
+
namespace Squadron.AzureServiceBus.Tests
{
- public class AzureNewServiceBusResourceTests
+ public class AzureNewServiceBusResourceTests
: IClassFixture>
- {
- private readonly AzureCloudServiceBusResource _resource;
-
- public AzureNewServiceBusResourceTests(
- AzureCloudServiceBusResource resource)
- {
- _resource = resource;
- }
-
-
- [Fact(Skip = "Can not run without Azure credentials")]
- public async Task PrepareAzureServiceBusResource_NewNamespace_NoError()
- {
- ITopicClient topicClient = _resource.GetTopicClient("foo");
- await topicClient.SendAsync(new Message(Encoding.UTF8.GetBytes("Hello")));
- IQueueClient queueClient = _resource.GetQueueClient("bar");
- }
+ {
+ private readonly AzureCloudServiceBusResource _resource;
+
+ public AzureNewServiceBusResourceTests(
+ AzureCloudServiceBusResource resource)
+ {
+ _resource = resource;
+ }
+
+ [Fact(Skip = "Can not run without Azure credentials")]
+ public async Task PrepareAzureServiceBusResource_NewNamespace_NoError()
+ {
+ ITopicClient topicClient = _resource.GetTopicClient("foo");
+ await topicClient.SendAsync(new Message(Encoding.UTF8.GetBytes("Hello")));
+ IQueueClient queueClient = _resource.GetQueueClient("bar");
+ }
}
}
diff --git a/src/AzureCloudServiceBus.Tests/TestExistingNamespaceAzureServiceBusOptions.cs b/src/AzureCloudServiceBus.Tests/TestExistingNamespaceAzureServiceBusOptions.cs
index 281bc5c..f71ff18 100644
--- a/src/AzureCloudServiceBus.Tests/TestExistingNamespaceAzureServiceBusOptions.cs
+++ b/src/AzureCloudServiceBus.Tests/TestExistingNamespaceAzureServiceBusOptions.cs
@@ -1,15 +1,15 @@
using System;
-
-namespace Squadron.AzureServiceBus.Tests
-{
- public class TestExistingNamespaceAzureServiceBusOptions : AzureCloudServiceBusOptions
- {
- public override void Configure(ServiceBusOptionsBuilder builder)
- {
- builder.Namespace("spc-a-squadron-sb01")
- .AddTopic("foo")
- .AddSubscription("test1", "EventType = 'test1'");
- builder.AddQueue("bar");
- }
- }
-}
+
+namespace Squadron.AzureServiceBus.Tests
+{
+ public class TestExistingNamespaceAzureServiceBusOptions : AzureCloudServiceBusOptions
+ {
+ public override void Configure(ServiceBusOptionsBuilder builder)
+ {
+ builder.Namespace("spc-a-squadron-sb01")
+ .AddTopic("foo")
+ .AddSubscription("test1", "EventType = 'test1'");
+ builder.AddQueue("bar");
+ }
+ }
+}
diff --git a/src/AzureCloudServiceBus/AzureCloudServiceBusOptions.cs b/src/AzureCloudServiceBus/AzureCloudServiceBusOptions.cs
index 029ff37..643463e 100644
--- a/src/AzureCloudServiceBus/AzureCloudServiceBusOptions.cs
+++ b/src/AzureCloudServiceBus/AzureCloudServiceBusOptions.cs
@@ -1,22 +1,21 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.Extensions.Configuration;
-using Squadron.AzureCloud;
-
-namespace Squadron
-{
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.Extensions.Configuration;
+using Squadron.AzureCloud;
+
+namespace Squadron
+{
///
/// ServiceBus resources options
///
///
- public abstract class AzureCloudServiceBusOptions : AzureResourceOptions
- {
+ public abstract class AzureCloudServiceBusOptions : AzureResourceOptions
+ {
///
/// Configures the ServiceBus
///
/// The builder.
- public abstract void Configure(ServiceBusOptionsBuilder builder);
-
- }
-}
+ public abstract void Configure(ServiceBusOptionsBuilder builder);
+ }
+}
diff --git a/src/AzureCloudServiceBus/AzureCloudServiceBusResource.cs b/src/AzureCloudServiceBus/AzureCloudServiceBusResource.cs
index 5f19be3..8ffbc68 100644
--- a/src/AzureCloudServiceBus/AzureCloudServiceBusResource.cs
+++ b/src/AzureCloudServiceBus/AzureCloudServiceBusResource.cs
@@ -1,252 +1,251 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
+using System.Diagnostics;
+using System.Linq;
using System.Text;
-using System.Threading.Tasks;
-using Microsoft.Azure.ServiceBus;
-using Squadron.AzureCloud;
-using Xunit;
-using Xunit.Abstractions;
-using Xunit.Sdk;
-
+using System.Threading.Tasks;
+using Microsoft.Azure.ServiceBus;
+using Squadron.AzureCloud;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
namespace Squadron
-{
- ///
- /// Defines a Azure Cloud ServiceBus namespace
- ///
+{
+ ///
+ /// Defines a Azure Cloud ServiceBus namespace
+ ///
/// Option to initialize the resource
- public class AzureCloudServiceBusResource
- : AzureResource, IAsyncLifetime
- where TOptions : AzureCloudServiceBusOptions,
- new()
- {
- private ServiceBusManager _serviceBusManager;
-
- private ServiceBusModel _serviceBusModel;
- private readonly IMessageSink _messageSink;
-
+ public class AzureCloudServiceBusResource
+ : AzureResource, IAsyncLifetime
+ where TOptions : AzureCloudServiceBusOptions,
+ new()
+ {
+ private ServiceBusManager _serviceBusManager;
+ private ServiceBusModel _serviceBusModel;
+ private readonly IMessageSink _messageSink;
+
///
/// Initializes a new instance of the class.
///
/// The message sink.
- public AzureCloudServiceBusResource(IMessageSink messageSink)
- {
- _messageSink = messageSink;
- }
-
- ///
- /// ConnectionString to access the Azure ServiceBus
- ///
- public string ConnectionString { get; private set; }
-
- ///
- /// Get a TopicClient
- ///
- /// Topic name
- /// Retry policy
- ///
- public ITopicClient GetTopicClient(string name,
- RetryPolicy retryPolicy = null)
- {
- var topicName = GetTopic(name);
- return new TopicClient(ConnectionString, topicName, retryPolicy);
- }
-
-
+ public AzureCloudServiceBusResource(IMessageSink messageSink)
+ {
+ _messageSink = messageSink;
+ }
+
+ ///
+ /// ConnectionString to access the Azure ServiceBus
+ ///
+ public string ConnectionString { get; private set; }
+
+ ///
+ /// Get a TopicClient
+ ///
+ /// Topic name
+ /// Retry policy
+ ///
+ public ITopicClient GetTopicClient(string name,
+ RetryPolicy retryPolicy = null)
+ {
+ var topicName = GetTopic(name);
+ return new TopicClient(ConnectionString, topicName, retryPolicy);
+ }
+
+
///
/// Creates a new topic
///
/// The builder.
/// Client to access the created topic
- public async Task CreateTopicAsync(Action configure)
- {
- var builder = ServiceBusTopicBuilder.New();
- configure(builder);
- ServiceBusTopicModel topic = builder.Build();
-
- await CreateTopicAsync(topic);
- _serviceBusModel.Topics.Add(topic);
- return GetTopicClient(topic.Name);
- }
-
- ///
- /// Get a SubscriptionClient
- ///
- /// Topic name
- /// Subscription name
- /// Receive Mode
- /// Retry Policy
- ///
- public ISubscriptionClient GetSubscriptionClient(
- string topic,
- string name,
- ReceiveMode receiveMode = ReceiveMode.PeekLock,
- RetryPolicy retryPolicy = null)
- {
- var topicName = GetTopic(topic);
- return new SubscriptionClient(
- ConnectionString,
- topicName,
- name,
- receiveMode,
- retryPolicy);
- }
-
- ///
- /// Get a QueueClient
- ///
- /// Queue name
- /// Receive Mode
- /// Retry Policy
- ///
- public IQueueClient GetQueueClient(string name,
- ReceiveMode receiveMode = ReceiveMode.PeekLock,
- RetryPolicy retryPolicy = null)
- {
- var queueName = GetQueue(name);
-
- return new QueueClient(ConnectionString, queueName, receiveMode, retryPolicy);
- }
-
- private string GetTopic(string name)
- {
- ServiceBusTopicModel topic = _serviceBusModel.Topics
- .FirstOrDefault(x => x.Name.Equals(name,
- StringComparison.InvariantCultureIgnoreCase));
-
- if (topic == null)
- throw new InvalidOperationException($"No topic found with name: {name}");
-
- return topic.CreatedName;
- }
-
- private string GetQueue(string name)
- {
- ServiceBusQueueModel queue = _serviceBusModel.Queues
- .FirstOrDefault(x => x.Name.Equals(name,
- StringComparison.InvariantCultureIgnoreCase));
-
- if (queue == null)
- throw new InvalidOperationException($"No queue found with name: {name}");
-
- return queue.CreatedName;
- }
-
+ public async Task CreateTopicAsync(Action configure)
+ {
+ var builder = ServiceBusTopicBuilder.New();
+ configure(builder);
+ ServiceBusTopicModel topic = builder.Build();
+
+ await CreateTopicAsync(topic);
+ _serviceBusModel.Topics.Add(topic);
+ return GetTopicClient(topic.Name);
+ }
+
+ ///
+ /// Get a SubscriptionClient
+ ///
+ /// Topic name
+ /// Subscription name
+ /// Receive Mode
+ /// Retry Policy
+ ///
+ public ISubscriptionClient GetSubscriptionClient(
+ string topic,
+ string name,
+ ReceiveMode receiveMode = ReceiveMode.PeekLock,
+ RetryPolicy retryPolicy = null)
+ {
+ var topicName = GetTopic(topic);
+ return new SubscriptionClient(
+ ConnectionString,
+ topicName,
+ name,
+ receiveMode,
+ retryPolicy);
+ }
+
+ ///
+ /// Get a QueueClient
+ ///
+ /// Queue name
+ /// Receive Mode
+ /// Retry Policy
+ ///
+ public IQueueClient GetQueueClient(string name,
+ ReceiveMode receiveMode = ReceiveMode.PeekLock,
+ RetryPolicy retryPolicy = null)
+ {
+ var queueName = GetQueue(name);
+
+ return new QueueClient(ConnectionString, queueName, receiveMode, retryPolicy);
+ }
+
+ private string GetTopic(string name)
+ {
+ ServiceBusTopicModel topic = _serviceBusModel.Topics
+ .FirstOrDefault(x => x.Name.Equals(name,
+ StringComparison.InvariantCultureIgnoreCase));
+
+ if (topic == null)
+ throw new InvalidOperationException($"No topic found with name: {name}");
+
+ return topic.CreatedName;
+ }
+
+ private string GetQueue(string name)
+ {
+ ServiceBusQueueModel queue = _serviceBusModel.Queues
+ .FirstOrDefault(x => x.Name.Equals(name,
+ StringComparison.InvariantCultureIgnoreCase));
+
+ if (queue == null)
+ throw new InvalidOperationException($"No queue found with name: {name}");
+
+ return queue.CreatedName;
+ }
+
///
/// Initialize the resource
///
- public override async Task InitializeAsync()
- {
- await base.InitializeAsync();
- BuildOptions();
- InitializeServiceBusManager();
- await PrepareNamespaceAsync();
- await PrepareTopicsAsync();
- await PrepareQueuesAsync();
-
- ConnectionString = await _serviceBusManager.GetConnectionString();
- }
-
- private void BuildOptions()
+ public override async Task InitializeAsync()
{
- var builder = ServiceBusOptionsBuilder.New();
- var options = new TOptions();
- options.Configure(builder);
- LoadResourceConfiguration(builder);
- _serviceBusModel = builder.Build();
- }
-
- private void InitializeServiceBusManager()
+ await base.InitializeAsync();
+ BuildOptions();
+ InitializeServiceBusManager();
+ await PrepareNamespaceAsync();
+ await PrepareTopicsAsync();
+ await PrepareQueuesAsync();
+
+ ConnectionString = await _serviceBusManager.GetConnectionString();
+ }
+
+ private void BuildOptions()
{
- _serviceBusManager = new ServiceBusManager(
- AzureConfig.Credentials,
- new AzureResourceIdentifier
- {
- SubscriptionId = AzureConfig.SubscriptionId,
- ResourceGroupName = AzureConfig.ResourceGroup,
- Name = _serviceBusModel.Namespace
- });
- }
-
- private async Task PrepareNamespaceAsync()
+ var builder = ServiceBusOptionsBuilder.New();
+ var options = new TOptions();
+ options.Configure(builder);
+ LoadResourceConfiguration(builder);
+ _serviceBusModel = builder.Build();
+ }
+
+ private void InitializeServiceBusManager()
{
- if (_serviceBusModel.Namespace == null)
- {
- _serviceBusModel.ProvisioningMode = ServiceBusProvisioningMode.CreateAndDelete;
- _serviceBusModel.Namespace = await
- _serviceBusManager.CreateNamespaceAsync(AzureConfig.DefaultLocation);
- }
- }
-
- private async Task PrepareQueuesAsync()
+ _serviceBusManager = new ServiceBusManager(
+ AzureConfig.Credentials,
+ new AzureResourceIdentifier
+ {
+ SubscriptionId = AzureConfig.SubscriptionId,
+ ResourceGroupName = AzureConfig.ResourceGroup,
+ Name = _serviceBusModel.Namespace
+ });
+ }
+
+ private async Task PrepareNamespaceAsync()
{
- foreach (ServiceBusQueueModel queue in _serviceBusModel.Queues)
- {
- if (_serviceBusModel.ProvisioningMode == ServiceBusProvisioningMode.UseExisting)
- {
- queue.CreatedName = $"{queue.Name}_{DateTime.UtcNow.Ticks}";
- }
- else
- {
- queue.CreatedName = queue.Name;
- }
- await _serviceBusManager.CreateQueueAsync(queue.CreatedName);
- }
- }
-
- private async Task PrepareTopicsAsync()
+ if (_serviceBusModel.Namespace == null)
+ {
+ _serviceBusModel.ProvisioningMode = AzureResourceProvisioningMode.CreateAndDelete;
+ _serviceBusModel.Namespace = await
+ _serviceBusManager.CreateNamespaceAsync(AzureConfig.DefaultLocation);
+ }
+ }
+
+ private async Task PrepareQueuesAsync()
{
- foreach (ServiceBusTopicModel topic in _serviceBusModel.Topics)
- {
- _messageSink.OnMessage(
- new DiagnosticMessage($"Creating topic {topic.CreatedName}"));
- await CreateTopicAsync(topic);
- }
- }
-
- private async Task CreateTopicAsync(ServiceBusTopicModel topic)
- {
- if (_serviceBusModel.ProvisioningMode == ServiceBusProvisioningMode.UseExisting)
- {
- topic.CreatedName = $"{topic.Name}_{DateTime.UtcNow.Ticks}";
- }
- else
- {
- topic.CreatedName = topic.Name;
- }
- await _serviceBusManager.CreateTopic(topic);
- return topic.CreatedName;
- }
-
- ///
+ foreach (ServiceBusQueueModel queue in _serviceBusModel.Queues)
+ {
+ if (_serviceBusModel.ProvisioningMode == AzureResourceProvisioningMode.UseExisting)
+ {
+ queue.CreatedName = $"{queue.Name}_{DateTime.UtcNow.Ticks}";
+ }
+ else
+ {
+ queue.CreatedName = queue.Name;
+ }
+ await _serviceBusManager.CreateQueueAsync(queue.CreatedName);
+ }
+ }
+
+ private async Task PrepareTopicsAsync()
+ {
+ foreach (ServiceBusTopicModel topic in _serviceBusModel.Topics)
+ {
+ _messageSink.OnMessage(
+ new DiagnosticMessage($"Creating topic {topic.CreatedName}"));
+ await CreateTopicAsync(topic);
+ }
+ }
+
+ private async Task CreateTopicAsync(ServiceBusTopicModel topic)
+ {
+ if (_serviceBusModel.ProvisioningMode == AzureResourceProvisioningMode.UseExisting)
+ {
+ topic.CreatedName = $"{topic.Name}_{DateTime.UtcNow.Ticks}";
+ }
+ else
+ {
+ topic.CreatedName = topic.Name;
+ }
+ await _serviceBusManager.CreateTopic(topic);
+ return topic.CreatedName;
+ }
+
+ ///
/// Cleans up the resource
///
- public async Task DisposeAsync()
- {
- try
- {
- if (_serviceBusModel.ProvisioningMode == ServiceBusProvisioningMode.CreateAndDelete)
- {
- await _serviceBusManager.DeleteNamespaceAsync();
- }
- else
- {
- foreach (ServiceBusTopicModel topic in _serviceBusModel.Topics)
- {
- await _serviceBusManager.DeleteTopic(topic.CreatedName);
- }
- foreach (ServiceBusQueueModel queue in _serviceBusModel.Queues)
- {
- await _serviceBusManager.DeleteQueue(queue.CreatedName);
- }
- }
- }
- catch (Exception ex)
- {
- Trace.TraceWarning($"Error cleaning up azure resources: {ex.Message}");
- //do not fail test
- }
- }
+ public async Task DisposeAsync()
+ {
+ try
+ {
+ if (_serviceBusModel.ProvisioningMode == AzureResourceProvisioningMode.CreateAndDelete)
+ {
+ await _serviceBusManager.DeleteNamespaceAsync();
+ }
+ else
+ {
+ foreach (ServiceBusTopicModel topic in _serviceBusModel.Topics)
+ {
+ await _serviceBusManager.DeleteTopic(topic.CreatedName);
+ }
+ foreach (ServiceBusQueueModel queue in _serviceBusModel.Queues)
+ {
+ await _serviceBusManager.DeleteQueue(queue.CreatedName);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Trace.TraceWarning($"Error cleaning up azure resources: {ex.Message}");
+ //do not fail test
+ }
+ }
}
}
diff --git a/src/AzureCloudServiceBus/Model/ServiceBusModel.cs b/src/AzureCloudServiceBus/Model/ServiceBusModel.cs
index 22ea150..4698f03 100644
--- a/src/AzureCloudServiceBus/Model/ServiceBusModel.cs
+++ b/src/AzureCloudServiceBus/Model/ServiceBusModel.cs
@@ -1,63 +1,48 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Squadron.AzureCloud;
-namespace Squadron
-{
+namespace Squadron
+{
///
/// Azure ServiceBus model
///
- public class ServiceBusModel
- {
+ public class ServiceBusModel
+ {
///
/// Gets or sets the namespace.
///
///
/// The namespace.
///
- public string Namespace { get; set; }
-
+ public string Namespace { get; set; }
+
///
/// Gets or sets the topics.
///
///
/// The topics.
///
- public List Topics { get; set; }
- = new List();
-
+ public List Topics { get; set; }
+ = new List();
+
///
/// Gets or sets the queues.
///
///
/// The queues.
///
- public List Queues { get; set; }
- = new List();
-
+ public List Queues { get; set; }
+ = new List();
+
///
/// Gets or sets the provisioning mode.
///
///
/// The provisioning mode.
///
- internal ServiceBusProvisioningMode ProvisioningMode { get; set; }
- = ServiceBusProvisioningMode.UseExisting;
- }
-
- ///
- /// Defines ServiceBUs provisioning modes
- ///
- internal enum ServiceBusProvisioningMode
- {
- ///
- /// The uan existing Azure resource
- ///
- UseExisting,
-
- ///
- /// Provision and delete resource
- ///
- CreateAndDelete
- }
-}
+ internal AzureResourceProvisioningMode ProvisioningMode { get; set; }
+ = AzureResourceProvisioningMode.UseExisting;
+ }
+}
diff --git a/src/AzureCloudServiceBus/Model/ServiceBusQueueModel.cs b/src/AzureCloudServiceBus/Model/ServiceBusQueueModel.cs
index 7e5b6db..4037a8b 100644
--- a/src/AzureCloudServiceBus/Model/ServiceBusQueueModel.cs
+++ b/src/AzureCloudServiceBus/Model/ServiceBusQueueModel.cs
@@ -1,33 +1,33 @@
-namespace Squadron
-{
+namespace Squadron
+{
///
/// ServiceBusQueue model
///
- public class ServiceBusQueueModel
- {
+ public class ServiceBusQueueModel
+ {
///
/// Initializes a new instance of the class.
///
/// The name.
- public ServiceBusQueueModel(string name)
- {
- Name = name;
- }
+ public ServiceBusQueueModel(string name)
+ {
+ Name = name;
+ }
///
/// Gets or sets the name.
///
///
/// The name.
///
- public string Name { get; set; }
-
+ public string Name { get; set; }
+
///
/// Gets or sets the generated name
///
///
/// The name of the created.
///
- internal string CreatedName { get; set; }
- }
-
-}
+ internal string CreatedName { get; set; }
+ }
+
+}
diff --git a/src/AzureCloudServiceBus/Model/ServiceBusTopicBuilder.cs b/src/AzureCloudServiceBus/Model/ServiceBusTopicBuilder.cs
index 874dab4..44f5887 100644
--- a/src/AzureCloudServiceBus/Model/ServiceBusTopicBuilder.cs
+++ b/src/AzureCloudServiceBus/Model/ServiceBusTopicBuilder.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
namespace Squadron
{
@@ -26,7 +26,6 @@ private ServiceBusTopicBuilder(string topicName)
public static ServiceBusTopicBuilder New()
=> new ServiceBusTopicBuilder();
-
///
/// Creates a new builder with a topic name
///
@@ -35,7 +34,6 @@ public static ServiceBusTopicBuilder New()
public static ServiceBusTopicBuilder New(string name)
=> new ServiceBusTopicBuilder(name);
-
///
/// Topic name
///
@@ -47,7 +45,6 @@ public ServiceBusTopicBuilder Name(string name)
return this;
}
-
///
/// Adds a subscription to the topic
///
diff --git a/src/AzureCloudServiceBus/ServiceBusManager.cs b/src/AzureCloudServiceBus/ServiceBusManager.cs
index 3622305..fff997b 100644
--- a/src/AzureCloudServiceBus/ServiceBusManager.cs
+++ b/src/AzureCloudServiceBus/ServiceBusManager.cs
@@ -78,7 +78,6 @@ internal async Task CreateTopic(ServiceBusTopicModel model)
}
}
-
internal async Task CreateQueueAsync(string name)
{
var pars = new SBQueue()
diff --git a/src/AzureCloudServiceBus/ServiceBusOptionsBuilder.cs b/src/AzureCloudServiceBus/ServiceBusOptionsBuilder.cs
index d2c60d6..b9d2a21 100644
--- a/src/AzureCloudServiceBus/ServiceBusOptionsBuilder.cs
+++ b/src/AzureCloudServiceBus/ServiceBusOptionsBuilder.cs
@@ -1,84 +1,79 @@
-using System.Collections.Generic;
-using System.Text;
-using Squadron.AzureCloud;
-
-namespace Squadron
-{
+using System.Collections.Generic;
+using System.Text;
+using Squadron.AzureCloud;
+
+namespace Squadron
+{
///
/// ServiceBusOptions builder
///
- public class ServiceBusOptionsBuilder : AzureResourceOptionsBuilder
- {
- private ServiceBusModel _model = new ServiceBusModel();
- private List _topics = new List();
- private List _queues = new List();
-
+ public class ServiceBusOptionsBuilder : AzureResourceOptionsBuilder
+ {
+ private ServiceBusModel _model = new ServiceBusModel();
+ private List _topics = new List();
+ private List _queues = new List();
+
///
/// Creates a new empty builder
///
///
- public static ServiceBusOptionsBuilder New() => new ServiceBusOptionsBuilder();
-
-
- private ServiceBusOptionsBuilder()
- : base()
- {
-
- }
-
+ public static ServiceBusOptionsBuilder New() => new ServiceBusOptionsBuilder();
+
+ private ServiceBusOptionsBuilder()
+ : base()
+ { }
+
///
/// Namespace
///
/// The namespace.
///
- public ServiceBusOptionsBuilder Namespace(string ns)
- {
- _model.Namespace = ns;
- return this;
- }
-
-
+ public ServiceBusOptionsBuilder Namespace(string ns)
+ {
+ _model.Namespace = ns;
+ return this;
+ }
+
///
/// Adds a topic.
///
/// The topic name.
///
- public ServiceBusTopicBuilder AddTopic(string name)
- {
- var topicBuilder = ServiceBusTopicBuilder.New(name);
- _topics.Add(topicBuilder);
- return topicBuilder;
- }
-
-
+ public ServiceBusTopicBuilder AddTopic(string name)
+ {
+ var topicBuilder = ServiceBusTopicBuilder.New(name);
+ _topics.Add(topicBuilder);
+ return topicBuilder;
+ }
+
///
/// Adds a queue.
///
/// The queue name.
///
- public ServiceBusQueueBuilder AddQueue(string name)
- {
- var queueBuilder = new ServiceBusQueueBuilder(name);
- _queues.Add(queueBuilder);
- return queueBuilder;
- }
-
-
+ public ServiceBusQueueBuilder AddQueue(string name)
+ {
+ var queueBuilder = new ServiceBusQueueBuilder(name);
+ _queues.Add(queueBuilder);
+ return queueBuilder;
+ }
+
+
///
/// Builds the options
///
///
- public ServiceBusModel Build()
- {
- foreach (ServiceBusTopicBuilder tb in _topics)
- {
- _model.Topics.Add(tb.Build());
- }
- foreach ( ServiceBusQueueBuilder qb in _queues)
- {
- _model.Queues.Add(qb.Build());
- }
- return _model;
- }
- }
-}
+ public ServiceBusModel Build()
+ {
+ foreach (ServiceBusTopicBuilder tb in _topics)
+ {
+ _model.Topics.Add(tb.Build());
+ }
+ foreach ( ServiceBusQueueBuilder qb in _queues)
+ {
+ _model.Queues.Add(qb.Build());
+ }
+ return _model;
+ }
+ }
+}
diff --git a/src/AzureCloudStorage.Tests/AzureCloudStorage.Tests.csproj b/src/AzureCloudStorage.Tests/AzureCloudStorage.Tests.csproj
new file mode 100644
index 0000000..e03035e
--- /dev/null
+++ b/src/AzureCloudStorage.Tests/AzureCloudStorage.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+ Squadron.AzureCloudStorage.Tests
+ Squadron.AzureCloudStorage.Tests
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+
+
diff --git a/src/AzureCloudStorage.Tests/AzureCloudStorageTests.cs b/src/AzureCloudStorage.Tests/AzureCloudStorageTests.cs
new file mode 100644
index 0000000..d2abc9e
--- /dev/null
+++ b/src/AzureCloudStorage.Tests/AzureCloudStorageTests.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Azure.Storage.Blobs;
+using Azure.Storage.Blobs.Models;
+using Azure.Storage.Blobs.Specialized;
+using FluentAssertions;
+using Squadron;
+using Xunit;
+
+namespace Squadron.AzureCloudStorage.Tests
+{
+ public class TestNewStorageAzureOptions : AzureCloudStorageAccountOptions
+ {
+ public override void Configure(StorageAccountOptionsBuilder builder)
+ {
+ builder
+ .AddBlobContainer("foo", isLegalHold: true)
+ .AddBlobContainer("bard", isLegalHold: false);
+ }
+ }
+
+ public class AzureCloudStorageTests
+ : IClassFixture>
+ {
+ private readonly AzureCloudStorageAccountResource _storageAccount;
+
+ public AzureCloudStorageTests(
+ AzureCloudStorageAccountResource storageAccount)
+ {
+ _storageAccount = storageAccount;
+ }
+
+ [Fact(Skip = "Can not run without Azure credentials")]
+ public async Task CreateBlobClient_UploadFile_ContentMatch()
+ {
+ //Arrange
+ BlobContainerClient container = _storageAccount.CreateBlobContainerClient("foo");
+ await container.CreateIfNotExistsAsync();
+ var inputText = "Hello_AzureCloudStorage";
+ var data = Encoding.UTF8.GetBytes(inputText);
+ var inputStream = new MemoryStream(data);
+
+ //Act
+ BlobClient blobClient = container.GetBlobClient("test.txt");
+ await blobClient.UploadAsync(inputStream);
+
+ //Assert
+ BlobDownloadInfo downloaded = blobClient.Download();
+ var reader = new StreamReader(downloaded.Content);
+ var downloadedText = reader.ReadToEnd();
+ downloadedText.Should().Be(inputText);
+ }
+
+ [Fact(Skip = "Can not run without Azure credentials")]
+ public void ConnectionString_NotNull()
+ {
+ //Arrange & Act
+ var connectionString = _storageAccount.ConnectionString;
+
+ //Assert
+ connectionString.Should().NotBeNull();
+ connectionString.Should().Contain("BlobEndpoint");
+ }
+ }
+}
diff --git a/src/AzureCloudStorage.Tests/appsettings.json b/src/AzureCloudStorage.Tests/appsettings.json
new file mode 100644
index 0000000..b0f7c5f
--- /dev/null
+++ b/src/AzureCloudStorage.Tests/appsettings.json
@@ -0,0 +1,15 @@
+{
+ //Overwrite with appsettings.user.json for local tests
+ "Squadron": {
+ "Azure": {
+ "SubscriptionId": "",
+ "ResourceGroup": "",
+ "DefaultLocation": "switzerlandnorth",
+ "Credentials": {
+ "TenantId": "",
+ "ClientId": "",
+ "Secret": ""
+ }
+ }
+ }
+}
diff --git a/src/AzureCloudStorage/AzureCloudStorage.csproj b/src/AzureCloudStorage/AzureCloudStorage.csproj
new file mode 100644
index 0000000..cfb8c88
--- /dev/null
+++ b/src/AzureCloudStorage/AzureCloudStorage.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+ Squadron.AzureCloudServiceBus
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AzureCloudStorage/AzureCloudStorageAccountResource.cs b/src/AzureCloudStorage/AzureCloudStorageAccountResource.cs
new file mode 100644
index 0000000..04582eb
--- /dev/null
+++ b/src/AzureCloudStorage/AzureCloudStorageAccountResource.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Azure.Storage.Blobs;
+using Squadron.AzureCloud;
+using Xunit;
+
+namespace Squadron
+{
+ ///
+ /// StorageAccount resources options
+ ///
+ ///
+ public abstract class AzureCloudStorageAccountOptions : AzureResourceOptions
+ {
+ ///
+ /// Configures the ServiceBus
+ ///
+ /// The builder.
+ public abstract void Configure(StorageAccountOptionsBuilder builder);
+ }
+
+ public class AzureCloudStorageAccountResource
+ : AzureResource, IAsyncLifetime
+ where TOptions : AzureCloudStorageAccountOptions,
+ new()
+ {
+ private StorageAccountManager _storageAccountManager;
+ private AzureStorageModel _storageModel;
+
+ public string ConnectionString { get; private set; }
+
+ public override async Task InitializeAsync()
+ {
+ await base.InitializeAsync();
+ BuildOptions();
+ InitializeStorageAccountManager();
+
+ await PrepareAccountAsync();
+ await PrepareBlobContainersAsync();
+
+ ConnectionString = await _storageAccountManager.GetConnectionString();
+ }
+
+ public BlobServiceClient CreateBlobClient()
+ {
+ return new BlobServiceClient(ConnectionString);
+ }
+
+ public BlobContainerClient CreateBlobContainerClient(string name)
+ {
+ var createdName = GetBlobContainerName(name);
+
+ return CreateBlobClient().GetBlobContainerClient(createdName);
+ }
+
+ public string GetBlobContainerName(string name)
+ {
+ BlobContainer container = _storageModel.BlobContainers
+ .FirstOrDefault(x => x.CreatedName == name);
+
+ if (container == null)
+ throw new InvalidOperationException($"No container with name: {name} exists");
+
+ return container.CreatedName;
+ }
+
+ public async Task DisposeAsync()
+ {
+ await DeleteBlobContainersAsync();
+
+ if (_storageModel.ProvisioningMode == AzureResourceProvisioningMode.CreateAndDelete)
+ {
+ await _storageAccountManager.DeleteAccountAsync();
+ }
+ }
+
+ private async Task DeleteBlobContainersAsync()
+ {
+ if (_storageModel.BlobContainers != null)
+ {
+ foreach (BlobContainer container in _storageModel.BlobContainers)
+ {
+ await _storageAccountManager.DeleteBlobContainerAsync(container);
+ }
+ }
+ }
+
+ private void BuildOptions()
+ {
+ var builder = StorageAccountOptionsBuilder.New();
+ var options = new TOptions();
+ options.Configure(builder);
+ LoadResourceConfiguration(builder);
+ _storageModel = builder.Build();
+ }
+
+ private void InitializeStorageAccountManager()
+ {
+ _storageAccountManager = new StorageAccountManager(
+ AzureConfig.Credentials,
+ new AzureResourceIdentifier
+ {
+ SubscriptionId = AzureConfig.SubscriptionId,
+ ResourceGroupName = AzureConfig.ResourceGroup,
+ Name = _storageModel.Name
+ });
+ }
+
+ private async Task PrepareAccountAsync()
+ {
+ if (_storageModel.Name == null)
+ {
+ _storageModel.ProvisioningMode = AzureResourceProvisioningMode.CreateAndDelete;
+ _storageModel.Name = await _storageAccountManager.CreateAccountAsync(
+ AzureConfig.DefaultLocation);
+ }
+ }
+
+ private async Task PrepareBlobContainersAsync()
+ {
+ if (_storageModel.BlobContainers != null)
+ {
+ foreach (BlobContainer container in _storageModel.BlobContainers)
+ {
+ if (_storageModel.ProvisioningMode == AzureResourceProvisioningMode.UseExisting)
+ {
+ container.CreatedName = $"{container.Name}{DateTime.UtcNow.Ticks}";
+ }
+ else
+ {
+ container.CreatedName = container.Name;
+ }
+
+ await _storageAccountManager.CreateContainerAsync(
+ container);
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/AzureCloudStorage/Model/AzureStorageAccountModel.cs b/src/AzureCloudStorage/Model/AzureStorageAccountModel.cs
new file mode 100644
index 0000000..d0e84e0
--- /dev/null
+++ b/src/AzureCloudStorage/Model/AzureStorageAccountModel.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Squadron.AzureCloud;
+
+namespace Squadron
+{
+ public class AzureStorageModel
+ {
+ public string Name { get; set; }
+
+ public IEnumerable BlobContainers { get; set; }
+
+ internal AzureResourceProvisioningMode ProvisioningMode { get; set; }
+ = AzureResourceProvisioningMode.UseExisting;
+ }
+
+ public class BlobContainer
+ {
+ public string Name { get; set; }
+
+ public string CreatedName { get; set; }
+
+ public bool IsLegalHold { get; set; }
+ }
+}
diff --git a/src/AzureCloudStorage/StorageAccountManager.cs b/src/AzureCloudStorage/StorageAccountManager.cs
new file mode 100644
index 0000000..0a3fbf5
--- /dev/null
+++ b/src/AzureCloudStorage/StorageAccountManager.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Squadron.AzureCloud;
+using Microsoft.Azure.Management.Storage;
+using Microsoft.Azure.Management.Storage.Models;
+using System.Threading.Tasks;
+using Microsoft.Rest;
+using System.Linq;
+
+namespace Squadron
+{
+ internal sealed class StorageAccountManager
+ {
+ private StorageManagementClient _client;
+ private readonly AzureCredentials _azureCredentials;
+ private readonly AzureResourceIdentifier _identifier;
+
+ internal StorageAccountManager(
+ AzureCredentials azureCredentials,
+ AzureResourceIdentifier identifier)
+ {
+ _azureCredentials =
+ azureCredentials ?? throw new ArgumentNullException(nameof(azureCredentials));
+ _identifier =
+ identifier ?? throw new ArgumentNullException(nameof(identifier));
+ }
+
+ private async Task EnsureAuthenticatedAsync()
+ {
+ if (_client is null)
+ {
+ var tm = new AzureAdTokenManager();
+ TokenCredentials token = await tm.RequestTokenAsync(_azureCredentials);
+ _client = new StorageManagementClient(token)
+ {
+ SubscriptionId = _identifier.SubscriptionId,
+ };
+ }
+ }
+
+ internal async Task CreateAccountAsync(string location)
+ {
+ await EnsureAuthenticatedAsync();
+
+ var name = $"squadron{Guid.NewGuid().ToString("N").Substring(0,8)}";
+
+ var createParams = new StorageAccountCreateParameters
+ {
+ Sku = new Sku(SkuName.StandardLRS),
+ Location = location,
+ AccessTier = AccessTier.Hot,
+ Kind = "BlobStorage"
+ };
+
+ await _client.StorageAccounts.CreateAsync(
+ _identifier.ResourceGroupName,
+ name,
+ createParams);
+
+ _identifier.Name = name;
+
+ return name;
+ }
+
+ internal async Task DeleteAccountAsync()
+ {
+ await _client.StorageAccounts.DeleteAsync(
+ _identifier.ResourceGroupName,
+ _identifier.Name);
+ }
+
+ internal async Task CreateContainerAsync(
+ BlobContainer container)
+ {
+ await EnsureAuthenticatedAsync();
+
+ var createModel = new Microsoft.Azure.Management.Storage.Models.BlobContainer
+ {
+ PublicAccess = PublicAccess.None,
+ };
+
+ await _client.BlobContainers.CreateAsync(
+ _identifier.ResourceGroupName,
+ _identifier.Name,
+ container.CreatedName,
+ createModel);
+
+ if (container.IsLegalHold)
+ {
+ await _client.BlobContainers.SetLegalHoldAsync(
+ _identifier.ResourceGroupName,
+ _identifier.Name,
+ container.CreatedName,
+ new List { "Squadron" });
+ }
+ }
+
+ internal async Task DeleteBlobContainerAsync(BlobContainer container)
+ {
+ if (container.IsLegalHold)
+ {
+ await _client.BlobContainers.ClearLegalHoldAsync(
+ _identifier.ResourceGroupName,
+ _identifier.Name,
+ container.CreatedName,
+ new List { "Squadron" });
+ }
+
+ await _client.BlobContainers.DeleteAsync(
+ _identifier.ResourceGroupName,
+ _identifier.Name,
+ container.CreatedName);
+ }
+
+ internal async Task GetConnectionString()
+ {
+ StorageAccountListKeysResult keys = await _client.StorageAccounts
+ .ListKeysAsync(_identifier.ResourceGroupName, _identifier.Name);
+
+ var key = keys.Keys.First().Value;
+
+ var connectionString = $"DefaultEndpointsProtocol=https;" +
+ $"AccountName={_identifier.Name};" +
+ $"AccountKey={key};EndpointSuffix=core.windows.net";
+
+ return connectionString;
+ }
+ }
+}
diff --git a/src/AzureCloudStorage/StorageAccountOptionsBuilder.cs b/src/AzureCloudStorage/StorageAccountOptionsBuilder.cs
new file mode 100644
index 0000000..fd57c86
--- /dev/null
+++ b/src/AzureCloudStorage/StorageAccountOptionsBuilder.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using Squadron.AzureCloud;
+
+namespace Squadron
+{
+ public class StorageAccountOptionsBuilder : AzureResourceOptionsBuilder
+ {
+ private List _blobContainers = new List();
+ private AzureStorageModel _model = new AzureStorageModel();
+
+ ///
+ /// Creates a new empty builder
+ ///
+ ///
+ public static StorageAccountOptionsBuilder New()
+ => new StorageAccountOptionsBuilder();
+
+ public StorageAccountOptionsBuilder WithName(string name)
+ {
+ _model.Name = name;
+
+ return this;
+ }
+
+ private StorageAccountOptionsBuilder()
+ : base()
+ { }
+
+ public AzureStorageModel Build()
+ {
+ _model.BlobContainers = _blobContainers;
+
+ return _model;
+ }
+
+ public StorageAccountOptionsBuilder AddBlobContainer(
+ string name,
+ bool isLegalHold = false)
+ {
+ _blobContainers.Add(new BlobContainer
+ {
+ Name = name,
+ IsLegalHold = isLegalHold
+ });
+
+ return this;
+ }
+ }
+}
+
diff --git a/src/AzureStorage.Tests/AzureStorageBlobResourceTests.cs b/src/AzureStorage.Tests/AzureStorageBlobResourceTests.cs
index 6f4f393..faa07aa 100644
--- a/src/AzureStorage.Tests/AzureStorageBlobResourceTests.cs
+++ b/src/AzureStorage.Tests/AzureStorageBlobResourceTests.cs
@@ -1,48 +1,48 @@
using System.Text;
-using System.Threading.Tasks;
-using FluentAssertions;
-using Microsoft.Azure.Storage.Blob;
-using Xunit;
-
+using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.Azure.Storage.Blob;
+using Xunit;
+
namespace Squadron.AzureStorage.Tests
-{
+{
public class AzureStorageBlobResourceTests : IClassFixture
- {
- private readonly AzureStorageBlobResource _azureStorageResource;
-
- public AzureStorageBlobResourceTests(AzureStorageBlobResource azureStorageResource)
- {
- _azureStorageResource = azureStorageResource;
- }
-
- [Fact]
- public async Task CreateBlobClient_UploadFile_ContentMatch()
- {
- //Arrange
- CloudBlobClient blobClient = _azureStorageResource.CreateBlobClient();
- CloudBlobContainer container = blobClient.GetContainerReference("foo");
- await container.CreateIfNotExistsAsync();
- string inputText = "Hello_AzureStorage";
- var data = Encoding.UTF8.GetBytes(inputText);
-
- //Act
- CloudBlockBlob textFile = container.GetBlockBlobReference("test.txt");
- await textFile.UploadFromByteArrayAsync(data, 0, data.Length);
-
- //Assert
- string downloaded = await textFile.DownloadTextAsync();
- downloaded.Should().Be(inputText);
- }
-
- [Fact]
- public void ConnectionString_NotNull()
- {
- //Arrange & Act
- string connectionString = _azureStorageResource.ConnectionString;
-
- //Assert
- connectionString.Should().NotBeNull();
- connectionString.Should().Contain("BlobEndpoint");
- }
- }
+ {
+ private readonly AzureStorageBlobResource _azureStorageResource;
+
+ public AzureStorageBlobResourceTests(AzureStorageBlobResource azureStorageResource)
+ {
+ _azureStorageResource = azureStorageResource;
+ }
+
+ [Fact]
+ public async Task CreateBlobClient_UploadFile_ContentMatch()
+ {
+ //Arrange
+ CloudBlobClient blobClient = _azureStorageResource.CreateBlobClient();
+ CloudBlobContainer container = blobClient.GetContainerReference("foo");
+ await container.CreateIfNotExistsAsync();
+ string inputText = "Hello_AzureStorage";
+ var data = Encoding.UTF8.GetBytes(inputText);
+
+ //Act
+ CloudBlockBlob textFile = container.GetBlockBlobReference("test.txt");
+ await textFile.UploadFromByteArrayAsync(data, 0, data.Length);
+
+ //Assert
+ string downloaded = await textFile.DownloadTextAsync();
+ downloaded.Should().Be(inputText);
+ }
+
+ [Fact]
+ public void ConnectionString_NotNull()
+ {
+ //Arrange & Act
+ string connectionString = _azureStorageResource.ConnectionString;
+
+ //Assert
+ connectionString.Should().NotBeNull();
+ connectionString.Should().Contain("BlobEndpoint");
+ }
+ }
}
diff --git a/src/Core/DockerContainerManager.cs b/src/Core/DockerContainerManager.cs
index 88e75dc..43b15ab 100644
--- a/src/Core/DockerContainerManager.cs
+++ b/src/Core/DockerContainerManager.cs
@@ -1,567 +1,567 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Docker.DotNet;
-using Docker.DotNet.Models;
-using Polly;
-using Version = System.Version;
-
-namespace Squadron
-{
- ///
- /// Manager to work with docker containers
- ///
- ///
- public class DockerContainerManager : IDockerContainerManager
- {
- ///
- public ContainerInstance Instance { get; } = new ContainerInstance();
-
- private readonly ContainerResourceSettings _settings;
- private readonly DockerConfiguration _dockerConfiguration;
- private readonly AuthConfig _authConfig = null;
-
- private readonly DockerClient _client = null;
-
- private static IDictionary _uniqueNetworkNames =
- new Dictionary();
-
- private static readonly object _createNetworkLock = new object();
-
- private readonly AsyncPolicy retryPolicy = Policy
- .Handle()
- .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(2));
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The settings.
- ///
- public DockerContainerManager(ContainerResourceSettings settings,
- DockerConfiguration dockerConfiguration)
- {
- _settings = settings;
- _dockerConfiguration = dockerConfiguration;
- _client = new DockerClientConfiguration(
- LocalDockerUri(),
- null,
- TimeSpan.FromMinutes(5)
- )
- .CreateClient(Version.Parse("1.25"));
- _authConfig = GetAuthConfig();
- }
-
- private AuthConfig GetAuthConfig()
- {
- if (_settings.RegistryName != null)
- {
- DockerRegistryConfiguration registryConfig = _dockerConfiguration.Registries
- .FirstOrDefault(x => x.Name.Equals(
- _settings.RegistryName,
- StringComparison.InvariantCultureIgnoreCase));
-
- if (registryConfig == null)
- {
- throw new InvalidOperationException(
- $"No container registry with name '{_settings.RegistryName}'" +
- "found in configuration");
- }
-
- return new AuthConfig
- {
- ServerAddress = registryConfig.Address,
- Username = registryConfig.Username,
- Password = registryConfig.Password
- };
- }
- return new AuthConfig();
- }
-
- private static Uri LocalDockerUri()
- {
-#if NET461
- return new Uri("npipe://./pipe/docker_engine");
-#else
- var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- return isWindows ?
- new Uri("npipe://./pipe/docker_engine") :
- new Uri("unix:/var/run/docker.sock");
-#endif
- }
-
-
-
- ///
- public async Task CreateAndStartContainerAsync()
- {
- if (!_settings.PreferLocalImage || !await ImageExists())
- {
- await PullImageAsync();
- }
-
- await CreateContainerAsync();
- await StartContainerAsync();
- await ConnectToNetworksAsync();
- await ResolveHostAddressAsync();
-
- if (!Instance.IsRunning)
- {
- var logs = await ConsumeLogsAsync(TimeSpan.FromSeconds(5));
- throw new ContainerException(
- $"Container exited with following logs: \r\n {logs}");
- }
- }
-
- ///
- public async Task StopContainerAsync()
- {
- var stopOptions = new ContainerStopParameters
- {
- WaitBeforeKillSeconds = 5
- };
-
- bool stopped = await _client.Containers
- .StopContainerAsync(Instance.Id, stopOptions, default);
-
- return stopped;
- }
-
- public async Task PauseAsync(TimeSpan resumeAfter)
- {
- await _client.Containers.PauseContainerAsync(Instance.Id);
- Task.Delay(resumeAfter)
- .ContinueWith((c) => ResumeAsync())
- .Start();
- }
-
- public async Task ResumeAsync()
- {
- await _client.Containers.UnpauseContainerAsync(Instance.Id);
- }
-
- ///
- public async Task RemoveContainerAsync()
- {
- var removeOptions = new ContainerRemoveParameters
- {
- Force = true,
- RemoveVolumes = true
- };
-
- try
- {
- await retryPolicy
- .ExecuteAsync(async () =>
- {
- await _client.Containers
- .RemoveContainerAsync(Instance.Id, removeOptions);
-
- foreach (string network in _settings.Networks)
- {
- await RemoveNetworkIfUnused(network);
- }
- });
- }
- catch (Exception ex)
- {
- throw new ContainerException(
- $"Error in RemoveContainer: {_settings.UniqueContainerName}", ex);
- }
- }
-
- ///
- public async Task CopyToContainerAsync(CopyContext context)
- {
- using (var archiver = new TarArchiver(context.Source))
- {
- await _client.Containers.ExtractArchiveToContainerAsync(
- Instance.Id,
- new ContainerPathStatParameters
- {
- AllowOverwriteDirWithFile = true,
- Path = context.DestinationFolder.Replace("\\", "/")
- }, archiver.Stream);
- }
- }
-
-
- ///
- public async Task InvokeCommandAsync(
- ContainerExecCreateParameters parameters)
- {
- ContainerExecCreateResponse response = await _client.Containers
- .ExecCreateContainerAsync(
- Instance.Id,
- parameters);
-
- if (!string.IsNullOrEmpty(response.ID))
- {
- using (MultiplexedStream stream = await _client.Containers
- .StartAndAttachContainerExecAsync(
- response.ID, false))
- {
- (string stdout, string stderr) output = await stream
- .ReadOutputToEndAsync(CancellationToken.None);
-
- if (!string.IsNullOrEmpty(output.stderr) && output.stderr.Contains("error"))
- {
- var error = new StringBuilder();
- error.AppendLine($"Error when invoking command \"{string.Join(" ", parameters.Cmd)}\"");
- error.AppendLine(output.stderr);
-
- throw new ContainerException(error.ToString());
- }
- }
- }
- }
-
-
- ///
- public async Task ConsumeLogsAsync(TimeSpan timeout)
- {
- var containerStatsParameters = new ContainerLogsParameters
- {
- Follow = true,
- ShowStderr = true,
- ShowStdout = true
- };
-
- Stream logStream = await _client
- .Containers
- .GetContainerLogsAsync(
- Instance.Id,
- containerStatsParameters,
- default);
-
- var logs = await ReadAsync(logStream, timeout);
- Instance.Logs.Add(logs);
- Trace.TraceInformation(logs);
- return logs;
- }
-
- private async Task StartContainerAsync()
- {
- var containerStartParameters = new ContainerStartParameters();
-
- try
- {
- await retryPolicy
- .ExecuteAsync(async () =>
- {
- bool started = await _client.Containers.StartContainerAsync(
- Instance.Id,
- containerStartParameters);
-
- if (!started)
- {
- throw new ContainerException(
- "Docker container creation/startup failed.");
- }
- });
- }
- catch (Exception ex)
- {
- throw new ContainerException(
- $"Error in StartContainer: {_settings.UniqueContainerName}", ex);
- }
- }
-
-
- private async Task CreateContainerAsync()
- {
- var hostConfig = new HostConfig
- {
- PublishAllPorts = true
- };
-
- if (_settings.ExternalPort > 0)
- {
- hostConfig.PublishAllPorts = false;
- hostConfig.PortBindings = new Dictionary> {
- {
- _settings.InternalPort + "/tcp", new List {
- new PortBinding {
- HostPort = _settings.ExternalPort.ToString()
- }
- }
- }};
- }
-
- var startParams = new CreateContainerParameters
- {
- Name = _settings.UniqueContainerName,
- Image = _settings.ImageFullname,
- AttachStdout = true,
- AttachStderr = true,
- AttachStdin = false,
- Tty = false,
- HostConfig = hostConfig,
- Env = _settings.EnvironmentVariables,
- Cmd = _settings.Cmd
- };
-
-
- try
- {
- await retryPolicy
- .ExecuteAsync(async () =>
- {
- CreateContainerResponse response = await _client
- .Containers
- .CreateContainerAsync(startParams);
-
- if (string.IsNullOrEmpty(response.ID))
- {
- throw new ContainerException(
- "Could not create the container");
- }
- Instance.Id = response.ID;
- Instance.Name = startParams.Name;
- });
- }
- catch (Exception ex)
- {
- throw new ContainerException(
- $"Error in CreateContainer: {_settings.UniqueContainerName}", ex);
- }
- }
-
- public async Task ImageExists()
- {
- try
- {
- return await retryPolicy
- .ExecuteAsync(async () =>
- {
- IEnumerable listResponse =
- await _client.Images.ListImagesAsync(
- new ImagesListParameters { MatchName = _settings.ImageFullname });
-
- return listResponse.Any();
- }
- );
- }
- catch (Exception ex)
- {
- throw new ContainerException(
- $"Error in ImageExists: {_settings.ImageFullname }", ex);
- }
- }
-
- private async Task PullImageAsync()
- {
- void Handler(JSONMessage message)
- {
- if (!string.IsNullOrEmpty(message.ErrorMessage))
- {
- throw new ContainerException(
- $"Could not pull the image: {_settings.ImageFullname}. " +
- $"Error: {message.ErrorMessage}");
- }
- }
-
- try
- {
- await retryPolicy
- .ExecuteAsync(async () =>
- {
- await _client.Images.CreateImageAsync(
- new ImagesCreateParameters { FromImage = _settings.ImageFullname },
- _authConfig,
- new Progress(Handler));
- });
- }
- catch (Exception ex)
- {
- throw new ContainerException(
- $"Error in PullImage: {_settings.ImageFullname }", ex);
- }
- }
-
-
- private async Task ResolveHostAddressAsync()
- {
- ContainerInspectResponse inspectResponse = await _client
- .Containers
- .InspectContainerAsync(Instance.Id);
-
- ContainerAddressMode addressMode = GetAddressMode();
-
- if (addressMode == ContainerAddressMode.Port)
- {
- Instance.Address = "localhost";
- string containerPort = $"{_settings.InternalPort}/tcp";
- if (!inspectResponse.NetworkSettings.Ports.ContainsKey(containerPort))
- {
- throw new Exception($"Failed to resolve host port for {containerPort}");
- }
-
- PortBinding binding = inspectResponse
- .NetworkSettings
- .Ports[containerPort]
- .FirstOrDefault();
-
- if (binding == null || string.IsNullOrEmpty(binding.HostPort))
- {
- throw new Exception($"The resolved port binding is empty");
- }
-
- Instance.HostPort = int.Parse(binding.HostPort);
- }
- else
- {
- Instance.Address = inspectResponse.NetworkSettings.IPAddress;
- Instance.HostPort = _settings.InternalPort;
- }
- Instance.IsRunning = inspectResponse.State.Running;
- }
-
- private ContainerAddressMode GetAddressMode()
- {
- ContainerAddressMode addressMode = _dockerConfiguration.DefaultAddressMode;
- if (_settings.AddressMode != ContainerAddressMode.Auto)
- {
- //Overide by user setting
- addressMode = _settings.AddressMode;
- }
- if (addressMode == ContainerAddressMode.Auto)
- {
- //Default to port when not defined
- addressMode = ContainerAddressMode.Port;
- }
-#if !NET461
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- //OSX can only handle Port
- addressMode = ContainerAddressMode.Port;
- }
-#endif
- return addressMode;
- }
-
- private async Task ReadAsync(
- Stream logStream,
- TimeSpan timeout)
- {
- var result = new StringBuilder();
- var timeoutTask = Task.Delay(timeout);
- using (logStream)
- {
- const int size = 256;
- byte[] buffer = new byte[size];
-
- while (true)
- {
- Task readTask = logStream.ReadAsync(
- buffer, 0, size);
-
- if (await Task.WhenAny(readTask, timeoutTask) == timeoutTask)
- {
- logStream.Close();
- break;
- }
-
- var read = await readTask;
- if (read <= 0)
- {
- break;
- }
-
- char[] chunkChars = new char[read * 2];
-
- int consumed = 0;
- for (int i = 0; i < read; i++)
- {
- if (buffer[i] > 31 && buffer[i] < 128)
- {
- chunkChars[consumed++] = (char)buffer[i];
- }
- else if (buffer[i] == (byte)'\n')
- {
- chunkChars[consumed++] = '\r';
- chunkChars[consumed++] = '\n';
- }
- else if (buffer[i] == (byte)'\t')
- {
- chunkChars[consumed++] = '\t';
- }
- }
-
- string chunk = new string(
- chunkChars, 0, consumed);
-
- result.Append(chunk);
- }
- }
- return result.ToString();
- }
-
- private async Task ConnectToNetworksAsync()
- {
- foreach (string networkName in _settings.Networks)
- {
- string networkId = GetNetworkId(networkName);
-
- await retryPolicy.ExecuteAsync(async () =>
- {
- await _client.Networks.ConnectNetworkAsync(
- networkId,
- new NetworkConnectParameters()
- {
- Container = Instance.Id
- });
- });
- }
- }
-
- private string GetNetworkId(string networkName)
- {
- // Lock to ensure thread safety of static list
- lock (_createNetworkLock)
- {
- if (_uniqueNetworkNames.ContainsKey(networkName))
- {
- return _uniqueNetworkNames[networkName];
- }
-
- return CreateNetwork(networkName);
- }
- }
-
- private string CreateNetwork(string networkName)
- {
- string uniqueNetworkName = UniqueNameGenerator.CreateNetworkName(networkName);
-
- NetworksCreateResponse response = _client.Networks.CreateNetworkAsync(
- new NetworksCreateParameters()
- {
- Name = uniqueNetworkName
- }).Result;
-
- _uniqueNetworkNames.Add(networkName, uniqueNetworkName);
- return response.ID;
- }
-
- private async Task RemoveNetworkIfUnused(string networkName)
- {
- string uniqueNetworkName = _uniqueNetworkNames[networkName];
- await retryPolicy
- .ExecuteAsync(async () =>
- {
- NetworkResponse inspectResponse = (await _client.Networks.ListNetworksAsync())
- .Single(n => n.Name == uniqueNetworkName);
-
- if (!inspectResponse.Containers.Any())
- {
- await _client.Networks.DeleteNetworkAsync(inspectResponse.ID);
- }
- });
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Docker.DotNet;
+using Docker.DotNet.Models;
+using Polly;
+using Version = System.Version;
+
+namespace Squadron
+{
+ ///
+ /// Manager to work with docker containers
+ ///
+ ///
+ public class DockerContainerManager : IDockerContainerManager
+ {
+ ///
+ public ContainerInstance Instance { get; } = new ContainerInstance();
+
+ private readonly ContainerResourceSettings _settings;
+ private readonly DockerConfiguration _dockerConfiguration;
+ private readonly AuthConfig _authConfig = null;
+
+ private readonly DockerClient _client = null;
+
+ private static IDictionary _uniqueNetworkNames =
+ new Dictionary();
+
+ private static readonly object _createNetworkLock = new object();
+
+ private readonly AsyncPolicy retryPolicy = Policy
+ .Handle()
+ .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(2));
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The settings.
+ ///
+ public DockerContainerManager(ContainerResourceSettings settings,
+ DockerConfiguration dockerConfiguration)
+ {
+ _settings = settings;
+ _dockerConfiguration = dockerConfiguration;
+ _client = new DockerClientConfiguration(
+ LocalDockerUri(),
+ null,
+ TimeSpan.FromMinutes(5)
+ )
+ .CreateClient(Version.Parse("1.25"));
+ _authConfig = GetAuthConfig();
+ }
+
+ private AuthConfig GetAuthConfig()
+ {
+ if (_settings.RegistryName != null)
+ {
+ DockerRegistryConfiguration registryConfig = _dockerConfiguration.Registries
+ .FirstOrDefault(x => x.Name.Equals(
+ _settings.RegistryName,
+ StringComparison.InvariantCultureIgnoreCase));
+
+ if (registryConfig == null)
+ {
+ throw new InvalidOperationException(
+ $"No container registry with name '{_settings.RegistryName}'" +
+ "found in configuration");
+ }
+
+ return new AuthConfig
+ {
+ ServerAddress = registryConfig.Address,
+ Username = registryConfig.Username,
+ Password = registryConfig.Password
+ };
+ }
+ return new AuthConfig();
+ }
+
+ private static Uri LocalDockerUri()
+ {
+#if NET461
+ return new Uri("npipe://./pipe/docker_engine");
+#else
+ var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ return isWindows ?
+ new Uri("npipe://./pipe/docker_engine") :
+ new Uri("unix:/var/run/docker.sock");
+#endif
+ }
+
+
+
+ ///
+ public async Task CreateAndStartContainerAsync()
+ {
+ if (!_settings.PreferLocalImage || !await ImageExists())
+ {
+ await PullImageAsync();
+ }
+
+ await CreateContainerAsync();
+ await StartContainerAsync();
+ await ConnectToNetworksAsync();
+ await ResolveHostAddressAsync();
+
+ if (!Instance.IsRunning)
+ {
+ var logs = await ConsumeLogsAsync(TimeSpan.FromSeconds(5));
+ throw new ContainerException(
+ $"Container exited with following logs: \r\n {logs}");
+ }
+ }
+
+ ///
+ public async Task StopContainerAsync()
+ {
+ var stopOptions = new ContainerStopParameters
+ {
+ WaitBeforeKillSeconds = 5
+ };
+
+ bool stopped = await _client.Containers
+ .StopContainerAsync(Instance.Id, stopOptions, default);
+
+ return stopped;
+ }
+
+ public async Task PauseAsync(TimeSpan resumeAfter)
+ {
+ await _client.Containers.PauseContainerAsync(Instance.Id);
+ Task.Delay(resumeAfter)
+ .ContinueWith((c) => ResumeAsync())
+ .Start();
+ }
+
+ public async Task ResumeAsync()
+ {
+ await _client.Containers.UnpauseContainerAsync(Instance.Id);
+ }
+
+ ///
+ public async Task RemoveContainerAsync()
+ {
+ var removeOptions = new ContainerRemoveParameters
+ {
+ Force = true,
+ RemoveVolumes = true
+ };
+
+ try
+ {
+ await retryPolicy
+ .ExecuteAsync(async () =>
+ {
+ await _client.Containers
+ .RemoveContainerAsync(Instance.Id, removeOptions);
+
+ foreach (string network in _settings.Networks)
+ {
+ await RemoveNetworkIfUnused(network);
+ }
+ });
+ }
+ catch (Exception ex)
+ {
+ throw new ContainerException(
+ $"Error in RemoveContainer: {_settings.UniqueContainerName}", ex);
+ }
+ }
+
+ ///
+ public async Task CopyToContainerAsync(CopyContext context)
+ {
+ using (var archiver = new TarArchiver(context.Source))
+ {
+ await _client.Containers.ExtractArchiveToContainerAsync(
+ Instance.Id,
+ new ContainerPathStatParameters
+ {
+ AllowOverwriteDirWithFile = true,
+ Path = context.DestinationFolder.Replace("\\", "/")
+ }, archiver.Stream);
+ }
+ }
+
+
+ ///
+ public async Task InvokeCommandAsync(
+ ContainerExecCreateParameters parameters)
+ {
+ ContainerExecCreateResponse response = await _client.Containers
+ .ExecCreateContainerAsync(
+ Instance.Id,
+ parameters);
+
+ if (!string.IsNullOrEmpty(response.ID))
+ {
+ using (MultiplexedStream stream = await _client.Containers
+ .StartAndAttachContainerExecAsync(
+ response.ID, false))
+ {
+ (string stdout, string stderr) output = await stream
+ .ReadOutputToEndAsync(CancellationToken.None);
+
+ if (!string.IsNullOrEmpty(output.stderr) && output.stderr.Contains("error"))
+ {
+ var error = new StringBuilder();
+ error.AppendLine($"Error when invoking command \"{string.Join(" ", parameters.Cmd)}\"");
+ error.AppendLine(output.stderr);
+
+ throw new ContainerException(error.ToString());
+ }
+ }
+ }
+ }
+
+
+ ///
+ public async Task ConsumeLogsAsync(TimeSpan timeout)
+ {
+ var containerStatsParameters = new ContainerLogsParameters
+ {
+ Follow = true,
+ ShowStderr = true,
+ ShowStdout = true
+ };
+
+ Stream logStream = await _client
+ .Containers
+ .GetContainerLogsAsync(
+ Instance.Id,
+ containerStatsParameters,
+ default);
+
+ var logs = await ReadAsync(logStream, timeout);
+ Instance.Logs.Add(logs);
+ Trace.TraceInformation(logs);
+ return logs;
+ }
+
+ private async Task StartContainerAsync()
+ {
+ var containerStartParameters = new ContainerStartParameters();
+
+ try
+ {
+ await retryPolicy
+ .ExecuteAsync(async () =>
+ {
+ bool started = await _client.Containers.StartContainerAsync(
+ Instance.Id,
+ containerStartParameters);
+
+ if (!started)
+ {
+ throw new ContainerException(
+ "Docker container creation/startup failed.");
+ }
+ });
+ }
+ catch (Exception ex)
+ {
+ throw new ContainerException(
+ $"Error in StartContainer: {_settings.UniqueContainerName}", ex);
+ }
+ }
+
+
+ private async Task CreateContainerAsync()
+ {
+ var hostConfig = new HostConfig
+ {
+ PublishAllPorts = true
+ };
+
+ if (_settings.ExternalPort > 0)
+ {
+ hostConfig.PublishAllPorts = false;
+ hostConfig.PortBindings = new Dictionary> {
+ {
+ _settings.InternalPort + "/tcp", new List {
+ new PortBinding {
+ HostPort = _settings.ExternalPort.ToString()
+ }
+ }
+ }};
+ }
+
+ var startParams = new CreateContainerParameters
+ {
+ Name = _settings.UniqueContainerName,
+ Image = _settings.ImageFullname,
+ AttachStdout = true,
+ AttachStderr = true,
+ AttachStdin = false,
+ Tty = false,
+ HostConfig = hostConfig,
+ Env = _settings.EnvironmentVariables,
+ Cmd = _settings.Cmd
+ };
+
+
+ try
+ {
+ await retryPolicy
+ .ExecuteAsync(async () =>
+ {
+ CreateContainerResponse response = await _client
+ .Containers
+ .CreateContainerAsync(startParams);
+
+ if (string.IsNullOrEmpty(response.ID))
+ {
+ throw new ContainerException(
+ "Could not create the container");
+ }
+ Instance.Id = response.ID;
+ Instance.Name = startParams.Name;
+ });
+ }
+ catch (Exception ex)
+ {
+ throw new ContainerException(
+ $"Error in CreateContainer: {_settings.UniqueContainerName}", ex);
+ }
+ }
+
+ public async Task ImageExists()
+ {
+ try
+ {
+ return await retryPolicy
+ .ExecuteAsync(async () =>
+ {
+ IEnumerable listResponse =
+ await _client.Images.ListImagesAsync(
+ new ImagesListParameters { MatchName = _settings.ImageFullname });
+
+ return listResponse.Any();
+ }
+ );
+ }
+ catch (Exception ex)
+ {
+ throw new ContainerException(
+ $"Error in ImageExists: {_settings.ImageFullname }", ex);
+ }
+ }
+
+ private async Task PullImageAsync()
+ {
+ void Handler(JSONMessage message)
+ {
+ if (!string.IsNullOrEmpty(message.ErrorMessage))
+ {
+ throw new ContainerException(
+ $"Could not pull the image: {_settings.ImageFullname}. " +
+ $"Error: {message.ErrorMessage}");
+ }
+ }
+
+ try
+ {
+ await retryPolicy
+ .ExecuteAsync(async () =>
+ {
+ await _client.Images.CreateImageAsync(
+ new ImagesCreateParameters { FromImage = _settings.ImageFullname },
+ _authConfig,
+ new Progress(Handler));
+ });
+ }
+ catch (Exception ex)
+ {
+ throw new ContainerException(
+ $"Error in PullImage: {_settings.ImageFullname }", ex);
+ }
+ }
+
+
+ private async Task ResolveHostAddressAsync()
+ {
+ ContainerInspectResponse inspectResponse = await _client
+ .Containers
+ .InspectContainerAsync(Instance.Id);
+
+ ContainerAddressMode addressMode = GetAddressMode();
+
+ if (addressMode == ContainerAddressMode.Port)
+ {
+ Instance.Address = "localhost";
+ string containerPort = $"{_settings.InternalPort}/tcp";
+ if (!inspectResponse.NetworkSettings.Ports.ContainsKey(containerPort))
+ {
+ throw new Exception($"Failed to resolve host port for {containerPort}");
+ }
+
+ PortBinding binding = inspectResponse
+ .NetworkSettings
+ .Ports[containerPort]
+ .FirstOrDefault();
+
+ if (binding == null || string.IsNullOrEmpty(binding.HostPort))
+ {
+ throw new Exception($"The resolved port binding is empty");
+ }
+
+ Instance.HostPort = int.Parse(binding.HostPort);
+ }
+ else
+ {
+ Instance.Address = inspectResponse.NetworkSettings.IPAddress;
+ Instance.HostPort = _settings.InternalPort;
+ }
+ Instance.IsRunning = inspectResponse.State.Running;
+ }
+
+ private ContainerAddressMode GetAddressMode()
+ {
+ ContainerAddressMode addressMode = _dockerConfiguration.DefaultAddressMode;
+ if (_settings.AddressMode != ContainerAddressMode.Auto)
+ {
+ //Overide by user setting
+ addressMode = _settings.AddressMode;
+ }
+ if (addressMode == ContainerAddressMode.Auto)
+ {
+ //Default to port when not defined
+ addressMode = ContainerAddressMode.Port;
+ }
+#if !NET461
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ //OSX can only handle Port
+ addressMode = ContainerAddressMode.Port;
+ }
+#endif
+ return addressMode;
+ }
+
+ private async Task ReadAsync(
+ Stream logStream,
+ TimeSpan timeout)
+ {
+ var result = new StringBuilder();
+ var timeoutTask = Task.Delay(timeout);
+ using (logStream)
+ {
+ const int size = 256;
+ byte[] buffer = new byte[size];
+
+ while (true)
+ {
+ Task readTask = logStream.ReadAsync(
+ buffer, 0, size);
+
+ if (await Task.WhenAny(readTask, timeoutTask) == timeoutTask)
+ {
+ logStream.Close();
+ break;
+ }
+
+ var read = await readTask;
+ if (read <= 0)
+ {
+ break;
+ }
+
+ char[] chunkChars = new char[read * 2];
+
+ int consumed = 0;
+ for (int i = 0; i < read; i++)
+ {
+ if (buffer[i] > 31 && buffer[i] < 128)
+ {
+ chunkChars[consumed++] = (char)buffer[i];
+ }
+ else if (buffer[i] == (byte)'\n')
+ {
+ chunkChars[consumed++] = '\r';
+ chunkChars[consumed++] = '\n';
+ }
+ else if (buffer[i] == (byte)'\t')
+ {
+ chunkChars[consumed++] = '\t';
+ }
+ }
+
+ string chunk = new string(
+ chunkChars, 0, consumed);
+
+ result.Append(chunk);
+ }
+ }
+ return result.ToString();
+ }
+
+ private async Task ConnectToNetworksAsync()
+ {
+ foreach (string networkName in _settings.Networks)
+ {
+ string networkId = GetNetworkId(networkName);
+
+ await retryPolicy.ExecuteAsync(async () =>
+ {
+ await _client.Networks.ConnectNetworkAsync(
+ networkId,
+ new NetworkConnectParameters()
+ {
+ Container = Instance.Id
+ });
+ });
+ }
+ }
+
+ private string GetNetworkId(string networkName)
+ {
+ // Lock to ensure thread safety of static list
+ lock (_createNetworkLock)
+ {
+ if (_uniqueNetworkNames.ContainsKey(networkName))
+ {
+ return _uniqueNetworkNames[networkName];
+ }
+
+ return CreateNetwork(networkName);
+ }
+ }
+
+ private string CreateNetwork(string networkName)
+ {
+ string uniqueNetworkName = UniqueNameGenerator.CreateNetworkName(networkName);
+
+ NetworksCreateResponse response = _client.Networks.CreateNetworkAsync(
+ new NetworksCreateParameters()
+ {
+ Name = uniqueNetworkName
+ }).Result;
+
+ _uniqueNetworkNames.Add(networkName, uniqueNetworkName);
+ return response.ID;
+ }
+
+ private async Task RemoveNetworkIfUnused(string networkName)
+ {
+ string uniqueNetworkName = _uniqueNetworkNames[networkName];
+ await retryPolicy
+ .ExecuteAsync(async () =>
+ {
+ NetworkResponse inspectResponse = (await _client.Networks.ListNetworksAsync())
+ .Single(n => n.Name == uniqueNetworkName);
+
+ if (!inspectResponse.Containers.Any())
+ {
+ await _client.Networks.DeleteNetworkAsync(inspectResponse.ID);
+ }
+ });
+ }
+ }
+}
diff --git a/src/Dependencies.props b/src/Dependencies.props
index 718a32d..74228bb 100644
--- a/src/Dependencies.props
+++ b/src/Dependencies.props
@@ -4,8 +4,8 @@
- netcoreapp2.2;netcoreapp3.0
+ netcoreapp3.1
netstandard2.0
- netstandard2.0;netcoreapp3.0
+ netstandard2.0;netcoreapp3.1
diff --git a/src/PostgreSql/PostgreSqlResource.cs b/src/PostgreSql/PostgreSqlResource.cs
index dcae5c4..44ee9af 100644
--- a/src/PostgreSql/PostgreSqlResource.cs
+++ b/src/PostgreSql/PostgreSqlResource.cs
@@ -1,103 +1,103 @@
-using System.Threading.Tasks;
-using Npgsql;
-using Xunit;
-
+using System.Threading.Tasks;
+using Npgsql;
+using Xunit;
+
namespace Squadron
-{
- ///
- public class PostgreSqlResource : PostgreSqlResource { }
-
- ///
- /// Represents a PostgreSQL database that can be used by unit tests.
+{
+ ///
+ public class PostgreSqlResource : PostgreSqlResource { }
+
+ ///
+ /// Represents a PostgreSQL database that can be used by unit tests.
///
- public class PostgreSqlResource
- : ContainerResource, IAsyncLifetime
- where TOptions : ContainerResourceOptions, new()
- {
-
- ///
- /// Connection string to access to database
- ///
- public string ConnectionString { get; private set; }
-
- ///
- protected override void OnSettingsBuilded(ContainerResourceSettings settings)
- {
- settings.EnvironmentVariables.Add($"POSTGRES_USER={settings.Username}");
- settings.EnvironmentVariables.Add($"POSTGRES_PASSWORD={settings.Password}");
- //This is required to run psql commands
- settings.EnvironmentVariables.Add($"PGPASSWORD=={settings.Password}");
- }
-
-
- ///
- public async override Task InitializeAsync()
- {
- await base.InitializeAsync();
- ConnectionString = BuildConnectionString(Settings.Username);
- await Initializer.WaitAsync(new PostgreSqlStatus(ConnectionString));
- }
-
- private string BuildConnectionString(string database)
- {
+ public class PostgreSqlResource
+ : ContainerResource, IAsyncLifetime
+ where TOptions : ContainerResourceOptions, new()
+ {
+
+ ///
+ /// Connection string to access to database
+ ///
+ public string ConnectionString { get; private set; }
+
+ ///
+ protected override void OnSettingsBuilded(ContainerResourceSettings settings)
+ {
+ settings.EnvironmentVariables.Add($"POSTGRES_USER={settings.Username}");
+ settings.EnvironmentVariables.Add($"POSTGRES_PASSWORD={settings.Password}");
+ //This is required to run psql commands
+ settings.EnvironmentVariables.Add($"PGPASSWORD=={settings.Password}");
+ }
+
+
+ ///
+ public async override Task InitializeAsync()
+ {
+ await base.InitializeAsync();
+ ConnectionString = BuildConnectionString(Settings.Username);
+ await Initializer.WaitAsync(new PostgreSqlStatus(ConnectionString));
+ }
+
+ private string BuildConnectionString(string database)
+ {
return $"Host={Manager.Instance.Address};Port={Manager.Instance.HostPort};" +
$"Username={Settings.Username};Password={Settings.Password};" +
- $"Database={database}";
- }
-
+ $"Database={database}";
+ }
+
///
/// Gets the connection string for a give database.
///
/// The database.
///
- public string GetConnectionString(string database)
- {
- return BuildConnectionString(database);
- }
-
-
- ///
- /// Get an Connection for the default database
- ///
- ///
- public NpgsqlConnection GetConnection()
- {
- return new NpgsqlConnection(ConnectionString);
- }
-
-
- ///
- /// Gets a Connection for the given database
- ///
- /// Database name
- ///
- public NpgsqlConnection GetConnection(string dbName)
- {
- return new NpgsqlConnection(BuildConnectionString(dbName));
- }
-
- ///
- /// Creates an new database with the given name
- ///
- /// Database name
- ///
- public async Task CreateDatabaseAsync(string dbName)
- {
- await Manager.InvokeCommandAsync(CreateDbCommand.Execute(dbName, Settings));
- }
-
- ///
- /// Runs a PqlScript on the database
- ///
+ public string GetConnectionString(string database)
+ {
+ return BuildConnectionString(database);
+ }
+
+
+ ///
+ /// Get an Connection for the default database
+ ///
+ ///
+ public NpgsqlConnection GetConnection()
+ {
+ return new NpgsqlConnection(ConnectionString);
+ }
+
+
+ ///
+ /// Gets a Connection for the given database
+ ///
+ /// Database name
+ ///
+ public NpgsqlConnection GetConnection(string dbName)
+ {
+ return new NpgsqlConnection(BuildConnectionString(dbName));
+ }
+
+ ///
+ /// Creates an new database with the given name
+ ///
+ /// Database name
+ ///
+ public async Task CreateDatabaseAsync(string dbName)
+ {
+ await Manager.InvokeCommandAsync(CreateDbCommand.Execute(dbName, Settings));
+ }
+
+ ///
+ /// Runs a PqlScript on the database
+ ///
///
- ///
- ///
- public async Task RunSqlScriptAsync(string sqlScript, string dbName)
- {
- var copyContext = CopyContext.CreateFromFileContent(sqlScript, "sql", "tmp");
- await Manager.CopyToContainerAsync(copyContext);
- await Manager.InvokeCommandAsync(
- PSqlCommand.ExecuteFile(copyContext.Destination, dbName, Settings));
- }
+ ///
+ ///
+ public async Task RunSqlScriptAsync(string sqlScript, string dbName)
+ {
+ var copyContext = CopyContext.CreateFromFileContent(sqlScript, "sql", "tmp");
+ await Manager.CopyToContainerAsync(copyContext);
+ await Manager.InvokeCommandAsync(
+ PSqlCommand.ExecuteFile(copyContext.Destination, dbName, Settings));
+ }
}
}
diff --git a/src/Squadron.sln b/src/Squadron.sln
index dc3314f..2d5abd0 100644
--- a/src/Squadron.sln
+++ b/src/Squadron.sln
@@ -48,7 +48,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Compose.Tests", "Compose.Te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RavenDB", "RavenDB\RavenDB.csproj", "{37BCF337-AA5F-4075-93D8-166F5A30E001}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RavenDB.Tests", "RavenDB.Tests\RavenDB.Tests.csproj", "{812303A5-B7A2-4F95-AAC7-F96DD228BBE3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RavenDB.Tests", "RavenDB.Tests\RavenDB.Tests.csproj", "{812303A5-B7A2-4F95-AAC7-F96DD228BBE3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureCloudStorage", "AzureCloudStorage\AzureCloudStorage.csproj", "{80D20407-AE21-40E1-8F8E-165638ED77C2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureCloudStorage.Tests", "AzureCloudStorage.Tests\AzureCloudStorage.Tests.csproj", "{8EBD73C7-BDC4-445E-900E-432CA274A237}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -336,6 +340,30 @@ Global
{812303A5-B7A2-4F95-AAC7-F96DD228BBE3}.Release|x64.Build.0 = Release|Any CPU
{812303A5-B7A2-4F95-AAC7-F96DD228BBE3}.Release|x86.ActiveCfg = Release|Any CPU
{812303A5-B7A2-4F95-AAC7-F96DD228BBE3}.Release|x86.Build.0 = Release|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Debug|x64.Build.0 = Debug|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Debug|x86.Build.0 = Debug|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Release|x64.ActiveCfg = Release|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Release|x64.Build.0 = Release|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Release|x86.ActiveCfg = Release|Any CPU
+ {80D20407-AE21-40E1-8F8E-165638ED77C2}.Release|x86.Build.0 = Release|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Debug|x64.Build.0 = Debug|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Debug|x86.Build.0 = Debug|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Release|x64.ActiveCfg = Release|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Release|x64.Build.0 = Release|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Release|x86.ActiveCfg = Release|Any CPU
+ {8EBD73C7-BDC4-445E-900E-432CA274A237}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -344,6 +372,8 @@ Global
{A98B7527-6D64-4A7A-A1E2-3D8427EDD41A} = {5E244D5B-B032-458A-BBD2-0A403BBA61F9}
{2354DBB6-5B58-4B50-9DE8-AC28AFDBFF30} = {5E244D5B-B032-458A-BBD2-0A403BBA61F9}
{7B569704-0166-49DB-A7EC-93DF3A3C9C5F} = {5E244D5B-B032-458A-BBD2-0A403BBA61F9}
+ {80D20407-AE21-40E1-8F8E-165638ED77C2} = {5E244D5B-B032-458A-BBD2-0A403BBA61F9}
+ {8EBD73C7-BDC4-445E-900E-432CA274A237} = {5E244D5B-B032-458A-BBD2-0A403BBA61F9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {68767DE6-FA2A-4A9F-A7D3-AF07328CB620}
diff --git a/src/TestProject.props b/src/TestProject.props
index 8ff5859..3b0eef0 100644
--- a/src/TestProject.props
+++ b/src/TestProject.props
@@ -7,22 +7,20 @@
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-