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 readonly Mock<ArmDeploymentCollection> subscriptionDeploymentsMock;
private const string validSubId = "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d"; private const string validSubId = "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d";
private const string validRgName = "test-rg"; private const string validRgName = "test-rg";
private const string validLocation = "eastus";
private const string smapleFiles = "../../../SampleFiles"; private const string smapleFiles = "../../../SampleFiles";
private const string standaloneTemplate = $"{smapleFiles}/storage-account.json"; private const string standaloneTemplate = $"{smapleFiles}/storage-account.json";
private const string templateWithParams = $"{smapleFiles}/storage-account-needs-params.json"; private const string templateWithParams = $"{smapleFiles}/storage-account-needs-params.json";
@ -92,19 +93,20 @@ public class ArmDeploymentServiceTests {
{ {
var subMock = SetUpSubscriptionMock(validSubId); var subMock = SetUpSubscriptionMock(validSubId);
SetUpDeploymentsMock(subscriptionDeploymentsMock); SetUpDeploymentsMock(subscriptionDeploymentsMock);
await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, templateWithParams, parameters); await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, validLocation, templateWithParams, parameters);
VerifyDeploymentsMock(subscriptionDeploymentsMock); VerifyDeploymentsMock(subscriptionDeploymentsMock);
} }
[Theory] [Theory]
[InlineData("", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")] [InlineData("main.bicep", "", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "")] [InlineData("", "eastus", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
public async Task DeployArmToSubscriptionAsync_MissingParameter_ThrowsException(string templatePath, string subId) [InlineData("main.bicep", "eastus", "")]
public async Task DeployArmToSubscriptionAsync_MissingParameter_ThrowsException(string templatePath, string location, string subId)
{ {
var subMock = SetUpSubscriptionMock(subId); var subMock = SetUpSubscriptionMock(subId);
SetUpDeploymentsMock(subscriptionDeploymentsMock); SetUpDeploymentsMock(subscriptionDeploymentsMock);
var ex = await Assert.ThrowsAsync<ArgumentException>( 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); 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"; var excepectedMessage = "Deployment template validation failed";
SetUpDeploymentExceptionMock(subscriptionDeploymentsMock, new RequestFailedException(excepectedMessage)); SetUpDeploymentExceptionMock(subscriptionDeploymentsMock, new RequestFailedException(excepectedMessage));
var ex = await Assert.ThrowsAsync<RequestFailedException>( var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, templateWithParams) async () => await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, validLocation, templateWithParams)
); );
Assert.Equal(excepectedMessage, ex.Message); Assert.Equal(excepectedMessage, ex.Message);
} }
@ -127,13 +129,22 @@ public class ArmDeploymentServiceTests {
var subMock = SetUpSubscriptionMock(validSubId); var subMock = SetUpSubscriptionMock(validSubId);
SetUpDeploymentsMock(subscriptionDeploymentsMock); SetUpDeploymentsMock(subscriptionDeploymentsMock);
var ex = await Assert.ThrowsAsync<RequestFailedException>( 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); Assert.Equal("Subscription Not Found", ex.Message);
} }
[Fact] [Fact]
public async Task CreateDeploymentContent_WithoutParameters() 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 subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName); var rgMock = SetUpResourceGroupMock(subMock, validRgName);

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

@ -78,6 +78,64 @@ public class DeploymentServiceTests
Assert.Equal(expectedMessage, result.ErrorMessage); 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")] [Fact(Skip = "Not Implemented")]
public async Task DeleteGroup_DeletesAllResources() public async Task DeleteGroup_DeletesAllResources()
{ {
@ -97,6 +155,13 @@ public class DeploymentServiceTests
SubscriptionNameOrId = Guid.NewGuid().ToString() 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) private DeploymentGroupRequest SetUpGroupRequest(string bicepFilePath, string resourceGroupName, string subscriptionNameOrId)
{ {
return new DeploymentGroupRequest 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) private void SetUpSuccessfulTranspilation(string bicepFilePath, string armTemplatePath)
{ {
bicepTranspileServiceMock.Setup(x => x.BuildAsync(bicepFilePath)).ReturnsAsync(armTemplatePath); bicepTranspileServiceMock.Setup(x => x.BuildAsync(bicepFilePath)).ReturnsAsync(armTemplatePath);
@ -183,6 +258,52 @@ public class DeploymentServiceTests
Times.Once); 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() private void VerifyNoDeployments()
{ {
armDeploymentMock.Verify(x => x.DeployArmToResourceGroupAsync( armDeploymentMock.Verify(x => x.DeployArmToResourceGroupAsync(
@ -192,5 +313,13 @@ public class DeploymentServiceTests
It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()), It.IsAny<Azure.WaitUntil>()),
Times.Never); 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); 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); 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); 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 templateContent = (await File.ReadAllTextAsync(armTemplatePath)).TrimEnd();
var properties = new ArmDeploymentProperties(ArmDeploymentMode.Incremental) { var properties = new ArmDeploymentProperties(ArmDeploymentMode.Incremental)
{
Template = BinaryData.FromString(templateContent) Template = BinaryData.FromString(templateContent)
}; };
if (!string.IsNullOrWhiteSpace(parametersPath)) { if (!string.IsNullOrWhiteSpace(parametersPath))
{
var paramteresContent = (await File.ReadAllTextAsync(parametersPath)).TrimEnd(); var paramteresContent = (await File.ReadAllTextAsync(parametersPath)).TrimEnd();
properties.Parameters = BinaryData.FromString(parametersPath); 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 // 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) public override async Task<DeploymentResult> DeleteGroup(DeleteGroupRequest request, ServerCallContext context)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

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

@ -2,5 +2,5 @@ namespace BenchPress.TestEngine.Services;
public interface IArmDeploymentService { 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>> 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. // Other scopes: subscription, management group, and tenant.
service Deployment { service Deployment {
rpc DeploymentGroupCreate (DeploymentGroupRequest) returns (DeploymentResult); rpc DeploymentGroupCreate (DeploymentGroupRequest) returns (DeploymentResult);
rpc DeploymentSubCreate (DeploymentSubRequest) returns (DeploymentResult);
rpc DeleteGroup (DeleteGroupRequest) returns (DeploymentResult); rpc DeleteGroup (DeleteGroupRequest) returns (DeploymentResult);
} }
@ -18,6 +19,13 @@ message DeploymentGroupRequest {
string subscription_name_or_id = 4; 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 { message DeleteGroupRequest {
string resource_group_name = 1; string resource_group_name = 1;
string subscription_name_or_id = 2; string subscription_name_or_id = 2;