зеркало из https://github.com/Azure/benchpress.git
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:
Родитель
25e107cd93
Коммит
3aed3b129b
|
@ -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!");
|
||||
|
|
Загрузка…
Ссылка в новой задаче