expose gRPC endpoint for deploying at the subscription scope (#24)

* expose gRPC endpoint for deploying at the subscription scope

* not the template name I wanted
This commit is contained in:
jessica-ern 2022-11-10 13:38:33 -06:00 коммит произвёл GitHub
Родитель 027e4f456b
Коммит 59a2644e76
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 207 добавлений и 16 удалений

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

@ -12,6 +12,7 @@ public class ArmDeploymentServiceTests {
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";
@ -92,19 +93,20 @@ public class ArmDeploymentServiceTests {
{
var subMock = SetUpSubscriptionMock(validSubId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, templateWithParams, parameters);
await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, validLocation, templateWithParams, parameters);
VerifyDeploymentsMock(subscriptionDeploymentsMock);
}
[Theory]
[InlineData("", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "")]
public async Task DeployArmToSubscriptionAsync_MissingParameter_ThrowsException(string templatePath, string subId)
[InlineData("main.bicep", "", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("", "eastus", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "eastus", "")]
public async Task DeployArmToSubscriptionAsync_MissingParameter_ThrowsException(string templatePath, string location, string subId)
{
var subMock = SetUpSubscriptionMock(subId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
var ex = await Assert.ThrowsAsync<ArgumentException>(
async () => await armDeploymentService.DeployArmToSubscriptionAsync(subId, templatePath)
async () => await armDeploymentService.DeployArmToSubscriptionAsync(subId, location, templatePath)
);
Assert.Equal("One or more parameters were missing or empty", ex.Message);
}
@ -116,7 +118,7 @@ public class ArmDeploymentServiceTests {
var excepectedMessage = "Deployment template validation failed";
SetUpDeploymentExceptionMock(subscriptionDeploymentsMock, new RequestFailedException(excepectedMessage));
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, templateWithParams)
async () => await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, validLocation, templateWithParams)
);
Assert.Equal(excepectedMessage, ex.Message);
}
@ -127,13 +129,22 @@ public class ArmDeploymentServiceTests {
var subMock = SetUpSubscriptionMock(validSubId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToSubscriptionAsync("The Wrong Subscription", standaloneTemplate)
async () => await armDeploymentService.DeployArmToSubscriptionAsync("The Wrong Subscription", validLocation, standaloneTemplate)
);
Assert.Equal("Subscription Not Found", ex.Message);
}
[Fact]
public async Task CreateDeploymentContent_WithoutParameters()
{
var subMock = SetUpSubscriptionMock(validSubId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, validLocation, standaloneTemplate);
VerifyDeploymentsMock(subscriptionDeploymentsMock);
}
[Fact]
public async Task CreateDeploymentContent_WithoutLocation()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);

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

