Deploy (an ARM Template) to a Resource Group (#13)

* Injected ArmClient into the BicepService

* added resource group deployment code. Includes temporary testing code in the Framework

* wrapped the ArmClient in a service to ease mocking

* Add some deployment group tests

* add tests for the ArmDeploymentService

* remove testing code from framework, but keep the gRPC packages

* bicep will not be transpiling the parameter files

* add comment

* line ending

* addressing PR comments: parameter validation and variable renames

* rename test

* cut down duplicate lines
This commit is contained in:
jessica-ern 2022-11-08 10:41:23 -06:00 коммит произвёл GitHub
Родитель 25e107cd93
Коммит 3aed3b129b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 639 добавлений и 18 удалений

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

@ -0,0 +1,240 @@
using Azure;
using Azure.ResourceManager;
using Azure.ResourceManager.Resources;
using Azure.ResourceManager.Resources.Models;
namespace BenchPress.TestEngine.Tests;
public class ArmDeploymentServiceTests {
private readonly ArmDeploymentService armDeploymentService;
private readonly Mock<ArmClient> armClientMock;
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 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";
public ArmDeploymentServiceTests()
{
armClientMock = new Mock<ArmClient>(MockBehavior.Strict);
groupDeploymentsMock = new Mock<ArmDeploymentCollection>();
subscriptionDeploymentsMock = new Mock<ArmDeploymentCollection>();
armDeploymentService = new TestArmDeploymentService(groupDeploymentsMock.Object, subscriptionDeploymentsMock.Object, armClientMock.Object);
}
[Fact]
public async Task DeployArmToResourceGroupAsync_Deploys()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, templateWithParams, parameters);
VerifyDeploymentsMock(groupDeploymentsMock);
}
[Theory]
[InlineData("main.bicep", "", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("", "rg-test", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "rg-test", "")]
public async Task DeployArmToResourceGroupAsync_MissingParameter_ThrowsException(string templatePath, string rgName, string subId)
{
var subMock = SetUpSubscriptionMock(subId);
var rgMock = SetUpResourceGroupMock(subMock, rgName);
SetUpDeploymentsMock(groupDeploymentsMock);
var ex = await Assert.ThrowsAsync<ArgumentException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(subId, rgName, templatePath)
);
Assert.Equal("One or more parameters were missing or empty", ex.Message);
}
[Fact]
public async Task DeployArmToResourceGroupAsync_InvalidTemplate_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
var excepectedMessage = "Deployment template validation failed";
SetUpDeploymentExceptionMock(groupDeploymentsMock, new RequestFailedException(excepectedMessage));
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, templateWithParams)
);
Assert.Equal(excepectedMessage, ex.Message);
}
[Fact]
public async Task DeployArmToResourceGroupAsync_SubscriptionNotFound_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync("The Wrong Subscription", validRgName, standaloneTemplate)
);
Assert.Equal("Subscription Not Found", ex.Message);
}
[Fact]
public async Task DeployArmToResourceGroupAsync_ResourceGroupNotFound_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, "the-wrong-rg", standaloneTemplate)
);
Assert.Equal("Resource Group Not Found", ex.Message);
}
[Fact]
public async Task DeployArmToSubscriptionAsync_Deploys()
{
var subMock = SetUpSubscriptionMock(validSubId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, templateWithParams, parameters);
VerifyDeploymentsMock(subscriptionDeploymentsMock);
}
[Theory]
[InlineData("", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "")]
public async Task DeployArmToSubscriptionAsync_MissingParameter_ThrowsException(string templatePath, string subId)
{
var subMock = SetUpSubscriptionMock(subId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
var ex = await Assert.ThrowsAsync<ArgumentException>(
async () => await armDeploymentService.DeployArmToSubscriptionAsync(subId, templatePath)
);
Assert.Equal("One or more parameters were missing or empty", ex.Message);
}
[Fact]
public async Task DeployArmToSubscriptionAsync_InvalidTemplate_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var excepectedMessage = "Deployment template validation failed";
SetUpDeploymentExceptionMock(subscriptionDeploymentsMock, new RequestFailedException(excepectedMessage));
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, templateWithParams)
);
Assert.Equal(excepectedMessage, ex.Message);
}
[Fact]
public async Task DeployArmToSubscriptionAsync_SubscriptionNotFound_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToSubscriptionAsync("The Wrong Subscription", standaloneTemplate)
);
Assert.Equal("Subscription Not Found", ex.Message);
}
[Fact]
public async Task CreateDeploymentContent_WithoutParameters()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, standaloneTemplate);
VerifyDeploymentsMock(groupDeploymentsMock);
}
[Fact]
public async Task CreateDeploymentContent_TemplateNotFound_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
var ex = await Assert.ThrowsAsync<FileNotFoundException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, "invalid-file.json")
);
}
[Fact]
public async Task CreateDeploymentContent_ParametersNotFound_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
var ex = await Assert.ThrowsAsync<FileNotFoundException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, standaloneTemplate, "invalid-file.json")
);
}
private Mock<SubscriptionResource> SetUpSubscriptionMock(string nameOrId)
{
var subMock = new Mock<SubscriptionResource>();
var collectionMock = new Mock<SubscriptionCollection>();
var response = Azure.Response.FromValue(subMock.Object, Mock.Of<Azure.Response>());
collectionMock.Setup(x => x.GetAsync(It.IsAny<string>(), default)).ThrowsAsync(new Azure.RequestFailedException("Subscription Not Found"));
collectionMock.Setup(x => x.GetAsync(nameOrId, default)).ReturnsAsync(response);
armClientMock.Setup(x => x.GetSubscriptions()).Returns(collectionMock.Object);
return subMock;
}
private Mock<ResourceGroupResource> SetUpResourceGroupMock(Mock<SubscriptionResource> subMock, string rgName)
{
var rgMock = new Mock<ResourceGroupResource>();
var collectionMock = new Mock<ResourceGroupCollection>();
var response = Azure.Response.FromValue(rgMock.Object, Mock.Of<Azure.Response>());
collectionMock.Setup(x => x.GetAsync(It.IsAny<string>(), default)).ThrowsAsync(new Azure.RequestFailedException("Resource Group Not Found"));
collectionMock.Setup(x => x.GetAsync(rgName, default)).ReturnsAsync(response);
subMock.Setup(x => x.GetResourceGroups()).Returns(collectionMock.Object);
return rgMock;
}
private void SetUpDeploymentsMock(Mock<ArmDeploymentCollection> deploymentsMock)
{
deploymentsMock.Setup(x => x.CreateOrUpdateAsync(
It.IsAny<Azure.WaitUntil>(),
It.IsAny<string>(),
It.IsAny<ArmDeploymentContent>(),
default))
.ReturnsAsync(Mock.Of<ArmOperation<ArmDeploymentResource>>());
}
private void SetUpDeploymentExceptionMock<T>(Mock<ArmDeploymentCollection> deploymentsMock, T exception) where T : Exception
{
deploymentsMock.Setup(x => x.CreateOrUpdateAsync(
It.IsAny<Azure.WaitUntil>(),
It.IsAny<string>(),
It.IsAny<ArmDeploymentContent>(),
default))
.ThrowsAsync(exception);
}
private void VerifyDeploymentsMock(Mock<ArmDeploymentCollection> deploymentsMock)
{
deploymentsMock.Verify(x => x.CreateOrUpdateAsync(
It.IsAny<Azure.WaitUntil>(),
It.IsAny<string>(),
It.IsAny<ArmDeploymentContent>(),
default),
Times.Once);
}
// This test class mocks the extension methods for GetArmDeployments()
private class TestArmDeploymentService : ArmDeploymentService {
private readonly ArmDeploymentCollection rgDeploymentCollection;
private readonly ArmDeploymentCollection subDeploymentCollection;
public TestArmDeploymentService(ArmDeploymentCollection rgDeploymentCollection, ArmDeploymentCollection subDeploymentCollection, ArmClient client) : base(client)
{
this.rgDeploymentCollection = rgDeploymentCollection;
this.subDeploymentCollection = subDeploymentCollection;
}
protected override Task<ArmOperation<ArmDeploymentResource>> CreateGroupDeployment(ResourceGroupResource rg, WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent)
{
return rgDeploymentCollection.CreateOrUpdateAsync(waitUntil, deploymentName, deploymentContent);
}
protected override Task<ArmOperation<ArmDeploymentResource>> CreateSubscriptionDeployment(SubscriptionResource sub, WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent)
{
return subDeploymentCollection.CreateOrUpdateAsync(waitUntil, deploymentName, deploymentContent);
}
}
}

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

