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 - -