Skip to content

Commit

Permalink
#1925 & #1928 Specific config handler and throw error if no CloudProv…
Browse files Browse the repository at this point in the history
…ider (#1972)

* #1925 & #1928 Specific config handler and throw error if no CloudProvider

- Add AWS & Azure specific config handlers
- Add AWSS3Storage configuration
- Throw exception if CloudProvider undefined or unknown

* #1925

- Missing TU for AWSS3StorageConnectionString in DevConfig

* #1925 Null reference

- Add ! for null reference possible
  • Loading branch information
delager authored Mar 30, 2023
1 parent 3ae1037 commit 34899e1
Show file tree
Hide file tree
Showing 18 changed files with 532 additions and 44 deletions.
1 change: 1 addition & 0 deletions src/AzureIoTHub.Portal.Domain/ConfigHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,6 @@ public abstract class ConfigHandler
public abstract string AWSAccess { get; }
public abstract string AWSAccessSecret { get; }
public abstract string AWSRegion { get; }
public abstract string AWSS3StorageConnectionString { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) CGI France. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace AzureIoTHub.Portal.Domain.Exceptions
{
using System;
using AzureIoTHub.Portal.Domain.Shared.Constants;

public class InvalidCloudProviderException : BaseException
{
public InvalidCloudProviderException(string detail, Exception? innerException = null) : base(ErrorTitles.InvalidCloudProvider, detail, innerException)
{
}
}
}
6 changes: 6 additions & 0 deletions src/AzureIoTHub.Portal.Domain/Shared/Constants/ErrorTitles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,11 @@ public static class ErrorTitles
public const string ResourceNotFound = "Resource Not Found";

public const string ResourceAlreadyExists = "Resource Already Exists";

public const string InvalidCloudProvider = "Invalid Cloud Provider";

public const string InvalidCloudProviderUndefined = "The CloudProvider configuration is undefined";

public const string InvalidCloudProviderIncorrect = "The CloudProvider configuration is incorrect";
}
}
1 change: 1 addition & 0 deletions src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ internal abstract class ConfigHandlerBase : ConfigHandler
internal const string AWSAccessKey = "AWS:Access";
internal const string AWSAccessSecretKey = "AWS:AccessSecret";
internal const string AWSRegionKey = "AWS:Region";
internal const string AWSS3StorageConnectionStringKey = "AWS:S3Storage:ConnectionString";
}
}
14 changes: 13 additions & 1 deletion src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace AzureIoTHub.Portal.Infrastructure
{
using System;
using AzureIoTHub.Portal.Domain;
using AzureIoTHub.Portal.Domain.Exceptions;
using AzureIoTHub.Portal.Domain.Shared.Constants;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

Expand All @@ -15,9 +17,19 @@ public static ConfigHandler Create(IHostEnvironment env, IConfiguration config)
ArgumentNullException.ThrowIfNull(env, nameof(env));
ArgumentNullException.ThrowIfNull(config, nameof(config));

if (config[ConfigHandlerBase.CloudProviderKey] == null)
{
throw new InvalidCloudProviderException(ErrorTitles.InvalidCloudProviderUndefined);
}

if (env.IsProduction())
{
return new ProductionConfigHandler(config);
return config[ConfigHandlerBase.CloudProviderKey] switch
{
CloudProviders.AWS => new ProductionAWSConfigHandler(config),
CloudProviders.Azure => new ProductionAzureConfigHandler(config),
_ => throw new InvalidCloudProviderException(ErrorTitles.InvalidCloudProviderIncorrect),
};
}

return new DevelopmentConfigHandler(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,13 @@ internal DevelopmentConfigHandler(IConfiguration config)

public override string MySQLConnectionString => this.config[MySQLConnectionStringKey]!;

public override string DbProvider => this.config.GetValue(DbProviderKey, DbProviders.PostgreSQL);
public override string DbProvider => this.config.GetValue(DbProviderKey, DbProviders.PostgreSQL)!;

public override string CloudProvider => this.config[CloudProviderKey];
public override string CloudProvider => this.config[CloudProviderKey]!;

public override string AWSAccess => this.config[AWSAccessKey];
public override string AWSAccessSecret => this.config[AWSAccessSecretKey];
public override string AWSRegion => this.config[AWSRegionKey];
public override string AWSAccess => this.config[AWSAccessKey]!;
public override string AWSAccessSecret => this.config[AWSAccessSecretKey]!;
public override string AWSRegion => this.config[AWSRegionKey]!;
public override string AWSS3StorageConnectionString => this.config[AWSS3StorageConnectionStringKey]!;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) CGI France. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace AzureIoTHub.Portal.Infrastructure
{
using AzureIoTHub.Portal.Domain.Shared.Constants;
using Microsoft.Extensions.Configuration;

internal class ProductionAWSConfigHandler : ConfigHandlerBase
{
private readonly IConfiguration config;

internal ProductionAWSConfigHandler(IConfiguration config)
{
this.config = config;
}

public override string PortalName => this.config[PortalNameKey]!;

public override int SyncDatabaseJobRefreshIntervalInMinutes => this.config.GetValue(SyncDatabaseJobRefreshIntervalKey, 5);

public override int MetricExporterRefreshIntervalInSeconds => this.config.GetValue(MetricExporterRefreshIntervalKey, 30);

public override int MetricLoaderRefreshIntervalInMinutes => this.config.GetValue(MetricLoaderRefreshIntervalKey, 10);

public override string IoTHubConnectionString => this.config[IoTHubConnectionStringKey]!;

public override string IoTHubEventHubEndpoint => this.config.GetValue(IoTHubEventHubEndpointKey, string.Empty)!;

public override string IoTHubEventHubConsumerGroup => this.config.GetValue(IoTHubEventHubConsumerGroupKey, "iothub-portal")!;

public override string DPSConnectionString => this.config[DPSConnectionStringKey]!;

public override string DPSEndpoint => this.config[DPSServiceEndpointKey]!;

public override string DPSScopeID => this.config[DPSIDScopeKey]!;

public override string StorageAccountConnectionString => throw new NotImplementedException();

public override int StorageAccountDeviceModelImageMaxAge => throw new NotImplementedException();

public override bool UseSecurityHeaders => this.config.GetValue(UseSecurityHeadersKey, true);

public override string OIDCScope => this.config[OIDCScopeKey]!;

public override string OIDCAuthority => this.config[OIDCAuthorityKey]!;

public override string OIDCMetadataUrl => this.config[OIDCMetadataUrlKey]!;

public override string OIDCClientId => this.config[OIDCClientIdKey]!;

public override string OIDCApiClientId => this.config[OIDCApiClientIdKey]!;

public override bool OIDCValidateIssuer => this.config.GetValue(OIDCValidateIssuerKey, true);

public override bool OIDCValidateAudience => this.config.GetValue(OIDCValidateAudienceKey, true);

public override bool OIDCValidateLifetime => this.config.GetValue(OIDCValidateLifetimeKey, true);

public override bool OIDCValidateIssuerSigningKey => this.config.GetValue(OIDCValidateIssuerSigningKeyKey, true);

public override bool OIDCValidateActor => this.config.GetValue(OIDCValidateActorKey, false);

public override bool OIDCValidateTokenReplay => this.config.GetValue(OIDCValidateTokenReplayKey, false);

public override bool IsLoRaEnabled => bool.Parse(this.config[IsLoRaFeatureEnabledKey] ?? "true");

public override string LoRaKeyManagementUrl => this.config[LoRaKeyManagementUrlKey]!;

public override string LoRaKeyManagementCode => this.config[LoRaKeyManagementCodeKey]!;

public override string LoRaKeyManagementApiVersion => this.config[LoRaKeyManagementApiVersionKey]!;

public override bool IdeasEnabled => this.config.GetValue(IdeasEnabledKey, false);
public override string IdeasUrl => this.config.GetValue(IdeasUrlKey, string.Empty)!;
public override string IdeasAuthenticationHeader => this.config.GetValue(IdeasAuthenticationHeaderKey, "Ocp-Apim-Subscription-Key")!;
public override string IdeasAuthenticationToken => this.config.GetValue(IdeasAuthenticationTokenKey, string.Empty)!;

public override string PostgreSQLConnectionString => this.config[PostgreSQLConnectionStringKey]!;

public override string MySQLConnectionString => this.config[MySQLConnectionStringKey]!;

public override string DbProvider => this.config.GetValue(DbProviderKey, DbProviders.PostgreSQL)!;

public override string CloudProvider => this.config[CloudProviderKey]!;

public override string AWSAccess => this.config[AWSAccessKey]!;
public override string AWSAccessSecret => this.config[AWSAccessSecretKey]!;
public override string AWSRegion => this.config[AWSRegionKey]!;
public override string AWSS3StorageConnectionString => this.config[AWSS3StorageConnectionStringKey]!;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ namespace AzureIoTHub.Portal.Infrastructure
using AzureIoTHub.Portal.Domain.Shared.Constants;
using Microsoft.Extensions.Configuration;

internal class ProductionConfigHandler : ConfigHandlerBase
internal class ProductionAzureConfigHandler : ConfigHandlerBase
{
private readonly IConfiguration config;

internal ProductionConfigHandler(IConfiguration config)
internal ProductionAzureConfigHandler(IConfiguration config)
{
this.config = config;
}
Expand Down Expand Up @@ -78,14 +78,17 @@ internal ProductionConfigHandler(IConfiguration config)
public override string LoRaKeyManagementApiVersion => this.config[LoRaKeyManagementApiVersionKey]!;

public override bool IdeasEnabled => this.config.GetValue(IdeasEnabledKey, false);
public override string IdeasUrl => this.config.GetValue(IdeasUrlKey, string.Empty);
public override string IdeasAuthenticationHeader => this.config.GetValue(IdeasAuthenticationHeaderKey, "Ocp-Apim-Subscription-Key");
public override string IdeasAuthenticationToken => this.config.GetValue(IdeasAuthenticationTokenKey, string.Empty);
public override string IdeasUrl => this.config.GetValue(IdeasUrlKey, string.Empty)!;
public override string IdeasAuthenticationHeader => this.config.GetValue(IdeasAuthenticationHeaderKey, "Ocp-Apim-Subscription-Key")!;
public override string IdeasAuthenticationToken => this.config.GetValue(IdeasAuthenticationTokenKey, string.Empty)!;

public override string CloudProvider => this.config[CloudProviderKey];
public override string CloudProvider => this.config[CloudProviderKey]!;

public override string AWSAccess => this.config[AWSAccessKey];
public override string AWSAccessSecret => this.config[AWSAccessSecretKey];
public override string AWSRegion => this.config[AWSRegionKey];
public override string AWSAccess => throw new NotImplementedException();

public override string AWSAccessSecret => throw new NotImplementedException();

public override string AWSRegion => throw new NotImplementedException();
public override string AWSS3StorageConnectionString => throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static async Task MigrateDeviceModelCommands(this PortalDbContext ctx, Co
});
#pragma warning restore CS8629 // Nullable value type may be null.

if (config is ProductionConfigHandler)
if (config is ProductionAzureConfigHandler)
{
_ = await table.DeleteEntityAsync(item.PartitionKey, item.RowKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static async Task MigrateDeviceModelProperties(this PortalDbContext ctx,
});
#pragma warning restore CS8629 // Nullable value type may be null.

if (config is ProductionConfigHandler)
if (config is ProductionAzureConfigHandler)
{
_ = await table.DeleteEntityAsync(item.PartitionKey, item.RowKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static async Task MigrateDeviceModels(this PortalDbContext ctx, ConfigHan
});
#pragma warning restore CS8629 // Nullable value type may be null.

if (config is ProductionConfigHandler)
if (config is ProductionAzureConfigHandler)
{
_ = await table.DeleteEntityAsync(item.PartitionKey, item.RowKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static async Task MigrateDeviceTags(this PortalDbContext ctx, ConfigHandl
});
#pragma warning restore CS8629 // Nullable value type may be null.

if (config is ProductionConfigHandler)
if (config is ProductionAzureConfigHandler)
{
_ = await table.DeleteEntityAsync(item.PartitionKey, item.RowKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static async Task MigrateEdgeDeviceModelCommands(this PortalDbContext ctx
});
#pragma warning restore CS8629 // Nullable value type may be null.

if (config is ProductionConfigHandler)
if (config is ProductionAzureConfigHandler)
{
_ = await table.DeleteEntityAsync(item.PartitionKey, item.RowKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static async Task MigrateEdgeDeviceModels(this PortalDbContext ctx, Confi
});
#pragma warning restore CS8629 // Nullable value type may be null.

if (config is ProductionConfigHandler)
if (config is ProductionAzureConfigHandler)
{
_ = await table.DeleteEntityAsync(item.PartitionKey, item.RowKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure
{
using AzureIoTHub.Portal.Domain.Exceptions;
using AzureIoTHub.Portal.Domain.Shared.Constants;
using AzureIoTHub.Portal.Infrastructure;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Moq;
Expand Down Expand Up @@ -31,6 +34,8 @@ public void WhenUsingDevEnvironmentShouldReturnDevelopmentConfigHandler()
// Arrange
_ = this.mockHostEnvironment.Setup(c => c.EnvironmentName)
.Returns(Environments.Development);
_ = this.mockConfiguration.Setup(cnf => cnf[ConfigHandlerBase.CloudProviderKey])
.Returns(CloudProviders.Azure);

// Act
var result = ConfigHandlerFactory.Create(this.mockHostEnvironment.Object, this.mockConfiguration.Object);
Expand All @@ -41,17 +46,67 @@ public void WhenUsingDevEnvironmentShouldReturnDevelopmentConfigHandler()


[Test]
public void WhenUsingProdEnvironmentShouldReturnProductionConfigHandler()
public void WhenUsingProdAzureEnvironmentShouldReturnProductionAzureConfigHandler()
{
// Arrange
_ = this.mockHostEnvironment.Setup(c => c.EnvironmentName)
.Returns(Environments.Production);
_ = this.mockConfiguration.Setup(cnf => cnf[ConfigHandlerBase.CloudProviderKey])
.Returns(CloudProviders.Azure);

// Act
var result = ConfigHandlerFactory.Create(this.mockHostEnvironment.Object, this.mockConfiguration.Object);

// Assert
Assert.IsAssignableFrom<ProductionConfigHandler>(result);
Assert.IsAssignableFrom<ProductionAzureConfigHandler>(result);
}

[Test]
public void WhenUsingProdAWSEnvironmentShouldReturnProductionAWSConfigHandler()
{
// Arrange
_ = this.mockHostEnvironment.Setup(c => c.EnvironmentName)
.Returns(Environments.Production);
_ = this.mockConfiguration.Setup(cnf => cnf[ConfigHandlerBase.CloudProviderKey])
.Returns(CloudProviders.AWS);

// Act
var result = ConfigHandlerFactory.Create(this.mockHostEnvironment.Object, this.mockConfiguration.Object);

// Assert
Assert.IsAssignableFrom<ProductionAWSConfigHandler>(result);
}

[Test]
public void WhenNoConfigCloudProviderShouldThrowInvalidCloudProviderException()
{
// Arrange
_ = this.mockHostEnvironment.Setup(c => c.EnvironmentName)
.Returns(Environments.Production);
_ = this.mockConfiguration.Setup(cnf => cnf[ConfigHandlerBase.CloudProviderKey])
.Returns((string)null);

// Act
var result = () => ConfigHandlerFactory.Create(this.mockHostEnvironment.Object, this.mockConfiguration.Object);

// Assert
_ = result.Should().Throw<InvalidCloudProviderException>().WithMessage(ErrorTitles.InvalidCloudProviderUndefined);
}

[Test]
public void WhenWrongCloudProviderShouldThrowInvalidCloudProviderException()
{
// Arrange
_ = this.mockHostEnvironment.Setup(c => c.EnvironmentName)
.Returns(Environments.Production);
_ = this.mockConfiguration.Setup(cnf => cnf[ConfigHandlerBase.CloudProviderKey])
.Returns("Test");

// Act
var result = () => ConfigHandlerFactory.Create(this.mockHostEnvironment.Object, this.mockConfiguration.Object);

// Assert
_ = result.Should().Throw<InvalidCloudProviderException>().WithMessage(ErrorTitles.InvalidCloudProviderIncorrect);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ private DevelopmentConfigHandler CreateDevelopmentConfigHandler()
[TestCase(ConfigHandlerBase.AWSAccessKey, nameof(ConfigHandlerBase.AWSAccess))]
[TestCase(ConfigHandlerBase.AWSAccessSecretKey, nameof(ConfigHandlerBase.AWSAccessSecret))]
[TestCase(ConfigHandlerBase.AWSRegionKey, nameof(ConfigHandlerBase.AWSRegion))]
[TestCase(ConfigHandlerBase.AWSS3StorageConnectionStringKey, nameof(ConfigHandlerBase.AWSS3StorageConnectionString))]
[TestCase(ConfigHandlerBase.CloudProviderKey, nameof(ConfigHandlerBase.CloudProvider))]
public void SettingsShouldGetValueFromAppSettings(string configKey, string configPropertyName)
{
Expand Down
Loading

0 comments on commit 34899e1

Please sign in to comment.