@ -9,6 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.ResourceManager" Version="1.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="xunit" Version="2.4.1" />

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

@ -1,30 +1,80 @@
namespace BenchPress.TestEngine.Tests;
using Azure.ResourceManager;
using Azure.ResourceManager.Resources;
namespace BenchPress.TestEngine.Tests;
public class BicepServiceTests
{
private readonly BicepService bicepService;
private readonly ServerCallContext context;
private readonly Mock<IArmDeploymentService> armDeploymentMock;
public BicepServiceTests()
{
var logger = new Mock<ILogger<BicepService>>().Object;
bicepService = new BicepService(logger);
var logger = Mock.Of<ILogger<BicepService>>();
armDeploymentMock = new Mock<IArmDeploymentService>(MockBehavior.Strict);
bicepService = new BicepService(logger, armDeploymentMock.Object);
context = new MockServerCallContext();
}
[Fact(Skip = "Not Implemented")]
public async Task DeploymentGroupCreate_DeploysResourceGroup()
[Fact]
public async Task DeploymentGroupCreate_DeploysResourceGroup_WithTranspiledFiles()
{
var request = new DeploymentGroupRequest
{
BicepFilePath = "main.bicep",
ParameterFilePath = "parameters.json",
ResourceGroupName = "test-rg",
SubscriptionNameOrId = new Guid().ToString()
};
var result = await bicepService.DeploymentGroupCreate(request, context);
// TODO: set up successful transpilation
var templatePath = validGroupRequest.BicepFilePath;
SetUpSuccessfulGroupDeployment(validGroupRequest, templatePath);
var result = await bicepService.DeploymentGroupCreate(validGroupRequest, context);
Assert.True(result.Success);
VerifyGroupDeployment(validGroupRequest, templatePath);
}
[Theory]
[InlineData("main.bicep", "", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("", "rg-test", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "rg-test", "")]
public async Task DeploymentGroupCreate_FailsOnMissingParameters(string bicepFilePath, string resourceGroupName, string subscriptionNameOrId)
{
var request = SetUpGroupRequest(bicepFilePath, resourceGroupName, subscriptionNameOrId);
// TODO: set up successful transpilation
var templatePath = request.BicepFilePath;
SetUpSuccessfulGroupDeployment(request, templatePath);
var result = await bicepService.DeploymentGroupCreate(request, context);
Assert.False(result.Success);
// TODO: verify transpile wasn't called
VerifyNoDeployments();
}
[Fact(Skip = "Not Fully Implemented")]
public async Task DeploymentGroupCreate_ReturnsFailureOnTranspileException()
{
var expectedMessage = "the bicep file was malformed";
// TODO: set up exception throwing transpilation
SetUpSuccessfulGroupDeployment(validGroupRequest, "template.json");
var result = await bicepService.DeploymentGroupCreate(validGroupRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedMessage, result.ErrorMessage);
}
[Fact]
public async Task DeploymentGroupCreate_ReturnsFailureOnFailedDeployment()
{
// TODO: set up successful transpilation
var expectedReason = "Failure occured during deployment";
SetUpFailedGroupDeployment(expectedReason);
var result = await bicepService.DeploymentGroupCreate(validGroupRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedReason, result.ErrorMessage);
}
[Fact]
public async Task DeploymentGroupCreate_ReturnsFailureOnDeploymentException()
{
// TODO: set up successful transpilation
var expectedMessage = "the template was malformed";
SetUpExceptionThrowingGroupDeployment(new Exception(expectedMessage));
var result = await bicepService.DeploymentGroupCreate(validGroupRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedMessage, result.ErrorMessage);
}
[Fact(Skip = "Not Implemented")]
@ -33,9 +83,86 @@ public class BicepServiceTests
var request = new DeleteGroupRequest
{
ResourceGroupName = "test-rg",
SubscriptionNameOrId = new Guid().ToString()
SubscriptionNameOrId = Guid.NewGuid().ToString()
};
var result = await bicepService.DeleteGroup(request, context);
Assert.True(result.Success);
}
private readonly DeploymentGroupRequest validGroupRequest = new DeploymentGroupRequest
{
BicepFilePath = "main.bicep",
ResourceGroupName = "test-rg",
SubscriptionNameOrId = Guid.NewGuid().ToString()
};
private DeploymentGroupRequest SetUpGroupRequest(string bicepFilePath, string resourceGroupName, string subscriptionNameOrId) {
return new DeploymentGroupRequest
{
BicepFilePath = bicepFilePath,
ResourceGroupName = resourceGroupName,
SubscriptionNameOrId = subscriptionNameOrId
};
}
private ArmOperation<ArmDeploymentResource> SetupDeploymentOperation(bool success, string reason) {
var responseMock = new Mock<Azure.Response>();
responseMock.Setup(x => x.IsError).Returns(!success);
responseMock.Setup(x => x.ReasonPhrase).Returns(reason);
var operationMock = new Mock<ArmOperation<ArmDeploymentResource>>();
operationMock.Setup(x => x.WaitForCompletionResponse(default)).Returns(responseMock.Object);
return operationMock.Object;
}
private void SetUpSuccessfulGroupDeployment(DeploymentGroupRequest request, string templatePath) {
var operation = SetupDeploymentOperation(true, "OK");
armDeploymentMock.Setup(x => x.DeployArmToResourceGroupAsync(
request.SubscriptionNameOrId,
request.ResourceGroupName,
templatePath,
request.ParameterFilePath,
It.IsAny<Azure.WaitUntil>()))
.ReturnsAsync(operation);
}
private void SetUpFailedGroupDeployment(string reason) {
var operation = SetupDeploymentOperation(false, reason);
armDeploymentMock.Setup(x => x.DeployArmToResourceGroupAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()))
.ReturnsAsync(operation);
}
private void SetUpExceptionThrowingGroupDeployment(Exception ex) {
armDeploymentMock.Setup(x => x.DeployArmToResourceGroupAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()))
.ThrowsAsync(ex);
}
private void VerifyGroupDeployment(DeploymentGroupRequest request, string templatePath) {
armDeploymentMock.Verify(x => x.DeployArmToResourceGroupAsync(
request.SubscriptionNameOrId,
request.ResourceGroupName,
templatePath,
request.ParameterFilePath,
It.IsAny<Azure.WaitUntil>()),
Times.Once);
}
private void VerifyNoDeployments() {
armDeploymentMock.Verify(x => x.DeployArmToResourceGroupAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()),
Times.Never);
}
}

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

