Skip to content

Commit ade602e

Browse files
committed
Added support for Azure resources deployment in Bicep language
1 parent 1ff6b2f commit ade602e

24 files changed

+5320
-57
lines changed

src/Resources/ResourceManager/Implementation/CmdletBase/ResourceWithParameterCmdletBase.cs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,21 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15-
using System;
16-
using System.Collections;
17-
using System.Collections.Generic;
18-
using System.Linq;
19-
using System.Management.Automation;
2015
using Microsoft.Azure.Commands.Common.Authentication;
2116
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
2217
using Microsoft.Azure.Commands.ResourceManager.Cmdlets.Components;
2318
using Microsoft.Azure.Commands.ResourceManager.Cmdlets.Utilities;
2419
using Microsoft.Azure.Management.ResourceManager;
2520
using Microsoft.WindowsAzure.Commands.Utilities.Common;
21+
2622
using Newtonsoft.Json.Linq;
2723

24+
using System;
25+
using System.Collections;
26+
using System.Collections.Generic;
27+
using System.Linq;
28+
using System.Management.Automation;
29+
2830
namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation
2931
{
3032
public abstract class ResourceWithParameterCmdletBase : ResourceManagerCmdletBase
@@ -110,24 +112,24 @@ protected ResourceWithParameterCmdletBase()
110112
public Hashtable TemplateObject { get; set; }
111113

112114
[Parameter(ParameterSetName = TemplateFileParameterObjectParameterSetName,
113-
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the template file.")]
115+
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the template file. Supported template file type: json and bicep.")]
114116
[Parameter(ParameterSetName = TemplateFileParameterFileParameterSetName,
115-
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the template file.")]
117+
Mandatory = true, ValueFromPipelineByPropertyName = true)]
116118
[Parameter(ParameterSetName = TemplateFileParameterUriParameterSetName,
117-
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the template file.")]
119+
Mandatory = true, ValueFromPipelineByPropertyName = true)]
118120
[Parameter(ParameterSetName = ParameterlessTemplateFileParameterSetName,
119-
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the template file.")]
121+
Mandatory = true, ValueFromPipelineByPropertyName = true)]
120122
[ValidateNotNullOrEmpty]
121123
public string TemplateFile { get; set; }
122124

123125
[Parameter(ParameterSetName = TemplateUriParameterObjectParameterSetName,
124-
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Uri to the template file.")]
126+
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Uri to the template file. Supported template file type: json and bicep.")]
125127
[Parameter(ParameterSetName = TemplateUriParameterFileParameterSetName,
126-
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Uri to the template file.")]
128+
Mandatory = true, ValueFromPipelineByPropertyName = true)]
127129
[Parameter(ParameterSetName = TemplateUriParameterUriParameterSetName,
128-
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Uri to the template file.")]
130+
Mandatory = true, ValueFromPipelineByPropertyName = true)]
129131
[Parameter(ParameterSetName = ParameterlessTemplateUriParameterSetName,
130-
Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Uri to the template file.")]
132+
Mandatory = true, ValueFromPipelineByPropertyName = true)]
131133
[ValidateNotNullOrEmpty]
132134
public string TemplateUri { get; set; }
133135

@@ -170,6 +172,10 @@ public ITemplateSpecsClient TemplateSpecsClient
170172

171173
public virtual object GetDynamicParameters()
172174
{
175+
if (BicepUtility.IsBicepFile(TemplateFile) ||
176+
BicepUtility.IsBicepFile(TemplateUri))
177+
BuildAndUseBicepTemplate();
178+
173179
if (!this.IsParameterBound(c => c.SkipTemplateParameterPrompt))
174180
{
175181
// Resolve the static parameter names for this cmdlet:
@@ -408,5 +414,18 @@ protected string[] GetStaticParameterNames()
408414
CmdletInfo cmdletInfo = new CmdletInfo(commandName, this.GetType());
409415
return cmdletInfo.Parameters.Keys.ToArray();
410416
}
417+
418+
protected void BuildAndUseBicepTemplate()
419+
{
420+
if (BicepUtility.IsBicepFile(TemplateFile))
421+
{
422+
TemplateFile = BicepUtility.BuildFile(this.ExecuteScript<Object>, TemplateFile);
423+
}
424+
425+
if (BicepUtility.IsBicepFile(TemplateUri))
426+
{
427+
TemplateUri = BicepUtility.BuildFile(this.ExecuteScript<Object>, TemplateUri);
428+
}
429+
}
411430
}
412431
}

src/Resources/ResourceManager/Properties/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Resources/ResourceManager/Properties/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,4 +494,7 @@ You can help us improve the accuracy of the result by opening an issue here: htt
494494
<data name="InvalidChangeType" xml:space="preserve">
495495
<value>Unrecognized resource change {0}: {1}. Specify one ore more values in the following list and try again: {2}.</value>
496496
</data>
497+
<data name="BicepNotFound" xml:space="preserve">
498+
<value>Cannot find Bicep. Please add Bicep to your PATH or visit https://github.com/Azure/bicep/releases to install Bicep.</value>
499+
</data>
497500
</root>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
16+
using Microsoft.Azure.Commands.Common.Exceptions;
17+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
18+
19+
using System;
20+
using System.Collections.Generic;
21+
using System.IO;
22+
23+
namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Utilities
24+
{
25+
internal static class BicepUtility
26+
{
27+
public static bool IsBicepExecutable { get; private set; } = false;
28+
29+
public static bool IsBicepFile(string templateFilePath)
30+
{
31+
return ".bicep".Equals(Path.GetExtension(templateFilePath), System.StringComparison.OrdinalIgnoreCase);
32+
}
33+
34+
public delegate List<T> ScriptExecutor<T>(string script);
35+
36+
public static bool CheckBicepExecutable<T>(ScriptExecutor<T> executeScript)
37+
{
38+
try
39+
{
40+
executeScript("get-command bicep");
41+
}
42+
catch
43+
{
44+
IsBicepExecutable = false;
45+
return IsBicepExecutable;
46+
}
47+
IsBicepExecutable = true;
48+
return IsBicepExecutable;
49+
}
50+
51+
public static string BuildFile<T>(ScriptExecutor<T> executeScript, string bicepTemplateFilePath)
52+
{
53+
if (!IsBicepExecutable && !CheckBicepExecutable(executeScript))
54+
{
55+
throw new AzPSApplicationException(Properties.Resources.BicepNotFound);
56+
}
57+
58+
string tempPath = Path.Combine(Path.GetTempPath(), Path.GetFileName(bicepTemplateFilePath));
59+
60+
try{
61+
if (Uri.IsWellFormedUriString(bicepTemplateFilePath, UriKind.Absolute))
62+
{
63+
FileUtilities.DataStore.WriteFile(tempPath, GeneralUtilities.DownloadFile(bicepTemplateFilePath));
64+
}
65+
else if (FileUtilities.DataStore.FileExists(bicepTemplateFilePath))
66+
{
67+
File.Copy(bicepTemplateFilePath, tempPath, true);
68+
}
69+
executeScript($"bicep build '{tempPath}'");
70+
return tempPath.Replace(".bicep", ".json");
71+
}
72+
finally
73+
{
74+
File.Delete(tempPath);
75+
}
76+
77+
}
78+
}
79+
}

src/Resources/Resources.Test/Resources.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<ItemGroup>
2828
<None Update="Resources\*.json" CopyToOutputDirectory="PreserveNewest" />
2929
<None Update="*.json" CopyToOutputDirectory="PreserveNewest" />
30+
<None Update="*.bicep" CopyToOutputDirectory="PreserveNewest" />
3031
<None Update="ScenarioTests\*.pfx" CopyToOutputDirectory="PreserveNewest" />
3132
</ItemGroup>
3233

src/Resources/Resources.Test/ScenarioTests/DeploymentTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,19 @@ public void TestNewDeploymentWithQueryString()
155155
{
156156
TestRunner.RunTestScript("Test-NewDeploymentWithQueryString");
157157
}
158+
159+
[Fact]
160+
[Trait(Category.AcceptanceType, Category.LiveOnly)]
161+
public void TestNewDeploymentFromBicepFile()
162+
{
163+
TestRunner.RunTestScript("Test-NewDeploymentFromBicepFile");
164+
}
165+
166+
[Fact]
167+
[Trait(Category.AcceptanceType, Category.LiveOnly)]
168+
public void TestTestDeploymentFromBicepFile()
169+
{
170+
TestRunner.RunTestScript("Test-TestDeploymentFromBicepFile");
171+
}
158172
}
159173
}

src/Resources/Resources.Test/ScenarioTests/DeploymentTests.ps1

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,12 +662,77 @@ function Test-NewDeploymentWithQueryString
662662

663663
# Assert
664664
Assert-AreEqual Succeeded $deployment.ProvisioningState
665-
666665
}
667666

668667
finally
669668
{
670669
# Cleanup
671670
Clean-ResourceGroup $rgname
672671
}
672+
}
673+
674+
<#
675+
.SYNOPSIS
676+
Tests deployment via Bicep file.
677+
#>
678+
function Test-NewDeploymentFromBicepFile
679+
{
680+
# Setup
681+
$rgname = Get-ResourceGroupName
682+
$rname = Get-ResourceName
683+
$rglocation = "West US 2"
684+
$expectedTags = @{"key1"="value1"; "key2"="value2";}
685+
686+
try
687+
{
688+
# Test
689+
New-AzResourceGroup -Name $rgname -Location $rglocation
690+
691+
$deployment = New-AzResourceGroupDeployment -Name $rname -ResourceGroupName $rgname -TemplateFile sampleDeploymentBicepFile.bicep -Tag $expectedTags
692+
693+
# Assert
694+
Assert-AreEqual Succeeded $deployment.ProvisioningState
695+
Assert-True { AreHashtableEqual $expectedTags $deployment.Tags }
696+
697+
$subId = (Get-AzContext).Subscription.SubscriptionId
698+
$deploymentId = "/subscriptions/$subId/resourcegroups/$rgname/providers/Microsoft.Resources/deployments/$rname"
699+
$getById = Get-AzResourceGroupDeployment -Id $deploymentId
700+
Assert-AreEqual $getById.DeploymentName $deployment.DeploymentName
701+
702+
[hashtable]$actualTags = $getById.Tags
703+
Assert-True { AreHashtableEqual $expectedTags $getById.Tags }
704+
}
705+
finally
706+
{
707+
# Cleanup
708+
Clean-ResourceGroup $rgname
709+
}
710+
}
711+
712+
<#
713+
.SYNOPSIS
714+
Tests deployment template via bicep file.
715+
#>
716+
function Test-TestDeploymentFromBicepFile
717+
{
718+
# Setup
719+
$rgname = Get-ResourceGroupName
720+
$rname = Get-ResourceName
721+
$location = "West US 2"
722+
723+
# Test
724+
try
725+
{
726+
New-AzResourceGroup -Name $rgname -Location $location
727+
728+
$list = Test-AzResourceGroupDeployment -ResourceGroupName $rgname -TemplateFile sampleDeploymentBicepFile.bicep
729+
730+
# Assert
731+
Assert-AreEqual 0 @($list).Count
732+
}
733+
finally
734+
{
735+
# Cleanup
736+
Clean-ResourceGroup $rgname
737+
}
673738
}

0 commit comments

Comments
 (0)