FileSerice Wrapper - Integration with ArmDeploymentService and TranspileBicepSerivice (#26)

* transpile bicep to arm template feature

* bicep submodule restructured

* rename the bicepSubmodule to BicepExecute

* change method signature and make async

* Renaming Bicep Service - main have existing BicepService.cs

* additional tests added

* corrections for feedbacks

* actual manual deployment is tested

* changed from method wrapping Bicep.Cli into direct calling Bicep.Cli.Programs.Main

* fixing linting errors

* additional mock test to throw exception and dotnet workflow update

* linting fix

* liniting fix

* linting fix

* linting fix

* linting fix

* linting fix

* linting fix

* linting fix

* linting fix

* linting fix

* linting fix

* linting fix

* linting fix

* come on now

* come on now

* come on now

* adding submodule checkout

* turn off dotnet build

* file service interface

* file service implementation

* changes

* file delete feature

* file service implementation

* file service implementation

* tested locally

* adding file service into transpile bicep

* file service integration

* integrate BicepTranspileService (#23)

* fix build errors

* fix linitn errors

* corrections for feedbacks

* parameter content

* change async

* FileNotFoundException test added

* DeploymenServiceTests FileService setup

* transpile bicep test mokc refactoring

Co-authored-by: Jessica Ern <jessicaern@microsoft.com>
Co-authored-by: jessica-ern <107070686+jessica-ern@users.noreply.github.com>
This commit is contained in:
Dilmurod Makhamadaliev 2022-11-10 18:19:40 -05:00 коммит произвёл GitHub
Родитель 6788879f35
Коммит 9b853fa996
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 109 добавлений и 36 удалений

Просмотреть файл

@ -8,22 +8,25 @@ namespace BenchPress.TestEngine.Tests;
public class ArmDeploymentServiceTests {
private readonly ArmDeploymentService armDeploymentService;
private readonly Mock<ArmClient> armClientMock;
private readonly Mock<IFileService> fileServiceMock;
private readonly Mock<ArmDeploymentCollection> groupDeploymentsMock;
private readonly Mock<ArmDeploymentCollection> subscriptionDeploymentsMock;
private const string validSubId = "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d";
private const string validRgName = "test-rg";
private const string validLocation = "eastus";
private const string smapleFiles = "../../../SampleFiles";
private const string standaloneTemplate = $"{smapleFiles}/storage-account.json";
private const string templateWithParams = $"{smapleFiles}/storage-account-needs-params.json";
private const string parameters = $"{smapleFiles}/params.json";
private const string standaloneTemplate = "storage-account.json";
private const string templateWithParams = "storage-account-needs-params.json";
private const string parameters = "params.json";
public ArmDeploymentServiceTests()
{
armClientMock = new Mock<ArmClient>(MockBehavior.Strict);
fileServiceMock = new Mock<IFileService>(MockBehavior.Strict);
groupDeploymentsMock = new Mock<ArmDeploymentCollection>();
subscriptionDeploymentsMock = new Mock<ArmDeploymentCollection>();
armDeploymentService = new TestArmDeploymentService(groupDeploymentsMock.Object, subscriptionDeploymentsMock.Object, armClientMock.Object);
armDeploymentService = new TestArmDeploymentService(groupDeploymentsMock.Object, subscriptionDeploymentsMock.Object, armClientMock.Object, fileServiceMock.Object);
fileServiceMock.Setup(fs => fs.ReadAllTextAsync(It.IsAny<string>())).ThrowsAsync(new FileNotFoundException());
fileServiceMock.Setup(fs => fs.ReadAllTextAsync(It.IsIn(new [] {standaloneTemplate, templateWithParams, parameters}))).ReturnsAsync("");
}
[Fact]
@ -56,12 +59,12 @@ public class ArmDeploymentServiceTests {
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
var excepectedMessage = "Deployment template validation failed";
SetUpDeploymentExceptionMock(groupDeploymentsMock, new RequestFailedException(excepectedMessage));
var expectedMessage = "Deployment template validation failed";
SetUpDeploymentExceptionMock(groupDeploymentsMock, new RequestFailedException(expectedMessage));
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, templateWithParams)
);
Assert.Equal(excepectedMessage, ex.Message);
Assert.Equal(expectedMessage, ex.Message);
}
[Fact]
@ -232,7 +235,7 @@ public class ArmDeploymentServiceTests {
private readonly ArmDeploymentCollection rgDeploymentCollection;
private readonly ArmDeploymentCollection subDeploymentCollection;
public TestArmDeploymentService(ArmDeploymentCollection rgDeploymentCollection, ArmDeploymentCollection subDeploymentCollection, ArmClient client) : base(client)
public TestArmDeploymentService(ArmDeploymentCollection rgDeploymentCollection, ArmDeploymentCollection subDeploymentCollection, ArmClient client, IFileService fileService) : base(client, fileService)
{
this.rgDeploymentCollection = rgDeploymentCollection;
this.subDeploymentCollection = subDeploymentCollection;

Просмотреть файл

@ -3,13 +3,21 @@ namespace BenchPress.TestEngine.Tests;
public class BicepTranspileServiceTests
{
private readonly Mock<IBicepExecute> mockBicepSubmodule;
private readonly Mock<IFileService> mockFileService;
private readonly BicepTranspileService bicepTranspileService;
public BicepTranspileServiceTests()
{
mockBicepSubmodule = new Mock<IBicepExecute>();
mockFileService = new Mock<IFileService>();
var logger = Mock.Of<ILogger<BicepTranspileService>>();
bicepTranspileService = new BicepTranspileService(mockBicepSubmodule.Object, logger);
bicepTranspileService = new BicepTranspileService(mockBicepSubmodule.Object, logger, mockFileService.Object);
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ReturnsAsync(0);
mockFileService.Setup(fs => fs.FileExists(It.IsAny<string>())).Returns(true);
mockFileService.Setup(fs => fs.GetFileExtension(It.IsAny<string>())).Returns(".bicep");
mockFileService.Setup(fs => fs.ChangeFileExtension(It.IsAny<string>(),It.IsAny<string>())).Returns("a/b/c/test.json");
}
[Theory]
@ -18,7 +26,7 @@ public class BicepTranspileServiceTests
[InlineData(" ")]
public async Task Build_NullInputPath_Throws(string inputFile)
{
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ReturnsAsync(0);
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
await Assert.ThrowsAsync<ArgumentNullException>(async () => await bicepTranspileService.BuildAsync(inputFile));
mockBicepSubmodule.Verify(p => p.ExecuteCommandAsync(It.IsAny<string[]>()), Times.Never);
@ -28,13 +36,12 @@ public class BicepTranspileServiceTests
[InlineData("a/b/c/test.bicep")]
public async Task Build_GeneratedArmTemplateExist(string inputFile)
{
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ReturnsAsync(0);
var inputBicepFilePath = Path.GetFullPath(inputFile);
var expectedPath = Path.GetFullPath(Path.ChangeExtension(inputBicepFilePath, ".json"));
var armPath = await bicepTranspileService.BuildAsync(inputBicepFilePath);
var args = new[] { "build", inputBicepFilePath, "--outfile", expectedPath };
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
var outFile = "a/b/c/test.json";
var armPath = await bicepTranspileService.BuildAsync(inputFile);
var args = new[] { "build", inputFile, "--outfile", outFile };
Assert.Equal(expectedPath, armPath);
Assert.Equal(outFile, armPath);
mockBicepSubmodule.Verify(p => p.ExecuteCommandAsync(args), Times.Once);
}
@ -42,10 +49,10 @@ public class BicepTranspileServiceTests
[InlineData("a/b/c/test.txt")]
public async Task Build_NonBicepFileInputPath_Throws(string inputFile)
{
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ReturnsAsync(0);
var inputBicepFilePath = Path.GetFullPath(inputFile);
mockFileService.Setup(fs => fs.GetFileExtension(It.IsAny<string>())).Returns(".txt");
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
await Assert.ThrowsAsync<ArgumentException>(async () => await bicepTranspileService.BuildAsync(inputBicepFilePath));
await Assert.ThrowsAsync<ArgumentException>(async () => await bicepTranspileService.BuildAsync(inputFile));
}
[Theory]
@ -53,8 +60,17 @@ public class BicepTranspileServiceTests
public async Task Build_BicepModuleNotImplemented_Throws(string inputFile)
{
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ThrowsAsync(new ApplicationException("Bicep transpilation failed"));
var inputBicepFilePath = Path.GetFullPath(inputFile);
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
await Assert.ThrowsAsync<ApplicationException>(async () => await bicepTranspileService.BuildAsync(inputBicepFilePath));
await Assert.ThrowsAsync<ApplicationException>(async () => await bicepTranspileService.BuildAsync(inputFile));
}
[Theory]
[InlineData("a/b/c/test.bicep")]
public async Task Build_FileNotFoundException_Throws(string inputFile)
{
mockFileService.Setup(fs => fs.FileExists(It.IsAny<string>())).Returns(false);
await Assert.ThrowsAsync<FileNotFoundException>(async () => await bicepTranspileService.BuildAsync(inputFile));
}
}

Просмотреть файл

@ -23,6 +23,7 @@ builder.Services.AddAzureClients(builder => {
});
builder.Services.AddSingleton<IArmDeploymentService, ArmDeploymentService>();
builder.Services.AddSingleton<IBicepTranspileService, BicepTranspileService>();
builder.Services.AddSingleton<IFileService, FileService>();
builder.Services.AddSingleton<IBicepExecute, BicepExecute>();
var app = builder.Build();

Просмотреть файл

@ -3,12 +3,16 @@ using Azure.ResourceManager.Resources.Models;
namespace BenchPress.TestEngine.Services;
public class ArmDeploymentService : IArmDeploymentService {
public class ArmDeploymentService : IArmDeploymentService
{
private readonly ArmClient client;
private readonly IFileService fileService;
private string NewDeploymentName { get { return $"benchpress-{Guid.NewGuid().ToString()}"; } }
public ArmDeploymentService(ArmClient client) {
public ArmDeploymentService(ArmClient client, IFileService fileService)
{
this.client = client;
this.fileService = fileService;
}
public async Task<ArmOperation<ArmDeploymentResource>> DeployArmToResourceGroupAsync(string subscriptionNameOrId, string resourceGroupName, string armTemplatePath, string? parametersPath = null, WaitUntil waitUntil = WaitUntil.Completed)
@ -28,23 +32,25 @@ public class ArmDeploymentService : IArmDeploymentService {
return await CreateSubscriptionDeployment(sub, waitUtil, NewDeploymentName, deploymentContent);
}
private void ValidateParameters(params string[] parameters) {
if(parameters.Any(s => string.IsNullOrWhiteSpace(s))) {
private void ValidateParameters(params string[] parameters)
{
if (parameters.Any(s => string.IsNullOrWhiteSpace(s)))
{
throw new ArgumentException("One or more parameters were missing or empty");
}
}
private async Task<ArmDeploymentContent> CreateDeploymentContent(string armTemplatePath, string? parametersPath, string? location = null) {
var templateContent = (await File.ReadAllTextAsync(armTemplatePath)).TrimEnd();
var templateContent = (await fileService.ReadAllTextAsync(armTemplatePath)).TrimEnd();
var properties = new ArmDeploymentProperties(ArmDeploymentMode.Incremental)
{
Template = BinaryData.FromString(templateContent)
};
if (!string.IsNullOrWhiteSpace(parametersPath))
{
var paramteresContent = (await File.ReadAllTextAsync(parametersPath)).TrimEnd();
properties.Parameters = BinaryData.FromString(parametersPath);
var parametersContent = (await fileService.ReadAllTextAsync(parametersPath)).TrimEnd();
properties.Parameters = BinaryData.FromString(parametersContent);
}
var content = new ArmDeploymentContent(properties);
@ -57,11 +63,13 @@ public class ArmDeploymentService : IArmDeploymentService {
}
// These extension methods are wrapped to allow mocking in our tests
protected virtual async Task<ArmOperation<ArmDeploymentResource>> CreateGroupDeployment(ResourceGroupResource rg, Azure.WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent) {
protected virtual async Task<ArmOperation<ArmDeploymentResource>> CreateGroupDeployment(ResourceGroupResource rg, Azure.WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent)
{
return await rg.GetArmDeployments().CreateOrUpdateAsync(waitUntil, deploymentName, deploymentContent);
}
protected virtual async Task<ArmOperation<ArmDeploymentResource>> CreateSubscriptionDeployment(SubscriptionResource sub, Azure.WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent) {
protected virtual async Task<ArmOperation<ArmDeploymentResource>> CreateSubscriptionDeployment(SubscriptionResource sub, Azure.WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent)
{
return await sub.GetArmDeployments().CreateOrUpdateAsync(waitUntil, deploymentName, deploymentContent);
}
}

Просмотреть файл

@ -5,12 +5,14 @@ namespace BenchPress.TestEngine.Services;
public class BicepTranspileService : IBicepTranspileService
{
private IBicepExecute bicepExecute;
private IFileService fileService;
private readonly ILogger<BicepTranspileService> logger;
public BicepTranspileService(IBicepExecute bicepExecute, ILogger<BicepTranspileService> logger)
public BicepTranspileService(IBicepExecute bicepExecute, ILogger<BicepTranspileService> logger, IFileService fileService)
{
this.bicepExecute = bicepExecute;
this.logger = logger;
this.fileService = fileService;
}
public async Task<string> BuildAsync(string inputPath)
@ -20,13 +22,20 @@ public class BicepTranspileService : IBicepTranspileService
throw new ArgumentNullException(nameof(inputPath));
}
if (Path.GetExtension(inputPath) != ".bicep")
if(!fileService.FileExists(inputPath))
{
throw new FileNotFoundException(nameof(inputPath));
}
var extension = fileService.GetFileExtension(inputPath);
if (extension != ".bicep")
{
throw new ArgumentException("Passed file is not a bicep file. File path: " + inputPath);
}
inputPath = Path.GetFullPath(inputPath);
string outputPath = Path.ChangeExtension(inputPath, ".json");
inputPath = fileService.GetFileFullPath(inputPath);
string outputPath = fileService.ChangeFileExtension(inputPath, ".json");
logger.LogInformation("Invoking Bicep Submodule");
try

Просмотреть файл

@ -0,0 +1,26 @@
namespace BenchPress.TestEngine.Services;
public class FileService : IFileService
{
public bool FileExists(string filePath)
{
FileInfo file = new FileInfo(filePath);
return file.Exists;
}
public string GetFileFullPath(string filePath)
{
return Path.GetFullPath(filePath);
}
public string GetFileExtension(string filePath)
{
return Path.GetExtension(filePath);
}
public string ChangeFileExtension(string filePath, string extension)
{
return Path.ChangeExtension(filePath, extension);
}
public async Task<string> ReadAllTextAsync(string filePath)
{
return await File.ReadAllTextAsync(filePath);
}
}

Просмотреть файл

@ -0,0 +1,10 @@
namespace BenchPress.TestEngine.Services;
public interface IFileService
{
public bool FileExists(string filePath);
string GetFileFullPath(string filePath);
string ChangeFileExtension(string filePath, string extension);
string GetFileExtension(string filePath);
Task<string> ReadAllTextAsync(string filePath);
}