@ -0,0 +1,9 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"projectName": {
"value": "jern"
}
}
}

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

@ -0,0 +1,62 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"projectName": {
"type": "string",
"minLength": 3,
"maxLength": 11,
"metadata": {
"description": "Specify a project name that is used to generate resource names."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Specify a location for the resources."
}
},
"storageSKU": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_RAGRS",
"Standard_ZRS",
"Premium_LRS",
"Premium_ZRS",
"Standard_GZRS",
"Standard_RAGZRS"
],
"metadata": {
"description": "Specify the storage account type."
}
}
},
"variables": {
"storageAccountName": "[concat(parameters('projectName'), uniqueString(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "[variables('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('storageSKU')]"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
],
"outputs": {
"storageEndpoint": {
"type": "object",
"value": "[reference(variables('storageAccountName')).primaryEndpoints]"
}
}
}

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

@ -0,0 +1,63 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"projectName": {
"type": "string",
"defaultValue": "jern",
"minLength": 3,
"maxLength": 11,
"metadata": {
"description": "Specify a project name that is used to generate resource names."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Specify a location for the resources."
}
},
"storageSKU": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_RAGRS",
"Standard_ZRS",
"Premium_LRS",
"Premium_ZRS",
"Standard_GZRS",
"Standard_RAGZRS"
],
"metadata": {
"description": "Specify the storage account type."
}
}
},
"variables": {
"storageAccountName": "[concat(parameters('projectName'), uniqueString(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "[variables('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('storageSKU')]"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
],
"outputs": {
"storageEndpoint": {
"type": "object",
"value": "[reference(variables('storageAccountName')).primaryEndpoints]"
}
}
}

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

@ -12,7 +12,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.7.0" />
<PackageReference Include="Azure.ResourceManager" Version="1.3.1" />
<PackageReference Include="Azure.ResourceManager.Resources" Version="1.3.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.6.0" />
</ItemGroup>
<ItemGroup>

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

@ -1,5 +1,7 @@
using BenchPress.TestEngine.Services;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Azure;
using Azure.Identity;
var builder = WebApplication.CreateBuilder(args);
@ -14,6 +16,12 @@ builder.WebHost.ConfigureKestrel(options =>
// Add services to the container.
builder.Services.AddGrpc();
builder.Services.AddAzureClients(builder => {
builder.AddClient<ArmClient, ArmClientOptions>(options => {
return new ArmClient(new DefaultAzureCredential());
});
});
builder.Services.AddSingleton<IArmDeploymentService, ArmDeploymentService>();
var app = builder.Build();

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

@ -0,0 +1,59 @@
using Azure;
using Azure.ResourceManager.Resources.Models;
namespace BenchPress.TestEngine.Services;
public class ArmDeploymentService : IArmDeploymentService {
private readonly ArmClient client;
private string NewDeploymentName { get { return $"benchpress-{Guid.NewGuid().ToString()}"; } }
public ArmDeploymentService(ArmClient client) {
this.client = client;
}
public async Task<ArmOperation<ArmDeploymentResource>> DeployArmToResourceGroupAsync(string subscriptionNameOrId, string resourceGroupName, string armTemplatePath, string? parametersPath = null, WaitUntil waitUntil = WaitUntil.Completed)
{
ValidateParameters(subscriptionNameOrId, resourceGroupName, armTemplatePath);
SubscriptionResource sub = await client.GetSubscriptions().GetAsync(subscriptionNameOrId);
ResourceGroupResource rg = await sub.GetResourceGroups().GetAsync(resourceGroupName);
var deploymentContent = await CreateDeploymentContent(armTemplatePath, parametersPath);
return await CreateGroupDeployment(rg, waitUntil, NewDeploymentName, deploymentContent);
}
public async Task<ArmOperation<ArmDeploymentResource>> DeployArmToSubscriptionAsync(string subscriptionNameOrId, string armTemplatePath, string? parametersPath = null, WaitUntil waitUtil = WaitUntil.Completed)
{
ValidateParameters(subscriptionNameOrId, armTemplatePath);
SubscriptionResource sub = await client.GetSubscriptions().GetAsync(subscriptionNameOrId);
var deploymentContent = await CreateDeploymentContent(armTemplatePath, parametersPath);
return await CreateSubscriptionDeployment(sub, waitUtil, NewDeploymentName, deploymentContent);
}
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) {
var templateContent = (await File.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);
}
return new ArmDeploymentContent(properties);
}
// 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) {
return await rg.GetArmDeployments().CreateOrUpdateAsync(waitUntil, deploymentName, 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);
}
}

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