@ -78,6 +78,64 @@ public class DeploymentServiceTests
Assert.Equal(expectedMessage, result.ErrorMessage);
}
[Fact]
public async Task DeploymentSubCreate_DeploysToSub_WithTranspiledFiles()
{
SetUpSuccessfulTranspilation(validSubRequest.BicepFilePath, templatePath);
SetUpSuccessfulSubDeployment(validSubRequest, templatePath);
var result = await deploymentService.DeploymentSubCreate(validSubRequest, context);
Assert.True(result.Success);
VerifySubDeployment(validSubRequest, templatePath);
}
[Theory]
[InlineData("main.bicep", "", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("", "eastus", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "eastus", "")]
public async Task DeploymentSubCreate_FailsOnMissingParameters(string bicepFilePath, string location, string subscriptionNameOrId)
{
var request = SetUpSubRequest(bicepFilePath, location, subscriptionNameOrId);
SetUpSuccessfulTranspilation(validSubRequest.BicepFilePath, templatePath);
SetUpSuccessfulSubDeployment(request, templatePath);
var result = await deploymentService.DeploymentSubCreate(request, context);
Assert.False(result.Success);
VerifyNoTranspilation();
VerifyNoDeployments();
}
[Fact]
public async Task DeploymentSubCreate_ReturnsFailureOnTranspileException()
{
var expectedMessage = "the bicep file was malformed";
SetUpExceptionThrowingTranspilation(new Exception(expectedMessage));
SetUpSuccessfulSubDeployment(validSubRequest, "template.json");
var result = await deploymentService.DeploymentSubCreate(validSubRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedMessage, result.ErrorMessage);
}
[Fact]
public async Task DeploymentSubCreate_ReturnsFailureOnFailedDeployment()
{
SetUpSuccessfulTranspilation(validSubRequest.BicepFilePath, templatePath);
var expectedReason = "Failure occured during deployment";
SetUpFailedSubDeployment(expectedReason);
var result = await deploymentService.DeploymentSubCreate(validSubRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedReason, result.ErrorMessage);
}
[Fact]
public async Task DeploymentSubpCreate_ReturnsFailureOnDeploymentException()
{
SetUpSuccessfulTranspilation(validSubRequest.BicepFilePath, templatePath);
var expectedMessage = "the template was malformed";
SetUpExceptionThrowingSubDeployment(new Exception(expectedMessage));
var result = await deploymentService.DeploymentSubCreate(validSubRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedMessage, result.ErrorMessage);
}
[Fact(Skip = "Not Implemented")]
public async Task DeleteGroup_DeletesAllResources()
{
@ -97,6 +155,13 @@ public class DeploymentServiceTests
SubscriptionNameOrId = Guid.NewGuid().ToString()
};
private readonly DeploymentSubRequest validSubRequest = new DeploymentSubRequest
{
BicepFilePath = "main.bicep",
Location = "eastus",
SubscriptionNameOrId = Guid.NewGuid().ToString()
};
private DeploymentGroupRequest SetUpGroupRequest(string bicepFilePath, string resourceGroupName, string subscriptionNameOrId)
{
return new DeploymentGroupRequest
@ -107,6 +172,16 @@ public class DeploymentServiceTests
};
}
private DeploymentSubRequest SetUpSubRequest(string bicepFilePath, string location, string subscriptionNameOrId)
{
return new DeploymentSubRequest
{
BicepFilePath = bicepFilePath,
Location = location,
SubscriptionNameOrId = subscriptionNameOrId
};
}
private void SetUpSuccessfulTranspilation(string bicepFilePath, string armTemplatePath)
{
bicepTranspileServiceMock.Setup(x => x.BuildAsync(bicepFilePath)).ReturnsAsync(armTemplatePath);
@ -183,6 +258,52 @@ public class DeploymentServiceTests
Times.Once);
}
private void SetUpSuccessfulSubDeployment(DeploymentSubRequest request, string templatePath)
{
var operation = SetupDeploymentOperation(true, "OK");
armDeploymentMock.Setup(x => x.DeployArmToSubscriptionAsync(
request.SubscriptionNameOrId,
request.Location,
templatePath,
request.ParameterFilePath,
It.IsAny<Azure.WaitUntil>()))
.ReturnsAsync(operation);
}
private void SetUpFailedSubDeployment(string reason)
{
var operation = SetupDeploymentOperation(false, reason);
armDeploymentMock.Setup(x => x.DeployArmToSubscriptionAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()))
.ReturnsAsync(operation);
}
private void SetUpExceptionThrowingSubDeployment(Exception ex)
{
armDeploymentMock.Setup(x => x.DeployArmToSubscriptionAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()))
.ThrowsAsync(ex);
}
private void VerifySubDeployment(DeploymentSubRequest request, string templatePath)
{
armDeploymentMock.Verify(x => x.DeployArmToSubscriptionAsync(
request.SubscriptionNameOrId,
request.Location,
templatePath,
request.ParameterFilePath,
It.IsAny<Azure.WaitUntil>()),
Times.Once);
}
private void VerifyNoDeployments()
{
armDeploymentMock.Verify(x => x.DeployArmToResourceGroupAsync(
@ -192,5 +313,13 @@ public class DeploymentServiceTests
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()),
Times.Never);
armDeploymentMock.Verify(x => x.DeployArmToSubscriptionAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()),
Times.Never);
}
}

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

