Skip to content

Commit

Permalink
Added support for Azure resources deployment in Bicep language
Browse files Browse the repository at this point in the history
  • Loading branch information
BethanyZhou committed Feb 8, 2021
1 parent 1ff6b2f commit 3fce0c8
Show file tree
Hide file tree
Showing 24 changed files with 5,328 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@
// limitations under the License.
// ----------------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using Microsoft.Azure.Commands.Common.Authentication;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.ResourceManager.Cmdlets.Components;
using Microsoft.Azure.Commands.ResourceManager.Cmdlets.Utilities;
using Microsoft.Azure.Management.ResourceManager;
using Microsoft.WindowsAzure.Commands.Utilities.Common;

using Newtonsoft.Json.Linq;

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;

namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation
{
public abstract class ResourceWithParameterCmdletBase : ResourceManagerCmdletBase
Expand Down Expand Up @@ -110,24 +112,24 @@ protected ResourceWithParameterCmdletBase()
public Hashtable TemplateObject { get; set; }

[Parameter(ParameterSetName = TemplateFileParameterObjectParameterSetName,
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the template file.")]
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the template file. Supported template file type: json and bicep.")]
[Parameter(ParameterSetName = TemplateFileParameterFileParameterSetName,
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the template file.")]
Mandatory = true, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = TemplateFileParameterUriParameterSetName,
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the template file.")]
Mandatory = true, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = ParameterlessTemplateFileParameterSetName,
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the template file.")]
Mandatory = true, ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
public string TemplateFile { get; set; }

[Parameter(ParameterSetName = TemplateUriParameterObjectParameterSetName,
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Uri to the template file.")]
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Uri to the template file. Supported template file type: json and bicep.")]
[Parameter(ParameterSetName = TemplateUriParameterFileParameterSetName,
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Uri to the template file.")]
Mandatory = true, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = TemplateUriParameterUriParameterSetName,
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Uri to the template file.")]
Mandatory = true, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = ParameterlessTemplateUriParameterSetName,
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Uri to the template file.")]
Mandatory = true, ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
public string TemplateUri { get; set; }

Expand Down Expand Up @@ -170,6 +172,8 @@ public ITemplateSpecsClient TemplateSpecsClient

public virtual object GetDynamicParameters()
{
RedirectToStandardARMTemplateFile();

if (!this.IsParameterBound(c => c.SkipTemplateParameterPrompt))
{
// Resolve the static parameter names for this cmdlet:
Expand Down Expand Up @@ -408,5 +412,28 @@ protected string[] GetStaticParameterNames()
CmdletInfo cmdletInfo = new CmdletInfo(commandName, this.GetType());
return cmdletInfo.Parameters.Keys.ToArray();
}

/// <summary>
/// Build and redirect the input template file to standard ARM json template file.
/// </summary>
protected void RedirectToStandardARMTemplateFile()
{
if (BicepUtility.IsBicepFile(TemplateFile) ||
BicepUtility.IsBicepFile(TemplateUri))
BuildAndUseBicepTemplate();
}

protected void BuildAndUseBicepTemplate()
{
if (BicepUtility.IsBicepFile(TemplateFile))
{
TemplateFile = BicepUtility.BuildFile(this.ExecuteScript<Object>, TemplateFile);
}

if (BicepUtility.IsBicepFile(TemplateUri))
{
TemplateUri = BicepUtility.BuildFile(this.ExecuteScript<Object>, TemplateUri);
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Resources/ResourceManager/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -494,4 +494,7 @@ You can help us improve the accuracy of the result by opening an issue here: htt
<data name="InvalidChangeType" xml:space="preserve">
<value>Unrecognized resource change {0}: {1}. Specify one ore more values in the following list and try again: {2}.</value>
</data>
<data name="BicepNotFound" xml:space="preserve">
<value>Cannot find Bicep. Please add Bicep to your PATH or visit https://github.com/Azure/bicep/releases to install Bicep.</value>
</data>
</root>
79 changes: 79 additions & 0 deletions src/Resources/ResourceManager/Utilities/BicepUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// ----------------------------------------------------------------------------------
//
// Copyright Microsoft Corporation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------------

using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.Common.Exceptions;
using Microsoft.WindowsAzure.Commands.Utilities.Common;

using System;
using System.Collections.Generic;
using System.IO;

namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Utilities
{
internal static class BicepUtility
{
public static bool IsBicepExecutable { get; private set; } = false;

public static bool IsBicepFile(string templateFilePath)
{
return ".bicep".Equals(Path.GetExtension(templateFilePath), System.StringComparison.OrdinalIgnoreCase);
}

public delegate List<T> ScriptExecutor<T>(string script);

public static bool CheckBicepExecutable<T>(ScriptExecutor<T> executeScript)
{
try
{
executeScript("get-command bicep");
}
catch
{
IsBicepExecutable = false;
return IsBicepExecutable;
}
IsBicepExecutable = true;
return IsBicepExecutable;
}

public static string BuildFile<T>(ScriptExecutor<T> executeScript, string bicepTemplateFilePath)
{
if (!IsBicepExecutable && !CheckBicepExecutable(executeScript))
{
throw new AzPSApplicationException(Properties.Resources.BicepNotFound);
}

string tempPath = Path.Combine(Path.GetTempPath(), Path.GetFileName(bicepTemplateFilePath));

try{
if (Uri.IsWellFormedUriString(bicepTemplateFilePath, UriKind.Absolute))
{
FileUtilities.DataStore.WriteFile(tempPath, GeneralUtilities.DownloadFile(bicepTemplateFilePath));
}
else if (FileUtilities.DataStore.FileExists(bicepTemplateFilePath))
{
File.Copy(bicepTemplateFilePath, tempPath, true);
}
executeScript($"bicep build '{tempPath}'");
return tempPath.Replace(".bicep", ".json");
}
finally
{
File.Delete(tempPath);
}

}
}
}
1 change: 1 addition & 0 deletions src/Resources/Resources.Test/Resources.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<ItemGroup>
<None Update="Resources\*.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="*.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="*.bicep" CopyToOutputDirectory="PreserveNewest" />
<None Update="ScenarioTests\*.pfx" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Expand Down
14 changes: 14 additions & 0 deletions src/Resources/Resources.Test/ScenarioTests/DeploymentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,5 +155,19 @@ public void TestNewDeploymentWithQueryString()
{
TestRunner.RunTestScript("Test-NewDeploymentWithQueryString");
}

[Fact]
[Trait(Category.AcceptanceType, Category.LiveOnly)]
public void TestNewDeploymentFromBicepFile()
{
TestRunner.RunTestScript("Test-NewDeploymentFromBicepFile");
}

[Fact]
[Trait(Category.AcceptanceType, Category.LiveOnly)]
public void TestTestDeploymentFromBicepFile()
{
TestRunner.RunTestScript("Test-TestDeploymentFromBicepFile");
}
}
}
67 changes: 66 additions & 1 deletion src/Resources/Resources.Test/ScenarioTests/DeploymentTests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -662,12 +662,77 @@ function Test-NewDeploymentWithQueryString

# Assert
Assert-AreEqual Succeeded $deployment.ProvisioningState

}

finally
{
# Cleanup
Clean-ResourceGroup $rgname
}
}

<#
.SYNOPSIS
Tests deployment via Bicep file.
#>
function Test-NewDeploymentFromBicepFile
{
# Setup
$rgname = Get-ResourceGroupName
$rname = Get-ResourceName
$rglocation = "West US 2"
$expectedTags = @{"key1"="value1"; "key2"="value2";}

try
{
# Test
New-AzResourceGroup -Name $rgname -Location $rglocation

$deployment = New-AzResourceGroupDeployment -Name $rname -ResourceGroupName $rgname -TemplateFile sampleDeploymentBicepFile.bicep -Tag $expectedTags

# Assert
Assert-AreEqual Succeeded $deployment.ProvisioningState
Assert-True { AreHashtableEqual $expectedTags $deployment.Tags }

$subId = (Get-AzContext).Subscription.SubscriptionId
$deploymentId = "/subscriptions/$subId/resourcegroups/$rgname/providers/Microsoft.Resources/deployments/$rname"
$getById = Get-AzResourceGroupDeployment -Id $deploymentId
Assert-AreEqual $getById.DeploymentName $deployment.DeploymentName

[hashtable]$actualTags = $getById.Tags
Assert-True { AreHashtableEqual $expectedTags $getById.Tags }
}
finally
{
# Cleanup
Clean-ResourceGroup $rgname
}
}

<#
.SYNOPSIS
Tests deployment template via bicep file.
#>
function Test-TestDeploymentFromBicepFile
{
# Setup
$rgname = Get-ResourceGroupName
$rname = Get-ResourceName
$location = "West US 2"

# Test
try
{
New-AzResourceGroup -Name $rgname -Location $location

$list = Test-AzResourceGroupDeployment -ResourceGroupName $rgname -TemplateFile sampleDeploymentBicepFile.bicep

# Assert
Assert-AreEqual 0 @($list).Count
}
finally
{
# Cleanup
Clean-ResourceGroup $rgname
}
}
Loading

0 comments on commit 3fce0c8

Please sign in to comment.