@ -3,15 +3,42 @@ namespace BenchPress.TestEngine.Services;
public class BicepService : Bicep.BicepBase
{
private readonly ILogger<BicepService> logger;
private readonly IArmDeploymentService armDeploymentService;
public BicepService(ILogger<BicepService> logger)
public BicepService(ILogger<BicepService> logger, IArmDeploymentService armDeploymentService)
{
this.logger = logger;
this.armDeploymentService = armDeploymentService;
}
public override async Task<DeploymentResult> DeploymentGroupCreate(DeploymentGroupRequest request, ServerCallContext context)
{
throw new NotImplementedException();
if (string.IsNullOrWhiteSpace(request.BicepFilePath)
|| string.IsNullOrWhiteSpace(request.ResourceGroupName)
|| string.IsNullOrWhiteSpace(request.SubscriptionNameOrId))
{
return new DeploymentResult {
Success = false,
ErrorMessage = $"One or more of the following required parameters was missing: {nameof(request.BicepFilePath)}, {nameof(request.ResourceGroupName)}, and {nameof(request.SubscriptionNameOrId)}"
};
}
try {
// TODO: pass in transpiled arm template instead
var deployment = await armDeploymentService.DeployArmToResourceGroupAsync(request.SubscriptionNameOrId, request.ResourceGroupName, request.BicepFilePath, request.ParameterFilePath);
var response = deployment.WaitForCompletionResponse();
return new DeploymentResult {
Success = !response.IsError,
ErrorMessage = response.ReasonPhrase
};
} catch (Exception ex) {
return new DeploymentResult {
Success = false,
ErrorMessage = ex.Message
};
}
}
public override async Task<DeploymentResult> DeleteGroup(DeleteGroupRequest request, ServerCallContext context)

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

@ -0,0 +1,6 @@
namespace BenchPress.TestEngine.Services;
public interface IArmDeploymentService {
Task<ArmOperation<ArmDeploymentResource>> DeployArmToResourceGroupAsync(string subscriptionNameOrId, string resourceGroupName, string armTemplatePath, string? parametersPath = null, Azure.WaitUntil waitUtil = Azure.WaitUntil.Completed);
Task<ArmOperation<ArmDeploymentResource>> DeployArmToSubscriptionAsync(string subscriptionNameOrId, string armTemplatePath, string? parametersPath = null, Azure.WaitUntil waitUtil = Azure.WaitUntil.Completed);
}

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

@ -1,2 +1,4 @@
global using Grpc.Core;
global using Azure.ResourceManager;
global using Azure.ResourceManager.Resources;
global using BenchPress.TestEngine;

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

@ -11,4 +11,18 @@
<GlobalAnalyzerConfigFiles Include="../../../.globalconfig" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="../../../protos/bicep.proto" GrpcServices="Client" />
<Protobuf Include="../../../protos/resource_group.proto" GrpcServices="Client" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.21.6" />
<PackageReference Include="Grpc.Net.Client" Version="2.49.0" />
<PackageReference Include="Grpc.Tools" Version="2.49.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

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

@ -1,2 +1 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
Console.WriteLine("Hello World!");