@ -20,11 +20,11 @@ public class ArmDeploymentService : IArmDeploymentService {
return await CreateGroupDeployment(rg, waitUntil, NewDeploymentName, deploymentContent);
}
public async Task<ArmOperation<ArmDeploymentResource>> DeployArmToSubscriptionAsync(string subscriptionNameOrId, string armTemplatePath, string? parametersPath = null, WaitUntil waitUtil = WaitUntil.Completed)
public async Task<ArmOperation<ArmDeploymentResource>> DeployArmToSubscriptionAsync(string subscriptionNameOrId, string location, string armTemplatePath, string? parametersPath = null, WaitUntil waitUtil = WaitUntil.Completed)
{
ValidateParameters(subscriptionNameOrId, armTemplatePath);
ValidateParameters(subscriptionNameOrId, location, armTemplatePath);
SubscriptionResource sub = await client.GetSubscriptions().GetAsync(subscriptionNameOrId);
var deploymentContent = await CreateDeploymentContent(armTemplatePath, parametersPath);
var deploymentContent = await CreateDeploymentContent(armTemplatePath, parametersPath, location);
return await CreateSubscriptionDeployment(sub, waitUtil, NewDeploymentName, deploymentContent);
}
@ -34,18 +34,26 @@ public class ArmDeploymentService : IArmDeploymentService {
}
}
private async Task<ArmDeploymentContent> CreateDeploymentContent(string armTemplatePath, string? parametersPath) {
private async Task<ArmDeploymentContent> CreateDeploymentContent(string armTemplatePath, string? parametersPath, string? location = null) {
var templateContent = (await File.ReadAllTextAsync(armTemplatePath)).TrimEnd();
var properties = new ArmDeploymentProperties(ArmDeploymentMode.Incremental) {
var properties = new ArmDeploymentProperties(ArmDeploymentMode.Incremental)
{
Template = BinaryData.FromString(templateContent)
};
if (!string.IsNullOrWhiteSpace(parametersPath)) {
if (!string.IsNullOrWhiteSpace(parametersPath))
{
var paramteresContent = (await File.ReadAllTextAsync(parametersPath)).TrimEnd();
properties.Parameters = BinaryData.FromString(parametersPath);
}
return new ArmDeploymentContent(properties);
var content = new ArmDeploymentContent(properties);
if (!string.IsNullOrWhiteSpace(location))
{
content.Location = location;
}
return content;
}
// These extension methods are wrapped to allow mocking in our tests

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

@ -49,6 +49,41 @@ public class DeploymentService : Deployment.DeploymentBase
}
}
public override async Task<DeploymentResult> DeploymentSubCreate(DeploymentSubRequest request, ServerCallContext context)
{
if (string.IsNullOrWhiteSpace(request.BicepFilePath)
|| string.IsNullOrWhiteSpace(request.Location)
|| 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.Location)}, and {nameof(request.SubscriptionNameOrId)}"
};
}
try
{
var armTemplatePath = await bicepTranspileService.BuildAsync(request.BicepFilePath);
var deployment = await armDeploymentService.DeployArmToSubscriptionAsync(request.SubscriptionNameOrId, request.Location, armTemplatePath, 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)
{
throw new NotImplementedException();

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

@ -2,5 +2,5 @@ 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);
Task<ArmOperation<ArmDeploymentResource>> DeployArmToSubscriptionAsync(string subscriptionNameOrId, string location, string armTemplatePath, string? parametersPath = null, Azure.WaitUntil waitUtil = Azure.WaitUntil.Completed);
}

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

@ -8,6 +8,7 @@ option csharp_namespace = "BenchPress.TestEngine";
// Other scopes: subscription, management group, and tenant.
service Deployment {
rpc DeploymentGroupCreate (DeploymentGroupRequest) returns (DeploymentResult);
rpc DeploymentSubCreate (DeploymentSubRequest) returns (DeploymentResult);
rpc DeleteGroup (DeleteGroupRequest) returns (DeploymentResult);
}
@ -18,6 +19,13 @@ message DeploymentGroupRequest {
string subscription_name_or_id = 4;
}
message DeploymentSubRequest {
string bicep_file_path = 1;
string parameter_file_path = 2;
string location = 3;
string subscription_name_or_id = 4;
}
message DeleteGroupRequest {
string resource_group_name = 1;
string subscription_name_or_id = 2;