Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Hackathon - Bicep Deploy] Add deploy/validate/what-if command #15096

Merged
merged 8 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"preLaunchTask": "Build CLI",
"program": "${workspaceFolder}/src/Bicep.Cli/bin/Debug/net8.0/bicep",
"args": [
"build",
"deploy",
"${file}"
],
"env": {
Expand Down
7 changes: 7 additions & 0 deletions Bicep.sln
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Deploy", "Deploy", "{96FF0B
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.Deploy", "src\Bicep.Deploy\Bicep.Deploy.csproj", "{E4C7A152-76D5-43EC-8CDE-5179D284F8F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.Deploy.UnitTests", "src\Bicep.Deploy.UnitTests\Bicep.Deploy.UnitTests.csproj", "{2ABB4394-7B22-40D7-8FF1-AD37581040F6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -203,6 +205,10 @@ Global
{E4C7A152-76D5-43EC-8CDE-5179D284F8F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4C7A152-76D5-43EC-8CDE-5179D284F8F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4C7A152-76D5-43EC-8CDE-5179D284F8F0}.Release|Any CPU.Build.0 = Release|Any CPU
{2ABB4394-7B22-40D7-8FF1-AD37581040F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2ABB4394-7B22-40D7-8FF1-AD37581040F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2ABB4394-7B22-40D7-8FF1-AD37581040F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2ABB4394-7B22-40D7-8FF1-AD37581040F6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -233,6 +239,7 @@ Global
{3F25D072-7A7E-419D-8425-6F2C7CF42BFD} = {8F8DCFBC-A0DC-4E40-93C8-B4FB99FBD757}
{A7D359D9-654A-4FAF-9BC2-DA9667EF8756} = {8F8DCFBC-A0DC-4E40-93C8-B4FB99FBD757}
{E4C7A152-76D5-43EC-8CDE-5179D284F8F0} = {96FF0BB4-F23F-45A7-9671-53438CF7E8E3}
{2ABB4394-7B22-40D7-8FF1-AD37581040F6} = {96FF0BB4-F23F-45A7-9671-53438CF7E8E3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {21F77282-91E7-4304-B1EF-FADFA4F39E37}
Expand Down
3 changes: 3 additions & 0 deletions docs/experimental-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ The following features can be optionally enabled through your `bicepconfig.json`
### `assertions`
Should be enabled in tandem with `testFramework` experimental feature flag for expected functionality. Allows you to author boolean assertions using the `assert` keyword comparing the actual value of a parameter, variable, or resource name to an expected value. Assert statements can only be written directly within the Bicep file whose resources they reference. For more information, see [Bicep Experimental Test Framework](https://github.com/Azure/bicep/issues/11967).

### `deploymentFile`
Enables support for Bicep deployment file. See [Bicep deployment file proposal](https://gist.github.com/shenglol/bd7bd24343b24edd965ba411048c1eb2#file-bicep-deployment-file-md).

### `extendableParamFiles`
Enables the ability to extend bicepparam files from other bicepparam files. For more information, see [Extendable Bicep Params Files](./experimental/extendable-param-files.md).

Expand Down
118 changes: 118 additions & 0 deletions src/Bicep.Cli.IntegrationTests/DeployCommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Immutable;
using Azure;
using Azure.Core;
using Azure.ResourceManager;
using Azure.ResourceManager.Resources;
using Azure.ResourceManager.Resources.Mocking;
using Azure.ResourceManager.Resources.Models;
using Bicep.Core.Configuration;
using Bicep.Core.Models;
using Bicep.Core.Samples;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Mock;
using Bicep.Core.UnitTests.Utils;
using Bicep.Deploy;
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Bicep.Cli.IntegrationTests;

[TestClass]
public class DeployCommandTests : TestBase
{
[TestMethod]
public async Task Deploy_ZeroFiles_ShouldFail_WithExpectedErrorMessage()
{
var (output, error, result) = await Bicep("deploy");

using (new AssertionScope())
{
result.Should().Be(1);
output.Should().BeEmpty();

error.Should().NotBeEmpty();
error.Should().Contain($"The input file path was not specified");
}
}

[TestMethod]
public async Task Deploy_With_Incorrect_Bicep_Deployment_File_Extension_ShouldFail_WithExpectedErrorMessage()
{
var bicepDeployPath = FileHelper.SaveResultFile(TestContext, "main.wrongExt", "");
var (output, error, result) = await Bicep("deploy", bicepDeployPath);

result.Should().Be(1);
output.Should().BeEmpty();
error.Should().Contain($"\"{bicepDeployPath}\" was not recognized as a Bicep deployment file.");
}

[TestMethod]
[Ignore("This test is failing due to incomplete integration with codegen. The test should be re-enabled afterwards.")]
public async Task Deploy_RequestFailedException_ShouldFail_WithExpectedErrorMessage()
{
var bicepDeployPath = FileHelper.SaveResultFile(TestContext, "main.bicepdeploy", "");

var deploymentManagerFactory = StrictMock.Of<IDeploymentManagerFactory>();
var deploymentManager = StrictMock.Of<IDeploymentManager>()
.Setup(x => x.CreateOrUpdateAsync(It.IsAny<ArmDeploymentDefinition>(), It.IsAny<Action<ImmutableSortedSet<ArmDeploymentOperation>>>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new RequestFailedException("Mock deployment request failed"));

deploymentManagerFactory
.Setup(x => x.CreateDeploymentManager(It.IsAny<RootConfiguration>()))
.Returns(StrictMock.Of<IDeploymentManager>().Object);

var settings = new InvocationSettings(new(DeploymentFileEnabled: true));
var (output, error, result) = await Bicep(
settings,
services => services.AddSingleton(deploymentManagerFactory.Object), CancellationToken.None, "deploy", bicepDeployPath);
using (new AssertionScope())
{
error.Should().StartWith("Unable to deploy: Mock deployment request failed");
output.Should().BeEmpty();
result.Should().Be(1);
}
}

[DataTestMethod]
[Ignore("This test is failing due to incomplete integration with codegen. The test should be re-enabled afterwards.")]
[BaselineData_BicepDeploy.TestData(Filter = BaselineData_BicepDeploy.TestDataFilterType.ValidOnly)]
[TestCategory(BaselineHelper.BaselineTestCategory)]
public async Task Deploy_Valid_Deployment_File_Should_Succeed(BaselineData_BicepDeploy baselineData)
{
var data = baselineData.GetData(TestContext);

var mockArmDeploymentResourceData = ArmResourcesModelFactory
.ArmDeploymentData(properties: ArmResourcesModelFactory.ArmDeploymentPropertiesExtended(provisioningState: "Succeeded"));

var mockArmDeploymentResource = StrictMock.Of<ArmDeploymentResource>();
mockArmDeploymentResource.SetupGet(x => x.Data).Returns(mockArmDeploymentResourceData);

var deploymentManagerFactory = StrictMock.Of<IDeploymentManagerFactory>();
var deploymentManager = StrictMock.Of<IDeploymentManager>();
deploymentManager.Setup(x => x.CreateOrUpdateAsync(It.IsAny<ArmDeploymentDefinition>(), It.IsAny<Action<ImmutableSortedSet<ArmDeploymentOperation>>>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(mockArmDeploymentResource.Object));


deploymentManagerFactory
.Setup(x => x.CreateDeploymentManager(It.IsAny<RootConfiguration>()))
.Returns(deploymentManager.Object);

var settings = new InvocationSettings(new(DeploymentFileEnabled: true));
var (output, error, result) = await Bicep(
settings,
services => services.AddSingleton(deploymentManagerFactory.Object), CancellationToken.None, "deploy", data.DeployFile.OutputFilePath);

using (new AssertionScope())
{
result.Should().Be(0);
output.Should().NotBeEmpty();
AssertNoErrors(error);
}
}
}
110 changes: 110 additions & 0 deletions src/Bicep.Cli.IntegrationTests/ValidateCommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure;
using Azure.ResourceManager.Resources;
using Azure.ResourceManager.Resources.Models;
using Bicep.Core.Configuration;
using Bicep.Core.Models;
using Bicep.Core.Samples;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Mock;
using Bicep.Core.UnitTests.Utils;
using Bicep.Deploy;
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Bicep.Cli.IntegrationTests;

[TestClass]
public class ValidateCommandTests : TestBase
{
[TestMethod]
public async Task Validate_ZeroFiles_ShouldFail_WithExpectedErrorMessage()
{
var (output, error, result) = await Bicep("validate");

using (new AssertionScope())
{
result.Should().Be(1);
output.Should().BeEmpty();

error.Should().NotBeEmpty();
error.Should().Contain($"The input file path was not specified");
}
}

[TestMethod]
public async Task Validate_With_Incorrect_Bicep_Deployment_File_Extension_ShouldFail_WithExpectedErrorMessage()
{
var bicepDeployPath = FileHelper.SaveResultFile(TestContext, "main.wrongExt", "");
var (output, error, result) = await Bicep("validate", bicepDeployPath);

result.Should().Be(1);
output.Should().BeEmpty();
error.Should().Contain($"\"{bicepDeployPath}\" was not recognized as a Bicep deployment file.");
}

[TestMethod]
[Ignore("This test is failing due to incomplete integration with codegen. The test should be re-enabled afterwards.")]
public async Task Validate_RequestFailedException_ShouldFail_WithExpectedErrorMessage()
{
var bicepDeployPath = FileHelper.SaveResultFile(TestContext, "main.bicepdeploy", "");

var deploymentManagerFactory = StrictMock.Of<IDeploymentManagerFactory>();
var deploymentManager = StrictMock.Of<IDeploymentManager>()
.Setup(x => x.ValidateAsync(It.IsAny<ArmDeploymentDefinition>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new RequestFailedException("Mock validation request failed"));

deploymentManagerFactory
.Setup(x => x.CreateDeploymentManager(It.IsAny<RootConfiguration>()))
.Returns(StrictMock.Of<IDeploymentManager>().Object);

var settings = new InvocationSettings(new(DeploymentFileEnabled: true));
var (output, error, result) = await Bicep(
settings,
services => services.AddSingleton(deploymentManagerFactory.Object), CancellationToken.None, "validate", bicepDeployPath);
using (new AssertionScope())
{
error.Should().StartWith("Unable to validate: Mock validation request failed");
output.Should().BeEmpty();
result.Should().Be(1);
}
}

[DataTestMethod]
[Ignore("This test is failing due to incomplete integration with codegen. The test should be re-enabled afterwards.")]
[BaselineData_BicepDeploy.TestData(Filter = BaselineData_BicepDeploy.TestDataFilterType.ValidOnly)]
[TestCategory(BaselineHelper.BaselineTestCategory)]
public async Task Validate_Valid_Deployment_File_Should_Succeed(BaselineData_BicepDeploy baselineData)
{
var data = baselineData.GetData(TestContext);

var mockArmDeploymentValidateResult = ArmResourcesModelFactory
.ArmDeploymentValidateResult(properties: ArmResourcesModelFactory.ArmDeploymentPropertiesExtended(provisioningState: "Succeeded"));

var deploymentManagerFactory = StrictMock.Of<IDeploymentManagerFactory>();
var deploymentManager = StrictMock.Of<IDeploymentManager>();
deploymentManager.Setup(x => x.ValidateAsync(It.IsAny<ArmDeploymentDefinition>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(mockArmDeploymentValidateResult));

deploymentManagerFactory
.Setup(x => x.CreateDeploymentManager(It.IsAny<RootConfiguration>()))
.Returns(deploymentManager.Object);

var settings = new InvocationSettings(new(DeploymentFileEnabled: true));
var (output, error, result) = await Bicep(
settings,
services => services.AddSingleton(deploymentManagerFactory.Object), CancellationToken.None, "validate", data.DeployFile.OutputFilePath);

using (new AssertionScope())
{
result.Should().Be(0);
output.Should().NotBeEmpty();
AssertNoErrors(error);
}
}
}
108 changes: 108 additions & 0 deletions src/Bicep.Cli.IntegrationTests/WhatIfCommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure;
using Azure.ResourceManager.Resources.Models;
using Bicep.Core.Configuration;
using Bicep.Core.Models;
using Bicep.Core.Samples;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Mock;
using Bicep.Core.UnitTests.Utils;
using Bicep.Deploy;
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Bicep.Cli.IntegrationTests;

[TestClass]
public class WhatIfCommandTests : TestBase
{
[TestMethod]
public async Task WhatIf_ZeroFiles_ShouldFail_WithExpectedErrorMessage()
{
var (output, error, result) = await Bicep("what-if");

using (new AssertionScope())
{
result.Should().Be(1);
output.Should().BeEmpty();

error.Should().NotBeEmpty();
error.Should().Contain($"The input file path was not specified");
}
}

[TestMethod]
public async Task WhatIf_With_Incorrect_Bicep_Deployment_File_Extension_ShouldFail_WithExpectedErrorMessage()
{
var bicepDeployPath = FileHelper.SaveResultFile(TestContext, "main.wrongExt", "");
var (output, error, result) = await Bicep("what-if", bicepDeployPath);

result.Should().Be(1);
output.Should().BeEmpty();
error.Should().Contain($"\"{bicepDeployPath}\" was not recognized as a Bicep deployment file.");
}

[TestMethod]
[Ignore("This test is failing due to incomplete integration with codegen. The test should be re-enabled afterwards.")]
public async Task WhatIf_RequestFailedException_ShouldFail_WithExpectedErrorMessage()
{
var bicepDeployPath = FileHelper.SaveResultFile(TestContext, "main.bicepdeploy", "");

var deploymentManagerFactory = StrictMock.Of<IDeploymentManagerFactory>();
var deploymentManager = StrictMock.Of<IDeploymentManager>()
.Setup(x => x.ValidateAsync(It.IsAny<ArmDeploymentDefinition>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new RequestFailedException("Mock what-if request failed"));

deploymentManagerFactory
.Setup(x => x.CreateDeploymentManager(It.IsAny<RootConfiguration>()))
.Returns(StrictMock.Of<IDeploymentManager>().Object);

var settings = new InvocationSettings(new(DeploymentFileEnabled: true));
var (output, error, result) = await Bicep(
settings,
services => services.AddSingleton(deploymentManagerFactory.Object), CancellationToken.None, "what-if", bicepDeployPath);
using (new AssertionScope())
{
error.Should().StartWith("Unable to run what-if: Mock what-if request failed");
output.Should().BeEmpty();
result.Should().Be(1);
}
}

[DataTestMethod]
[Ignore("This test is failing due to incomplete integration with codegen. The test should be re-enabled afterwards.")]
[BaselineData_BicepDeploy.TestData(Filter = BaselineData_BicepDeploy.TestDataFilterType.ValidOnly)]
[TestCategory(BaselineHelper.BaselineTestCategory)]
public async Task WhatIf_Valid_Deployment_File_Should_Succeed(BaselineData_BicepDeploy baselineData)
{
var data = baselineData.GetData(TestContext);

var mockArmWhatIfResult = ArmResourcesModelFactory.WhatIfOperationResult(status: "Succeeded");

var deploymentManagerFactory = StrictMock.Of<IDeploymentManagerFactory>();
var deploymentManager = StrictMock.Of<IDeploymentManager>();
deploymentManager.Setup(x => x.WhatIfAsync(It.IsAny<ArmDeploymentDefinition>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(mockArmWhatIfResult));

deploymentManagerFactory
.Setup(x => x.CreateDeploymentManager(It.IsAny<RootConfiguration>()))
.Returns(deploymentManager.Object);

var settings = new InvocationSettings(new(DeploymentFileEnabled: true));
var (output, error, result) = await Bicep(
settings,
services => services.AddSingleton(deploymentManagerFactory.Object), CancellationToken.None, "what-if", data.DeployFile.OutputFilePath);

using (new AssertionScope())
{
result.Should().Be(0);
output.Should().NotBeEmpty();
AssertNoErrors(error);
}
}
}
Loading
Loading